opencode-swarm 7.3.1 → 7.3.3

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
@@ -33,7 +33,7 @@ var package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "opencode-swarm",
36
- version: "7.3.1",
36
+ version: "7.3.3",
37
37
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
38
38
  main: "dist/index.js",
39
39
  types: "dist/index.d.ts",
@@ -125,6 +125,7 @@ var init_tool_names = __esm(() => {
125
125
  "check_gate_status",
126
126
  "completion_verify",
127
127
  "submit_council_verdicts",
128
+ "submit_phase_council_verdicts",
128
129
  "declare_council_criteria",
129
130
  "sbom_generate",
130
131
  "checkpoint",
@@ -272,6 +273,7 @@ var init_constants = __esm(() => {
272
273
  "completion_verify",
273
274
  "complexity_hotspots",
274
275
  "submit_council_verdicts",
276
+ "submit_phase_council_verdicts",
275
277
  "declare_council_criteria",
276
278
  "detect_domains",
277
279
  "evidence_check",
@@ -531,6 +533,7 @@ var init_constants = __esm(() => {
531
533
  check_gate_status: "check the gate status of a specific task",
532
534
  completion_verify: "verify completed tasks have required evidence",
533
535
  submit_council_verdicts: "submit pre-collected council member verdicts for synthesis (architect MUST dispatch critic/reviewer/sme/test_engineer/explorer as Agent tasks first; this tool synthesizes only, it does not contact members)",
536
+ submit_phase_council_verdicts: "submit pre-collected phase-level council member verdicts for holistic phase synthesis (architect MUST dispatch all 5 council members with phase-scoped context first; this tool synthesizes only, it does not contact members)",
534
537
  declare_council_criteria: "pre-declare acceptance criteria for a task before the coder starts work; criteria are read back during council evaluation",
535
538
  detect_domains: "detect which SME domains are relevant for a given text",
536
539
  extract_code_blocks: "extract code blocks from text content and save them to files",
@@ -25603,136 +25606,128 @@ function createDelegationGateHook(config2, directory) {
25603
25606
  hasReviewer = true;
25604
25607
  if (targetAgent === "test_engineer")
25605
25608
  hasTestEngineer = true;
25606
- if (!councilActive) {
25607
- const stageBParallelEnabled = config2.parallelization?.stageB?.parallel?.enabled === true;
25608
- if (stageBParallelEnabled) {
25609
- if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
25610
- const stageBEligibleStates = [
25611
- "coder_delegated",
25612
- "pre_check_passed",
25613
- "reviewer_run"
25614
- ];
25615
- for (const [taskId, state] of session.taskWorkflowStates) {
25616
- if (!stageBEligibleStates.includes(state))
25617
- continue;
25618
- const eligibleState = state;
25619
- recordStageBCompletion(session, taskId, targetAgent);
25620
- if (hasBothStageBCompletions(session, taskId)) {
25621
- try {
25622
- if (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed") {
25623
- advanceTaskState(session, taskId, "reviewer_run", {
25624
- telemetrySessionId: input.sessionID
25625
- });
25626
- }
25627
- advanceTaskState(session, taskId, "tests_run", {
25609
+ const stageBParallelEnabled = config2.parallelization?.stageB?.parallel?.enabled === true;
25610
+ if (stageBParallelEnabled) {
25611
+ if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
25612
+ const stageBEligibleStates = [
25613
+ "coder_delegated",
25614
+ "pre_check_passed",
25615
+ "reviewer_run"
25616
+ ];
25617
+ for (const [taskId, state] of session.taskWorkflowStates) {
25618
+ if (!stageBEligibleStates.includes(state))
25619
+ continue;
25620
+ const eligibleState = state;
25621
+ recordStageBCompletion(session, taskId, targetAgent);
25622
+ if (hasBothStageBCompletions(session, taskId)) {
25623
+ try {
25624
+ if (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed") {
25625
+ advanceTaskState(session, taskId, "reviewer_run", {
25628
25626
  telemetrySessionId: input.sessionID
25629
25627
  });
25630
- } catch (err2) {
25631
- warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25632
- }
25633
- }
25634
- }
25635
- const seedTaskId = getSeedTaskId(session);
25636
- if (seedTaskId) {
25637
- for (const [, otherSession] of swarmState.agentSessions) {
25638
- if (otherSession === session)
25639
- continue;
25640
- if (!otherSession.taskWorkflowStates)
25641
- continue;
25642
- if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
25643
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25644
- }
25645
- const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
25646
- if (!seedState || !stageBEligibleStates.includes(seedState)) {
25647
- continue;
25648
- }
25649
- const seedEligibleState = seedState;
25650
- recordStageBCompletion(otherSession, seedTaskId, targetAgent);
25651
- if (hasBothStageBCompletions(otherSession, seedTaskId)) {
25652
- try {
25653
- if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
25654
- advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
25655
- }
25656
- advanceTaskState(otherSession, seedTaskId, "tests_run", {
25657
- emitTelemetry: false
25658
- });
25659
- } catch (err2) {
25660
- warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25661
- }
25662
25628
  }
25629
+ advanceTaskState(session, taskId, "tests_run", {
25630
+ telemetrySessionId: input.sessionID
25631
+ });
25632
+ } catch (err2) {
25633
+ warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25663
25634
  }
25664
25635
  }
25665
25636
  }
25666
- } else {
25667
- if (targetAgent === "reviewer" && session.taskWorkflowStates) {
25668
- for (const [taskId, state] of session.taskWorkflowStates) {
25669
- if (state === "coder_delegated" || state === "pre_check_passed") {
25637
+ const seedTaskId = getSeedTaskId(session);
25638
+ if (seedTaskId) {
25639
+ for (const [, otherSession] of swarmState.agentSessions) {
25640
+ if (otherSession === session)
25641
+ continue;
25642
+ if (!otherSession.taskWorkflowStates)
25643
+ continue;
25644
+ if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
25645
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25646
+ }
25647
+ const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
25648
+ if (!seedState || !stageBEligibleStates.includes(seedState)) {
25649
+ continue;
25650
+ }
25651
+ const seedEligibleState = seedState;
25652
+ recordStageBCompletion(otherSession, seedTaskId, targetAgent);
25653
+ if (hasBothStageBCompletions(otherSession, seedTaskId)) {
25670
25654
  try {
25671
- advanceTaskState(session, taskId, "reviewer_run", {
25672
- telemetrySessionId: input.sessionID
25655
+ if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
25656
+ advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
25657
+ }
25658
+ advanceTaskState(otherSession, seedTaskId, "tests_run", {
25659
+ emitTelemetry: false
25673
25660
  });
25674
25661
  } catch (err2) {
25675
- warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25662
+ warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25676
25663
  }
25677
25664
  }
25678
25665
  }
25679
25666
  }
25680
- if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
25681
- for (const [taskId, state] of session.taskWorkflowStates) {
25682
- if (state === "reviewer_run") {
25683
- try {
25684
- advanceTaskState(session, taskId, "tests_run", {
25685
- telemetrySessionId: input.sessionID
25686
- });
25687
- } catch (err2) {
25688
- warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25689
- }
25667
+ }
25668
+ } else {
25669
+ if (targetAgent === "reviewer" && session.taskWorkflowStates) {
25670
+ for (const [taskId, state] of session.taskWorkflowStates) {
25671
+ if (state === "coder_delegated" || state === "pre_check_passed") {
25672
+ try {
25673
+ advanceTaskState(session, taskId, "reviewer_run", {
25674
+ telemetrySessionId: input.sessionID
25675
+ });
25676
+ } catch (err2) {
25677
+ warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25690
25678
  }
25691
25679
  }
25692
25680
  }
25693
- if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
25694
- for (const [, otherSession] of swarmState.agentSessions) {
25695
- if (otherSession === session)
25696
- continue;
25697
- if (!otherSession.taskWorkflowStates)
25698
- continue;
25699
- if (targetAgent === "reviewer") {
25700
- const seedTaskId = getSeedTaskId(session);
25701
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25702
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25703
- }
25704
- for (const [
25705
- taskId,
25706
- state
25707
- ] of otherSession.taskWorkflowStates) {
25708
- if (state === "coder_delegated" || state === "pre_check_passed") {
25709
- try {
25710
- advanceTaskState(otherSession, taskId, "reviewer_run", {
25711
- emitTelemetry: false
25712
- });
25713
- } catch (err2) {
25714
- warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25715
- }
25681
+ }
25682
+ if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
25683
+ for (const [taskId, state] of session.taskWorkflowStates) {
25684
+ if (state === "reviewer_run") {
25685
+ try {
25686
+ advanceTaskState(session, taskId, "tests_run", {
25687
+ telemetrySessionId: input.sessionID
25688
+ });
25689
+ } catch (err2) {
25690
+ warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25691
+ }
25692
+ }
25693
+ }
25694
+ }
25695
+ if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
25696
+ for (const [, otherSession] of swarmState.agentSessions) {
25697
+ if (otherSession === session)
25698
+ continue;
25699
+ if (!otherSession.taskWorkflowStates)
25700
+ continue;
25701
+ if (targetAgent === "reviewer") {
25702
+ const seedTaskId = getSeedTaskId(session);
25703
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25704
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25705
+ }
25706
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
25707
+ if (state === "coder_delegated" || state === "pre_check_passed") {
25708
+ try {
25709
+ advanceTaskState(otherSession, taskId, "reviewer_run", {
25710
+ emitTelemetry: false
25711
+ });
25712
+ } catch (err2) {
25713
+ warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25716
25714
  }
25717
25715
  }
25718
25716
  }
25719
- if (targetAgent === "test_engineer") {
25720
- const seedTaskId = getSeedTaskId(session);
25721
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25722
- otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25723
- }
25724
- for (const [
25725
- taskId,
25726
- state
25727
- ] of otherSession.taskWorkflowStates) {
25728
- if (state === "reviewer_run") {
25729
- try {
25730
- advanceTaskState(otherSession, taskId, "tests_run", {
25731
- emitTelemetry: false
25732
- });
25733
- } catch (err2) {
25734
- warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25735
- }
25717
+ }
25718
+ if (targetAgent === "test_engineer") {
25719
+ const seedTaskId = getSeedTaskId(session);
25720
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25721
+ otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25722
+ }
25723
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
25724
+ if (state === "reviewer_run") {
25725
+ try {
25726
+ advanceTaskState(otherSession, taskId, "tests_run", {
25727
+ emitTelemetry: false
25728
+ });
25729
+ } catch (err2) {
25730
+ warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25736
25731
  }
25737
25732
  }
25738
25733
  }
@@ -25792,75 +25787,73 @@ function createDelegationGateHook(config2, directory) {
25792
25787
  if (target === "test_engineer")
25793
25788
  hasTestEngineer = true;
25794
25789
  }
25795
- if (!councilActive) {
25796
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25797
- session.qaSkipCount = 0;
25798
- session.qaSkipTaskIds = [];
25790
+ if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25791
+ session.qaSkipCount = 0;
25792
+ session.qaSkipTaskIds = [];
25793
+ }
25794
+ if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
25795
+ for (const [taskId, state] of session.taskWorkflowStates) {
25796
+ if (state === "coder_delegated" || state === "pre_check_passed") {
25797
+ try {
25798
+ advanceTaskState(session, taskId, "reviewer_run");
25799
+ } catch (err2) {
25800
+ warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25801
+ }
25802
+ }
25799
25803
  }
25800
- if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
25801
- for (const [taskId, state] of session.taskWorkflowStates) {
25802
- if (state === "coder_delegated" || state === "pre_check_passed") {
25803
- try {
25804
- advanceTaskState(session, taskId, "reviewer_run");
25805
- } catch (err2) {
25806
- warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25807
- }
25804
+ }
25805
+ if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
25806
+ for (const [taskId, state] of session.taskWorkflowStates) {
25807
+ if (state === "reviewer_run") {
25808
+ try {
25809
+ advanceTaskState(session, taskId, "tests_run");
25810
+ } catch (err2) {
25811
+ warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25808
25812
  }
25809
25813
  }
25810
25814
  }
25811
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
25812
- for (const [taskId, state] of session.taskWorkflowStates) {
25813
- if (state === "reviewer_run") {
25815
+ }
25816
+ if (lastCoderIndex !== -1 && hasReviewer) {
25817
+ for (const [, otherSession] of swarmState.agentSessions) {
25818
+ if (otherSession === session)
25819
+ continue;
25820
+ if (!otherSession.taskWorkflowStates)
25821
+ continue;
25822
+ const seedTaskId = getSeedTaskId(session);
25823
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25824
+ otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25825
+ }
25826
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
25827
+ if (state === "coder_delegated" || state === "pre_check_passed") {
25814
25828
  try {
25815
- advanceTaskState(session, taskId, "tests_run");
25829
+ advanceTaskState(otherSession, taskId, "reviewer_run", {
25830
+ emitTelemetry: false
25831
+ });
25816
25832
  } catch (err2) {
25817
- warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25833
+ warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25818
25834
  }
25819
25835
  }
25820
25836
  }
25821
25837
  }
25822
- if (lastCoderIndex !== -1 && hasReviewer) {
25823
- for (const [, otherSession] of swarmState.agentSessions) {
25824
- if (otherSession === session)
25825
- continue;
25826
- if (!otherSession.taskWorkflowStates)
25827
- continue;
25828
- const seedTaskId = getSeedTaskId(session);
25829
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25830
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25831
- }
25832
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
25833
- if (state === "coder_delegated" || state === "pre_check_passed") {
25834
- try {
25835
- advanceTaskState(otherSession, taskId, "reviewer_run", {
25836
- emitTelemetry: false
25837
- });
25838
- } catch (err2) {
25839
- warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25840
- }
25841
- }
25842
- }
25838
+ }
25839
+ if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25840
+ for (const [, otherSession] of swarmState.agentSessions) {
25841
+ if (otherSession === session)
25842
+ continue;
25843
+ if (!otherSession.taskWorkflowStates)
25844
+ continue;
25845
+ const seedTaskId = getSeedTaskId(session);
25846
+ if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25847
+ otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25843
25848
  }
25844
- }
25845
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25846
- for (const [, otherSession] of swarmState.agentSessions) {
25847
- if (otherSession === session)
25848
- continue;
25849
- if (!otherSession.taskWorkflowStates)
25850
- continue;
25851
- const seedTaskId = getSeedTaskId(session);
25852
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25853
- otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25854
- }
25855
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
25856
- if (state === "reviewer_run") {
25857
- try {
25858
- advanceTaskState(otherSession, taskId, "tests_run", {
25859
- emitTelemetry: false
25860
- });
25861
- } catch (err2) {
25862
- warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25863
- }
25849
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
25850
+ if (state === "reviewer_run") {
25851
+ try {
25852
+ advanceTaskState(otherSession, taskId, "tests_run", {
25853
+ emitTelemetry: false
25854
+ });
25855
+ } catch (err2) {
25856
+ warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25864
25857
  }
25865
25858
  }
25866
25859
  }
@@ -56153,106 +56146,101 @@ ${validation.warnings.join(`
56153
56146
  function buildCouncilWorkflow(council) {
56154
56147
  if (council?.enabled !== true)
56155
56148
  return "";
56156
- return `## COUNCIL WORKFLOW (submit_council_verdicts)
56149
+ return `## COUNCIL WORKFLOW (submit_phase_council_verdicts)
56157
56150
 
56158
- CRITICAL: \`submit_council_verdicts\` does NOT run council members.
56151
+ CRITICAL: \`submit_phase_council_verdicts\` does NOT run council members.
56159
56152
  It synthesizes verdicts that you must collect BEFORE calling it.
56160
56153
 
56161
- When \`council.enabled\` is true, every task goes through this verification
56162
- gate before advancing to \`complete\`. The council REPLACES Stage B
56163
- (reviewer + test_engineer as standalone delegations). Stage A
56164
- (\`pre_check_batch\`) still runs as the pre-review gate.
56154
+ When \`council.enabled\` is true and \`council_mode\` is enabled in the QA gate
56155
+ profile, a phase-level council review is required before calling \`phase_complete\`.
56156
+ Stage B (reviewer + test_engineer) ALWAYS runs per-task as normal.
56157
+ Stage B always runs per-task council is an ADDITIONAL verification layer at PHASE LEVEL, never a replacement for Stage B.
56165
56158
 
56166
- ### Phase 0 Pre-declare criteria (at plan time, BEFORE dispatching the coder)
56167
- Call \`declare_council_criteria\` for each task with at least 3 concrete,
56168
- testable acceptance criteria. Mark functional/correctness criteria
56169
- \`mandatory: true\`; style/naming criteria \`mandatory: false\`. Criterion ids
56170
- follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
56171
- \`.swarm/council/{taskId}.json\` and read back automatically at synthesis time.
56159
+ ### WHEN TO RUN COUNCIL
56160
+ After ALL tasks in the current phase have been marked \`completed\` and their
56161
+ Stage B gates have passed, and BEFORE calling \`phase_complete\`, convene the
56162
+ phase council for a Phase Dossier Assembly — a holistic review of cross-cutting concerns,
56163
+ behavioral cohesion, and the full body of work completed in the phase.
56164
+
56165
+ ## PHASE COUNCIL
56172
56166
 
56173
56167
  ### MANDATORY SEQUENCE — never skip or reorder
56174
56168
 
56175
- #### STEP 1 — DISPATCH all council members as parallel Agent tasks
56176
- Dispatch \`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`, and \`explorer\`
56177
- (or at minimum \`council.minimumMembers\` distinct members; default 3) in a
56178
- SINGLE message using parallel Agent tool calls. Provide each member with
56179
- their role-specific scope:
56180
- - \`critic\` 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
56181
- - \`reviewer\` semantic diff summary + blast radius (files importing changed files) + style guide
56182
- - \`sme\` task domain context + relevant knowledge base entries
56183
- - \`test_engineer\` changed test files + coverage delta + known mutation gaps
56184
- - \`explorer\` — full diff + original task intent + any prior slop findings
56185
- (explorer hunts for lazy implementations, hallucinated APIs,
56186
- cargo-cult patterns, spec drift, lazy abstractions)
56187
-
56188
- Wait for ALL dispatched agents to return their verdict objects.
56169
+ #### STEP 1 — DISPATCH all 5 council members in parallel (phase-scoped)
56170
+ In a SINGLE message, dispatch \`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`,
56171
+ and \`explorer\` as parallel Agent tasks. Each member receives phase-scoped context:
56172
+ - \`critic\` — full diff for the phase + all task specs + approved-plan baseline (via \`get_approved_plan\`) + spec-intent drift analysis
56173
+ - \`reviewer\` — phase-wide semantic diff summary + blast radius across all changed files
56174
+ - \`sme\` phase domain context + knowledge base entries relevant to the phase
56175
+ - \`test_engineer\` all changed test files for the phase + coverage delta + known mutation gaps
56176
+ - \`explorer\` full phase diff + original task intents + prior slop findings across all tasks
56177
+ (hunts for lazy implementations, hallucinated APIs, cargo-cult patterns,
56178
+ spec drift, lazy abstractions introduced anywhere in the phase)
56179
+
56180
+ Wait for ALL dispatched agents to return their verdict objects before proceeding.
56189
56181
 
56190
56182
  #### STEP 2 — COLLECT verdicts
56191
56183
  Read each agent's response and extract their \`CouncilMemberVerdict\` object.
56192
- Each member must return all fields: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
56184
+ Each member must return: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
56193
56185
  \`confidence\` (0.0–1.0), \`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`,
56194
56186
  \`durationMs\`.
56195
56187
 
56196
56188
  Do NOT fabricate, infer, or substitute a verdict. If an agent did not return
56197
- a valid verdict, re-dispatch that agent.
56189
+ a valid verdict object, re-dispatch that agent.
56198
56190
 
56199
- #### STEP 3 — CALL submit_council_verdicts
56200
- ONLY after collecting real verdicts from real agent dispatches, call
56201
- \`submit_council_verdicts\` with the collected verdicts array, the task id,
56202
- swarm id, and current round number (1-indexed).
56191
+ #### STEP 3 — CALL submit_phase_council_verdicts
56192
+ ONLY after collecting real verdicts from all dispatched agents, call
56193
+ \`submit_phase_council_verdicts\` with:
56194
+ - \`phaseNumber\`: the phase number just completed (integer, e.g. \`1\`)
56195
+ - \`swarmId\`: the swarm identifier (e.g. \`"mega"\`)
56196
+ - \`phaseSummary\`: a 2–4 sentence plain-language summary of what the phase accomplished
56197
+ - \`verdicts\`: the array of collected \`CouncilMemberVerdict\` objects
56198
+ - \`roundNumber\`: 1-indexed (default 1 on first council call for this phase)
56199
+
56200
+ This writes \`.swarm/evidence/{phase}/phase-council.json\`, which Gate 5 in
56201
+ \`phase_complete\` will read and validate.
56203
56202
 
56204
56203
  #### STEP 4 — READ the response
56205
- Inspect \`membersAbsent\` in the response. If \`membersAbsent\` is non-empty,
56206
- the council is incomplete — dispatch the missing members and re-collect.
56207
- Inspect \`overallVerdict\`. APPROVE is valid only when \`membersAbsent\` is
56208
- empty (or fewer members than \`council.minimumMembers\` are absent).
56209
-
56210
- The response also includes: \`vetoedBy\`, \`unifiedFeedbackMd\`,
56211
- \`requiredFixesCount\`, \`advisoryFindingsCount\`, \`allCriteriaMet\`,
56212
- \`quorumSize\`, \`quorumMet\`.
56213
-
56214
- If \`success: false\` and \`reason: 'insufficient_quorum'\`, the response
56215
- includes \`membersVoted\`, \`membersAbsent\`, and \`quorumRequired\` — dispatch
56216
- the absent members and re-call the tool.
56217
-
56218
- #### STEP 5 ACT on the verdict
56219
- - **APPROVE**: Advance task to complete via \`update_task_status\`. If
56220
- \`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as
56221
- a single non-blocking note. Otherwise, advance silently.
56222
- - **CONCERNS**: Send \`unifiedFeedbackMd\` to the coder as ONE coherent
56223
- document. Do NOT enumerate individual member verdicts.
56224
- Increment \`roundNumber\` on the next council call. CONCERNS
56225
- does not block advancement at the update_task_status level —
56226
- decide per severity whether to advance or retry.
56227
- - **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
56228
- with the BLOCKING flag. The coder must resolve all
56229
- \`requiredFixes\` before re-submitting. Maximum
56230
- \`council.maxRounds\` rounds (default 3). If
56231
- \`roundNumber >= maxRounds\` and verdict is still REJECT,
56232
- surface \`unifiedFeedbackMd\` to the user and HALT — do NOT
56233
- auto-advance.
56234
-
56235
- ### ANTI-PATTERNS — any of these are council bypass violations
56236
- - ✗ Calling \`submit_council_verdicts\` without first dispatching council members.
56237
- - ✗ Passing a verdict you inferred or fabricated rather than received from a dispatched agent.
56204
+ Inspect \`membersAbsent\`. If non-empty, dispatch the missing members and re-collect.
56205
+ Inspect \`overallVerdict\`.
56206
+
56207
+ If \`success: false\` and \`reason: 'insufficient_quorum'\`:
56208
+ dispatch the absent members and re-call \`submit_phase_council_verdicts\`.
56209
+
56210
+ #### STEP 5 — ACT on the verdict, then call phase_complete
56211
+ - **APPROVE**: Call \`phase_complete\`. Gate 5 will pass.
56212
+ If \`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as a single
56213
+ non-blocking advisory note to the team before proceeding.
56214
+ - **CONCERNS**: Evaluate severity. Minor concerns → call \`phase_complete\` and
56215
+ surface \`unifiedFeedbackMd\` as a non-blocking note. Significant concerns →
56216
+ send \`unifiedFeedbackMd\` to the coder as ONE coherent document for resolution
56217
+ before calling \`phase_complete\`. Increment \`roundNumber\` on re-council.
56218
+ - **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
56219
+ with the BLOCKING flag. The coder must resolve all \`requiredFixes\` before
56220
+ the phase council is re-convened. Maximum \`council.maxRounds\` rounds (default 3).
56221
+ If \`roundNumber >= maxRounds\` and verdict is still REJECT, surface
56222
+ \`unifiedFeedbackMd\` to the user and HALT — do NOT auto-advance.
56223
+
56224
+ ### ANTI-PATTERNS phase council bypass violations
56225
+ - Calling \`submit_phase_council_verdicts\` without first dispatching all 5 members.
56226
+ - Passing verdicts inferred or fabricated rather than received from dispatched agents.
56238
56227
  - ✗ Claiming "Council APPROVED" when \`membersAbsent\` is non-empty.
56239
- - ✗ Treating a prior round's APPROVE as valid for a new task or new round.
56240
- - ✗ Incrementing \`roundNumber\` without re-dispatching all members for the new round.
56228
+ - ✗ Omitting per-task review gates (reviewer + test_engineer) because council mode is on these gates are mandatory regardless.
56229
+ - ✗ Calling \`phase_complete\` before council evidence has been written (Gate 5 will block you).
56230
+ - ✗ Treating a prior phase's council verdict as valid for a new phase.
56231
+ - ✗ Incrementing \`roundNumber\` without re-dispatching members for the new round.
56241
56232
 
56242
56233
  ### ROUND 2 DELIBERATION
56243
- If round 1 produces REJECT or CONCERNS, dispatch only the disputing members
56244
- for round 2 focused on the specific disagreement areas. Round 2 must produce
56245
- NEW agent responses — do NOT reuse round 1 verdicts with a higher
56246
- \`roundNumber\`.
56234
+ If round 1 produces REJECT or CONCERNS requiring re-work, dispatch only the
56235
+ dissenting members for round 2 focused on the specific areas they flagged.
56236
+ Round 2 must produce NEW agent responses — never reuse round 1 verdicts.
56247
56237
 
56248
56238
  ### Retry protocol
56249
- On re-submission after REJECT or CONCERNS, the council reads the same
56250
- pre-declared criteria and receives (a) the previous synthesis findings plus
56251
- (b) the diff of what changed since the last round. Council members verify
56252
- prior findings are resolved without re-reviewing unchanged code. The
56253
- architect resolves any \`unresolvedConflicts\` in \`unifiedFeedbackMd\` BEFORE
56254
- sending it to the coder — the coder never sees contradictory instructions
56255
- from different members.`;
56239
+ On re-submission after REJECT/CONCERNS: council members receive (a) the previous
56240
+ synthesis findings plus (b) the diff of what changed since the last round.
56241
+ Members verify prior findings are resolved without re-reviewing unchanged code.
56242
+ The architect resolves any \`unresolvedConflicts\` in \`unifiedFeedbackMd\` BEFORE
56243
+ sending it to the coder the coder never sees contradictory instructions.`;
56256
56244
  }
56257
56245
  function buildYourToolsList(council) {
56258
56246
  const tools = AGENT_TOOL_MAP.architect ?? [];
@@ -56260,7 +56248,7 @@ function buildYourToolsList(council) {
56260
56248
  const qaCouncilEnabled = council?.enabled === true;
56261
56249
  const generalCouncilEnabled = council?.general?.enabled === true;
56262
56250
  const filtered = sorted.filter((t) => {
56263
- if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria")) {
56251
+ if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria" || t === "submit_phase_council_verdicts")) {
56264
56252
  return false;
56265
56253
  }
56266
56254
  if (!generalCouncilEnabled && t === "convene_general_council") {
@@ -56294,7 +56282,7 @@ function buildAvailableToolsList(council) {
56294
56282
  const qaCouncilEnabled = council?.enabled === true;
56295
56283
  const generalCouncilEnabled = council?.general?.enabled === true;
56296
56284
  const filtered = sorted.filter((t) => {
56297
- if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria")) {
56285
+ if (!qaCouncilEnabled && (t === "submit_council_verdicts" || t === "declare_council_criteria" || t === "submit_phase_council_verdicts")) {
56298
56286
  return false;
56299
56287
  }
56300
56288
  if (!generalCouncilEnabled && t === "convene_general_council") {
@@ -56680,7 +56668,7 @@ TIER 3 — CRITICAL
56680
56668
  Pipeline: Full Stage A. Stage B = {{AGENT_PREFIX}}reviewer×2 + {{AGENT_PREFIX}}test_engineer×2.
56681
56669
  Rationale: Security paths need adversarial review.
56682
56670
 
56683
- If council is authoritative for the current plan, skip Stage B entries above and use council Phase 1 dispatch as the review pass.
56671
+ Council mode is additive Stage B always runs per-task in both modes. The council runs holistically at phase end via \`submit_phase_council_verdicts\` before calling \`phase_complete\`. Council is supplemental; Stage B is mandatory in all modes.
56684
56672
 
56685
56673
  CLASSIFICATION RULES:
56686
56674
  - Multi-tier → use HIGHEST tier.
@@ -56707,7 +56695,7 @@ Stage B runs by default for TIER 1-3 classifications. Stage A passing does not s
56707
56695
  Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
56708
56696
  You MUST delegate to each Stage B agent and wait for their response.
56709
56697
 
56710
- When council is authoritative for the current plan (\`pluginConfig.council.enabled === true\` AND \`QaGates.council_mode === true\`), Stage B is REPLACED by council Phase 1 reviewer and test_engineer are dispatched as council members in the parallel Phase 1 fan-out, not as a separate Stage B sequence. Do not run Stage B a second time after the council has rendered a verdict. Stage A (precheckbatch) still runs as the pre-review gate in both modes.
56698
+ Stage B (reviewer + test_engineer) **always runs per-task** regardless of council mode it is never replaced, never omitted, never deferred. When \`council_mode\` is enabled in the QA gate profile, a **phase-level** council review is additionally required before calling \`phase_complete\`: dispatch all 5 council members, collect their verdicts, call \`submit_phase_council_verdicts\`, then call \`phase_complete\` (Gate 5 validates the resulting \`phase-council.json\` evidence). Stage A (\`pre_check_batch\`) still runs as the pre-review gate for each task.
56711
56699
 
56712
56700
  A task is complete ONLY when BOTH stages pass.
56713
56701
 
@@ -65000,7 +64988,7 @@ var init_curator_drift = __esm(() => {
65000
64988
  // src/index.ts
65001
64989
  init_package();
65002
64990
  init_agents2();
65003
- import * as path109 from "node:path";
64991
+ import * as path110 from "node:path";
65004
64992
 
65005
64993
  // src/background/index.ts
65006
64994
  init_event_bus();
@@ -65386,7 +65374,7 @@ function createAgentActivityHooks(config3, directory) {
65386
65374
  const duration5 = Date.now() - entry.startTime;
65387
65375
  const explicitSuccess = typeof output.success === "boolean" ? output.success : undefined;
65388
65376
  const explicitFailure = explicitSuccess === false || !!output.error;
65389
- const success3 = explicitFailure ? false : true;
65377
+ const success3 = !explicitFailure;
65390
65378
  const key = entry.tool;
65391
65379
  const existing = swarmState.toolAggregates.get(key) ?? {
65392
65380
  tool: key,
@@ -76481,6 +76469,10 @@ function writeCouncilEvidence(workingDir, synthesis) {
76481
76469
  }
76482
76470
  }
76483
76471
 
76472
+ // src/council/council-service.ts
76473
+ import fs59 from "node:fs";
76474
+ import path77 from "node:path";
76475
+
76484
76476
  // src/council/types.ts
76485
76477
  var COUNCIL_DEFAULTS = {
76486
76478
  enabled: false,
@@ -76605,6 +76597,143 @@ function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisory
76605
76597
  return lines.join(`
76606
76598
  `);
76607
76599
  }
76600
+ function synthesizePhaseCouncilAdvisory(phaseNumber, phaseSummary, verdicts, roundNumber, config3 = {}, workingDir) {
76601
+ const cfg = { ...COUNCIL_DEFAULTS, ...config3 };
76602
+ const timestamp = new Date().toISOString();
76603
+ const scope = "phase";
76604
+ const quorumSize = new Set(verdicts.map((v) => v.agent)).size;
76605
+ const rejectingMembers = verdicts.filter((v) => v.verdict === "REJECT").map((v) => v.agent);
76606
+ let overallVerdict;
76607
+ if (cfg.vetoPriority && rejectingMembers.length > 0) {
76608
+ overallVerdict = "REJECT";
76609
+ } else if (verdicts.some((v) => v.verdict === "CONCERNS") || !cfg.vetoPriority && rejectingMembers.length > 0) {
76610
+ overallVerdict = "CONCERNS";
76611
+ } else {
76612
+ overallVerdict = "APPROVE";
76613
+ }
76614
+ const unresolvedConflicts = detectConflicts(verdicts);
76615
+ const rejectingSet = new Set(rejectingMembers);
76616
+ const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
76617
+ const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
76618
+ const advisoryFindings = [
76619
+ ...vetoFindings.filter((f) => f.severity === "LOW"),
76620
+ ...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
76621
+ ];
76622
+ const advisoryNotes = [];
76623
+ if (advisoryFindings.length > 0) {
76624
+ advisoryNotes.push(`Phase ${phaseNumber} council found ${advisoryFindings.length} advisory finding(s). Review before proceeding to next phase.`);
76625
+ }
76626
+ if (verdicts.length < 3) {
76627
+ advisoryNotes.push(`Phase council quorum is ${verdicts.length} members — consider convening additional members for broader review coverage.`);
76628
+ }
76629
+ const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
76630
+ const allCriteriaMet = allUnmetIds.size === 0 && verdicts.length > 0;
76631
+ const unifiedFeedbackMd = buildPhaseCouncilFeedback(phaseNumber, phaseSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
76632
+ const evidencePath = `.swarm/evidence/${phaseNumber}/phase-council.json`;
76633
+ const baseDir = workingDir ?? process.cwd();
76634
+ const evidenceDir = path77.join(baseDir, ".swarm", "evidence", String(phaseNumber));
76635
+ fs59.mkdirSync(evidenceDir, { recursive: true });
76636
+ const evidenceFile = path77.join(evidenceDir, "phase-council.json");
76637
+ const evidenceBundle = {
76638
+ entries: [
76639
+ {
76640
+ type: "phase-council",
76641
+ phase_number: phaseNumber,
76642
+ scope: "phase",
76643
+ timestamp,
76644
+ verdict: overallVerdict,
76645
+ quorumSize,
76646
+ phaseSummary,
76647
+ requiredFixes: requiredFixes.map((f) => ({
76648
+ severity: f.severity,
76649
+ category: f.category,
76650
+ location: f.location,
76651
+ detail: f.detail,
76652
+ evidence: f.evidence
76653
+ })),
76654
+ advisoryNotes,
76655
+ advisoryFindings: advisoryFindings.map((f) => ({
76656
+ severity: f.severity,
76657
+ category: f.category,
76658
+ location: f.location,
76659
+ detail: f.detail,
76660
+ evidence: f.evidence
76661
+ })),
76662
+ roundNumber,
76663
+ allCriteriaMet
76664
+ }
76665
+ ]
76666
+ };
76667
+ try {
76668
+ const tempFile = `${evidenceFile}.tmp-${Date.now()}`;
76669
+ fs59.writeFileSync(tempFile, JSON.stringify(evidenceBundle, null, 2), "utf-8");
76670
+ fs59.renameSync(tempFile, evidenceFile);
76671
+ } catch (writeErr) {
76672
+ console.warn(`[phase-council] Failed to write phase-council evidence to ${evidenceFile}: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}`);
76673
+ }
76674
+ return {
76675
+ phaseNumber,
76676
+ scope,
76677
+ timestamp,
76678
+ overallVerdict,
76679
+ vetoedBy: rejectingMembers.length > 0 ? rejectingMembers : null,
76680
+ memberVerdicts: verdicts,
76681
+ unresolvedConflicts,
76682
+ requiredFixes,
76683
+ advisoryFindings,
76684
+ advisoryNotes,
76685
+ unifiedFeedbackMd,
76686
+ roundNumber,
76687
+ allCriteriaMet,
76688
+ quorumSize,
76689
+ evidencePath,
76690
+ phaseSummary
76691
+ };
76692
+ }
76693
+ function buildPhaseCouncilFeedback(phaseNumber, phaseSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
76694
+ const lines = [
76695
+ `## Phase Council Review — Round ${roundNumber}/${maxRounds}`,
76696
+ `**Phase:** ${phaseNumber} **Overall verdict:** ${verdict}`,
76697
+ ""
76698
+ ];
76699
+ if (phaseSummary) {
76700
+ lines.push(`**Phase Summary:** ${phaseSummary}`);
76701
+ lines.push("");
76702
+ }
76703
+ if (vetoedBy.length > 0) {
76704
+ lines.push(`> ⛔ **BLOCKED** by: ${vetoedBy.join(", ")}`);
76705
+ lines.push("");
76706
+ }
76707
+ if (requiredFixes.length > 0) {
76708
+ lines.push("### Required Fixes (must resolve before re-submission)");
76709
+ for (const f of requiredFixes) {
76710
+ lines.push(`- **[${f.severity}]** \`${f.location}\` — ${f.detail}`, ` _Evidence:_ ${f.evidence}`);
76711
+ }
76712
+ lines.push("");
76713
+ }
76714
+ if (conflicts.length > 0) {
76715
+ lines.push("### Conflicts to Resolve");
76716
+ lines.push("_The following reviewers gave contradictory instructions. Architect must resolve before sending to coder._");
76717
+ for (const c of conflicts) {
76718
+ lines.push(`- ${c}`);
76719
+ }
76720
+ lines.push("");
76721
+ }
76722
+ if (advisoryFindings.length > 0) {
76723
+ lines.push("### Advisory Findings (non-blocking)");
76724
+ for (const f of advisoryFindings) {
76725
+ lines.push(`- **[${f.severity}]** \`${f.location}\` — ${f.detail}`);
76726
+ }
76727
+ lines.push("");
76728
+ }
76729
+ if (verdict === "APPROVE") {
76730
+ lines.push("> ✅ **Phase council approved.** Phase may proceed to completion.");
76731
+ } else if (roundNumber >= maxRounds) {
76732
+ lines.push(`> ⚠️ **Max rounds (${maxRounds}) reached.** Escalate to user — do not auto-advance.`);
76733
+ }
76734
+ return lines.join(`
76735
+ `);
76736
+ }
76608
76737
 
76609
76738
  // src/council/criteria-store.ts
76610
76739
  import { existsSync as existsSync39, mkdirSync as mkdirSync20, readFileSync as readFileSync37, writeFileSync as writeFileSync13 } from "node:fs";
@@ -76773,8 +76902,8 @@ var submit_council_verdicts = createSwarmTool({
76773
76902
  // src/tools/convene-general-council.ts
76774
76903
  init_zod();
76775
76904
  init_loader();
76776
- import * as fs59 from "node:fs";
76777
- import * as path77 from "node:path";
76905
+ import * as fs60 from "node:fs";
76906
+ import * as path78 from "node:path";
76778
76907
 
76779
76908
  // src/council/general-council-advisory.ts
76780
76909
  var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
@@ -77202,13 +77331,13 @@ var convene_general_council = createSwarmTool({
77202
77331
  const round1 = input.round1Responses;
77203
77332
  const round2 = input.round2Responses ?? [];
77204
77333
  const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
77205
- const evidenceDir = path77.join(workingDir, ".swarm", "council", "general");
77334
+ const evidenceDir = path78.join(workingDir, ".swarm", "council", "general");
77206
77335
  const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
77207
77336
  const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
77208
- const evidencePath = path77.join(evidenceDir, evidenceFile);
77337
+ const evidencePath = path78.join(evidenceDir, evidenceFile);
77209
77338
  try {
77210
- await fs59.promises.mkdir(evidenceDir, { recursive: true });
77211
- await fs59.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
77339
+ await fs60.promises.mkdir(evidenceDir, { recursive: true });
77340
+ await fs60.promises.writeFile(evidencePath, JSON.stringify(result, null, 2));
77212
77341
  } catch (err2) {
77213
77342
  const message = err2 instanceof Error ? err2.message : String(err2);
77214
77343
  console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
@@ -77439,8 +77568,8 @@ init_scope_persistence();
77439
77568
  init_state();
77440
77569
  init_task_id();
77441
77570
  init_create_tool();
77442
- import * as fs60 from "node:fs";
77443
- import * as path78 from "node:path";
77571
+ import * as fs61 from "node:fs";
77572
+ import * as path79 from "node:path";
77444
77573
  function validateTaskIdFormat2(taskId) {
77445
77574
  return validateTaskIdFormat(taskId);
77446
77575
  }
@@ -77514,8 +77643,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77514
77643
  };
77515
77644
  }
77516
77645
  }
77517
- normalizedDir = path78.normalize(args2.working_directory);
77518
- const pathParts = normalizedDir.split(path78.sep);
77646
+ normalizedDir = path79.normalize(args2.working_directory);
77647
+ const pathParts = normalizedDir.split(path79.sep);
77519
77648
  if (pathParts.includes("..")) {
77520
77649
  return {
77521
77650
  success: false,
@@ -77525,11 +77654,11 @@ async function executeDeclareScope(args2, fallbackDir) {
77525
77654
  ]
77526
77655
  };
77527
77656
  }
77528
- const resolvedDir = path78.resolve(normalizedDir);
77657
+ const resolvedDir = path79.resolve(normalizedDir);
77529
77658
  try {
77530
- const realPath = fs60.realpathSync(resolvedDir);
77531
- const planPath2 = path78.join(realPath, ".swarm", "plan.json");
77532
- if (!fs60.existsSync(planPath2)) {
77659
+ const realPath = fs61.realpathSync(resolvedDir);
77660
+ const planPath2 = path79.join(realPath, ".swarm", "plan.json");
77661
+ if (!fs61.existsSync(planPath2)) {
77533
77662
  return {
77534
77663
  success: false,
77535
77664
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -77552,8 +77681,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77552
77681
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
77553
77682
  }
77554
77683
  const directory = normalizedDir || fallbackDir;
77555
- const planPath = path78.resolve(directory, ".swarm", "plan.json");
77556
- if (!fs60.existsSync(planPath)) {
77684
+ const planPath = path79.resolve(directory, ".swarm", "plan.json");
77685
+ if (!fs61.existsSync(planPath)) {
77557
77686
  return {
77558
77687
  success: false,
77559
77688
  message: "No plan found",
@@ -77562,7 +77691,7 @@ async function executeDeclareScope(args2, fallbackDir) {
77562
77691
  }
77563
77692
  let planContent;
77564
77693
  try {
77565
- planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
77694
+ planContent = JSON.parse(fs61.readFileSync(planPath, "utf-8"));
77566
77695
  } catch {
77567
77696
  return {
77568
77697
  success: false,
@@ -77592,8 +77721,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77592
77721
  const normalizeErrors = [];
77593
77722
  const dir = normalizedDir || fallbackDir || process.cwd();
77594
77723
  const mergedFiles = rawMergedFiles.map((file3) => {
77595
- if (path78.isAbsolute(file3)) {
77596
- const relativePath = path78.relative(dir, file3).replace(/\\/g, "/");
77724
+ if (path79.isAbsolute(file3)) {
77725
+ const relativePath = path79.relative(dir, file3).replace(/\\/g, "/");
77597
77726
  if (relativePath.startsWith("..")) {
77598
77727
  normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
77599
77728
  return file3;
@@ -77653,8 +77782,8 @@ var declare_scope = createSwarmTool({
77653
77782
  // src/tools/diff.ts
77654
77783
  init_zod();
77655
77784
  import * as child_process7 from "node:child_process";
77656
- import * as fs61 from "node:fs";
77657
- import * as path79 from "node:path";
77785
+ import * as fs62 from "node:fs";
77786
+ import * as path80 from "node:path";
77658
77787
  init_create_tool();
77659
77788
  var MAX_DIFF_LINES = 500;
77660
77789
  var DIFF_TIMEOUT_MS = 30000;
@@ -77683,20 +77812,20 @@ function validateBase(base) {
77683
77812
  function validatePaths(paths) {
77684
77813
  if (!paths)
77685
77814
  return null;
77686
- for (const path80 of paths) {
77687
- if (!path80 || path80.length === 0) {
77815
+ for (const path81 of paths) {
77816
+ if (!path81 || path81.length === 0) {
77688
77817
  return "empty path not allowed";
77689
77818
  }
77690
- if (path80.length > MAX_PATH_LENGTH) {
77819
+ if (path81.length > MAX_PATH_LENGTH) {
77691
77820
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
77692
77821
  }
77693
- if (SHELL_METACHARACTERS2.test(path80)) {
77822
+ if (SHELL_METACHARACTERS2.test(path81)) {
77694
77823
  return "path contains shell metacharacters";
77695
77824
  }
77696
- if (path80.startsWith("-")) {
77825
+ if (path81.startsWith("-")) {
77697
77826
  return 'path cannot start with "-" (option-like arguments not allowed)';
77698
77827
  }
77699
- if (CONTROL_CHAR_PATTERN2.test(path80)) {
77828
+ if (CONTROL_CHAR_PATTERN2.test(path81)) {
77700
77829
  return "path contains control characters";
77701
77830
  }
77702
77831
  }
@@ -77802,8 +77931,8 @@ var diff = createSwarmTool({
77802
77931
  if (parts2.length >= 3) {
77803
77932
  const additions = parseInt(parts2[0], 10) || 0;
77804
77933
  const deletions = parseInt(parts2[1], 10) || 0;
77805
- const path80 = parts2[2];
77806
- files.push({ path: path80, additions, deletions });
77934
+ const path81 = parts2[2];
77935
+ files.push({ path: path81, additions, deletions });
77807
77936
  }
77808
77937
  }
77809
77938
  const contractChanges = [];
@@ -77843,7 +77972,7 @@ var diff = createSwarmTool({
77843
77972
  } else if (base === "unstaged") {
77844
77973
  const oldRef = `:${file3.path}`;
77845
77974
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
77846
- newContent = fs61.readFileSync(path79.join(directory, file3.path), "utf-8");
77975
+ newContent = fs62.readFileSync(path80.join(directory, file3.path), "utf-8");
77847
77976
  } else {
77848
77977
  const oldRef = `${base}:${file3.path}`;
77849
77978
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
@@ -77917,8 +78046,8 @@ var diff = createSwarmTool({
77917
78046
  // src/tools/diff-summary.ts
77918
78047
  init_zod();
77919
78048
  import * as child_process8 from "node:child_process";
77920
- import * as fs62 from "node:fs";
77921
- import * as path80 from "node:path";
78049
+ import * as fs63 from "node:fs";
78050
+ import * as path81 from "node:path";
77922
78051
  init_create_tool();
77923
78052
  var diff_summary = createSwarmTool({
77924
78053
  description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
@@ -77966,7 +78095,7 @@ var diff_summary = createSwarmTool({
77966
78095
  }
77967
78096
  try {
77968
78097
  let oldContent;
77969
- const newContent = fs62.readFileSync(path80.join(workingDir, filePath), "utf-8");
78098
+ const newContent = fs63.readFileSync(path81.join(workingDir, filePath), "utf-8");
77970
78099
  if (fileExistsInHead) {
77971
78100
  oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
77972
78101
  encoding: "utf-8",
@@ -78194,8 +78323,8 @@ Use these as DOMAIN values when delegating to @sme.`;
78194
78323
  init_zod();
78195
78324
  init_create_tool();
78196
78325
  init_path_security();
78197
- import * as fs63 from "node:fs";
78198
- import * as path81 from "node:path";
78326
+ import * as fs64 from "node:fs";
78327
+ import * as path82 from "node:path";
78199
78328
  var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
78200
78329
  var MAX_EVIDENCE_FILES = 1000;
78201
78330
  var EVIDENCE_DIR3 = ".swarm/evidence";
@@ -78222,9 +78351,9 @@ function validateRequiredTypes(input) {
78222
78351
  return null;
78223
78352
  }
78224
78353
  function isPathWithinSwarm2(filePath, cwd) {
78225
- const normalizedCwd = path81.resolve(cwd);
78226
- const swarmPath = path81.join(normalizedCwd, ".swarm");
78227
- const normalizedPath = path81.resolve(filePath);
78354
+ const normalizedCwd = path82.resolve(cwd);
78355
+ const swarmPath = path82.join(normalizedCwd, ".swarm");
78356
+ const normalizedPath = path82.resolve(filePath);
78228
78357
  return normalizedPath.startsWith(swarmPath);
78229
78358
  }
78230
78359
  function parseCompletedTasks(planContent) {
@@ -78240,12 +78369,12 @@ function parseCompletedTasks(planContent) {
78240
78369
  }
78241
78370
  function readEvidenceFiles(evidenceDir, _cwd) {
78242
78371
  const evidence = [];
78243
- if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
78372
+ if (!fs64.existsSync(evidenceDir) || !fs64.statSync(evidenceDir).isDirectory()) {
78244
78373
  return evidence;
78245
78374
  }
78246
78375
  let files;
78247
78376
  try {
78248
- files = fs63.readdirSync(evidenceDir);
78377
+ files = fs64.readdirSync(evidenceDir);
78249
78378
  } catch {
78250
78379
  return evidence;
78251
78380
  }
@@ -78254,14 +78383,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78254
78383
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
78255
78384
  continue;
78256
78385
  }
78257
- const filePath = path81.join(evidenceDir, filename);
78386
+ const filePath = path82.join(evidenceDir, filename);
78258
78387
  try {
78259
- const resolvedPath = path81.resolve(filePath);
78260
- const evidenceDirResolved = path81.resolve(evidenceDir);
78388
+ const resolvedPath = path82.resolve(filePath);
78389
+ const evidenceDirResolved = path82.resolve(evidenceDir);
78261
78390
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
78262
78391
  continue;
78263
78392
  }
78264
- const stat6 = fs63.lstatSync(filePath);
78393
+ const stat6 = fs64.lstatSync(filePath);
78265
78394
  if (!stat6.isFile()) {
78266
78395
  continue;
78267
78396
  }
@@ -78270,7 +78399,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78270
78399
  }
78271
78400
  let fileStat;
78272
78401
  try {
78273
- fileStat = fs63.statSync(filePath);
78402
+ fileStat = fs64.statSync(filePath);
78274
78403
  if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
78275
78404
  continue;
78276
78405
  }
@@ -78279,7 +78408,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78279
78408
  }
78280
78409
  let content;
78281
78410
  try {
78282
- content = fs63.readFileSync(filePath, "utf-8");
78411
+ content = fs64.readFileSync(filePath, "utf-8");
78283
78412
  } catch {
78284
78413
  continue;
78285
78414
  }
@@ -78375,7 +78504,7 @@ var evidence_check = createSwarmTool({
78375
78504
  return JSON.stringify(errorResult, null, 2);
78376
78505
  }
78377
78506
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
78378
- const planPath = path81.join(cwd, PLAN_FILE);
78507
+ const planPath = path82.join(cwd, PLAN_FILE);
78379
78508
  if (!isPathWithinSwarm2(planPath, cwd)) {
78380
78509
  const errorResult = {
78381
78510
  error: "plan file path validation failed",
@@ -78389,7 +78518,7 @@ var evidence_check = createSwarmTool({
78389
78518
  }
78390
78519
  let planContent;
78391
78520
  try {
78392
- planContent = fs63.readFileSync(planPath, "utf-8");
78521
+ planContent = fs64.readFileSync(planPath, "utf-8");
78393
78522
  } catch {
78394
78523
  const result2 = {
78395
78524
  message: "No completed tasks found in plan.",
@@ -78407,7 +78536,7 @@ var evidence_check = createSwarmTool({
78407
78536
  };
78408
78537
  return JSON.stringify(result2, null, 2);
78409
78538
  }
78410
- const evidenceDir = path81.join(cwd, EVIDENCE_DIR3);
78539
+ const evidenceDir = path82.join(cwd, EVIDENCE_DIR3);
78411
78540
  const evidence = readEvidenceFiles(evidenceDir, cwd);
78412
78541
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
78413
78542
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -78424,8 +78553,8 @@ var evidence_check = createSwarmTool({
78424
78553
  // src/tools/file-extractor.ts
78425
78554
  init_zod();
78426
78555
  init_create_tool();
78427
- import * as fs64 from "node:fs";
78428
- import * as path82 from "node:path";
78556
+ import * as fs65 from "node:fs";
78557
+ import * as path83 from "node:path";
78429
78558
  var EXT_MAP = {
78430
78559
  python: ".py",
78431
78560
  py: ".py",
@@ -78487,8 +78616,8 @@ var extract_code_blocks = createSwarmTool({
78487
78616
  execute: async (args2, directory) => {
78488
78617
  const { content, output_dir, prefix } = args2;
78489
78618
  const targetDir = output_dir || directory;
78490
- if (!fs64.existsSync(targetDir)) {
78491
- fs64.mkdirSync(targetDir, { recursive: true });
78619
+ if (!fs65.existsSync(targetDir)) {
78620
+ fs65.mkdirSync(targetDir, { recursive: true });
78492
78621
  }
78493
78622
  if (!content) {
78494
78623
  return "Error: content is required";
@@ -78506,16 +78635,16 @@ var extract_code_blocks = createSwarmTool({
78506
78635
  if (prefix) {
78507
78636
  filename = `${prefix}_${filename}`;
78508
78637
  }
78509
- let filepath = path82.join(targetDir, filename);
78510
- const base = path82.basename(filepath, path82.extname(filepath));
78511
- const ext = path82.extname(filepath);
78638
+ let filepath = path83.join(targetDir, filename);
78639
+ const base = path83.basename(filepath, path83.extname(filepath));
78640
+ const ext = path83.extname(filepath);
78512
78641
  let counter = 1;
78513
- while (fs64.existsSync(filepath)) {
78514
- filepath = path82.join(targetDir, `${base}_${counter}${ext}`);
78642
+ while (fs65.existsSync(filepath)) {
78643
+ filepath = path83.join(targetDir, `${base}_${counter}${ext}`);
78515
78644
  counter++;
78516
78645
  }
78517
78646
  try {
78518
- fs64.writeFileSync(filepath, code.trim(), "utf-8");
78647
+ fs65.writeFileSync(filepath, code.trim(), "utf-8");
78519
78648
  savedFiles.push(filepath);
78520
78649
  } catch (error93) {
78521
78650
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -78774,8 +78903,8 @@ var gitingest = createSwarmTool({
78774
78903
  init_zod();
78775
78904
  init_create_tool();
78776
78905
  init_path_security();
78777
- import * as fs65 from "node:fs";
78778
- import * as path83 from "node:path";
78906
+ import * as fs66 from "node:fs";
78907
+ import * as path84 from "node:path";
78779
78908
  var MAX_FILE_PATH_LENGTH2 = 500;
78780
78909
  var MAX_SYMBOL_LENGTH = 256;
78781
78910
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
@@ -78823,7 +78952,7 @@ function validateSymbolInput(symbol3) {
78823
78952
  return null;
78824
78953
  }
78825
78954
  function isBinaryFile2(filePath, buffer) {
78826
- const ext = path83.extname(filePath).toLowerCase();
78955
+ const ext = path84.extname(filePath).toLowerCase();
78827
78956
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
78828
78957
  return false;
78829
78958
  }
@@ -78847,15 +78976,15 @@ function parseImports(content, targetFile, targetSymbol) {
78847
78976
  const imports = [];
78848
78977
  let _resolvedTarget;
78849
78978
  try {
78850
- _resolvedTarget = path83.resolve(targetFile);
78979
+ _resolvedTarget = path84.resolve(targetFile);
78851
78980
  } catch {
78852
78981
  _resolvedTarget = targetFile;
78853
78982
  }
78854
- const targetBasename = path83.basename(targetFile, path83.extname(targetFile));
78983
+ const targetBasename = path84.basename(targetFile, path84.extname(targetFile));
78855
78984
  const targetWithExt = targetFile;
78856
78985
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
78857
- const normalizedTargetWithExt = path83.normalize(targetWithExt).replace(/\\/g, "/");
78858
- const normalizedTargetWithoutExt = path83.normalize(targetWithoutExt).replace(/\\/g, "/");
78986
+ const normalizedTargetWithExt = path84.normalize(targetWithExt).replace(/\\/g, "/");
78987
+ const normalizedTargetWithoutExt = path84.normalize(targetWithoutExt).replace(/\\/g, "/");
78859
78988
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
78860
78989
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
78861
78990
  const modulePath = match[1] || match[2] || match[3];
@@ -78878,9 +79007,9 @@ function parseImports(content, targetFile, targetSymbol) {
78878
79007
  }
78879
79008
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
78880
79009
  let isMatch = false;
78881
- const _targetDir = path83.dirname(targetFile);
78882
- const targetExt = path83.extname(targetFile);
78883
- const targetBasenameNoExt = path83.basename(targetFile, targetExt);
79010
+ const _targetDir = path84.dirname(targetFile);
79011
+ const targetExt = path84.extname(targetFile);
79012
+ const targetBasenameNoExt = path84.basename(targetFile, targetExt);
78884
79013
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
78885
79014
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
78886
79015
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -78937,7 +79066,7 @@ var SKIP_DIRECTORIES4 = new Set([
78937
79066
  function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
78938
79067
  let entries;
78939
79068
  try {
78940
- entries = fs65.readdirSync(dir);
79069
+ entries = fs66.readdirSync(dir);
78941
79070
  } catch (e) {
78942
79071
  stats.fileErrors.push({
78943
79072
  path: dir,
@@ -78948,13 +79077,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
78948
79077
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
78949
79078
  for (const entry of entries) {
78950
79079
  if (SKIP_DIRECTORIES4.has(entry)) {
78951
- stats.skippedDirs.push(path83.join(dir, entry));
79080
+ stats.skippedDirs.push(path84.join(dir, entry));
78952
79081
  continue;
78953
79082
  }
78954
- const fullPath = path83.join(dir, entry);
79083
+ const fullPath = path84.join(dir, entry);
78955
79084
  let stat6;
78956
79085
  try {
78957
- stat6 = fs65.statSync(fullPath);
79086
+ stat6 = fs66.statSync(fullPath);
78958
79087
  } catch (e) {
78959
79088
  stats.fileErrors.push({
78960
79089
  path: fullPath,
@@ -78965,7 +79094,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
78965
79094
  if (stat6.isDirectory()) {
78966
79095
  findSourceFiles2(fullPath, files, stats);
78967
79096
  } else if (stat6.isFile()) {
78968
- const ext = path83.extname(fullPath).toLowerCase();
79097
+ const ext = path84.extname(fullPath).toLowerCase();
78969
79098
  if (SUPPORTED_EXTENSIONS3.includes(ext)) {
78970
79099
  files.push(fullPath);
78971
79100
  }
@@ -79022,8 +79151,8 @@ var imports = createSwarmTool({
79022
79151
  return JSON.stringify(errorResult, null, 2);
79023
79152
  }
79024
79153
  try {
79025
- const targetFile = path83.resolve(file3);
79026
- if (!fs65.existsSync(targetFile)) {
79154
+ const targetFile = path84.resolve(file3);
79155
+ if (!fs66.existsSync(targetFile)) {
79027
79156
  const errorResult = {
79028
79157
  error: `target file not found: ${file3}`,
79029
79158
  target: file3,
@@ -79033,7 +79162,7 @@ var imports = createSwarmTool({
79033
79162
  };
79034
79163
  return JSON.stringify(errorResult, null, 2);
79035
79164
  }
79036
- const targetStat = fs65.statSync(targetFile);
79165
+ const targetStat = fs66.statSync(targetFile);
79037
79166
  if (!targetStat.isFile()) {
79038
79167
  const errorResult = {
79039
79168
  error: "target must be a file, not a directory",
@@ -79044,7 +79173,7 @@ var imports = createSwarmTool({
79044
79173
  };
79045
79174
  return JSON.stringify(errorResult, null, 2);
79046
79175
  }
79047
- const baseDir = path83.dirname(targetFile);
79176
+ const baseDir = path84.dirname(targetFile);
79048
79177
  const scanStats = {
79049
79178
  skippedDirs: [],
79050
79179
  skippedFiles: 0,
@@ -79059,12 +79188,12 @@ var imports = createSwarmTool({
79059
79188
  if (consumers.length >= MAX_CONSUMERS)
79060
79189
  break;
79061
79190
  try {
79062
- const stat6 = fs65.statSync(filePath);
79191
+ const stat6 = fs66.statSync(filePath);
79063
79192
  if (stat6.size > MAX_FILE_SIZE_BYTES7) {
79064
79193
  skippedFileCount++;
79065
79194
  continue;
79066
79195
  }
79067
- const buffer = fs65.readFileSync(filePath);
79196
+ const buffer = fs66.readFileSync(filePath);
79068
79197
  if (isBinaryFile2(filePath, buffer)) {
79069
79198
  skippedFileCount++;
79070
79199
  continue;
@@ -79589,8 +79718,8 @@ init_schema();
79589
79718
  init_qa_gate_profile();
79590
79719
  init_manager2();
79591
79720
  init_curator();
79592
- import * as fs67 from "node:fs";
79593
- import * as path85 from "node:path";
79721
+ import * as fs68 from "node:fs";
79722
+ import * as path86 from "node:path";
79594
79723
  init_knowledge_curator();
79595
79724
  init_knowledge_reader();
79596
79725
  init_knowledge_store();
@@ -79602,20 +79731,20 @@ init_file_locks();
79602
79731
  init_plan_schema();
79603
79732
  init_ledger();
79604
79733
  init_manager();
79605
- import * as fs66 from "node:fs";
79606
- import * as path84 from "node:path";
79734
+ import * as fs67 from "node:fs";
79735
+ import * as path85 from "node:path";
79607
79736
  async function writeCheckpoint(directory) {
79608
79737
  try {
79609
79738
  const plan = await loadPlan(directory);
79610
79739
  if (!plan)
79611
79740
  return;
79612
- const swarmDir = path84.join(directory, ".swarm");
79613
- fs66.mkdirSync(swarmDir, { recursive: true });
79614
- const jsonPath = path84.join(swarmDir, "SWARM_PLAN.json");
79615
- const mdPath = path84.join(swarmDir, "SWARM_PLAN.md");
79616
- fs66.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf8");
79741
+ const swarmDir = path85.join(directory, ".swarm");
79742
+ fs67.mkdirSync(swarmDir, { recursive: true });
79743
+ const jsonPath = path85.join(swarmDir, "SWARM_PLAN.json");
79744
+ const mdPath = path85.join(swarmDir, "SWARM_PLAN.md");
79745
+ fs67.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf8");
79617
79746
  const md = derivePlanMarkdown(plan);
79618
- fs66.writeFileSync(mdPath, md, "utf8");
79747
+ fs67.writeFileSync(mdPath, md, "utf8");
79619
79748
  } catch (error93) {
79620
79749
  console.warn(`[checkpoint] Failed to write SWARM_PLAN checkpoint: ${error93 instanceof Error ? error93.message : String(error93)}`);
79621
79750
  }
@@ -79847,8 +79976,8 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79847
79976
  let driftCheckEnabled = true;
79848
79977
  let driftHasSpecMd = false;
79849
79978
  try {
79850
- const specMdPath = path85.join(dir, ".swarm", "spec.md");
79851
- driftHasSpecMd = fs67.existsSync(specMdPath);
79979
+ const specMdPath = path86.join(dir, ".swarm", "spec.md");
79980
+ driftHasSpecMd = fs68.existsSync(specMdPath);
79852
79981
  const gatePlan = await loadPlan(dir);
79853
79982
  if (gatePlan) {
79854
79983
  const gatePlanId = `${gatePlan.swarm}-${gatePlan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
@@ -79869,9 +79998,9 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79869
79998
  } else {
79870
79999
  let phaseType;
79871
80000
  try {
79872
- const planPath = path85.join(dir, ".swarm", "plan.json");
79873
- if (fs67.existsSync(planPath)) {
79874
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80001
+ const planPath = path86.join(dir, ".swarm", "plan.json");
80002
+ if (fs68.existsSync(planPath)) {
80003
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
79875
80004
  const plan = JSON.parse(planRaw);
79876
80005
  const targetPhase = plan.phases?.find((p) => p.id === phase);
79877
80006
  phaseType = targetPhase?.type;
@@ -79882,11 +80011,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79882
80011
  warnings.push(`Phase ${phase} is annotated as 'non-code'. Drift verification was skipped per phase type annotation.`);
79883
80012
  } else {
79884
80013
  try {
79885
- const driftEvidencePath = path85.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
80014
+ const driftEvidencePath = path86.join(dir, ".swarm", "evidence", String(phase), "drift-verifier.json");
79886
80015
  let driftVerdictFound = false;
79887
80016
  let driftVerdictApproved = false;
79888
80017
  try {
79889
- const driftEvidenceContent = fs67.readFileSync(driftEvidencePath, "utf-8");
80018
+ const driftEvidenceContent = fs68.readFileSync(driftEvidencePath, "utf-8");
79890
80019
  const driftEvidence = JSON.parse(driftEvidenceContent);
79891
80020
  const entries = driftEvidence.entries ?? [];
79892
80021
  for (const entry of entries) {
@@ -79920,9 +80049,9 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79920
80049
  let incompleteTaskCount = 0;
79921
80050
  let planParseable = false;
79922
80051
  try {
79923
- const planPath = path85.join(dir, ".swarm", "plan.json");
79924
- if (fs67.existsSync(planPath)) {
79925
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80052
+ const planPath = path86.join(dir, ".swarm", "plan.json");
80053
+ if (fs68.existsSync(planPath)) {
80054
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
79926
80055
  const plan = JSON.parse(planRaw);
79927
80056
  planParseable = true;
79928
80057
  const planPhase = plan.phases?.find((p) => p.id === phase);
@@ -79987,11 +80116,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79987
80116
  const overrides = session2?.qaGateSessionOverrides ?? {};
79988
80117
  const effective = getEffectiveGates(profile, overrides);
79989
80118
  if (effective.hallucination_guard === true) {
79990
- const hgPath = path85.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
80119
+ const hgPath = path86.join(dir, ".swarm", "evidence", String(phase), "hallucination-guard.json");
79991
80120
  let hgVerdictFound = false;
79992
80121
  let hgVerdictApproved = false;
79993
80122
  try {
79994
- const hgContent = fs67.readFileSync(hgPath, "utf-8");
80123
+ const hgContent = fs68.readFileSync(hgPath, "utf-8");
79995
80124
  const hgBundle = JSON.parse(hgContent);
79996
80125
  for (const entry of hgBundle.entries ?? []) {
79997
80126
  if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
@@ -80059,11 +80188,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
80059
80188
  const overrides = session2?.qaGateSessionOverrides ?? {};
80060
80189
  const effective = getEffectiveGates(profile, overrides);
80061
80190
  if (effective.mutation_test === true) {
80062
- const mgPath = path85.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
80191
+ const mgPath = path86.join(dir, ".swarm", "evidence", String(phase), "mutation-gate.json");
80063
80192
  let mgVerdictFound = false;
80064
80193
  let mgVerdict;
80065
80194
  try {
80066
- const mgContent = fs67.readFileSync(mgPath, "utf-8");
80195
+ const mgContent = fs68.readFileSync(mgPath, "utf-8");
80067
80196
  const mgBundle = JSON.parse(mgContent);
80068
80197
  for (const entry of mgBundle.entries ?? []) {
80069
80198
  if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
@@ -80133,14 +80262,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
80133
80262
  const effective = getEffectiveGates(profile, overrides);
80134
80263
  if (effective.council_mode === true) {
80135
80264
  councilModeEnabled = true;
80136
- const pcPath = path85.join(dir, ".swarm", "evidence", String(phase), "phase-council.json");
80265
+ const pcPath = path86.join(dir, ".swarm", "evidence", String(phase), "phase-council.json");
80137
80266
  let pcVerdictFound = false;
80138
80267
  let _pcVerdict;
80139
80268
  let pcQuorumSize;
80140
80269
  let pcTimestamp;
80141
80270
  let pcPhaseNumber;
80142
80271
  try {
80143
- const pcContent = fs67.readFileSync(pcPath, "utf-8");
80272
+ const pcContent = fs68.readFileSync(pcPath, "utf-8");
80144
80273
  const pcBundle = JSON.parse(pcContent);
80145
80274
  for (const entry of pcBundle.entries ?? []) {
80146
80275
  if (typeof entry.type === "string" && entry.type === "phase-council" && typeof entry.verdict === "string") {
@@ -80249,11 +80378,11 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80249
80378
  status: "blocked",
80250
80379
  reason: "PHASE_COUNCIL_REQUIRED",
80251
80380
  phase_council_required: true,
80252
- message: `Phase ${phase} cannot be completed: council_mode is enabled and phase council evidence not found at .swarm/evidence/${phase}/phase-council.json. Convene a phase-level council (dispatch 5 members, collect verdicts, call submit_council_verdicts) before completing the phase.`,
80381
+ message: `Phase ${phase} cannot be completed: council_mode is enabled and phase council evidence not found at .swarm/evidence/${phase}/phase-council.json. Convene a phase-level council (dispatch 5 members, collect verdicts, call submit_phase_council_verdicts) before completing the phase.`,
80253
80382
  agentsDispatched,
80254
80383
  agentsMissing: [],
80255
80384
  warnings: [
80256
- `Phase council required — convene 5 council members (critic, reviewer, sme, test_engineer, explorer) for holistic phase review. Call submit_council_verdicts to synthesize verdicts and write phase-council.json evidence.`
80385
+ `Phase council required — convene 5 council members (critic, reviewer, sme, test_engineer, explorer) for holistic phase review. Call submit_phase_council_verdicts to synthesize verdicts and write phase-council.json evidence.`
80257
80386
  ]
80258
80387
  }, null, 2);
80259
80388
  }
@@ -80335,7 +80464,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80335
80464
  }
80336
80465
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
80337
80466
  try {
80338
- const projectName = path85.basename(dir);
80467
+ const projectName = path86.basename(dir);
80339
80468
  const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
80340
80469
  if (curationResult) {
80341
80470
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -80415,7 +80544,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80415
80544
  let phaseRequiredAgents;
80416
80545
  try {
80417
80546
  const planPath = validateSwarmPath(dir, "plan.json");
80418
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80547
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80419
80548
  const plan = JSON.parse(planRaw);
80420
80549
  const phaseObj = plan.phases.find((p) => p.id === phase);
80421
80550
  phaseRequiredAgents = phaseObj?.required_agents;
@@ -80430,7 +80559,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80430
80559
  if (agentsMissing.length > 0) {
80431
80560
  try {
80432
80561
  const planPath = validateSwarmPath(dir, "plan.json");
80433
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80562
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80434
80563
  const plan = JSON.parse(planRaw);
80435
80564
  const targetPhase = plan.phases.find((p) => p.id === phase);
80436
80565
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -80470,7 +80599,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80470
80599
  if (phaseCompleteConfig.regression_sweep?.enforce) {
80471
80600
  try {
80472
80601
  const planPath = validateSwarmPath(dir, "plan.json");
80473
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80602
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80474
80603
  const plan = JSON.parse(planRaw);
80475
80604
  const targetPhase = plan.phases.find((p) => p.id === phase);
80476
80605
  if (targetPhase) {
@@ -80524,7 +80653,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80524
80653
  }
80525
80654
  try {
80526
80655
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
80527
- fs67.appendFileSync(eventsPath, `${JSON.stringify(event)}
80656
+ fs68.appendFileSync(eventsPath, `${JSON.stringify(event)}
80528
80657
  `, "utf-8");
80529
80658
  } catch (writeError) {
80530
80659
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -80599,12 +80728,12 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80599
80728
  warnings.push(`Warning: failed to update plan.json phase status`);
80600
80729
  try {
80601
80730
  const planPath = validateSwarmPath(dir, "plan.json");
80602
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80731
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80603
80732
  const plan2 = JSON.parse(planRaw);
80604
80733
  const phaseObj = plan2.phases.find((p) => p.id === phase);
80605
80734
  if (phaseObj) {
80606
80735
  phaseObj.status = "complete";
80607
- fs67.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
80736
+ fs68.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
80608
80737
  }
80609
80738
  } catch {}
80610
80739
  } else if (plan) {
@@ -80641,12 +80770,12 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80641
80770
  warnings.push(`Warning: failed to update plan.json phase status`);
80642
80771
  try {
80643
80772
  const planPath = validateSwarmPath(dir, "plan.json");
80644
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80773
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80645
80774
  const plan = JSON.parse(planRaw);
80646
80775
  const phaseObj = plan.phases.find((p) => p.id === phase);
80647
80776
  if (phaseObj) {
80648
80777
  phaseObj.status = "complete";
80649
- fs67.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
80778
+ fs68.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
80650
80779
  }
80651
80780
  } catch {}
80652
80781
  }
@@ -80704,8 +80833,8 @@ init_discovery();
80704
80833
  init_utils();
80705
80834
  init_bun_compat();
80706
80835
  init_create_tool();
80707
- import * as fs68 from "node:fs";
80708
- import * as path86 from "node:path";
80836
+ import * as fs69 from "node:fs";
80837
+ import * as path87 from "node:path";
80709
80838
  var MAX_OUTPUT_BYTES5 = 52428800;
80710
80839
  var AUDIT_TIMEOUT_MS = 120000;
80711
80840
  function isValidEcosystem(value) {
@@ -80733,31 +80862,31 @@ function validateArgs3(args2) {
80733
80862
  function detectEcosystems(directory) {
80734
80863
  const ecosystems = [];
80735
80864
  const cwd = directory;
80736
- if (fs68.existsSync(path86.join(cwd, "package.json"))) {
80865
+ if (fs69.existsSync(path87.join(cwd, "package.json"))) {
80737
80866
  ecosystems.push("npm");
80738
80867
  }
80739
- if (fs68.existsSync(path86.join(cwd, "pyproject.toml")) || fs68.existsSync(path86.join(cwd, "requirements.txt"))) {
80868
+ if (fs69.existsSync(path87.join(cwd, "pyproject.toml")) || fs69.existsSync(path87.join(cwd, "requirements.txt"))) {
80740
80869
  ecosystems.push("pip");
80741
80870
  }
80742
- if (fs68.existsSync(path86.join(cwd, "Cargo.toml"))) {
80871
+ if (fs69.existsSync(path87.join(cwd, "Cargo.toml"))) {
80743
80872
  ecosystems.push("cargo");
80744
80873
  }
80745
- if (fs68.existsSync(path86.join(cwd, "go.mod"))) {
80874
+ if (fs69.existsSync(path87.join(cwd, "go.mod"))) {
80746
80875
  ecosystems.push("go");
80747
80876
  }
80748
80877
  try {
80749
- const files = fs68.readdirSync(cwd);
80878
+ const files = fs69.readdirSync(cwd);
80750
80879
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
80751
80880
  ecosystems.push("dotnet");
80752
80881
  }
80753
80882
  } catch {}
80754
- if (fs68.existsSync(path86.join(cwd, "Gemfile")) || fs68.existsSync(path86.join(cwd, "Gemfile.lock"))) {
80883
+ if (fs69.existsSync(path87.join(cwd, "Gemfile")) || fs69.existsSync(path87.join(cwd, "Gemfile.lock"))) {
80755
80884
  ecosystems.push("ruby");
80756
80885
  }
80757
- if (fs68.existsSync(path86.join(cwd, "pubspec.yaml"))) {
80886
+ if (fs69.existsSync(path87.join(cwd, "pubspec.yaml"))) {
80758
80887
  ecosystems.push("dart");
80759
80888
  }
80760
- if (fs68.existsSync(path86.join(cwd, "composer.lock"))) {
80889
+ if (fs69.existsSync(path87.join(cwd, "composer.lock"))) {
80761
80890
  ecosystems.push("composer");
80762
80891
  }
80763
80892
  return ecosystems;
@@ -81892,8 +82021,8 @@ var pkg_audit = createSwarmTool({
81892
82021
  // src/tools/placeholder-scan.ts
81893
82022
  init_zod();
81894
82023
  init_manager2();
81895
- import * as fs69 from "node:fs";
81896
- import * as path87 from "node:path";
82024
+ import * as fs70 from "node:fs";
82025
+ import * as path88 from "node:path";
81897
82026
  init_utils();
81898
82027
  init_create_tool();
81899
82028
  var MAX_FILE_SIZE = 1024 * 1024;
@@ -82016,7 +82145,7 @@ function isScaffoldFile(filePath) {
82016
82145
  if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
82017
82146
  return true;
82018
82147
  }
82019
- const filename = path87.basename(filePath);
82148
+ const filename = path88.basename(filePath);
82020
82149
  if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
82021
82150
  return true;
82022
82151
  }
@@ -82033,7 +82162,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
82033
82162
  if (regex.test(normalizedPath)) {
82034
82163
  return true;
82035
82164
  }
82036
- const filename = path87.basename(filePath);
82165
+ const filename = path88.basename(filePath);
82037
82166
  const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
82038
82167
  if (filenameRegex.test(filename)) {
82039
82168
  return true;
@@ -82042,7 +82171,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
82042
82171
  return false;
82043
82172
  }
82044
82173
  function isParserSupported(filePath) {
82045
- const ext = path87.extname(filePath).toLowerCase();
82174
+ const ext = path88.extname(filePath).toLowerCase();
82046
82175
  return SUPPORTED_PARSER_EXTENSIONS.has(ext);
82047
82176
  }
82048
82177
  function isPlanFile(filePath) {
@@ -82289,28 +82418,28 @@ async function placeholderScan(input, directory) {
82289
82418
  let filesScanned = 0;
82290
82419
  const filesWithFindings = new Set;
82291
82420
  for (const filePath of changed_files) {
82292
- const fullPath = path87.isAbsolute(filePath) ? filePath : path87.resolve(directory, filePath);
82293
- const resolvedDirectory = path87.resolve(directory);
82294
- if (!fullPath.startsWith(resolvedDirectory + path87.sep) && fullPath !== resolvedDirectory) {
82421
+ const fullPath = path88.isAbsolute(filePath) ? filePath : path88.resolve(directory, filePath);
82422
+ const resolvedDirectory = path88.resolve(directory);
82423
+ if (!fullPath.startsWith(resolvedDirectory + path88.sep) && fullPath !== resolvedDirectory) {
82295
82424
  continue;
82296
82425
  }
82297
- if (!fs69.existsSync(fullPath)) {
82426
+ if (!fs70.existsSync(fullPath)) {
82298
82427
  continue;
82299
82428
  }
82300
82429
  if (isAllowedByGlobs(filePath, allow_globs)) {
82301
82430
  continue;
82302
82431
  }
82303
- const relativeFilePath = path87.relative(directory, fullPath).replace(/\\/g, "/");
82432
+ const relativeFilePath = path88.relative(directory, fullPath).replace(/\\/g, "/");
82304
82433
  if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
82305
82434
  continue;
82306
82435
  }
82307
82436
  let content;
82308
82437
  try {
82309
- const stat6 = fs69.statSync(fullPath);
82438
+ const stat6 = fs70.statSync(fullPath);
82310
82439
  if (stat6.size > MAX_FILE_SIZE) {
82311
82440
  continue;
82312
82441
  }
82313
- content = fs69.readFileSync(fullPath, "utf-8");
82442
+ content = fs70.readFileSync(fullPath, "utf-8");
82314
82443
  } catch {
82315
82444
  continue;
82316
82445
  }
@@ -82371,8 +82500,8 @@ var placeholder_scan = createSwarmTool({
82371
82500
  }
82372
82501
  });
82373
82502
  // src/tools/pre-check-batch.ts
82374
- import * as fs72 from "node:fs";
82375
- import * as path90 from "node:path";
82503
+ import * as fs73 from "node:fs";
82504
+ import * as path91 from "node:path";
82376
82505
  init_zod();
82377
82506
  init_manager2();
82378
82507
  init_utils();
@@ -82509,8 +82638,8 @@ var quality_budget = createSwarmTool({
82509
82638
  init_zod();
82510
82639
  init_manager2();
82511
82640
  init_detector();
82512
- import * as fs71 from "node:fs";
82513
- import * as path89 from "node:path";
82641
+ import * as fs72 from "node:fs";
82642
+ import * as path90 from "node:path";
82514
82643
  import { extname as extname18 } from "node:path";
82515
82644
 
82516
82645
  // src/sast/rules/c.ts
@@ -83403,25 +83532,25 @@ init_create_tool();
83403
83532
  // src/tools/sast-baseline.ts
83404
83533
  init_utils2();
83405
83534
  import * as crypto8 from "node:crypto";
83406
- import * as fs70 from "node:fs";
83407
- import * as path88 from "node:path";
83535
+ import * as fs71 from "node:fs";
83536
+ import * as path89 from "node:path";
83408
83537
  var BASELINE_SCHEMA_VERSION = "1.0.0";
83409
83538
  var MAX_BASELINE_FINDINGS = 2000;
83410
83539
  var MAX_BASELINE_BYTES = 2 * 1048576;
83411
83540
  var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
83412
83541
  function normalizeFindingPath(directory, file3) {
83413
- const resolved = path88.isAbsolute(file3) ? file3 : path88.resolve(directory, file3);
83414
- const rel = path88.relative(path88.resolve(directory), resolved);
83542
+ const resolved = path89.isAbsolute(file3) ? file3 : path89.resolve(directory, file3);
83543
+ const rel = path89.relative(path89.resolve(directory), resolved);
83415
83544
  return rel.replace(/\\/g, "/");
83416
83545
  }
83417
83546
  function baselineRelPath(phase) {
83418
- return path88.join("evidence", String(phase), "sast-baseline.json");
83547
+ return path89.join("evidence", String(phase), "sast-baseline.json");
83419
83548
  }
83420
83549
  function tempRelPath(phase) {
83421
- return path88.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
83550
+ return path89.join("evidence", String(phase), `sast-baseline.json.tmp.${Date.now()}.${process.pid}`);
83422
83551
  }
83423
83552
  function lockRelPath(phase) {
83424
- return path88.join("evidence", String(phase), "sast-baseline.json.lock");
83553
+ return path89.join("evidence", String(phase), "sast-baseline.json.lock");
83425
83554
  }
83426
83555
  function getLine(lines, idx) {
83427
83556
  if (idx < 0 || idx >= lines.length)
@@ -83438,7 +83567,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
83438
83567
  }
83439
83568
  const lineNum = finding.location.line;
83440
83569
  try {
83441
- const content = fs70.readFileSync(finding.location.file, "utf-8");
83570
+ const content = fs71.readFileSync(finding.location.file, "utf-8");
83442
83571
  const lines = content.split(`
83443
83572
  `);
83444
83573
  const idx = lineNum - 1;
@@ -83469,7 +83598,7 @@ function assignOccurrenceIndices(findings, directory) {
83469
83598
  try {
83470
83599
  if (relFile.startsWith(".."))
83471
83600
  throw new Error("escapes workspace");
83472
- const content = fs70.readFileSync(finding.location.file, "utf-8");
83601
+ const content = fs71.readFileSync(finding.location.file, "utf-8");
83473
83602
  const lines = content.split(`
83474
83603
  `);
83475
83604
  const idx = lineNum - 1;
@@ -83498,11 +83627,11 @@ function assignOccurrenceIndices(findings, directory) {
83498
83627
  async function acquireLock(lockPath) {
83499
83628
  for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
83500
83629
  try {
83501
- const fd = fs70.openSync(lockPath, "wx");
83502
- fs70.closeSync(fd);
83630
+ const fd = fs71.openSync(lockPath, "wx");
83631
+ fs71.closeSync(fd);
83503
83632
  return () => {
83504
83633
  try {
83505
- fs70.unlinkSync(lockPath);
83634
+ fs71.unlinkSync(lockPath);
83506
83635
  } catch {}
83507
83636
  };
83508
83637
  } catch {
@@ -83542,13 +83671,13 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83542
83671
  message: e instanceof Error ? e.message : "Path validation failed"
83543
83672
  };
83544
83673
  }
83545
- fs70.mkdirSync(path88.dirname(baselinePath), { recursive: true });
83546
- fs70.mkdirSync(path88.dirname(tempPath), { recursive: true });
83674
+ fs71.mkdirSync(path89.dirname(baselinePath), { recursive: true });
83675
+ fs71.mkdirSync(path89.dirname(tempPath), { recursive: true });
83547
83676
  const releaseLock = await acquireLock(lockPath);
83548
83677
  try {
83549
83678
  let existing = null;
83550
83679
  try {
83551
- const raw = fs70.readFileSync(baselinePath, "utf-8");
83680
+ const raw = fs71.readFileSync(baselinePath, "utf-8");
83552
83681
  const parsed = JSON.parse(raw);
83553
83682
  if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
83554
83683
  existing = parsed;
@@ -83608,8 +83737,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83608
83737
  message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
83609
83738
  };
83610
83739
  }
83611
- fs70.writeFileSync(tempPath, json4, "utf-8");
83612
- fs70.renameSync(tempPath, baselinePath);
83740
+ fs71.writeFileSync(tempPath, json4, "utf-8");
83741
+ fs71.renameSync(tempPath, baselinePath);
83613
83742
  return {
83614
83743
  status: "merged",
83615
83744
  path: baselinePath,
@@ -83640,8 +83769,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83640
83769
  message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
83641
83770
  };
83642
83771
  }
83643
- fs70.writeFileSync(tempPath, json3, "utf-8");
83644
- fs70.renameSync(tempPath, baselinePath);
83772
+ fs71.writeFileSync(tempPath, json3, "utf-8");
83773
+ fs71.renameSync(tempPath, baselinePath);
83645
83774
  return {
83646
83775
  status: "written",
83647
83776
  path: baselinePath,
@@ -83666,7 +83795,7 @@ function loadBaseline(directory, phase) {
83666
83795
  };
83667
83796
  }
83668
83797
  try {
83669
- const raw = fs70.readFileSync(baselinePath, "utf-8");
83798
+ const raw = fs71.readFileSync(baselinePath, "utf-8");
83670
83799
  const parsed = JSON.parse(raw);
83671
83800
  if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
83672
83801
  return {
@@ -83708,17 +83837,17 @@ var SEVERITY_ORDER = {
83708
83837
  };
83709
83838
  function shouldSkipFile(filePath) {
83710
83839
  try {
83711
- const stats = fs71.statSync(filePath);
83840
+ const stats = fs72.statSync(filePath);
83712
83841
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
83713
83842
  return { skip: true, reason: "file too large" };
83714
83843
  }
83715
83844
  if (stats.size === 0) {
83716
83845
  return { skip: true, reason: "empty file" };
83717
83846
  }
83718
- const fd = fs71.openSync(filePath, "r");
83847
+ const fd = fs72.openSync(filePath, "r");
83719
83848
  const buffer = Buffer.alloc(8192);
83720
- const bytesRead = fs71.readSync(fd, buffer, 0, 8192, 0);
83721
- fs71.closeSync(fd);
83849
+ const bytesRead = fs72.readSync(fd, buffer, 0, 8192, 0);
83850
+ fs72.closeSync(fd);
83722
83851
  if (bytesRead > 0) {
83723
83852
  let nullCount = 0;
83724
83853
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -83757,7 +83886,7 @@ function countBySeverity(findings) {
83757
83886
  }
83758
83887
  function scanFileWithTierA(filePath, language) {
83759
83888
  try {
83760
- const content = fs71.readFileSync(filePath, "utf-8");
83889
+ const content = fs72.readFileSync(filePath, "utf-8");
83761
83890
  const findings = executeRulesSync(filePath, content, language);
83762
83891
  return findings.map((f) => ({
83763
83892
  rule_id: f.rule_id,
@@ -83810,13 +83939,13 @@ async function sastScan(input, directory, config3) {
83810
83939
  _filesSkipped++;
83811
83940
  continue;
83812
83941
  }
83813
- const resolvedPath = path89.isAbsolute(filePath) ? filePath : path89.resolve(directory, filePath);
83814
- const resolvedDirectory = path89.resolve(directory);
83815
- if (!resolvedPath.startsWith(resolvedDirectory + path89.sep) && resolvedPath !== resolvedDirectory) {
83942
+ const resolvedPath = path90.isAbsolute(filePath) ? filePath : path90.resolve(directory, filePath);
83943
+ const resolvedDirectory = path90.resolve(directory);
83944
+ if (!resolvedPath.startsWith(resolvedDirectory + path90.sep) && resolvedPath !== resolvedDirectory) {
83816
83945
  _filesSkipped++;
83817
83946
  continue;
83818
83947
  }
83819
- if (!fs71.existsSync(resolvedPath)) {
83948
+ if (!fs72.existsSync(resolvedPath)) {
83820
83949
  _filesSkipped++;
83821
83950
  continue;
83822
83951
  }
@@ -84123,18 +84252,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
84123
84252
  let resolved;
84124
84253
  const isWinAbs = isWindowsAbsolutePath(inputPath);
84125
84254
  if (isWinAbs) {
84126
- resolved = path90.win32.resolve(inputPath);
84127
- } else if (path90.isAbsolute(inputPath)) {
84128
- resolved = path90.resolve(inputPath);
84255
+ resolved = path91.win32.resolve(inputPath);
84256
+ } else if (path91.isAbsolute(inputPath)) {
84257
+ resolved = path91.resolve(inputPath);
84129
84258
  } else {
84130
- resolved = path90.resolve(baseDir, inputPath);
84259
+ resolved = path91.resolve(baseDir, inputPath);
84131
84260
  }
84132
- const workspaceResolved = path90.resolve(workspaceDir);
84261
+ const workspaceResolved = path91.resolve(workspaceDir);
84133
84262
  let relative20;
84134
84263
  if (isWinAbs) {
84135
- relative20 = path90.win32.relative(workspaceResolved, resolved);
84264
+ relative20 = path91.win32.relative(workspaceResolved, resolved);
84136
84265
  } else {
84137
- relative20 = path90.relative(workspaceResolved, resolved);
84266
+ relative20 = path91.relative(workspaceResolved, resolved);
84138
84267
  }
84139
84268
  if (relative20.startsWith("..")) {
84140
84269
  return "path traversal detected";
@@ -84199,7 +84328,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
84199
84328
  if (typeof file3 !== "string") {
84200
84329
  continue;
84201
84330
  }
84202
- const resolvedPath = path90.resolve(file3);
84331
+ const resolvedPath = path91.resolve(file3);
84203
84332
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
84204
84333
  if (validationError) {
84205
84334
  continue;
@@ -84356,7 +84485,7 @@ async function runSecretscanWithFiles(files, directory) {
84356
84485
  skippedFiles++;
84357
84486
  continue;
84358
84487
  }
84359
- const resolvedPath = path90.resolve(file3);
84488
+ const resolvedPath = path91.resolve(file3);
84360
84489
  const validationError = validatePath(resolvedPath, directory, directory);
84361
84490
  if (validationError) {
84362
84491
  skippedFiles++;
@@ -84374,14 +84503,14 @@ async function runSecretscanWithFiles(files, directory) {
84374
84503
  };
84375
84504
  }
84376
84505
  for (const file3 of validatedFiles) {
84377
- const ext = path90.extname(file3).toLowerCase();
84506
+ const ext = path91.extname(file3).toLowerCase();
84378
84507
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
84379
84508
  skippedFiles++;
84380
84509
  continue;
84381
84510
  }
84382
84511
  let stat6;
84383
84512
  try {
84384
- stat6 = fs72.statSync(file3);
84513
+ stat6 = fs73.statSync(file3);
84385
84514
  } catch {
84386
84515
  skippedFiles++;
84387
84516
  continue;
@@ -84392,7 +84521,7 @@ async function runSecretscanWithFiles(files, directory) {
84392
84521
  }
84393
84522
  let content;
84394
84523
  try {
84395
- const buffer = fs72.readFileSync(file3);
84524
+ const buffer = fs73.readFileSync(file3);
84396
84525
  if (buffer.includes(0)) {
84397
84526
  skippedFiles++;
84398
84527
  continue;
@@ -84593,7 +84722,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
84593
84722
  const preexistingFindings = [];
84594
84723
  for (const finding of findings) {
84595
84724
  const filePath = finding.location.file;
84596
- const normalised = path90.relative(directory, filePath).replace(/\\/g, "/");
84725
+ const normalised = path91.relative(directory, filePath).replace(/\\/g, "/");
84597
84726
  const changedLines = changedLineRanges.get(normalised);
84598
84727
  if (changedLines?.has(finding.location.line)) {
84599
84728
  newFindings.push(finding);
@@ -84644,7 +84773,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
84644
84773
  warn(`pre_check_batch: Invalid file path: ${file3}`);
84645
84774
  continue;
84646
84775
  }
84647
- changedFiles.push(path90.resolve(directory, file3));
84776
+ changedFiles.push(path91.resolve(directory, file3));
84648
84777
  }
84649
84778
  if (changedFiles.length === 0) {
84650
84779
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -84845,7 +84974,7 @@ var pre_check_batch = createSwarmTool({
84845
84974
  };
84846
84975
  return JSON.stringify(errorResult, null, 2);
84847
84976
  }
84848
- const resolvedDirectory = path90.resolve(typedArgs.directory);
84977
+ const resolvedDirectory = path91.resolve(typedArgs.directory);
84849
84978
  const workspaceAnchor = resolvedDirectory;
84850
84979
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
84851
84980
  if (dirError) {
@@ -84886,7 +85015,7 @@ var pre_check_batch = createSwarmTool({
84886
85015
  });
84887
85016
  // src/tools/repo-map.ts
84888
85017
  init_zod();
84889
- import * as path91 from "node:path";
85018
+ import * as path92 from "node:path";
84890
85019
  init_path_security();
84891
85020
  init_create_tool();
84892
85021
  var VALID_ACTIONS = [
@@ -84911,7 +85040,7 @@ function validateFile(p) {
84911
85040
  return "file contains control characters";
84912
85041
  if (containsPathTraversal(p))
84913
85042
  return "file contains path traversal";
84914
- if (path91.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
85043
+ if (path92.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
84915
85044
  return "file must be a workspace-relative path, not absolute";
84916
85045
  }
84917
85046
  return null;
@@ -84934,8 +85063,8 @@ function ok(action, payload) {
84934
85063
  }
84935
85064
  function toRelativeGraphPath(input, workspaceRoot) {
84936
85065
  const normalized = input.replace(/\\/g, "/");
84937
- if (path91.isAbsolute(normalized)) {
84938
- const rel = path91.relative(workspaceRoot, normalized).replace(/\\/g, "/");
85066
+ if (path92.isAbsolute(normalized)) {
85067
+ const rel = path92.relative(workspaceRoot, normalized).replace(/\\/g, "/");
84939
85068
  return normalizeGraphPath2(rel);
84940
85069
  }
84941
85070
  return normalizeGraphPath2(normalized);
@@ -85079,8 +85208,8 @@ var repo_map = createSwarmTool({
85079
85208
  // src/tools/req-coverage.ts
85080
85209
  init_zod();
85081
85210
  init_create_tool();
85082
- import * as fs73 from "node:fs";
85083
- import * as path92 from "node:path";
85211
+ import * as fs74 from "node:fs";
85212
+ import * as path93 from "node:path";
85084
85213
  var SPEC_FILE = ".swarm/spec.md";
85085
85214
  var EVIDENCE_DIR4 = ".swarm/evidence";
85086
85215
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -85139,19 +85268,19 @@ function extractObligationAndText(id, lineText) {
85139
85268
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
85140
85269
  function readTouchedFiles(evidenceDir, phase, cwd) {
85141
85270
  const touchedFiles = new Set;
85142
- if (!fs73.existsSync(evidenceDir) || !fs73.statSync(evidenceDir).isDirectory()) {
85271
+ if (!fs74.existsSync(evidenceDir) || !fs74.statSync(evidenceDir).isDirectory()) {
85143
85272
  return [];
85144
85273
  }
85145
85274
  let entries;
85146
85275
  try {
85147
- entries = fs73.readdirSync(evidenceDir);
85276
+ entries = fs74.readdirSync(evidenceDir);
85148
85277
  } catch {
85149
85278
  return [];
85150
85279
  }
85151
85280
  for (const entry of entries) {
85152
- const entryPath = path92.join(evidenceDir, entry);
85281
+ const entryPath = path93.join(evidenceDir, entry);
85153
85282
  try {
85154
- const stat6 = fs73.statSync(entryPath);
85283
+ const stat6 = fs74.statSync(entryPath);
85155
85284
  if (!stat6.isDirectory()) {
85156
85285
  continue;
85157
85286
  }
@@ -85165,14 +85294,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85165
85294
  if (entryPhase !== String(phase)) {
85166
85295
  continue;
85167
85296
  }
85168
- const evidenceFilePath = path92.join(entryPath, "evidence.json");
85297
+ const evidenceFilePath = path93.join(entryPath, "evidence.json");
85169
85298
  try {
85170
- const resolvedPath = path92.resolve(evidenceFilePath);
85171
- const evidenceDirResolved = path92.resolve(evidenceDir);
85172
- if (!resolvedPath.startsWith(evidenceDirResolved + path92.sep)) {
85299
+ const resolvedPath = path93.resolve(evidenceFilePath);
85300
+ const evidenceDirResolved = path93.resolve(evidenceDir);
85301
+ if (!resolvedPath.startsWith(evidenceDirResolved + path93.sep)) {
85173
85302
  continue;
85174
85303
  }
85175
- const stat6 = fs73.lstatSync(evidenceFilePath);
85304
+ const stat6 = fs74.lstatSync(evidenceFilePath);
85176
85305
  if (!stat6.isFile()) {
85177
85306
  continue;
85178
85307
  }
@@ -85184,7 +85313,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85184
85313
  }
85185
85314
  let content;
85186
85315
  try {
85187
- content = fs73.readFileSync(evidenceFilePath, "utf-8");
85316
+ content = fs74.readFileSync(evidenceFilePath, "utf-8");
85188
85317
  } catch {
85189
85318
  continue;
85190
85319
  }
@@ -85203,7 +85332,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85203
85332
  if (Array.isArray(diffEntry.files_changed)) {
85204
85333
  for (const file3 of diffEntry.files_changed) {
85205
85334
  if (typeof file3 === "string") {
85206
- touchedFiles.add(path92.resolve(cwd, file3));
85335
+ touchedFiles.add(path93.resolve(cwd, file3));
85207
85336
  }
85208
85337
  }
85209
85338
  }
@@ -85216,12 +85345,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85216
85345
  }
85217
85346
  function searchFileForKeywords(filePath, keywords, cwd) {
85218
85347
  try {
85219
- const resolvedPath = path92.resolve(filePath);
85220
- const cwdResolved = path92.resolve(cwd);
85348
+ const resolvedPath = path93.resolve(filePath);
85349
+ const cwdResolved = path93.resolve(cwd);
85221
85350
  if (!resolvedPath.startsWith(cwdResolved)) {
85222
85351
  return false;
85223
85352
  }
85224
- const content = fs73.readFileSync(resolvedPath, "utf-8");
85353
+ const content = fs74.readFileSync(resolvedPath, "utf-8");
85225
85354
  for (const keyword of keywords) {
85226
85355
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
85227
85356
  if (regex.test(content)) {
@@ -85351,10 +85480,10 @@ var req_coverage = createSwarmTool({
85351
85480
  }, null, 2);
85352
85481
  }
85353
85482
  const cwd = inputDirectory || directory;
85354
- const specPath = path92.join(cwd, SPEC_FILE);
85483
+ const specPath = path93.join(cwd, SPEC_FILE);
85355
85484
  let specContent;
85356
85485
  try {
85357
- specContent = fs73.readFileSync(specPath, "utf-8");
85486
+ specContent = fs74.readFileSync(specPath, "utf-8");
85358
85487
  } catch (readError) {
85359
85488
  return JSON.stringify({
85360
85489
  success: false,
@@ -85378,7 +85507,7 @@ var req_coverage = createSwarmTool({
85378
85507
  message: "No FR requirements found in spec.md"
85379
85508
  }, null, 2);
85380
85509
  }
85381
- const evidenceDir = path92.join(cwd, EVIDENCE_DIR4);
85510
+ const evidenceDir = path93.join(cwd, EVIDENCE_DIR4);
85382
85511
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
85383
85512
  const analyzedRequirements = [];
85384
85513
  let coveredCount = 0;
@@ -85404,12 +85533,12 @@ var req_coverage = createSwarmTool({
85404
85533
  requirements: analyzedRequirements
85405
85534
  };
85406
85535
  const reportFilename = `req-coverage-phase-${phase}.json`;
85407
- const reportPath = path92.join(evidenceDir, reportFilename);
85536
+ const reportPath = path93.join(evidenceDir, reportFilename);
85408
85537
  try {
85409
- if (!fs73.existsSync(evidenceDir)) {
85410
- fs73.mkdirSync(evidenceDir, { recursive: true });
85538
+ if (!fs74.existsSync(evidenceDir)) {
85539
+ fs74.mkdirSync(evidenceDir, { recursive: true });
85411
85540
  }
85412
- fs73.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
85541
+ fs74.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
85413
85542
  } catch (writeError) {
85414
85543
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
85415
85544
  }
@@ -85491,8 +85620,8 @@ init_plan_schema();
85491
85620
  init_qa_gate_profile();
85492
85621
  init_file_locks();
85493
85622
  import * as crypto9 from "node:crypto";
85494
- import * as fs74 from "node:fs";
85495
- import * as path93 from "node:path";
85623
+ import * as fs75 from "node:fs";
85624
+ import * as path94 from "node:path";
85496
85625
  init_ledger();
85497
85626
  init_manager();
85498
85627
  init_state();
@@ -85570,17 +85699,17 @@ async function executeSavePlan(args2, fallbackDir) {
85570
85699
  };
85571
85700
  }
85572
85701
  if (args2.working_directory && fallbackDir) {
85573
- const resolvedTarget = path93.resolve(args2.working_directory);
85574
- const resolvedRoot = path93.resolve(fallbackDir);
85702
+ const resolvedTarget = path94.resolve(args2.working_directory);
85703
+ const resolvedRoot = path94.resolve(fallbackDir);
85575
85704
  let fallbackExists = false;
85576
85705
  try {
85577
- fs74.accessSync(resolvedRoot, fs74.constants.F_OK);
85706
+ fs75.accessSync(resolvedRoot, fs75.constants.F_OK);
85578
85707
  fallbackExists = true;
85579
85708
  } catch {
85580
85709
  fallbackExists = false;
85581
85710
  }
85582
85711
  if (fallbackExists) {
85583
- const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path93.sep);
85712
+ const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path94.sep);
85584
85713
  if (isSubdirectory) {
85585
85714
  return {
85586
85715
  success: false,
@@ -85596,11 +85725,11 @@ async function executeSavePlan(args2, fallbackDir) {
85596
85725
  let specMtime;
85597
85726
  let specHash;
85598
85727
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
85599
- const specPath = path93.join(targetWorkspace, ".swarm", "spec.md");
85728
+ const specPath = path94.join(targetWorkspace, ".swarm", "spec.md");
85600
85729
  try {
85601
- const stat6 = await fs74.promises.stat(specPath);
85730
+ const stat6 = await fs75.promises.stat(specPath);
85602
85731
  specMtime = stat6.mtime.toISOString();
85603
- const content = await fs74.promises.readFile(specPath, "utf8");
85732
+ const content = await fs75.promises.readFile(specPath, "utf8");
85604
85733
  specHash = crypto9.createHash("sha256").update(content).digest("hex");
85605
85734
  } catch {
85606
85735
  return {
@@ -85612,10 +85741,10 @@ async function executeSavePlan(args2, fallbackDir) {
85612
85741
  }
85613
85742
  }
85614
85743
  if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
85615
- const contextPath = path93.join(targetWorkspace, ".swarm", "context.md");
85744
+ const contextPath = path94.join(targetWorkspace, ".swarm", "context.md");
85616
85745
  let contextContent = "";
85617
85746
  try {
85618
- contextContent = await fs74.promises.readFile(contextPath, "utf8");
85747
+ contextContent = await fs75.promises.readFile(contextPath, "utf8");
85619
85748
  } catch {}
85620
85749
  const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
85621
85750
  if (!hasPendingSection) {
@@ -85762,14 +85891,14 @@ async function executeSavePlan(args2, fallbackDir) {
85762
85891
  }
85763
85892
  await writeCheckpoint(dir).catch(() => {});
85764
85893
  try {
85765
- const markerPath = path93.join(dir, ".swarm", ".plan-write-marker");
85894
+ const markerPath = path94.join(dir, ".swarm", ".plan-write-marker");
85766
85895
  const marker = JSON.stringify({
85767
85896
  source: "save_plan",
85768
85897
  timestamp: new Date().toISOString(),
85769
85898
  phases_count: plan.phases.length,
85770
85899
  tasks_count: tasksCount
85771
85900
  });
85772
- await fs74.promises.writeFile(markerPath, marker, "utf8");
85901
+ await fs75.promises.writeFile(markerPath, marker, "utf8");
85773
85902
  } catch {}
85774
85903
  const warnings = [];
85775
85904
  let criticReviewFound = false;
@@ -85785,7 +85914,7 @@ async function executeSavePlan(args2, fallbackDir) {
85785
85914
  return {
85786
85915
  success: true,
85787
85916
  message: "Plan saved successfully",
85788
- plan_path: path93.join(dir, ".swarm", "plan.json"),
85917
+ plan_path: path94.join(dir, ".swarm", "plan.json"),
85789
85918
  phases_count: plan.phases.length,
85790
85919
  tasks_count: tasksCount,
85791
85920
  ...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
@@ -85837,8 +85966,8 @@ var save_plan = createSwarmTool({
85837
85966
  // src/tools/sbom-generate.ts
85838
85967
  init_zod();
85839
85968
  init_manager2();
85840
- import * as fs75 from "node:fs";
85841
- import * as path94 from "node:path";
85969
+ import * as fs76 from "node:fs";
85970
+ import * as path95 from "node:path";
85842
85971
 
85843
85972
  // src/sbom/detectors/index.ts
85844
85973
  init_utils();
@@ -86686,9 +86815,9 @@ function findManifestFiles(rootDir) {
86686
86815
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
86687
86816
  function searchDir(dir) {
86688
86817
  try {
86689
- const entries = fs75.readdirSync(dir, { withFileTypes: true });
86818
+ const entries = fs76.readdirSync(dir, { withFileTypes: true });
86690
86819
  for (const entry of entries) {
86691
- const fullPath = path94.join(dir, entry.name);
86820
+ const fullPath = path95.join(dir, entry.name);
86692
86821
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
86693
86822
  continue;
86694
86823
  }
@@ -86697,7 +86826,7 @@ function findManifestFiles(rootDir) {
86697
86826
  } else if (entry.isFile()) {
86698
86827
  for (const pattern of patterns) {
86699
86828
  if (simpleGlobToRegex(pattern).test(entry.name)) {
86700
- manifestFiles.push(path94.relative(rootDir, fullPath));
86829
+ manifestFiles.push(path95.relative(rootDir, fullPath));
86701
86830
  break;
86702
86831
  }
86703
86832
  }
@@ -86713,13 +86842,13 @@ function findManifestFilesInDirs(directories, workingDir) {
86713
86842
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
86714
86843
  for (const dir of directories) {
86715
86844
  try {
86716
- const entries = fs75.readdirSync(dir, { withFileTypes: true });
86845
+ const entries = fs76.readdirSync(dir, { withFileTypes: true });
86717
86846
  for (const entry of entries) {
86718
- const fullPath = path94.join(dir, entry.name);
86847
+ const fullPath = path95.join(dir, entry.name);
86719
86848
  if (entry.isFile()) {
86720
86849
  for (const pattern of patterns) {
86721
86850
  if (simpleGlobToRegex(pattern).test(entry.name)) {
86722
- found.push(path94.relative(workingDir, fullPath));
86851
+ found.push(path95.relative(workingDir, fullPath));
86723
86852
  break;
86724
86853
  }
86725
86854
  }
@@ -86732,11 +86861,11 @@ function findManifestFilesInDirs(directories, workingDir) {
86732
86861
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
86733
86862
  const dirs = new Set;
86734
86863
  for (const file3 of changedFiles) {
86735
- let currentDir = path94.dirname(file3);
86864
+ let currentDir = path95.dirname(file3);
86736
86865
  while (true) {
86737
- if (currentDir && currentDir !== "." && currentDir !== path94.sep) {
86738
- dirs.add(path94.join(workingDir, currentDir));
86739
- const parent = path94.dirname(currentDir);
86866
+ if (currentDir && currentDir !== "." && currentDir !== path95.sep) {
86867
+ dirs.add(path95.join(workingDir, currentDir));
86868
+ const parent = path95.dirname(currentDir);
86740
86869
  if (parent === currentDir)
86741
86870
  break;
86742
86871
  currentDir = parent;
@@ -86750,7 +86879,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
86750
86879
  }
86751
86880
  function ensureOutputDir(outputDir) {
86752
86881
  try {
86753
- fs75.mkdirSync(outputDir, { recursive: true });
86882
+ fs76.mkdirSync(outputDir, { recursive: true });
86754
86883
  } catch (error93) {
86755
86884
  if (!error93 || error93.code !== "EEXIST") {
86756
86885
  throw error93;
@@ -86820,7 +86949,7 @@ var sbom_generate = createSwarmTool({
86820
86949
  const changedFiles = obj.changed_files;
86821
86950
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
86822
86951
  const workingDir = directory;
86823
- const outputDir = path94.isAbsolute(relativeOutputDir) ? relativeOutputDir : path94.join(workingDir, relativeOutputDir);
86952
+ const outputDir = path95.isAbsolute(relativeOutputDir) ? relativeOutputDir : path95.join(workingDir, relativeOutputDir);
86824
86953
  let manifestFiles = [];
86825
86954
  if (scope === "all") {
86826
86955
  manifestFiles = findManifestFiles(workingDir);
@@ -86843,11 +86972,11 @@ var sbom_generate = createSwarmTool({
86843
86972
  const processedFiles = [];
86844
86973
  for (const manifestFile of manifestFiles) {
86845
86974
  try {
86846
- const fullPath = path94.isAbsolute(manifestFile) ? manifestFile : path94.join(workingDir, manifestFile);
86847
- if (!fs75.existsSync(fullPath)) {
86975
+ const fullPath = path95.isAbsolute(manifestFile) ? manifestFile : path95.join(workingDir, manifestFile);
86976
+ if (!fs76.existsSync(fullPath)) {
86848
86977
  continue;
86849
86978
  }
86850
- const content = fs75.readFileSync(fullPath, "utf-8");
86979
+ const content = fs76.readFileSync(fullPath, "utf-8");
86851
86980
  const components = detectComponents(manifestFile, content);
86852
86981
  processedFiles.push(manifestFile);
86853
86982
  if (components.length > 0) {
@@ -86860,8 +86989,8 @@ var sbom_generate = createSwarmTool({
86860
86989
  const bom = generateCycloneDX(allComponents);
86861
86990
  const bomJson = serializeCycloneDX(bom);
86862
86991
  const filename = generateSbomFilename();
86863
- const outputPath = path94.join(outputDir, filename);
86864
- fs75.writeFileSync(outputPath, bomJson, "utf-8");
86992
+ const outputPath = path95.join(outputDir, filename);
86993
+ fs76.writeFileSync(outputPath, bomJson, "utf-8");
86865
86994
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
86866
86995
  try {
86867
86996
  const timestamp = new Date().toISOString();
@@ -86903,8 +87032,8 @@ var sbom_generate = createSwarmTool({
86903
87032
  // src/tools/schema-drift.ts
86904
87033
  init_zod();
86905
87034
  init_create_tool();
86906
- import * as fs76 from "node:fs";
86907
- import * as path95 from "node:path";
87035
+ import * as fs77 from "node:fs";
87036
+ import * as path96 from "node:path";
86908
87037
  var SPEC_CANDIDATES = [
86909
87038
  "openapi.json",
86910
87039
  "openapi.yaml",
@@ -86936,28 +87065,28 @@ function normalizePath3(p) {
86936
87065
  }
86937
87066
  function discoverSpecFile(cwd, specFileArg) {
86938
87067
  if (specFileArg) {
86939
- const resolvedPath = path95.resolve(cwd, specFileArg);
86940
- const normalizedCwd = cwd.endsWith(path95.sep) ? cwd : cwd + path95.sep;
87068
+ const resolvedPath = path96.resolve(cwd, specFileArg);
87069
+ const normalizedCwd = cwd.endsWith(path96.sep) ? cwd : cwd + path96.sep;
86941
87070
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
86942
87071
  throw new Error("Invalid spec_file: path traversal detected");
86943
87072
  }
86944
- const ext = path95.extname(resolvedPath).toLowerCase();
87073
+ const ext = path96.extname(resolvedPath).toLowerCase();
86945
87074
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
86946
87075
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
86947
87076
  }
86948
- const stats = fs76.statSync(resolvedPath);
87077
+ const stats = fs77.statSync(resolvedPath);
86949
87078
  if (stats.size > MAX_SPEC_SIZE) {
86950
87079
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
86951
87080
  }
86952
- if (!fs76.existsSync(resolvedPath)) {
87081
+ if (!fs77.existsSync(resolvedPath)) {
86953
87082
  throw new Error(`Spec file not found: ${resolvedPath}`);
86954
87083
  }
86955
87084
  return resolvedPath;
86956
87085
  }
86957
87086
  for (const candidate of SPEC_CANDIDATES) {
86958
- const candidatePath = path95.resolve(cwd, candidate);
86959
- if (fs76.existsSync(candidatePath)) {
86960
- const stats = fs76.statSync(candidatePath);
87087
+ const candidatePath = path96.resolve(cwd, candidate);
87088
+ if (fs77.existsSync(candidatePath)) {
87089
+ const stats = fs77.statSync(candidatePath);
86961
87090
  if (stats.size <= MAX_SPEC_SIZE) {
86962
87091
  return candidatePath;
86963
87092
  }
@@ -86966,8 +87095,8 @@ function discoverSpecFile(cwd, specFileArg) {
86966
87095
  return null;
86967
87096
  }
86968
87097
  function parseSpec(specFile) {
86969
- const content = fs76.readFileSync(specFile, "utf-8");
86970
- const ext = path95.extname(specFile).toLowerCase();
87098
+ const content = fs77.readFileSync(specFile, "utf-8");
87099
+ const ext = path96.extname(specFile).toLowerCase();
86971
87100
  if (ext === ".json") {
86972
87101
  return parseJsonSpec(content);
86973
87102
  }
@@ -87038,12 +87167,12 @@ function extractRoutes(cwd) {
87038
87167
  function walkDir(dir) {
87039
87168
  let entries;
87040
87169
  try {
87041
- entries = fs76.readdirSync(dir, { withFileTypes: true });
87170
+ entries = fs77.readdirSync(dir, { withFileTypes: true });
87042
87171
  } catch {
87043
87172
  return;
87044
87173
  }
87045
87174
  for (const entry of entries) {
87046
- const fullPath = path95.join(dir, entry.name);
87175
+ const fullPath = path96.join(dir, entry.name);
87047
87176
  if (entry.isSymbolicLink()) {
87048
87177
  continue;
87049
87178
  }
@@ -87053,7 +87182,7 @@ function extractRoutes(cwd) {
87053
87182
  }
87054
87183
  walkDir(fullPath);
87055
87184
  } else if (entry.isFile()) {
87056
- const ext = path95.extname(entry.name).toLowerCase();
87185
+ const ext = path96.extname(entry.name).toLowerCase();
87057
87186
  const baseName = entry.name.toLowerCase();
87058
87187
  if (![".ts", ".js", ".mjs"].includes(ext)) {
87059
87188
  continue;
@@ -87071,7 +87200,7 @@ function extractRoutes(cwd) {
87071
87200
  }
87072
87201
  function extractRoutesFromFile(filePath) {
87073
87202
  const routes = [];
87074
- const content = fs76.readFileSync(filePath, "utf-8");
87203
+ const content = fs77.readFileSync(filePath, "utf-8");
87075
87204
  const lines = content.split(/\r?\n/);
87076
87205
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
87077
87206
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -87220,8 +87349,8 @@ init_zod();
87220
87349
  init_bun_compat();
87221
87350
  init_path_security();
87222
87351
  init_create_tool();
87223
- import * as fs77 from "node:fs";
87224
- import * as path96 from "node:path";
87352
+ import * as fs78 from "node:fs";
87353
+ import * as path97 from "node:path";
87225
87354
  var DEFAULT_MAX_RESULTS = 100;
87226
87355
  var DEFAULT_MAX_LINES = 200;
87227
87356
  var REGEX_TIMEOUT_MS = 5000;
@@ -87257,11 +87386,11 @@ function containsWindowsAttacks3(str) {
87257
87386
  }
87258
87387
  function isPathInWorkspace3(filePath, workspace) {
87259
87388
  try {
87260
- const resolvedPath = path96.resolve(workspace, filePath);
87261
- const realWorkspace = fs77.realpathSync(workspace);
87262
- const realResolvedPath = fs77.realpathSync(resolvedPath);
87263
- const relativePath = path96.relative(realWorkspace, realResolvedPath);
87264
- if (relativePath.startsWith("..") || path96.isAbsolute(relativePath)) {
87389
+ const resolvedPath = path97.resolve(workspace, filePath);
87390
+ const realWorkspace = fs78.realpathSync(workspace);
87391
+ const realResolvedPath = fs78.realpathSync(resolvedPath);
87392
+ const relativePath = path97.relative(realWorkspace, realResolvedPath);
87393
+ if (relativePath.startsWith("..") || path97.isAbsolute(relativePath)) {
87265
87394
  return false;
87266
87395
  }
87267
87396
  return true;
@@ -87274,12 +87403,12 @@ function validatePathForRead2(filePath, workspace) {
87274
87403
  }
87275
87404
  function findRgInEnvPath() {
87276
87405
  const searchPath = process.env.PATH ?? "";
87277
- for (const dir of searchPath.split(path96.delimiter)) {
87406
+ for (const dir of searchPath.split(path97.delimiter)) {
87278
87407
  if (!dir)
87279
87408
  continue;
87280
87409
  const isWindows = process.platform === "win32";
87281
- const candidate = path96.join(dir, isWindows ? "rg.exe" : "rg");
87282
- if (fs77.existsSync(candidate))
87410
+ const candidate = path97.join(dir, isWindows ? "rg.exe" : "rg");
87411
+ if (fs78.existsSync(candidate))
87283
87412
  return candidate;
87284
87413
  }
87285
87414
  return null;
@@ -87406,10 +87535,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
87406
87535
  return files;
87407
87536
  }
87408
87537
  try {
87409
- const entries = fs77.readdirSync(dir, { withFileTypes: true });
87538
+ const entries = fs78.readdirSync(dir, { withFileTypes: true });
87410
87539
  for (const entry of entries) {
87411
- const fullPath = path96.join(dir, entry.name);
87412
- const relativePath = path96.relative(workspace, fullPath);
87540
+ const fullPath = path97.join(dir, entry.name);
87541
+ const relativePath = path97.relative(workspace, fullPath);
87413
87542
  if (!validatePathForRead2(fullPath, workspace)) {
87414
87543
  continue;
87415
87544
  }
@@ -87450,13 +87579,13 @@ async function fallbackSearch(opts) {
87450
87579
  const matches = [];
87451
87580
  let total = 0;
87452
87581
  for (const file3 of files) {
87453
- const fullPath = path96.join(opts.workspace, file3);
87582
+ const fullPath = path97.join(opts.workspace, file3);
87454
87583
  if (!validatePathForRead2(fullPath, opts.workspace)) {
87455
87584
  continue;
87456
87585
  }
87457
87586
  let stats;
87458
87587
  try {
87459
- stats = fs77.statSync(fullPath);
87588
+ stats = fs78.statSync(fullPath);
87460
87589
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
87461
87590
  continue;
87462
87591
  }
@@ -87465,7 +87594,7 @@ async function fallbackSearch(opts) {
87465
87594
  }
87466
87595
  let content;
87467
87596
  try {
87468
- content = fs77.readFileSync(fullPath, "utf-8");
87597
+ content = fs78.readFileSync(fullPath, "utf-8");
87469
87598
  } catch {
87470
87599
  continue;
87471
87600
  }
@@ -87577,7 +87706,7 @@ var search = createSwarmTool({
87577
87706
  message: "Exclude pattern contains invalid Windows-specific sequence"
87578
87707
  }, null, 2);
87579
87708
  }
87580
- if (!fs77.existsSync(directory)) {
87709
+ if (!fs78.existsSync(directory)) {
87581
87710
  return JSON.stringify({
87582
87711
  error: true,
87583
87712
  type: "unknown",
@@ -87702,12 +87831,136 @@ var set_qa_gates = createSwarmTool({
87702
87831
  return JSON.stringify(await executeSetQaGates(typedArgs, directory), null, 2);
87703
87832
  }
87704
87833
  });
87834
+ // src/tools/submit-phase-council-verdicts.ts
87835
+ init_zod();
87836
+ init_loader();
87837
+ init_create_tool();
87838
+ init_resolve_working_directory();
87839
+ var VerdictSchema2 = exports_external.object({
87840
+ agent: exports_external.enum(["critic", "reviewer", "sme", "test_engineer", "explorer"]),
87841
+ verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
87842
+ confidence: exports_external.number().min(0).max(1),
87843
+ findings: exports_external.array(exports_external.object({
87844
+ severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
87845
+ category: exports_external.string().min(1),
87846
+ location: exports_external.string(),
87847
+ detail: exports_external.string(),
87848
+ evidence: exports_external.string()
87849
+ })),
87850
+ criteriaAssessed: exports_external.array(exports_external.string()),
87851
+ criteriaUnmet: exports_external.array(exports_external.string()),
87852
+ durationMs: exports_external.number().nonnegative()
87853
+ });
87854
+ var ArgsSchema4 = exports_external.object({
87855
+ phaseNumber: exports_external.number().int().min(1),
87856
+ swarmId: exports_external.string().min(1),
87857
+ phaseSummary: exports_external.string().min(1),
87858
+ roundNumber: exports_external.number().int().min(1).max(10).optional(),
87859
+ verdicts: exports_external.array(VerdictSchema2).min(1).max(5),
87860
+ working_directory: exports_external.string().optional()
87861
+ });
87862
+ var submit_phase_council_verdicts = createSwarmTool({
87863
+ description: "Submit pre-collected council member verdicts for PHASE-LEVEL synthesis. " + "PREREQUISITE — you MUST dispatch each council member (critic, reviewer, sme, " + "test_engineer, explorer) as separate Agent tasks with PHASE-SCOPED context and " + "collect their verdict responses BEFORE calling this tool. This tool performs " + "synthesis only — it does NOT dispatch, invoke, or contact council members. " + "Writes .swarm/evidence/{phase}/phase-council.json which is required by " + "phase_complete Gate 5 when council_mode is enabled. " + "Architect-only. Config-gated via council.enabled.",
87864
+ args: {
87865
+ phaseNumber: exports_external.number().int().min(1).describe("Phase number being reviewed (e.g. 1, 2, 3)"),
87866
+ swarmId: exports_external.string().min(1).describe('Swarm identifier, e.g. "mega"'),
87867
+ phaseSummary: exports_external.string().min(1).describe("2–4 sentence summary of what the phase accomplished"),
87868
+ roundNumber: exports_external.number().int().min(1).max(10).optional().describe("1-indexed round number. Defaults to 1."),
87869
+ verdicts: exports_external.array(exports_external.object({
87870
+ agent: exports_external.enum([
87871
+ "critic",
87872
+ "reviewer",
87873
+ "sme",
87874
+ "test_engineer",
87875
+ "explorer"
87876
+ ]),
87877
+ verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
87878
+ confidence: exports_external.number().min(0).max(1),
87879
+ findings: exports_external.array(exports_external.object({
87880
+ severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
87881
+ category: exports_external.string().min(1),
87882
+ location: exports_external.string(),
87883
+ detail: exports_external.string(),
87884
+ evidence: exports_external.string()
87885
+ })),
87886
+ criteriaAssessed: exports_external.array(exports_external.string()),
87887
+ criteriaUnmet: exports_external.array(exports_external.string()),
87888
+ durationMs: exports_external.number()
87889
+ })).min(1).max(5).describe("Collected CouncilMemberVerdict objects from all dispatched council members"),
87890
+ working_directory: exports_external.string().optional().describe("Working directory where the plan is located")
87891
+ },
87892
+ async execute(args2, directory) {
87893
+ const parsed = ArgsSchema4.safeParse(args2);
87894
+ if (!parsed.success) {
87895
+ return JSON.stringify({
87896
+ success: false,
87897
+ reason: "invalid arguments",
87898
+ errors: parsed.error.issues.map((i2) => ({
87899
+ path: i2.path.join("."),
87900
+ message: i2.message
87901
+ }))
87902
+ }, null, 2);
87903
+ }
87904
+ const input = parsed.data;
87905
+ const dirResult = resolveWorkingDirectory(input.working_directory, directory);
87906
+ if (!dirResult.success) {
87907
+ return JSON.stringify({ success: false, reason: dirResult.message }, null, 2);
87908
+ }
87909
+ const workingDir = dirResult.directory;
87910
+ const config3 = loadPluginConfig(workingDir);
87911
+ if (!config3.council?.enabled) {
87912
+ return JSON.stringify({
87913
+ success: false,
87914
+ reason: "council feature is disabled — set council.enabled: true in .opencode/opencode-swarm.json to enable"
87915
+ }, null, 2);
87916
+ }
87917
+ const effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
87918
+ const ALL_MEMBERS = [
87919
+ "critic",
87920
+ "reviewer",
87921
+ "sme",
87922
+ "test_engineer",
87923
+ "explorer"
87924
+ ];
87925
+ const distinctMembers = new Set(input.verdicts.map((v) => v.agent));
87926
+ const membersVoted = [...distinctMembers];
87927
+ const membersAbsent = ALL_MEMBERS.filter((m) => !distinctMembers.has(m));
87928
+ if (membersVoted.length < effectiveMinimum) {
87929
+ return JSON.stringify({
87930
+ success: false,
87931
+ reason: "insufficient_quorum",
87932
+ message: `Phase council quorum not met: ${membersVoted.length} of ${effectiveMinimum} required members provided verdicts. ` + `Members voted: [${membersVoted.join(", ")}]. ` + `Members absent: [${membersAbsent.join(", ")}]. ` + `Dispatch the absent council members with phase-scoped context and collect their verdicts before calling submit_phase_council_verdicts.`,
87933
+ membersVoted,
87934
+ membersAbsent,
87935
+ quorumRequired: effectiveMinimum
87936
+ }, null, 2);
87937
+ }
87938
+ const synthesis = synthesizePhaseCouncilAdvisory(input.phaseNumber, input.phaseSummary, input.verdicts, input.roundNumber ?? 1, config3.council, workingDir);
87939
+ return JSON.stringify({
87940
+ success: true,
87941
+ overallVerdict: synthesis.overallVerdict,
87942
+ vetoedBy: synthesis.vetoedBy,
87943
+ roundNumber: synthesis.roundNumber,
87944
+ allCriteriaMet: synthesis.allCriteriaMet,
87945
+ requiredFixesCount: synthesis.requiredFixes?.length ?? 0,
87946
+ advisoryFindingsCount: synthesis.advisoryFindings?.length ?? 0,
87947
+ unresolvedConflictsCount: synthesis.unresolvedConflicts?.length ?? 0,
87948
+ advisoryNotes: synthesis.advisoryNotes ?? [],
87949
+ membersVoted,
87950
+ membersAbsent,
87951
+ quorumSize: membersVoted.length,
87952
+ quorumMet: true,
87953
+ evidencePath: synthesis.evidencePath,
87954
+ unifiedFeedbackMd: synthesis.unifiedFeedbackMd
87955
+ }, null, 2);
87956
+ }
87957
+ });
87705
87958
  // src/tools/suggest-patch.ts
87706
87959
  init_zod();
87707
87960
  init_path_security();
87708
87961
  init_create_tool();
87709
- import * as fs78 from "node:fs";
87710
- import * as path97 from "node:path";
87962
+ import * as fs79 from "node:fs";
87963
+ import * as path98 from "node:path";
87711
87964
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
87712
87965
  function containsWindowsAttacks4(str) {
87713
87966
  if (/:[^\\/]/.test(str))
@@ -87721,14 +87974,14 @@ function containsWindowsAttacks4(str) {
87721
87974
  }
87722
87975
  function isPathInWorkspace4(filePath, workspace) {
87723
87976
  try {
87724
- const resolvedPath = path97.resolve(workspace, filePath);
87725
- if (!fs78.existsSync(resolvedPath)) {
87977
+ const resolvedPath = path98.resolve(workspace, filePath);
87978
+ if (!fs79.existsSync(resolvedPath)) {
87726
87979
  return true;
87727
87980
  }
87728
- const realWorkspace = fs78.realpathSync(workspace);
87729
- const realResolvedPath = fs78.realpathSync(resolvedPath);
87730
- const relativePath = path97.relative(realWorkspace, realResolvedPath);
87731
- if (relativePath.startsWith("..") || path97.isAbsolute(relativePath)) {
87981
+ const realWorkspace = fs79.realpathSync(workspace);
87982
+ const realResolvedPath = fs79.realpathSync(resolvedPath);
87983
+ const relativePath = path98.relative(realWorkspace, realResolvedPath);
87984
+ if (relativePath.startsWith("..") || path98.isAbsolute(relativePath)) {
87732
87985
  return false;
87733
87986
  }
87734
87987
  return true;
@@ -87900,7 +88153,7 @@ var suggestPatch = createSwarmTool({
87900
88153
  message: "changes cannot be empty"
87901
88154
  }, null, 2);
87902
88155
  }
87903
- if (!fs78.existsSync(directory)) {
88156
+ if (!fs79.existsSync(directory)) {
87904
88157
  return JSON.stringify({
87905
88158
  success: false,
87906
88159
  error: true,
@@ -87936,8 +88189,8 @@ var suggestPatch = createSwarmTool({
87936
88189
  });
87937
88190
  continue;
87938
88191
  }
87939
- const fullPath = path97.resolve(directory, change.file);
87940
- if (!fs78.existsSync(fullPath)) {
88192
+ const fullPath = path98.resolve(directory, change.file);
88193
+ if (!fs79.existsSync(fullPath)) {
87941
88194
  errors5.push({
87942
88195
  success: false,
87943
88196
  error: true,
@@ -87951,7 +88204,7 @@ var suggestPatch = createSwarmTool({
87951
88204
  }
87952
88205
  let content;
87953
88206
  try {
87954
- content = fs78.readFileSync(fullPath, "utf-8");
88207
+ content = fs79.readFileSync(fullPath, "utf-8");
87955
88208
  } catch (err3) {
87956
88209
  errors5.push({
87957
88210
  success: false,
@@ -88198,8 +88451,8 @@ var generate_mutants = createSwarmTool({
88198
88451
  // src/tools/lint-spec.ts
88199
88452
  init_spec_schema();
88200
88453
  init_create_tool();
88201
- import * as fs79 from "node:fs";
88202
- import * as path98 from "node:path";
88454
+ import * as fs80 from "node:fs";
88455
+ import * as path99 from "node:path";
88203
88456
  var SPEC_FILE_NAME = "spec.md";
88204
88457
  var SWARM_DIR2 = ".swarm";
88205
88458
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -88252,8 +88505,8 @@ var lint_spec = createSwarmTool({
88252
88505
  async execute(_args, directory) {
88253
88506
  const errors5 = [];
88254
88507
  const warnings = [];
88255
- const specPath = path98.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
88256
- if (!fs79.existsSync(specPath)) {
88508
+ const specPath = path99.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
88509
+ if (!fs80.existsSync(specPath)) {
88257
88510
  const result2 = {
88258
88511
  valid: false,
88259
88512
  specMtime: null,
@@ -88272,12 +88525,12 @@ var lint_spec = createSwarmTool({
88272
88525
  }
88273
88526
  let specMtime = null;
88274
88527
  try {
88275
- const stats = fs79.statSync(specPath);
88528
+ const stats = fs80.statSync(specPath);
88276
88529
  specMtime = stats.mtime.toISOString();
88277
88530
  } catch {}
88278
88531
  let content;
88279
88532
  try {
88280
- content = fs79.readFileSync(specPath, "utf-8");
88533
+ content = fs80.readFileSync(specPath, "utf-8");
88281
88534
  } catch (e) {
88282
88535
  const result2 = {
88283
88536
  valid: false,
@@ -88322,13 +88575,13 @@ var lint_spec = createSwarmTool({
88322
88575
  });
88323
88576
  // src/tools/mutation-test.ts
88324
88577
  init_zod();
88325
- import * as fs80 from "node:fs";
88326
- import * as path100 from "node:path";
88578
+ import * as fs81 from "node:fs";
88579
+ import * as path101 from "node:path";
88327
88580
 
88328
88581
  // src/mutation/engine.ts
88329
88582
  import { spawnSync as spawnSync3 } from "node:child_process";
88330
88583
  import { unlinkSync as unlinkSync13, writeFileSync as writeFileSync20 } from "node:fs";
88331
- import * as path99 from "node:path";
88584
+ import * as path100 from "node:path";
88332
88585
 
88333
88586
  // src/mutation/equivalence.ts
88334
88587
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -88463,7 +88716,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
88463
88716
  let patchFile;
88464
88717
  try {
88465
88718
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
88466
- patchFile = path99.join(workingDir, `.mutation_patch_${safeId2}.diff`);
88719
+ patchFile = path100.join(workingDir, `.mutation_patch_${safeId2}.diff`);
88467
88720
  try {
88468
88721
  writeFileSync20(patchFile, patch.patch);
88469
88722
  } catch (writeErr) {
@@ -88857,8 +89110,8 @@ var mutation_test = createSwarmTool({
88857
89110
  ];
88858
89111
  for (const filePath of uniquePaths) {
88859
89112
  try {
88860
- const resolvedPath = path100.resolve(cwd, filePath);
88861
- sourceFiles.set(filePath, fs80.readFileSync(resolvedPath, "utf-8"));
89113
+ const resolvedPath = path101.resolve(cwd, filePath);
89114
+ sourceFiles.set(filePath, fs81.readFileSync(resolvedPath, "utf-8"));
88862
89115
  } catch {}
88863
89116
  }
88864
89117
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -88876,8 +89129,8 @@ var mutation_test = createSwarmTool({
88876
89129
  init_zod();
88877
89130
  init_manager2();
88878
89131
  init_detector();
88879
- import * as fs81 from "node:fs";
88880
- import * as path101 from "node:path";
89132
+ import * as fs82 from "node:fs";
89133
+ import * as path102 from "node:path";
88881
89134
  init_create_tool();
88882
89135
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
88883
89136
  var BINARY_CHECK_BYTES = 8192;
@@ -88943,7 +89196,7 @@ async function syntaxCheck(input, directory, config3) {
88943
89196
  if (languages?.length) {
88944
89197
  const lowerLangs = languages.map((l) => l.toLowerCase());
88945
89198
  filesToCheck = filesToCheck.filter((file3) => {
88946
- const ext = path101.extname(file3.path).toLowerCase();
89199
+ const ext = path102.extname(file3.path).toLowerCase();
88947
89200
  const langDef = getLanguageForExtension(ext);
88948
89201
  const fileProfile = getProfileForFile(file3.path);
88949
89202
  const langId = fileProfile?.id || langDef?.id;
@@ -88956,7 +89209,7 @@ async function syntaxCheck(input, directory, config3) {
88956
89209
  let skippedCount = 0;
88957
89210
  for (const fileInfo of filesToCheck) {
88958
89211
  const { path: filePath } = fileInfo;
88959
- const fullPath = path101.isAbsolute(filePath) ? filePath : path101.join(directory, filePath);
89212
+ const fullPath = path102.isAbsolute(filePath) ? filePath : path102.join(directory, filePath);
88960
89213
  const result = {
88961
89214
  path: filePath,
88962
89215
  language: "",
@@ -88986,7 +89239,7 @@ async function syntaxCheck(input, directory, config3) {
88986
89239
  }
88987
89240
  let content;
88988
89241
  try {
88989
- content = fs81.readFileSync(fullPath, "utf8");
89242
+ content = fs82.readFileSync(fullPath, "utf8");
88990
89243
  } catch {
88991
89244
  result.skipped_reason = "file_read_error";
88992
89245
  skippedCount++;
@@ -89005,7 +89258,7 @@ async function syntaxCheck(input, directory, config3) {
89005
89258
  results.push(result);
89006
89259
  continue;
89007
89260
  }
89008
- const ext = path101.extname(filePath).toLowerCase();
89261
+ const ext = path102.extname(filePath).toLowerCase();
89009
89262
  const langDef = getLanguageForExtension(ext);
89010
89263
  result.language = profile?.id || langDef?.id || "unknown";
89011
89264
  const errors5 = extractSyntaxErrors(parser, content);
@@ -89097,8 +89350,8 @@ init_zod();
89097
89350
  init_utils();
89098
89351
  init_create_tool();
89099
89352
  init_path_security();
89100
- import * as fs82 from "node:fs";
89101
- import * as path102 from "node:path";
89353
+ import * as fs83 from "node:fs";
89354
+ import * as path103 from "node:path";
89102
89355
  var MAX_TEXT_LENGTH = 200;
89103
89356
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
89104
89357
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -89164,9 +89417,9 @@ function validatePathsInput(paths, cwd) {
89164
89417
  return { error: "paths contains path traversal", resolvedPath: null };
89165
89418
  }
89166
89419
  try {
89167
- const resolvedPath = path102.resolve(paths);
89168
- const normalizedCwd = path102.resolve(cwd);
89169
- const normalizedResolved = path102.resolve(resolvedPath);
89420
+ const resolvedPath = path103.resolve(paths);
89421
+ const normalizedCwd = path103.resolve(cwd);
89422
+ const normalizedResolved = path103.resolve(resolvedPath);
89170
89423
  if (!normalizedResolved.startsWith(normalizedCwd)) {
89171
89424
  return {
89172
89425
  error: "paths must be within the current working directory",
@@ -89182,13 +89435,13 @@ function validatePathsInput(paths, cwd) {
89182
89435
  }
89183
89436
  }
89184
89437
  function isSupportedExtension(filePath) {
89185
- const ext = path102.extname(filePath).toLowerCase();
89438
+ const ext = path103.extname(filePath).toLowerCase();
89186
89439
  return SUPPORTED_EXTENSIONS4.has(ext);
89187
89440
  }
89188
89441
  function findSourceFiles3(dir, files = []) {
89189
89442
  let entries;
89190
89443
  try {
89191
- entries = fs82.readdirSync(dir);
89444
+ entries = fs83.readdirSync(dir);
89192
89445
  } catch {
89193
89446
  return files;
89194
89447
  }
@@ -89197,10 +89450,10 @@ function findSourceFiles3(dir, files = []) {
89197
89450
  if (SKIP_DIRECTORIES5.has(entry)) {
89198
89451
  continue;
89199
89452
  }
89200
- const fullPath = path102.join(dir, entry);
89453
+ const fullPath = path103.join(dir, entry);
89201
89454
  let stat6;
89202
89455
  try {
89203
- stat6 = fs82.statSync(fullPath);
89456
+ stat6 = fs83.statSync(fullPath);
89204
89457
  } catch {
89205
89458
  continue;
89206
89459
  }
@@ -89293,7 +89546,7 @@ var todo_extract = createSwarmTool({
89293
89546
  return JSON.stringify(errorResult, null, 2);
89294
89547
  }
89295
89548
  const scanPath = resolvedPath;
89296
- if (!fs82.existsSync(scanPath)) {
89549
+ if (!fs83.existsSync(scanPath)) {
89297
89550
  const errorResult = {
89298
89551
  error: `path not found: ${pathsInput}`,
89299
89552
  total: 0,
@@ -89303,13 +89556,13 @@ var todo_extract = createSwarmTool({
89303
89556
  return JSON.stringify(errorResult, null, 2);
89304
89557
  }
89305
89558
  const filesToScan = [];
89306
- const stat6 = fs82.statSync(scanPath);
89559
+ const stat6 = fs83.statSync(scanPath);
89307
89560
  if (stat6.isFile()) {
89308
89561
  if (isSupportedExtension(scanPath)) {
89309
89562
  filesToScan.push(scanPath);
89310
89563
  } else {
89311
89564
  const errorResult = {
89312
- error: `unsupported file extension: ${path102.extname(scanPath)}`,
89565
+ error: `unsupported file extension: ${path103.extname(scanPath)}`,
89313
89566
  total: 0,
89314
89567
  byPriority: { high: 0, medium: 0, low: 0 },
89315
89568
  entries: []
@@ -89322,11 +89575,11 @@ var todo_extract = createSwarmTool({
89322
89575
  const allEntries = [];
89323
89576
  for (const filePath of filesToScan) {
89324
89577
  try {
89325
- const fileStat = fs82.statSync(filePath);
89578
+ const fileStat = fs83.statSync(filePath);
89326
89579
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
89327
89580
  continue;
89328
89581
  }
89329
- const content = fs82.readFileSync(filePath, "utf-8");
89582
+ const content = fs83.readFileSync(filePath, "utf-8");
89330
89583
  const entries = parseTodoComments(content, filePath, tagsSet);
89331
89584
  allEntries.push(...entries);
89332
89585
  } catch {}
@@ -89357,19 +89610,19 @@ init_loader();
89357
89610
  init_schema();
89358
89611
  init_qa_gate_profile();
89359
89612
  init_gate_evidence();
89360
- import * as fs84 from "node:fs";
89361
- import * as path104 from "node:path";
89613
+ import * as fs85 from "node:fs";
89614
+ import * as path105 from "node:path";
89362
89615
 
89363
89616
  // src/hooks/diff-scope.ts
89364
89617
  init_bun_compat();
89365
- import * as fs83 from "node:fs";
89366
- import * as path103 from "node:path";
89618
+ import * as fs84 from "node:fs";
89619
+ import * as path104 from "node:path";
89367
89620
  function getDeclaredScope(taskId, directory) {
89368
89621
  try {
89369
- const planPath = path103.join(directory, ".swarm", "plan.json");
89370
- if (!fs83.existsSync(planPath))
89622
+ const planPath = path104.join(directory, ".swarm", "plan.json");
89623
+ if (!fs84.existsSync(planPath))
89371
89624
  return null;
89372
- const raw = fs83.readFileSync(planPath, "utf-8");
89625
+ const raw = fs84.readFileSync(planPath, "utf-8");
89373
89626
  const plan = JSON.parse(raw);
89374
89627
  for (const phase of plan.phases ?? []) {
89375
89628
  for (const task of phase.tasks ?? []) {
@@ -89431,9 +89684,10 @@ async function validateDiffScope(taskId, directory) {
89431
89684
  const changedFiles = await getChangedFiles(directory);
89432
89685
  if (!changedFiles)
89433
89686
  return null;
89687
+ const nonSwarmFiles = changedFiles.filter((f) => !f.replace(/\\/g, "/").startsWith(".swarm/"));
89434
89688
  const normalise = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "");
89435
89689
  const normScope = new Set(declaredScope.map(normalise));
89436
- const undeclared = changedFiles.map(normalise).filter((f) => !normScope.has(f));
89690
+ const undeclared = nonSwarmFiles.map(normalise).filter((f) => !normScope.has(f));
89437
89691
  if (undeclared.length === 0)
89438
89692
  return null;
89439
89693
  const scopeStr = declaredScope.join(", ");
@@ -89485,7 +89739,7 @@ var TIER_3_PATTERNS = [
89485
89739
  ];
89486
89740
  function matchesTier3Pattern(files) {
89487
89741
  for (const file3 of files) {
89488
- const fileName = path104.basename(file3);
89742
+ const fileName = path105.basename(file3);
89489
89743
  for (const pattern of TIER_3_PATTERNS) {
89490
89744
  if (pattern.test(fileName)) {
89491
89745
  return true;
@@ -89499,8 +89753,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
89499
89753
  if (hasActiveTurboMode()) {
89500
89754
  const resolvedDir2 = workingDirectory;
89501
89755
  try {
89502
- const planPath = path104.join(resolvedDir2, ".swarm", "plan.json");
89503
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89756
+ const planPath = path105.join(resolvedDir2, ".swarm", "plan.json");
89757
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89504
89758
  const plan = JSON.parse(planRaw);
89505
89759
  for (const planPhase of plan.phases ?? []) {
89506
89760
  for (const task of planPhase.tasks ?? []) {
@@ -89569,8 +89823,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
89569
89823
  }
89570
89824
  try {
89571
89825
  const resolvedDir2 = workingDirectory;
89572
- const planPath = path104.join(resolvedDir2, ".swarm", "plan.json");
89573
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89826
+ const planPath = path105.join(resolvedDir2, ".swarm", "plan.json");
89827
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89574
89828
  const plan = JSON.parse(planRaw);
89575
89829
  for (const planPhase of plan.phases ?? []) {
89576
89830
  for (const task of planPhase.tasks ?? []) {
@@ -89713,65 +89967,6 @@ function recoverTaskStateFromDelegations(taskId) {
89713
89967
  }
89714
89968
  }
89715
89969
  }
89716
- function checkCouncilGate(workingDirectory, taskId) {
89717
- let councilEnabled = false;
89718
- let effectiveMinimum = 3;
89719
- try {
89720
- const config3 = loadPluginConfig(workingDirectory);
89721
- councilEnabled = config3.council?.enabled === true;
89722
- effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
89723
- } catch {
89724
- return { blocked: false, reason: "" };
89725
- }
89726
- if (!councilEnabled) {
89727
- return { blocked: false, reason: "" };
89728
- }
89729
- try {
89730
- const planPath = path104.join(workingDirectory, ".swarm", "plan.json");
89731
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89732
- const planObj = JSON.parse(planRaw);
89733
- if (planObj.swarm && planObj.title) {
89734
- const planId = `${planObj.swarm}-${planObj.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
89735
- const profile = getProfile(workingDirectory, planId);
89736
- if (!profile || !profile.gates.council_mode) {
89737
- return { blocked: false, reason: "" };
89738
- }
89739
- }
89740
- } catch {
89741
- return { blocked: false, reason: "" };
89742
- }
89743
- let evidence;
89744
- try {
89745
- evidence = readTaskEvidenceRaw(workingDirectory, taskId);
89746
- } catch {
89747
- return {
89748
- blocked: true,
89749
- reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task"
89750
- };
89751
- }
89752
- const councilGate = evidence?.gates?.council;
89753
- if (!councilGate) {
89754
- return {
89755
- blocked: true,
89756
- reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task"
89757
- };
89758
- }
89759
- if (councilGate.verdict === "REJECT") {
89760
- return {
89761
- blocked: true,
89762
- reason: "council gate blocked advancement — resolve requiredFixes and re-run submit_council_verdicts"
89763
- };
89764
- }
89765
- const rawQuorumSize = councilGate.quorumSize;
89766
- const quorumSize = typeof rawQuorumSize === "number" && Number.isFinite(rawQuorumSize) && rawQuorumSize >= 1 ? rawQuorumSize : 1;
89767
- if (quorumSize < effectiveMinimum) {
89768
- return {
89769
- blocked: true,
89770
- reason: `council gate blocked advancement — recorded verdict has insufficient quorum (${quorumSize} of ${effectiveMinimum} required members). Re-run submit_council_verdicts with the missing council members.`
89771
- };
89772
- }
89773
- return { blocked: false, reason: "" };
89774
- }
89775
89970
  async function executeUpdateTaskStatus(args2, fallbackDir) {
89776
89971
  const statusError = validateStatus(args2.status);
89777
89972
  if (statusError) {
@@ -89818,8 +90013,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89818
90013
  };
89819
90014
  }
89820
90015
  }
89821
- normalizedDir = path104.normalize(args2.working_directory);
89822
- const pathParts = normalizedDir.split(path104.sep);
90016
+ normalizedDir = path105.normalize(args2.working_directory);
90017
+ const pathParts = normalizedDir.split(path105.sep);
89823
90018
  if (pathParts.includes("..")) {
89824
90019
  return {
89825
90020
  success: false,
@@ -89829,11 +90024,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89829
90024
  ]
89830
90025
  };
89831
90026
  }
89832
- const resolvedDir = path104.resolve(normalizedDir);
90027
+ const resolvedDir = path105.resolve(normalizedDir);
89833
90028
  try {
89834
- const realPath = fs84.realpathSync(resolvedDir);
89835
- const planPath = path104.join(realPath, ".swarm", "plan.json");
89836
- if (!fs84.existsSync(planPath)) {
90029
+ const realPath = fs85.realpathSync(resolvedDir);
90030
+ const planPath = path105.join(realPath, ".swarm", "plan.json");
90031
+ if (!fs85.existsSync(planPath)) {
89837
90032
  return {
89838
90033
  success: false,
89839
90034
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -89864,22 +90059,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89864
90059
  }
89865
90060
  if (args2.status === "in_progress") {
89866
90061
  try {
89867
- const evidencePath = path104.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
89868
- fs84.mkdirSync(path104.dirname(evidencePath), { recursive: true });
89869
- const fd = fs84.openSync(evidencePath, "wx");
90062
+ const evidencePath = path105.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
90063
+ fs85.mkdirSync(path105.dirname(evidencePath), { recursive: true });
90064
+ const fd = fs85.openSync(evidencePath, "wx");
89870
90065
  let writeOk = false;
89871
90066
  try {
89872
- fs84.writeSync(fd, JSON.stringify({
90067
+ fs85.writeSync(fd, JSON.stringify({
89873
90068
  taskId: args2.task_id,
89874
90069
  required_gates: ["reviewer", "test_engineer"],
89875
90070
  gates: {}
89876
90071
  }, null, 2));
89877
90072
  writeOk = true;
89878
90073
  } finally {
89879
- fs84.closeSync(fd);
90074
+ fs85.closeSync(fd);
89880
90075
  if (!writeOk) {
89881
90076
  try {
89882
- fs84.unlinkSync(evidencePath);
90077
+ fs85.unlinkSync(evidencePath);
89883
90078
  } catch {}
89884
90079
  }
89885
90080
  }
@@ -89889,8 +90084,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89889
90084
  recoverTaskStateFromDelegations(args2.task_id);
89890
90085
  let phaseRequiresReviewer = true;
89891
90086
  try {
89892
- const planPath = path104.join(directory, ".swarm", "plan.json");
89893
- const planRaw = fs84.readFileSync(planPath, "utf-8");
90087
+ const planPath = path105.join(directory, ".swarm", "plan.json");
90088
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89894
90089
  const plan = JSON.parse(planRaw);
89895
90090
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
89896
90091
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -89907,14 +90102,6 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89907
90102
  };
89908
90103
  }
89909
90104
  }
89910
- const councilCheck = checkCouncilGate(directory, args2.task_id);
89911
- if (councilCheck.blocked) {
89912
- return {
89913
- success: false,
89914
- message: councilCheck.reason,
89915
- errors: [councilCheck.reason]
89916
- };
89917
- }
89918
90105
  }
89919
90106
  const lockTaskId = `update-task-status-${args2.task_id}-${Date.now()}`;
89920
90107
  const planFilePath = "plan.json";
@@ -90124,7 +90311,7 @@ function createWebSearchProvider(config3) {
90124
90311
  init_create_tool();
90125
90312
  init_resolve_working_directory();
90126
90313
  var MAX_RESULTS_HARD_CAP = 10;
90127
- var ArgsSchema4 = exports_external.object({
90314
+ var ArgsSchema5 = exports_external.object({
90128
90315
  query: exports_external.string().min(1).max(500),
90129
90316
  max_results: exports_external.number().int().min(1).max(20).optional(),
90130
90317
  working_directory: exports_external.string().optional()
@@ -90137,7 +90324,7 @@ var web_search = createSwarmTool({
90137
90324
  working_directory: exports_external.string().optional().describe("Project root for config resolution. Optional.")
90138
90325
  },
90139
90326
  execute: async (args2, directory) => {
90140
- const parsed = ArgsSchema4.safeParse(args2);
90327
+ const parsed = ArgsSchema5.safeParse(args2);
90141
90328
  if (!parsed.success) {
90142
90329
  const fail = {
90143
90330
  success: false,
@@ -90208,8 +90395,8 @@ init_utils2();
90208
90395
  init_ledger();
90209
90396
  init_manager();
90210
90397
  init_create_tool();
90211
- import fs85 from "node:fs";
90212
- import path105 from "node:path";
90398
+ import fs86 from "node:fs";
90399
+ import path106 from "node:path";
90213
90400
  function derivePlanId5(plan) {
90214
90401
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
90215
90402
  }
@@ -90260,7 +90447,7 @@ async function executeWriteDriftEvidence(args2, directory) {
90260
90447
  entries: [evidenceEntry]
90261
90448
  };
90262
90449
  const filename = "drift-verifier.json";
90263
- const relativePath = path105.join("evidence", String(phase), filename);
90450
+ const relativePath = path106.join("evidence", String(phase), filename);
90264
90451
  let validatedPath;
90265
90452
  try {
90266
90453
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90271,12 +90458,12 @@ async function executeWriteDriftEvidence(args2, directory) {
90271
90458
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90272
90459
  }, null, 2);
90273
90460
  }
90274
- const evidenceDir = path105.dirname(validatedPath);
90461
+ const evidenceDir = path106.dirname(validatedPath);
90275
90462
  try {
90276
- await fs85.promises.mkdir(evidenceDir, { recursive: true });
90277
- const tempPath = path105.join(evidenceDir, `.${filename}.tmp`);
90278
- await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90279
- await fs85.promises.rename(tempPath, validatedPath);
90463
+ await fs86.promises.mkdir(evidenceDir, { recursive: true });
90464
+ const tempPath = path106.join(evidenceDir, `.${filename}.tmp`);
90465
+ await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90466
+ await fs86.promises.rename(tempPath, validatedPath);
90280
90467
  let snapshotInfo;
90281
90468
  let snapshotError;
90282
90469
  let qaProfileLocked;
@@ -90370,8 +90557,8 @@ var write_drift_evidence = createSwarmTool({
90370
90557
  init_zod();
90371
90558
  init_utils2();
90372
90559
  init_create_tool();
90373
- import fs86 from "node:fs";
90374
- import path106 from "node:path";
90560
+ import fs87 from "node:fs";
90561
+ import path107 from "node:path";
90375
90562
  function normalizeVerdict2(verdict) {
90376
90563
  switch (verdict) {
90377
90564
  case "APPROVED":
@@ -90419,7 +90606,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
90419
90606
  entries: [evidenceEntry]
90420
90607
  };
90421
90608
  const filename = "hallucination-guard.json";
90422
- const relativePath = path106.join("evidence", String(phase), filename);
90609
+ const relativePath = path107.join("evidence", String(phase), filename);
90423
90610
  let validatedPath;
90424
90611
  try {
90425
90612
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90430,12 +90617,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
90430
90617
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90431
90618
  }, null, 2);
90432
90619
  }
90433
- const evidenceDir = path106.dirname(validatedPath);
90620
+ const evidenceDir = path107.dirname(validatedPath);
90434
90621
  try {
90435
- await fs86.promises.mkdir(evidenceDir, { recursive: true });
90436
- const tempPath = path106.join(evidenceDir, `.${filename}.tmp`);
90437
- await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90438
- await fs86.promises.rename(tempPath, validatedPath);
90622
+ await fs87.promises.mkdir(evidenceDir, { recursive: true });
90623
+ const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
90624
+ await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90625
+ await fs87.promises.rename(tempPath, validatedPath);
90439
90626
  return JSON.stringify({
90440
90627
  success: true,
90441
90628
  phase,
@@ -90481,8 +90668,8 @@ var write_hallucination_evidence = createSwarmTool({
90481
90668
  init_zod();
90482
90669
  init_utils2();
90483
90670
  init_create_tool();
90484
- import fs87 from "node:fs";
90485
- import path107 from "node:path";
90671
+ import fs88 from "node:fs";
90672
+ import path108 from "node:path";
90486
90673
  function normalizeVerdict3(verdict) {
90487
90674
  switch (verdict) {
90488
90675
  case "PASS":
@@ -90556,7 +90743,7 @@ async function executeWriteMutationEvidence(args2, directory) {
90556
90743
  entries: [evidenceEntry]
90557
90744
  };
90558
90745
  const filename = "mutation-gate.json";
90559
- const relativePath = path107.join("evidence", String(phase), filename);
90746
+ const relativePath = path108.join("evidence", String(phase), filename);
90560
90747
  let validatedPath;
90561
90748
  try {
90562
90749
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90567,12 +90754,12 @@ async function executeWriteMutationEvidence(args2, directory) {
90567
90754
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90568
90755
  }, null, 2);
90569
90756
  }
90570
- const evidenceDir = path107.dirname(validatedPath);
90757
+ const evidenceDir = path108.dirname(validatedPath);
90571
90758
  try {
90572
- await fs87.promises.mkdir(evidenceDir, { recursive: true });
90573
- const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
90574
- await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90575
- await fs87.promises.rename(tempPath, validatedPath);
90759
+ await fs88.promises.mkdir(evidenceDir, { recursive: true });
90760
+ const tempPath = path108.join(evidenceDir, `.${filename}.tmp`);
90761
+ await fs88.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90762
+ await fs88.promises.rename(tempPath, validatedPath);
90576
90763
  return JSON.stringify({
90577
90764
  success: true,
90578
90765
  phase,
@@ -90626,26 +90813,10 @@ init_write_retro();
90626
90813
  init_utils();
90627
90814
 
90628
90815
  // src/utils/gitignore-warning.ts
90629
- import * as fs88 from "node:fs";
90630
- import * as path108 from "node:path";
90631
- var _gitignoreWarningEmitted = false;
90632
- function findGitRoot(startDir) {
90633
- let current = startDir;
90634
- while (true) {
90635
- try {
90636
- const gitPath = path108.join(current, ".git");
90637
- const stat6 = fs88.statSync(gitPath);
90638
- if (stat6.isDirectory()) {
90639
- return current;
90640
- }
90641
- } catch {}
90642
- const parent = path108.dirname(current);
90643
- if (parent === current) {
90644
- return null;
90645
- }
90646
- current = parent;
90647
- }
90648
- }
90816
+ init_bun_compat();
90817
+ import * as fs89 from "node:fs";
90818
+ import * as path109 from "node:path";
90819
+ var _swarmGitExcludedChecked = false;
90649
90820
  function fileCoversSwarm(content) {
90650
90821
  for (const rawLine of content.split(`
90651
90822
  `)) {
@@ -90657,33 +90828,65 @@ function fileCoversSwarm(content) {
90657
90828
  }
90658
90829
  return false;
90659
90830
  }
90660
- function readFileSafe(filePath) {
90661
- try {
90662
- return fs88.readFileSync(filePath, "utf8");
90663
- } catch {
90664
- return null;
90665
- }
90666
- }
90667
- function warnIfSwarmNotGitignored(directory, quiet = false) {
90668
- if (_gitignoreWarningEmitted)
90831
+ async function ensureSwarmGitExcluded(directory, options = {}) {
90832
+ if (_swarmGitExcludedChecked)
90669
90833
  return;
90834
+ _swarmGitExcludedChecked = true;
90835
+ const { quiet = false } = options;
90670
90836
  try {
90671
- const gitRoot = findGitRoot(directory);
90837
+ const gitRootProc = bunSpawn(["git", "-C", directory, "rev-parse", "--show-toplevel"], { stdout: "pipe", stderr: "pipe" });
90838
+ const [gitRootExitCode, gitRootOutput] = await Promise.all([
90839
+ gitRootProc.exited,
90840
+ gitRootProc.stdout.text()
90841
+ ]);
90842
+ if (gitRootExitCode !== 0)
90843
+ return;
90844
+ const gitRoot = gitRootOutput.trim();
90672
90845
  if (!gitRoot)
90673
90846
  return;
90674
- const gitignoreContent = readFileSafe(path108.join(gitRoot, ".gitignore"));
90675
- if (gitignoreContent !== null && fileCoversSwarm(gitignoreContent)) {
90676
- _gitignoreWarningEmitted = true;
90847
+ const excludePathProc = bunSpawn(["git", "-C", directory, "rev-parse", "--git-path", "info/exclude"], { stdout: "pipe", stderr: "pipe" });
90848
+ const [excludePathExitCode, excludePathRaw] = await Promise.all([
90849
+ excludePathProc.exited,
90850
+ excludePathProc.stdout.text()
90851
+ ]);
90852
+ if (excludePathExitCode !== 0)
90677
90853
  return;
90678
- }
90679
- const excludeContent = readFileSafe(path108.join(gitRoot, ".git", "info", "exclude"));
90680
- if (excludeContent !== null && fileCoversSwarm(excludeContent)) {
90681
- _gitignoreWarningEmitted = true;
90854
+ const excludeRelPath = excludePathRaw.trim();
90855
+ if (!excludeRelPath)
90682
90856
  return;
90857
+ const excludePath = path109.isAbsolute(excludeRelPath) ? excludeRelPath : path109.join(directory, excludeRelPath);
90858
+ const checkIgnoreProc = bunSpawn(["git", "-C", directory, "check-ignore", "-q", ".swarm/.gitkeep"], { stdout: "pipe", stderr: "pipe" });
90859
+ const checkIgnoreExitCode = await checkIgnoreProc.exited;
90860
+ if (checkIgnoreExitCode !== 0) {
90861
+ try {
90862
+ fs89.mkdirSync(path109.dirname(excludePath), { recursive: true });
90863
+ let existing = "";
90864
+ try {
90865
+ existing = fs89.readFileSync(excludePath, "utf8");
90866
+ } catch {}
90867
+ if (!fileCoversSwarm(existing)) {
90868
+ fs89.appendFileSync(excludePath, `
90869
+ # opencode-swarm local runtime state
90870
+ .swarm/
90871
+ `, "utf8");
90872
+ if (!quiet) {
90873
+ console.warn("[opencode-swarm] Added .swarm/ to .git/info/exclude to prevent runtime state from appearing in git status.");
90874
+ }
90875
+ }
90876
+ } catch {}
90683
90877
  }
90684
- _gitignoreWarningEmitted = true;
90685
- if (!quiet) {
90686
- console.warn('[opencode-swarm] WARNING: .swarm/ is not in your .gitignore. Shell audit logs may contain API keys. Add ".swarm/" to your .gitignore to prevent accidental commits.');
90878
+ const trackedProc = bunSpawn(["git", "-C", directory, "ls-files", "--", ".swarm"], { stdout: "pipe", stderr: "pipe" });
90879
+ const [trackedExitCode, trackedOutput] = await Promise.all([
90880
+ trackedProc.exited,
90881
+ trackedProc.stdout.text()
90882
+ ]);
90883
+ if (trackedExitCode === 0 && trackedOutput.trim().length > 0) {
90884
+ console.warn(`[opencode-swarm] WARNING: .swarm/ files are tracked by Git.
90885
+ ` + `.swarm/ contains local runtime state and may contain sensitive session data.
90886
+ ` + `Ignoring will not affect already-tracked files. To stop tracking them, run:
90887
+ ` + ` git rm -r --cached .swarm
90888
+ ` + ` echo ".swarm/" >> .gitignore
90889
+ ` + ' git commit -m "Stop tracking opencode-swarm runtime state"');
90687
90890
  }
90688
90891
  } catch {}
90689
90892
  }
@@ -90791,10 +90994,10 @@ async function initializeOpenCodeSwarm(ctx) {
90791
90994
  }
90792
90995
  repoGraphHook.init().catch(() => {}).finally(() => clearTimeout(watchdog));
90793
90996
  });
90997
+ await ensureSwarmGitExcluded(ctx.directory, { quiet: config3.quiet });
90794
90998
  initTelemetry(ctx.directory);
90795
90999
  writeSwarmConfigExampleIfNew(ctx.directory);
90796
91000
  writeProjectConfigIfNew(ctx.directory, config3.quiet);
90797
- warnIfSwarmNotGitignored(ctx.directory, config3.quiet);
90798
91001
  if (config3.version_check !== false) {
90799
91002
  scheduleVersionCheck(package_default.version, (msg) => {
90800
91003
  if (config3.quiet) {
@@ -90921,7 +91124,7 @@ async function initializeOpenCodeSwarm(ctx) {
90921
91124
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
90922
91125
  preflightTriggerManager = new PTM(automationConfig);
90923
91126
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
90924
- const swarmDir = path109.resolve(ctx.directory, ".swarm");
91127
+ const swarmDir = path110.resolve(ctx.directory, ".swarm");
90925
91128
  statusArtifact = new ASA(swarmDir);
90926
91129
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
90927
91130
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {