opencode-swarm 7.3.0 → 7.3.2

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.0",
36
+ version: "7.3.2",
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",
@@ -25579,7 +25582,7 @@ function createDelegationGateHook(config2, directory) {
25579
25582
  });
25580
25583
  if (councilActive && result.overallVerdict === "APPROVE" && result.allCriteriaMet === true && (result.requiredFixesCount ?? 0) === 0) {
25581
25584
  try {
25582
- await advanceTaskStateAndPersist(session, taskId, "complete", directory, config2.council);
25585
+ await advanceTaskStateAndPersist(session, taskId, "complete", directory, { telemetrySessionId: input.sessionID }, config2.council);
25583
25586
  } catch (err2) {
25584
25587
  warn(`[delegation-gate] toolAfter submit_council_verdicts: could not advance ${taskId} → complete: ${err2 instanceof Error ? err2.message : String(err2)}`);
25585
25588
  }
@@ -25603,122 +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
- }
25625
- advanceTaskState(session, taskId, "tests_run");
25626
- } catch (err2) {
25627
- warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
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", {
25626
+ telemetrySessionId: input.sessionID
25627
+ });
25628
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)}`);
25629
25634
  }
25630
25635
  }
25631
- const seedTaskId = getSeedTaskId(session);
25632
- if (seedTaskId) {
25633
- for (const [, otherSession] of swarmState.agentSessions) {
25634
- if (otherSession === session)
25635
- continue;
25636
- if (!otherSession.taskWorkflowStates)
25637
- continue;
25638
- if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
25639
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25640
- }
25641
- const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
25642
- if (!seedState || !stageBEligibleStates.includes(seedState)) {
25643
- continue;
25644
- }
25645
- const seedEligibleState = seedState;
25646
- recordStageBCompletion(otherSession, seedTaskId, targetAgent);
25647
- if (hasBothStageBCompletions(otherSession, seedTaskId)) {
25648
- try {
25649
- if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
25650
- advanceTaskState(otherSession, seedTaskId, "reviewer_run");
25651
- }
25652
- advanceTaskState(otherSession, seedTaskId, "tests_run");
25653
- } catch (err2) {
25654
- warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25636
+ }
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)) {
25654
+ try {
25655
+ if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
25656
+ advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
25655
25657
  }
25658
+ advanceTaskState(otherSession, seedTaskId, "tests_run", {
25659
+ emitTelemetry: false
25660
+ });
25661
+ } catch (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)}`);
25656
25663
  }
25657
25664
  }
25658
25665
  }
25659
25666
  }
25660
- } else {
25661
- if (targetAgent === "reviewer" && session.taskWorkflowStates) {
25662
- for (const [taskId, state] of session.taskWorkflowStates) {
25663
- if (state === "coder_delegated" || state === "pre_check_passed") {
25664
- try {
25665
- advanceTaskState(session, taskId, "reviewer_run");
25666
- } catch (err2) {
25667
- warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25668
- }
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)}`);
25669
25678
  }
25670
25679
  }
25671
25680
  }
25672
- if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
25673
- for (const [taskId, state] of session.taskWorkflowStates) {
25674
- if (state === "reviewer_run") {
25675
- try {
25676
- advanceTaskState(session, taskId, "tests_run");
25677
- } catch (err2) {
25678
- warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25679
- }
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)}`);
25680
25691
  }
25681
25692
  }
25682
25693
  }
25683
- if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
25684
- for (const [, otherSession] of swarmState.agentSessions) {
25685
- if (otherSession === session)
25686
- continue;
25687
- if (!otherSession.taskWorkflowStates)
25688
- continue;
25689
- if (targetAgent === "reviewer") {
25690
- const seedTaskId = getSeedTaskId(session);
25691
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25692
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25693
- }
25694
- for (const [
25695
- taskId,
25696
- state
25697
- ] of otherSession.taskWorkflowStates) {
25698
- if (state === "coder_delegated" || state === "pre_check_passed") {
25699
- try {
25700
- advanceTaskState(otherSession, taskId, "reviewer_run");
25701
- } catch (err2) {
25702
- warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25703
- }
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)}`);
25704
25714
  }
25705
25715
  }
25706
25716
  }
25707
- if (targetAgent === "test_engineer") {
25708
- const seedTaskId = getSeedTaskId(session);
25709
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25710
- otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25711
- }
25712
- for (const [
25713
- taskId,
25714
- state
25715
- ] of otherSession.taskWorkflowStates) {
25716
- if (state === "reviewer_run") {
25717
- try {
25718
- advanceTaskState(otherSession, taskId, "tests_run");
25719
- } catch (err2) {
25720
- warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25721
- }
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)}`);
25722
25731
  }
25723
25732
  }
25724
25733
  }
@@ -25778,71 +25787,73 @@ function createDelegationGateHook(config2, directory) {
25778
25787
  if (target === "test_engineer")
25779
25788
  hasTestEngineer = true;
25780
25789
  }
25781
- if (!councilActive) {
25782
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25783
- session.qaSkipCount = 0;
25784
- 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
+ }
25785
25803
  }
25786
- if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
25787
- for (const [taskId, state] of session.taskWorkflowStates) {
25788
- if (state === "coder_delegated" || state === "pre_check_passed") {
25789
- try {
25790
- advanceTaskState(session, taskId, "reviewer_run");
25791
- } catch (err2) {
25792
- warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25793
- }
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)}`);
25794
25812
  }
25795
25813
  }
25796
25814
  }
25797
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
25798
- for (const [taskId, state] of session.taskWorkflowStates) {
25799
- 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") {
25800
25828
  try {
25801
- advanceTaskState(session, taskId, "tests_run");
25829
+ advanceTaskState(otherSession, taskId, "reviewer_run", {
25830
+ emitTelemetry: false
25831
+ });
25802
25832
  } catch (err2) {
25803
- 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)}`);
25804
25834
  }
25805
25835
  }
25806
25836
  }
25807
25837
  }
25808
- if (lastCoderIndex !== -1 && hasReviewer) {
25809
- for (const [, otherSession] of swarmState.agentSessions) {
25810
- if (otherSession === session)
25811
- continue;
25812
- if (!otherSession.taskWorkflowStates)
25813
- continue;
25814
- const seedTaskId = getSeedTaskId(session);
25815
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25816
- otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
25817
- }
25818
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
25819
- if (state === "coder_delegated" || state === "pre_check_passed") {
25820
- try {
25821
- advanceTaskState(otherSession, taskId, "reviewer_run");
25822
- } catch (err2) {
25823
- warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25824
- }
25825
- }
25826
- }
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");
25827
25848
  }
25828
- }
25829
- if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
25830
- for (const [, otherSession] of swarmState.agentSessions) {
25831
- if (otherSession === session)
25832
- continue;
25833
- if (!otherSession.taskWorkflowStates)
25834
- continue;
25835
- const seedTaskId = getSeedTaskId(session);
25836
- if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
25837
- otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
25838
- }
25839
- for (const [taskId, state] of otherSession.taskWorkflowStates) {
25840
- if (state === "reviewer_run") {
25841
- try {
25842
- advanceTaskState(otherSession, taskId, "tests_run");
25843
- } catch (err2) {
25844
- warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
25845
- }
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)}`);
25846
25857
  }
25847
25858
  }
25848
25859
  }
@@ -25966,7 +25977,7 @@ ${trimComment}${after}`;
25966
25977
  pendingCoderScopeByTaskId.delete(currentTaskId);
25967
25978
  }
25968
25979
  try {
25969
- await advanceTaskStateAndPersist(session, currentTaskId, "coder_delegated", directory);
25980
+ await advanceTaskStateAndPersist(session, currentTaskId, "coder_delegated", directory, { telemetrySessionId: sessionID });
25970
25981
  } catch (err2) {
25971
25982
  warn(`[delegation-gate] state machine warn: ${err2 instanceof Error ? err2.message : String(err2)}`);
25972
25983
  }
@@ -26492,7 +26503,7 @@ function isValidTaskId2(taskId) {
26492
26503
  const trimmed = taskId.trim();
26493
26504
  return trimmed.length > 0;
26494
26505
  }
26495
- function advanceTaskState(session, taskId, newState, councilConfig) {
26506
+ function advanceTaskState(session, taskId, newState, options, councilConfig) {
26496
26507
  if (!isValidTaskId2(taskId)) {
26497
26508
  return;
26498
26509
  }
@@ -26523,10 +26534,12 @@ function advanceTaskState(session, taskId, newState, councilConfig) {
26523
26534
  }
26524
26535
  }
26525
26536
  session.taskWorkflowStates.set(taskId, newState);
26526
- telemetry.taskStateChanged(session.agentName, taskId, newState, current);
26537
+ if (options?.emitTelemetry !== false) {
26538
+ telemetry.taskStateChanged(options?.telemetrySessionId ?? session.agentName, taskId, newState, current);
26539
+ }
26527
26540
  }
26528
- async function advanceTaskStateAndPersist(session, taskId, newState, directory, councilConfig) {
26529
- advanceTaskState(session, taskId, newState, councilConfig);
26541
+ async function advanceTaskStateAndPersist(session, taskId, newState, directory, options, councilConfig) {
26542
+ advanceTaskState(session, taskId, newState, options, councilConfig);
26530
26543
  if (newState !== "coder_delegated" && newState !== "complete") {
26531
26544
  return;
26532
26545
  }
@@ -56133,106 +56146,101 @@ ${validation.warnings.join(`
56133
56146
  function buildCouncilWorkflow(council) {
56134
56147
  if (council?.enabled !== true)
56135
56148
  return "";
56136
- return `## COUNCIL WORKFLOW (submit_council_verdicts)
56149
+ return `## COUNCIL WORKFLOW (submit_phase_council_verdicts)
56137
56150
 
56138
- CRITICAL: \`submit_council_verdicts\` does NOT run council members.
56151
+ CRITICAL: \`submit_phase_council_verdicts\` does NOT run council members.
56139
56152
  It synthesizes verdicts that you must collect BEFORE calling it.
56140
56153
 
56141
- When \`council.enabled\` is true, every task goes through this verification
56142
- gate before advancing to \`complete\`. The council REPLACES Stage B
56143
- (reviewer + test_engineer as standalone delegations). Stage A
56144
- (\`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.
56145
56158
 
56146
- ### Phase 0 Pre-declare criteria (at plan time, BEFORE dispatching the coder)
56147
- Call \`declare_council_criteria\` for each task with at least 3 concrete,
56148
- testable acceptance criteria. Mark functional/correctness criteria
56149
- \`mandatory: true\`; style/naming criteria \`mandatory: false\`. Criterion ids
56150
- follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
56151
- \`.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
56152
56166
 
56153
56167
  ### MANDATORY SEQUENCE — never skip or reorder
56154
56168
 
56155
- #### STEP 1 — DISPATCH all council members as parallel Agent tasks
56156
- Dispatch \`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`, and \`explorer\`
56157
- (or at minimum \`council.minimumMembers\` distinct members; default 3) in a
56158
- SINGLE message using parallel Agent tool calls. Provide each member with
56159
- their role-specific scope:
56160
- - \`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
56161
- - \`reviewer\` semantic diff summary + blast radius (files importing changed files) + style guide
56162
- - \`sme\` task domain context + relevant knowledge base entries
56163
- - \`test_engineer\` changed test files + coverage delta + known mutation gaps
56164
- - \`explorer\` — full diff + original task intent + any prior slop findings
56165
- (explorer hunts for lazy implementations, hallucinated APIs,
56166
- cargo-cult patterns, spec drift, lazy abstractions)
56167
-
56168
- 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.
56169
56181
 
56170
56182
  #### STEP 2 — COLLECT verdicts
56171
56183
  Read each agent's response and extract their \`CouncilMemberVerdict\` object.
56172
- Each member must return all fields: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
56184
+ Each member must return: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
56173
56185
  \`confidence\` (0.0–1.0), \`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`,
56174
56186
  \`durationMs\`.
56175
56187
 
56176
56188
  Do NOT fabricate, infer, or substitute a verdict. If an agent did not return
56177
- a valid verdict, re-dispatch that agent.
56189
+ a valid verdict object, re-dispatch that agent.
56178
56190
 
56179
- #### STEP 3 — CALL submit_council_verdicts
56180
- ONLY after collecting real verdicts from real agent dispatches, call
56181
- \`submit_council_verdicts\` with the collected verdicts array, the task id,
56182
- 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.
56183
56202
 
56184
56203
  #### STEP 4 — READ the response
56185
- Inspect \`membersAbsent\` in the response. If \`membersAbsent\` is non-empty,
56186
- the council is incomplete — dispatch the missing members and re-collect.
56187
- Inspect \`overallVerdict\`. APPROVE is valid only when \`membersAbsent\` is
56188
- empty (or fewer members than \`council.minimumMembers\` are absent).
56189
-
56190
- The response also includes: \`vetoedBy\`, \`unifiedFeedbackMd\`,
56191
- \`requiredFixesCount\`, \`advisoryFindingsCount\`, \`allCriteriaMet\`,
56192
- \`quorumSize\`, \`quorumMet\`.
56193
-
56194
- If \`success: false\` and \`reason: 'insufficient_quorum'\`, the response
56195
- includes \`membersVoted\`, \`membersAbsent\`, and \`quorumRequired\` — dispatch
56196
- the absent members and re-call the tool.
56197
-
56198
- #### STEP 5 ACT on the verdict
56199
- - **APPROVE**: Advance task to complete via \`update_task_status\`. If
56200
- \`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as
56201
- a single non-blocking note. Otherwise, advance silently.
56202
- - **CONCERNS**: Send \`unifiedFeedbackMd\` to the coder as ONE coherent
56203
- document. Do NOT enumerate individual member verdicts.
56204
- Increment \`roundNumber\` on the next council call. CONCERNS
56205
- does not block advancement at the update_task_status level —
56206
- decide per severity whether to advance or retry.
56207
- - **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
56208
- with the BLOCKING flag. The coder must resolve all
56209
- \`requiredFixes\` before re-submitting. Maximum
56210
- \`council.maxRounds\` rounds (default 3). If
56211
- \`roundNumber >= maxRounds\` and verdict is still REJECT,
56212
- surface \`unifiedFeedbackMd\` to the user and HALT — do NOT
56213
- auto-advance.
56214
-
56215
- ### ANTI-PATTERNS — any of these are council bypass violations
56216
- - ✗ Calling \`submit_council_verdicts\` without first dispatching council members.
56217
- - ✗ 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.
56218
56227
  - ✗ Claiming "Council APPROVED" when \`membersAbsent\` is non-empty.
56219
- - ✗ Treating a prior round's APPROVE as valid for a new task or new round.
56220
- - ✗ 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.
56221
56232
 
56222
56233
  ### ROUND 2 DELIBERATION
56223
- If round 1 produces REJECT or CONCERNS, dispatch only the disputing members
56224
- for round 2 focused on the specific disagreement areas. Round 2 must produce
56225
- NEW agent responses — do NOT reuse round 1 verdicts with a higher
56226
- \`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.
56227
56237
 
56228
56238
  ### Retry protocol
56229
- On re-submission after REJECT or CONCERNS, the council reads the same
56230
- pre-declared criteria and receives (a) the previous synthesis findings plus
56231
- (b) the diff of what changed since the last round. Council members verify
56232
- prior findings are resolved without re-reviewing unchanged code. The
56233
- architect resolves any \`unresolvedConflicts\` in \`unifiedFeedbackMd\` BEFORE
56234
- sending it to the coder — the coder never sees contradictory instructions
56235
- 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.`;
56236
56244
  }
56237
56245
  function buildYourToolsList(council) {
56238
56246
  const tools = AGENT_TOOL_MAP.architect ?? [];
@@ -56240,7 +56248,7 @@ function buildYourToolsList(council) {
56240
56248
  const qaCouncilEnabled = council?.enabled === true;
56241
56249
  const generalCouncilEnabled = council?.general?.enabled === true;
56242
56250
  const filtered = sorted.filter((t) => {
56243
- 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")) {
56244
56252
  return false;
56245
56253
  }
56246
56254
  if (!generalCouncilEnabled && t === "convene_general_council") {
@@ -56274,7 +56282,7 @@ function buildAvailableToolsList(council) {
56274
56282
  const qaCouncilEnabled = council?.enabled === true;
56275
56283
  const generalCouncilEnabled = council?.general?.enabled === true;
56276
56284
  const filtered = sorted.filter((t) => {
56277
- 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")) {
56278
56286
  return false;
56279
56287
  }
56280
56288
  if (!generalCouncilEnabled && t === "convene_general_council") {
@@ -56660,7 +56668,7 @@ TIER 3 — CRITICAL
56660
56668
  Pipeline: Full Stage A. Stage B = {{AGENT_PREFIX}}reviewer×2 + {{AGENT_PREFIX}}test_engineer×2.
56661
56669
  Rationale: Security paths need adversarial review.
56662
56670
 
56663
- 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.
56664
56672
 
56665
56673
  CLASSIFICATION RULES:
56666
56674
  - Multi-tier → use HIGHEST tier.
@@ -56687,7 +56695,7 @@ Stage B runs by default for TIER 1-3 classifications. Stage A passing does not s
56687
56695
  Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
56688
56696
  You MUST delegate to each Stage B agent and wait for their response.
56689
56697
 
56690
- 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.
56691
56699
 
56692
56700
  A task is complete ONLY when BOTH stages pass.
56693
56701
 
@@ -64980,7 +64988,7 @@ var init_curator_drift = __esm(() => {
64980
64988
  // src/index.ts
64981
64989
  init_package();
64982
64990
  init_agents2();
64983
- import * as path109 from "node:path";
64991
+ import * as path110 from "node:path";
64984
64992
 
64985
64993
  // src/background/index.ts
64986
64994
  init_event_bus();
@@ -65364,8 +65372,9 @@ function createAgentActivityHooks(config3, directory) {
65364
65372
  return;
65365
65373
  swarmState.activeToolCalls.delete(input.callID);
65366
65374
  const duration5 = Date.now() - entry.startTime;
65367
- const rawOutput = output.output;
65368
- const success3 = rawOutput !== null && rawOutput !== undefined;
65375
+ const explicitSuccess = typeof output.success === "boolean" ? output.success : undefined;
65376
+ const explicitFailure = explicitSuccess === false || !!output.error;
65377
+ const success3 = explicitFailure ? false : true;
65369
65378
  const key = entry.tool;
65370
65379
  const existing = swarmState.toolAggregates.get(key) ?? {
65371
65380
  tool: key,
@@ -76460,6 +76469,10 @@ function writeCouncilEvidence(workingDir, synthesis) {
76460
76469
  }
76461
76470
  }
76462
76471
 
76472
+ // src/council/council-service.ts
76473
+ import fs59 from "node:fs";
76474
+ import path77 from "node:path";
76475
+
76463
76476
  // src/council/types.ts
76464
76477
  var COUNCIL_DEFAULTS = {
76465
76478
  enabled: false,
@@ -76584,6 +76597,143 @@ function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisory
76584
76597
  return lines.join(`
76585
76598
  `);
76586
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
+ }
76587
76737
 
76588
76738
  // src/council/criteria-store.ts
76589
76739
  import { existsSync as existsSync39, mkdirSync as mkdirSync20, readFileSync as readFileSync37, writeFileSync as writeFileSync13 } from "node:fs";
@@ -76752,8 +76902,8 @@ var submit_council_verdicts = createSwarmTool({
76752
76902
  // src/tools/convene-general-council.ts
76753
76903
  init_zod();
76754
76904
  init_loader();
76755
- import * as fs59 from "node:fs";
76756
- import * as path77 from "node:path";
76905
+ import * as fs60 from "node:fs";
76906
+ import * as path78 from "node:path";
76757
76907
 
76758
76908
  // src/council/general-council-advisory.ts
76759
76909
  var ADVISORY_HEADER = "[general_council] (advisory; not blocking)";
@@ -77181,13 +77331,13 @@ var convene_general_council = createSwarmTool({
77181
77331
  const round1 = input.round1Responses;
77182
77332
  const round2 = input.round2Responses ?? [];
77183
77333
  const result = synthesizeGeneralCouncil(input.question, input.mode, round1, round2);
77184
- const evidenceDir = path77.join(workingDir, ".swarm", "council", "general");
77334
+ const evidenceDir = path78.join(workingDir, ".swarm", "council", "general");
77185
77335
  const safeTimestamp = result.timestamp.replace(/[:.]/g, "-");
77186
77336
  const evidenceFile = `${safeTimestamp}-${input.mode}.json`;
77187
- const evidencePath = path77.join(evidenceDir, evidenceFile);
77337
+ const evidencePath = path78.join(evidenceDir, evidenceFile);
77188
77338
  try {
77189
- await fs59.promises.mkdir(evidenceDir, { recursive: true });
77190
- 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));
77191
77341
  } catch (err2) {
77192
77342
  const message = err2 instanceof Error ? err2.message : String(err2);
77193
77343
  console.warn(`[convene_general_council] Failed to write evidence to ${evidencePath}: ${message}`);
@@ -77418,8 +77568,8 @@ init_scope_persistence();
77418
77568
  init_state();
77419
77569
  init_task_id();
77420
77570
  init_create_tool();
77421
- import * as fs60 from "node:fs";
77422
- import * as path78 from "node:path";
77571
+ import * as fs61 from "node:fs";
77572
+ import * as path79 from "node:path";
77423
77573
  function validateTaskIdFormat2(taskId) {
77424
77574
  return validateTaskIdFormat(taskId);
77425
77575
  }
@@ -77493,8 +77643,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77493
77643
  };
77494
77644
  }
77495
77645
  }
77496
- normalizedDir = path78.normalize(args2.working_directory);
77497
- const pathParts = normalizedDir.split(path78.sep);
77646
+ normalizedDir = path79.normalize(args2.working_directory);
77647
+ const pathParts = normalizedDir.split(path79.sep);
77498
77648
  if (pathParts.includes("..")) {
77499
77649
  return {
77500
77650
  success: false,
@@ -77504,11 +77654,11 @@ async function executeDeclareScope(args2, fallbackDir) {
77504
77654
  ]
77505
77655
  };
77506
77656
  }
77507
- const resolvedDir = path78.resolve(normalizedDir);
77657
+ const resolvedDir = path79.resolve(normalizedDir);
77508
77658
  try {
77509
- const realPath = fs60.realpathSync(resolvedDir);
77510
- const planPath2 = path78.join(realPath, ".swarm", "plan.json");
77511
- if (!fs60.existsSync(planPath2)) {
77659
+ const realPath = fs61.realpathSync(resolvedDir);
77660
+ const planPath2 = path79.join(realPath, ".swarm", "plan.json");
77661
+ if (!fs61.existsSync(planPath2)) {
77512
77662
  return {
77513
77663
  success: false,
77514
77664
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -77531,8 +77681,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77531
77681
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
77532
77682
  }
77533
77683
  const directory = normalizedDir || fallbackDir;
77534
- const planPath = path78.resolve(directory, ".swarm", "plan.json");
77535
- if (!fs60.existsSync(planPath)) {
77684
+ const planPath = path79.resolve(directory, ".swarm", "plan.json");
77685
+ if (!fs61.existsSync(planPath)) {
77536
77686
  return {
77537
77687
  success: false,
77538
77688
  message: "No plan found",
@@ -77541,7 +77691,7 @@ async function executeDeclareScope(args2, fallbackDir) {
77541
77691
  }
77542
77692
  let planContent;
77543
77693
  try {
77544
- planContent = JSON.parse(fs60.readFileSync(planPath, "utf-8"));
77694
+ planContent = JSON.parse(fs61.readFileSync(planPath, "utf-8"));
77545
77695
  } catch {
77546
77696
  return {
77547
77697
  success: false,
@@ -77571,8 +77721,8 @@ async function executeDeclareScope(args2, fallbackDir) {
77571
77721
  const normalizeErrors = [];
77572
77722
  const dir = normalizedDir || fallbackDir || process.cwd();
77573
77723
  const mergedFiles = rawMergedFiles.map((file3) => {
77574
- if (path78.isAbsolute(file3)) {
77575
- const relativePath = path78.relative(dir, file3).replace(/\\/g, "/");
77724
+ if (path79.isAbsolute(file3)) {
77725
+ const relativePath = path79.relative(dir, file3).replace(/\\/g, "/");
77576
77726
  if (relativePath.startsWith("..")) {
77577
77727
  normalizeErrors.push(`Path '${file3}' resolves outside the project directory`);
77578
77728
  return file3;
@@ -77632,8 +77782,8 @@ var declare_scope = createSwarmTool({
77632
77782
  // src/tools/diff.ts
77633
77783
  init_zod();
77634
77784
  import * as child_process7 from "node:child_process";
77635
- import * as fs61 from "node:fs";
77636
- import * as path79 from "node:path";
77785
+ import * as fs62 from "node:fs";
77786
+ import * as path80 from "node:path";
77637
77787
  init_create_tool();
77638
77788
  var MAX_DIFF_LINES = 500;
77639
77789
  var DIFF_TIMEOUT_MS = 30000;
@@ -77662,20 +77812,20 @@ function validateBase(base) {
77662
77812
  function validatePaths(paths) {
77663
77813
  if (!paths)
77664
77814
  return null;
77665
- for (const path80 of paths) {
77666
- if (!path80 || path80.length === 0) {
77815
+ for (const path81 of paths) {
77816
+ if (!path81 || path81.length === 0) {
77667
77817
  return "empty path not allowed";
77668
77818
  }
77669
- if (path80.length > MAX_PATH_LENGTH) {
77819
+ if (path81.length > MAX_PATH_LENGTH) {
77670
77820
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
77671
77821
  }
77672
- if (SHELL_METACHARACTERS2.test(path80)) {
77822
+ if (SHELL_METACHARACTERS2.test(path81)) {
77673
77823
  return "path contains shell metacharacters";
77674
77824
  }
77675
- if (path80.startsWith("-")) {
77825
+ if (path81.startsWith("-")) {
77676
77826
  return 'path cannot start with "-" (option-like arguments not allowed)';
77677
77827
  }
77678
- if (CONTROL_CHAR_PATTERN2.test(path80)) {
77828
+ if (CONTROL_CHAR_PATTERN2.test(path81)) {
77679
77829
  return "path contains control characters";
77680
77830
  }
77681
77831
  }
@@ -77781,8 +77931,8 @@ var diff = createSwarmTool({
77781
77931
  if (parts2.length >= 3) {
77782
77932
  const additions = parseInt(parts2[0], 10) || 0;
77783
77933
  const deletions = parseInt(parts2[1], 10) || 0;
77784
- const path80 = parts2[2];
77785
- files.push({ path: path80, additions, deletions });
77934
+ const path81 = parts2[2];
77935
+ files.push({ path: path81, additions, deletions });
77786
77936
  }
77787
77937
  }
77788
77938
  const contractChanges = [];
@@ -77822,7 +77972,7 @@ var diff = createSwarmTool({
77822
77972
  } else if (base === "unstaged") {
77823
77973
  const oldRef = `:${file3.path}`;
77824
77974
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
77825
- newContent = fs61.readFileSync(path79.join(directory, file3.path), "utf-8");
77975
+ newContent = fs62.readFileSync(path80.join(directory, file3.path), "utf-8");
77826
77976
  } else {
77827
77977
  const oldRef = `${base}:${file3.path}`;
77828
77978
  oldContent = fileExistsInRef(oldRef) ? getContentFromRef(oldRef) : "";
@@ -77896,8 +78046,8 @@ var diff = createSwarmTool({
77896
78046
  // src/tools/diff-summary.ts
77897
78047
  init_zod();
77898
78048
  import * as child_process8 from "node:child_process";
77899
- import * as fs62 from "node:fs";
77900
- import * as path80 from "node:path";
78049
+ import * as fs63 from "node:fs";
78050
+ import * as path81 from "node:path";
77901
78051
  init_create_tool();
77902
78052
  var diff_summary = createSwarmTool({
77903
78053
  description: "Generate a filtered semantic diff summary from AST analysis. Returns SemanticDiffSummary with optional filtering by classification or riskLevel.",
@@ -77945,7 +78095,7 @@ var diff_summary = createSwarmTool({
77945
78095
  }
77946
78096
  try {
77947
78097
  let oldContent;
77948
- const newContent = fs62.readFileSync(path80.join(workingDir, filePath), "utf-8");
78098
+ const newContent = fs63.readFileSync(path81.join(workingDir, filePath), "utf-8");
77949
78099
  if (fileExistsInHead) {
77950
78100
  oldContent = child_process8.execFileSync("git", ["show", `HEAD:${filePath}`], {
77951
78101
  encoding: "utf-8",
@@ -78173,8 +78323,8 @@ Use these as DOMAIN values when delegating to @sme.`;
78173
78323
  init_zod();
78174
78324
  init_create_tool();
78175
78325
  init_path_security();
78176
- import * as fs63 from "node:fs";
78177
- import * as path81 from "node:path";
78326
+ import * as fs64 from "node:fs";
78327
+ import * as path82 from "node:path";
78178
78328
  var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
78179
78329
  var MAX_EVIDENCE_FILES = 1000;
78180
78330
  var EVIDENCE_DIR3 = ".swarm/evidence";
@@ -78201,9 +78351,9 @@ function validateRequiredTypes(input) {
78201
78351
  return null;
78202
78352
  }
78203
78353
  function isPathWithinSwarm2(filePath, cwd) {
78204
- const normalizedCwd = path81.resolve(cwd);
78205
- const swarmPath = path81.join(normalizedCwd, ".swarm");
78206
- const normalizedPath = path81.resolve(filePath);
78354
+ const normalizedCwd = path82.resolve(cwd);
78355
+ const swarmPath = path82.join(normalizedCwd, ".swarm");
78356
+ const normalizedPath = path82.resolve(filePath);
78207
78357
  return normalizedPath.startsWith(swarmPath);
78208
78358
  }
78209
78359
  function parseCompletedTasks(planContent) {
@@ -78219,12 +78369,12 @@ function parseCompletedTasks(planContent) {
78219
78369
  }
78220
78370
  function readEvidenceFiles(evidenceDir, _cwd) {
78221
78371
  const evidence = [];
78222
- if (!fs63.existsSync(evidenceDir) || !fs63.statSync(evidenceDir).isDirectory()) {
78372
+ if (!fs64.existsSync(evidenceDir) || !fs64.statSync(evidenceDir).isDirectory()) {
78223
78373
  return evidence;
78224
78374
  }
78225
78375
  let files;
78226
78376
  try {
78227
- files = fs63.readdirSync(evidenceDir);
78377
+ files = fs64.readdirSync(evidenceDir);
78228
78378
  } catch {
78229
78379
  return evidence;
78230
78380
  }
@@ -78233,14 +78383,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78233
78383
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
78234
78384
  continue;
78235
78385
  }
78236
- const filePath = path81.join(evidenceDir, filename);
78386
+ const filePath = path82.join(evidenceDir, filename);
78237
78387
  try {
78238
- const resolvedPath = path81.resolve(filePath);
78239
- const evidenceDirResolved = path81.resolve(evidenceDir);
78388
+ const resolvedPath = path82.resolve(filePath);
78389
+ const evidenceDirResolved = path82.resolve(evidenceDir);
78240
78390
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
78241
78391
  continue;
78242
78392
  }
78243
- const stat6 = fs63.lstatSync(filePath);
78393
+ const stat6 = fs64.lstatSync(filePath);
78244
78394
  if (!stat6.isFile()) {
78245
78395
  continue;
78246
78396
  }
@@ -78249,7 +78399,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78249
78399
  }
78250
78400
  let fileStat;
78251
78401
  try {
78252
- fileStat = fs63.statSync(filePath);
78402
+ fileStat = fs64.statSync(filePath);
78253
78403
  if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
78254
78404
  continue;
78255
78405
  }
@@ -78258,7 +78408,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
78258
78408
  }
78259
78409
  let content;
78260
78410
  try {
78261
- content = fs63.readFileSync(filePath, "utf-8");
78411
+ content = fs64.readFileSync(filePath, "utf-8");
78262
78412
  } catch {
78263
78413
  continue;
78264
78414
  }
@@ -78354,7 +78504,7 @@ var evidence_check = createSwarmTool({
78354
78504
  return JSON.stringify(errorResult, null, 2);
78355
78505
  }
78356
78506
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0).map(normalizeEvidenceType);
78357
- const planPath = path81.join(cwd, PLAN_FILE);
78507
+ const planPath = path82.join(cwd, PLAN_FILE);
78358
78508
  if (!isPathWithinSwarm2(planPath, cwd)) {
78359
78509
  const errorResult = {
78360
78510
  error: "plan file path validation failed",
@@ -78368,7 +78518,7 @@ var evidence_check = createSwarmTool({
78368
78518
  }
78369
78519
  let planContent;
78370
78520
  try {
78371
- planContent = fs63.readFileSync(planPath, "utf-8");
78521
+ planContent = fs64.readFileSync(planPath, "utf-8");
78372
78522
  } catch {
78373
78523
  const result2 = {
78374
78524
  message: "No completed tasks found in plan.",
@@ -78386,7 +78536,7 @@ var evidence_check = createSwarmTool({
78386
78536
  };
78387
78537
  return JSON.stringify(result2, null, 2);
78388
78538
  }
78389
- const evidenceDir = path81.join(cwd, EVIDENCE_DIR3);
78539
+ const evidenceDir = path82.join(cwd, EVIDENCE_DIR3);
78390
78540
  const evidence = readEvidenceFiles(evidenceDir, cwd);
78391
78541
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
78392
78542
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -78403,8 +78553,8 @@ var evidence_check = createSwarmTool({
78403
78553
  // src/tools/file-extractor.ts
78404
78554
  init_zod();
78405
78555
  init_create_tool();
78406
- import * as fs64 from "node:fs";
78407
- import * as path82 from "node:path";
78556
+ import * as fs65 from "node:fs";
78557
+ import * as path83 from "node:path";
78408
78558
  var EXT_MAP = {
78409
78559
  python: ".py",
78410
78560
  py: ".py",
@@ -78466,8 +78616,8 @@ var extract_code_blocks = createSwarmTool({
78466
78616
  execute: async (args2, directory) => {
78467
78617
  const { content, output_dir, prefix } = args2;
78468
78618
  const targetDir = output_dir || directory;
78469
- if (!fs64.existsSync(targetDir)) {
78470
- fs64.mkdirSync(targetDir, { recursive: true });
78619
+ if (!fs65.existsSync(targetDir)) {
78620
+ fs65.mkdirSync(targetDir, { recursive: true });
78471
78621
  }
78472
78622
  if (!content) {
78473
78623
  return "Error: content is required";
@@ -78485,16 +78635,16 @@ var extract_code_blocks = createSwarmTool({
78485
78635
  if (prefix) {
78486
78636
  filename = `${prefix}_${filename}`;
78487
78637
  }
78488
- let filepath = path82.join(targetDir, filename);
78489
- const base = path82.basename(filepath, path82.extname(filepath));
78490
- 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);
78491
78641
  let counter = 1;
78492
- while (fs64.existsSync(filepath)) {
78493
- filepath = path82.join(targetDir, `${base}_${counter}${ext}`);
78642
+ while (fs65.existsSync(filepath)) {
78643
+ filepath = path83.join(targetDir, `${base}_${counter}${ext}`);
78494
78644
  counter++;
78495
78645
  }
78496
78646
  try {
78497
- fs64.writeFileSync(filepath, code.trim(), "utf-8");
78647
+ fs65.writeFileSync(filepath, code.trim(), "utf-8");
78498
78648
  savedFiles.push(filepath);
78499
78649
  } catch (error93) {
78500
78650
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -78753,8 +78903,8 @@ var gitingest = createSwarmTool({
78753
78903
  init_zod();
78754
78904
  init_create_tool();
78755
78905
  init_path_security();
78756
- import * as fs65 from "node:fs";
78757
- import * as path83 from "node:path";
78906
+ import * as fs66 from "node:fs";
78907
+ import * as path84 from "node:path";
78758
78908
  var MAX_FILE_PATH_LENGTH2 = 500;
78759
78909
  var MAX_SYMBOL_LENGTH = 256;
78760
78910
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
@@ -78802,7 +78952,7 @@ function validateSymbolInput(symbol3) {
78802
78952
  return null;
78803
78953
  }
78804
78954
  function isBinaryFile2(filePath, buffer) {
78805
- const ext = path83.extname(filePath).toLowerCase();
78955
+ const ext = path84.extname(filePath).toLowerCase();
78806
78956
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
78807
78957
  return false;
78808
78958
  }
@@ -78826,15 +78976,15 @@ function parseImports(content, targetFile, targetSymbol) {
78826
78976
  const imports = [];
78827
78977
  let _resolvedTarget;
78828
78978
  try {
78829
- _resolvedTarget = path83.resolve(targetFile);
78979
+ _resolvedTarget = path84.resolve(targetFile);
78830
78980
  } catch {
78831
78981
  _resolvedTarget = targetFile;
78832
78982
  }
78833
- const targetBasename = path83.basename(targetFile, path83.extname(targetFile));
78983
+ const targetBasename = path84.basename(targetFile, path84.extname(targetFile));
78834
78984
  const targetWithExt = targetFile;
78835
78985
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
78836
- const normalizedTargetWithExt = path83.normalize(targetWithExt).replace(/\\/g, "/");
78837
- const normalizedTargetWithoutExt = path83.normalize(targetWithoutExt).replace(/\\/g, "/");
78986
+ const normalizedTargetWithExt = path84.normalize(targetWithExt).replace(/\\/g, "/");
78987
+ const normalizedTargetWithoutExt = path84.normalize(targetWithoutExt).replace(/\\/g, "/");
78838
78988
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
78839
78989
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
78840
78990
  const modulePath = match[1] || match[2] || match[3];
@@ -78857,9 +79007,9 @@ function parseImports(content, targetFile, targetSymbol) {
78857
79007
  }
78858
79008
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
78859
79009
  let isMatch = false;
78860
- const _targetDir = path83.dirname(targetFile);
78861
- const targetExt = path83.extname(targetFile);
78862
- 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);
78863
79013
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
78864
79014
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
78865
79015
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -78916,7 +79066,7 @@ var SKIP_DIRECTORIES4 = new Set([
78916
79066
  function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
78917
79067
  let entries;
78918
79068
  try {
78919
- entries = fs65.readdirSync(dir);
79069
+ entries = fs66.readdirSync(dir);
78920
79070
  } catch (e) {
78921
79071
  stats.fileErrors.push({
78922
79072
  path: dir,
@@ -78927,13 +79077,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
78927
79077
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
78928
79078
  for (const entry of entries) {
78929
79079
  if (SKIP_DIRECTORIES4.has(entry)) {
78930
- stats.skippedDirs.push(path83.join(dir, entry));
79080
+ stats.skippedDirs.push(path84.join(dir, entry));
78931
79081
  continue;
78932
79082
  }
78933
- const fullPath = path83.join(dir, entry);
79083
+ const fullPath = path84.join(dir, entry);
78934
79084
  let stat6;
78935
79085
  try {
78936
- stat6 = fs65.statSync(fullPath);
79086
+ stat6 = fs66.statSync(fullPath);
78937
79087
  } catch (e) {
78938
79088
  stats.fileErrors.push({
78939
79089
  path: fullPath,
@@ -78944,7 +79094,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
78944
79094
  if (stat6.isDirectory()) {
78945
79095
  findSourceFiles2(fullPath, files, stats);
78946
79096
  } else if (stat6.isFile()) {
78947
- const ext = path83.extname(fullPath).toLowerCase();
79097
+ const ext = path84.extname(fullPath).toLowerCase();
78948
79098
  if (SUPPORTED_EXTENSIONS3.includes(ext)) {
78949
79099
  files.push(fullPath);
78950
79100
  }
@@ -79001,8 +79151,8 @@ var imports = createSwarmTool({
79001
79151
  return JSON.stringify(errorResult, null, 2);
79002
79152
  }
79003
79153
  try {
79004
- const targetFile = path83.resolve(file3);
79005
- if (!fs65.existsSync(targetFile)) {
79154
+ const targetFile = path84.resolve(file3);
79155
+ if (!fs66.existsSync(targetFile)) {
79006
79156
  const errorResult = {
79007
79157
  error: `target file not found: ${file3}`,
79008
79158
  target: file3,
@@ -79012,7 +79162,7 @@ var imports = createSwarmTool({
79012
79162
  };
79013
79163
  return JSON.stringify(errorResult, null, 2);
79014
79164
  }
79015
- const targetStat = fs65.statSync(targetFile);
79165
+ const targetStat = fs66.statSync(targetFile);
79016
79166
  if (!targetStat.isFile()) {
79017
79167
  const errorResult = {
79018
79168
  error: "target must be a file, not a directory",
@@ -79023,7 +79173,7 @@ var imports = createSwarmTool({
79023
79173
  };
79024
79174
  return JSON.stringify(errorResult, null, 2);
79025
79175
  }
79026
- const baseDir = path83.dirname(targetFile);
79176
+ const baseDir = path84.dirname(targetFile);
79027
79177
  const scanStats = {
79028
79178
  skippedDirs: [],
79029
79179
  skippedFiles: 0,
@@ -79038,12 +79188,12 @@ var imports = createSwarmTool({
79038
79188
  if (consumers.length >= MAX_CONSUMERS)
79039
79189
  break;
79040
79190
  try {
79041
- const stat6 = fs65.statSync(filePath);
79191
+ const stat6 = fs66.statSync(filePath);
79042
79192
  if (stat6.size > MAX_FILE_SIZE_BYTES7) {
79043
79193
  skippedFileCount++;
79044
79194
  continue;
79045
79195
  }
79046
- const buffer = fs65.readFileSync(filePath);
79196
+ const buffer = fs66.readFileSync(filePath);
79047
79197
  if (isBinaryFile2(filePath, buffer)) {
79048
79198
  skippedFileCount++;
79049
79199
  continue;
@@ -79568,8 +79718,8 @@ init_schema();
79568
79718
  init_qa_gate_profile();
79569
79719
  init_manager2();
79570
79720
  init_curator();
79571
- import * as fs67 from "node:fs";
79572
- import * as path85 from "node:path";
79721
+ import * as fs68 from "node:fs";
79722
+ import * as path86 from "node:path";
79573
79723
  init_knowledge_curator();
79574
79724
  init_knowledge_reader();
79575
79725
  init_knowledge_store();
@@ -79581,20 +79731,20 @@ init_file_locks();
79581
79731
  init_plan_schema();
79582
79732
  init_ledger();
79583
79733
  init_manager();
79584
- import * as fs66 from "node:fs";
79585
- import * as path84 from "node:path";
79734
+ import * as fs67 from "node:fs";
79735
+ import * as path85 from "node:path";
79586
79736
  async function writeCheckpoint(directory) {
79587
79737
  try {
79588
79738
  const plan = await loadPlan(directory);
79589
79739
  if (!plan)
79590
79740
  return;
79591
- const swarmDir = path84.join(directory, ".swarm");
79592
- fs66.mkdirSync(swarmDir, { recursive: true });
79593
- const jsonPath = path84.join(swarmDir, "SWARM_PLAN.json");
79594
- const mdPath = path84.join(swarmDir, "SWARM_PLAN.md");
79595
- 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");
79596
79746
  const md = derivePlanMarkdown(plan);
79597
- fs66.writeFileSync(mdPath, md, "utf8");
79747
+ fs67.writeFileSync(mdPath, md, "utf8");
79598
79748
  } catch (error93) {
79599
79749
  console.warn(`[checkpoint] Failed to write SWARM_PLAN checkpoint: ${error93 instanceof Error ? error93.message : String(error93)}`);
79600
79750
  }
@@ -79826,8 +79976,8 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79826
79976
  let driftCheckEnabled = true;
79827
79977
  let driftHasSpecMd = false;
79828
79978
  try {
79829
- const specMdPath = path85.join(dir, ".swarm", "spec.md");
79830
- driftHasSpecMd = fs67.existsSync(specMdPath);
79979
+ const specMdPath = path86.join(dir, ".swarm", "spec.md");
79980
+ driftHasSpecMd = fs68.existsSync(specMdPath);
79831
79981
  const gatePlan = await loadPlan(dir);
79832
79982
  if (gatePlan) {
79833
79983
  const gatePlanId = `${gatePlan.swarm}-${gatePlan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
@@ -79848,9 +79998,9 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79848
79998
  } else {
79849
79999
  let phaseType;
79850
80000
  try {
79851
- const planPath = path85.join(dir, ".swarm", "plan.json");
79852
- if (fs67.existsSync(planPath)) {
79853
- 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");
79854
80004
  const plan = JSON.parse(planRaw);
79855
80005
  const targetPhase = plan.phases?.find((p) => p.id === phase);
79856
80006
  phaseType = targetPhase?.type;
@@ -79861,11 +80011,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79861
80011
  warnings.push(`Phase ${phase} is annotated as 'non-code'. Drift verification was skipped per phase type annotation.`);
79862
80012
  } else {
79863
80013
  try {
79864
- 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");
79865
80015
  let driftVerdictFound = false;
79866
80016
  let driftVerdictApproved = false;
79867
80017
  try {
79868
- const driftEvidenceContent = fs67.readFileSync(driftEvidencePath, "utf-8");
80018
+ const driftEvidenceContent = fs68.readFileSync(driftEvidencePath, "utf-8");
79869
80019
  const driftEvidence = JSON.parse(driftEvidenceContent);
79870
80020
  const entries = driftEvidence.entries ?? [];
79871
80021
  for (const entry of entries) {
@@ -79899,9 +80049,9 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79899
80049
  let incompleteTaskCount = 0;
79900
80050
  let planParseable = false;
79901
80051
  try {
79902
- const planPath = path85.join(dir, ".swarm", "plan.json");
79903
- if (fs67.existsSync(planPath)) {
79904
- 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");
79905
80055
  const plan = JSON.parse(planRaw);
79906
80056
  planParseable = true;
79907
80057
  const planPhase = plan.phases?.find((p) => p.id === phase);
@@ -79966,11 +80116,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
79966
80116
  const overrides = session2?.qaGateSessionOverrides ?? {};
79967
80117
  const effective = getEffectiveGates(profile, overrides);
79968
80118
  if (effective.hallucination_guard === true) {
79969
- 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");
79970
80120
  let hgVerdictFound = false;
79971
80121
  let hgVerdictApproved = false;
79972
80122
  try {
79973
- const hgContent = fs67.readFileSync(hgPath, "utf-8");
80123
+ const hgContent = fs68.readFileSync(hgPath, "utf-8");
79974
80124
  const hgBundle = JSON.parse(hgContent);
79975
80125
  for (const entry of hgBundle.entries ?? []) {
79976
80126
  if (typeof entry.type === "string" && entry.type.includes("hallucination") && typeof entry.verdict === "string") {
@@ -80038,11 +80188,11 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
80038
80188
  const overrides = session2?.qaGateSessionOverrides ?? {};
80039
80189
  const effective = getEffectiveGates(profile, overrides);
80040
80190
  if (effective.mutation_test === true) {
80041
- 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");
80042
80192
  let mgVerdictFound = false;
80043
80193
  let mgVerdict;
80044
80194
  try {
80045
- const mgContent = fs67.readFileSync(mgPath, "utf-8");
80195
+ const mgContent = fs68.readFileSync(mgPath, "utf-8");
80046
80196
  const mgBundle = JSON.parse(mgContent);
80047
80197
  for (const entry of mgBundle.entries ?? []) {
80048
80198
  if (typeof entry.type === "string" && entry.type === "mutation-gate" && typeof entry.verdict === "string") {
@@ -80112,14 +80262,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
80112
80262
  const effective = getEffectiveGates(profile, overrides);
80113
80263
  if (effective.council_mode === true) {
80114
80264
  councilModeEnabled = true;
80115
- 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");
80116
80266
  let pcVerdictFound = false;
80117
80267
  let _pcVerdict;
80118
80268
  let pcQuorumSize;
80119
80269
  let pcTimestamp;
80120
80270
  let pcPhaseNumber;
80121
80271
  try {
80122
- const pcContent = fs67.readFileSync(pcPath, "utf-8");
80272
+ const pcContent = fs68.readFileSync(pcPath, "utf-8");
80123
80273
  const pcBundle = JSON.parse(pcContent);
80124
80274
  for (const entry of pcBundle.entries ?? []) {
80125
80275
  if (typeof entry.type === "string" && entry.type === "phase-council" && typeof entry.verdict === "string") {
@@ -80228,11 +80378,11 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80228
80378
  status: "blocked",
80229
80379
  reason: "PHASE_COUNCIL_REQUIRED",
80230
80380
  phase_council_required: true,
80231
- 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.`,
80232
80382
  agentsDispatched,
80233
80383
  agentsMissing: [],
80234
80384
  warnings: [
80235
- `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.`
80236
80386
  ]
80237
80387
  }, null, 2);
80238
80388
  }
@@ -80314,7 +80464,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80314
80464
  }
80315
80465
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
80316
80466
  try {
80317
- const projectName = path85.basename(dir);
80467
+ const projectName = path86.basename(dir);
80318
80468
  const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
80319
80469
  if (curationResult) {
80320
80470
  const sessionState = swarmState.agentSessions.get(sessionID);
@@ -80394,7 +80544,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80394
80544
  let phaseRequiredAgents;
80395
80545
  try {
80396
80546
  const planPath = validateSwarmPath(dir, "plan.json");
80397
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80547
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80398
80548
  const plan = JSON.parse(planRaw);
80399
80549
  const phaseObj = plan.phases.find((p) => p.id === phase);
80400
80550
  phaseRequiredAgents = phaseObj?.required_agents;
@@ -80409,7 +80559,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80409
80559
  if (agentsMissing.length > 0) {
80410
80560
  try {
80411
80561
  const planPath = validateSwarmPath(dir, "plan.json");
80412
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80562
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80413
80563
  const plan = JSON.parse(planRaw);
80414
80564
  const targetPhase = plan.phases.find((p) => p.id === phase);
80415
80565
  if (targetPhase && targetPhase.tasks.length > 0 && targetPhase.tasks.every((t) => t.status === "completed")) {
@@ -80449,7 +80599,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80449
80599
  if (phaseCompleteConfig.regression_sweep?.enforce) {
80450
80600
  try {
80451
80601
  const planPath = validateSwarmPath(dir, "plan.json");
80452
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80602
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80453
80603
  const plan = JSON.parse(planRaw);
80454
80604
  const targetPhase = plan.phases.find((p) => p.id === phase);
80455
80605
  if (targetPhase) {
@@ -80503,7 +80653,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80503
80653
  }
80504
80654
  try {
80505
80655
  const eventsPath = validateSwarmPath(dir, "events.jsonl");
80506
- fs67.appendFileSync(eventsPath, `${JSON.stringify(event)}
80656
+ fs68.appendFileSync(eventsPath, `${JSON.stringify(event)}
80507
80657
  `, "utf-8");
80508
80658
  } catch (writeError) {
80509
80659
  warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -80578,12 +80728,12 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80578
80728
  warnings.push(`Warning: failed to update plan.json phase status`);
80579
80729
  try {
80580
80730
  const planPath = validateSwarmPath(dir, "plan.json");
80581
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80731
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80582
80732
  const plan2 = JSON.parse(planRaw);
80583
80733
  const phaseObj = plan2.phases.find((p) => p.id === phase);
80584
80734
  if (phaseObj) {
80585
80735
  phaseObj.status = "complete";
80586
- fs67.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
80736
+ fs68.writeFileSync(planPath, JSON.stringify(plan2, null, 2), "utf-8");
80587
80737
  }
80588
80738
  } catch {}
80589
80739
  } else if (plan) {
@@ -80620,12 +80770,12 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
80620
80770
  warnings.push(`Warning: failed to update plan.json phase status`);
80621
80771
  try {
80622
80772
  const planPath = validateSwarmPath(dir, "plan.json");
80623
- const planRaw = fs67.readFileSync(planPath, "utf-8");
80773
+ const planRaw = fs68.readFileSync(planPath, "utf-8");
80624
80774
  const plan = JSON.parse(planRaw);
80625
80775
  const phaseObj = plan.phases.find((p) => p.id === phase);
80626
80776
  if (phaseObj) {
80627
80777
  phaseObj.status = "complete";
80628
- fs67.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
80778
+ fs68.writeFileSync(planPath, JSON.stringify(plan, null, 2), "utf-8");
80629
80779
  }
80630
80780
  } catch {}
80631
80781
  }
@@ -80683,8 +80833,8 @@ init_discovery();
80683
80833
  init_utils();
80684
80834
  init_bun_compat();
80685
80835
  init_create_tool();
80686
- import * as fs68 from "node:fs";
80687
- import * as path86 from "node:path";
80836
+ import * as fs69 from "node:fs";
80837
+ import * as path87 from "node:path";
80688
80838
  var MAX_OUTPUT_BYTES5 = 52428800;
80689
80839
  var AUDIT_TIMEOUT_MS = 120000;
80690
80840
  function isValidEcosystem(value) {
@@ -80712,31 +80862,31 @@ function validateArgs3(args2) {
80712
80862
  function detectEcosystems(directory) {
80713
80863
  const ecosystems = [];
80714
80864
  const cwd = directory;
80715
- if (fs68.existsSync(path86.join(cwd, "package.json"))) {
80865
+ if (fs69.existsSync(path87.join(cwd, "package.json"))) {
80716
80866
  ecosystems.push("npm");
80717
80867
  }
80718
- 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"))) {
80719
80869
  ecosystems.push("pip");
80720
80870
  }
80721
- if (fs68.existsSync(path86.join(cwd, "Cargo.toml"))) {
80871
+ if (fs69.existsSync(path87.join(cwd, "Cargo.toml"))) {
80722
80872
  ecosystems.push("cargo");
80723
80873
  }
80724
- if (fs68.existsSync(path86.join(cwd, "go.mod"))) {
80874
+ if (fs69.existsSync(path87.join(cwd, "go.mod"))) {
80725
80875
  ecosystems.push("go");
80726
80876
  }
80727
80877
  try {
80728
- const files = fs68.readdirSync(cwd);
80878
+ const files = fs69.readdirSync(cwd);
80729
80879
  if (files.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
80730
80880
  ecosystems.push("dotnet");
80731
80881
  }
80732
80882
  } catch {}
80733
- 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"))) {
80734
80884
  ecosystems.push("ruby");
80735
80885
  }
80736
- if (fs68.existsSync(path86.join(cwd, "pubspec.yaml"))) {
80886
+ if (fs69.existsSync(path87.join(cwd, "pubspec.yaml"))) {
80737
80887
  ecosystems.push("dart");
80738
80888
  }
80739
- if (fs68.existsSync(path86.join(cwd, "composer.lock"))) {
80889
+ if (fs69.existsSync(path87.join(cwd, "composer.lock"))) {
80740
80890
  ecosystems.push("composer");
80741
80891
  }
80742
80892
  return ecosystems;
@@ -81871,8 +82021,8 @@ var pkg_audit = createSwarmTool({
81871
82021
  // src/tools/placeholder-scan.ts
81872
82022
  init_zod();
81873
82023
  init_manager2();
81874
- import * as fs69 from "node:fs";
81875
- import * as path87 from "node:path";
82024
+ import * as fs70 from "node:fs";
82025
+ import * as path88 from "node:path";
81876
82026
  init_utils();
81877
82027
  init_create_tool();
81878
82028
  var MAX_FILE_SIZE = 1024 * 1024;
@@ -81995,7 +82145,7 @@ function isScaffoldFile(filePath) {
81995
82145
  if (SCAFFOLD_PATH_PATTERNS.some((pattern) => pattern.test(normalizedPath))) {
81996
82146
  return true;
81997
82147
  }
81998
- const filename = path87.basename(filePath);
82148
+ const filename = path88.basename(filePath);
81999
82149
  if (SCAFFOLD_FILENAME_PATTERNS.some((pattern) => pattern.test(filename))) {
82000
82150
  return true;
82001
82151
  }
@@ -82012,7 +82162,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
82012
82162
  if (regex.test(normalizedPath)) {
82013
82163
  return true;
82014
82164
  }
82015
- const filename = path87.basename(filePath);
82165
+ const filename = path88.basename(filePath);
82016
82166
  const filenameRegex = new RegExp(`^${regexPattern}$`, "i");
82017
82167
  if (filenameRegex.test(filename)) {
82018
82168
  return true;
@@ -82021,7 +82171,7 @@ function isAllowedByGlobs(filePath, allowGlobs) {
82021
82171
  return false;
82022
82172
  }
82023
82173
  function isParserSupported(filePath) {
82024
- const ext = path87.extname(filePath).toLowerCase();
82174
+ const ext = path88.extname(filePath).toLowerCase();
82025
82175
  return SUPPORTED_PARSER_EXTENSIONS.has(ext);
82026
82176
  }
82027
82177
  function isPlanFile(filePath) {
@@ -82268,28 +82418,28 @@ async function placeholderScan(input, directory) {
82268
82418
  let filesScanned = 0;
82269
82419
  const filesWithFindings = new Set;
82270
82420
  for (const filePath of changed_files) {
82271
- const fullPath = path87.isAbsolute(filePath) ? filePath : path87.resolve(directory, filePath);
82272
- const resolvedDirectory = path87.resolve(directory);
82273
- 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) {
82274
82424
  continue;
82275
82425
  }
82276
- if (!fs69.existsSync(fullPath)) {
82426
+ if (!fs70.existsSync(fullPath)) {
82277
82427
  continue;
82278
82428
  }
82279
82429
  if (isAllowedByGlobs(filePath, allow_globs)) {
82280
82430
  continue;
82281
82431
  }
82282
- const relativeFilePath = path87.relative(directory, fullPath).replace(/\\/g, "/");
82432
+ const relativeFilePath = path88.relative(directory, fullPath).replace(/\\/g, "/");
82283
82433
  if (FILE_ALLOWLIST.some((allowed) => relativeFilePath.endsWith(allowed))) {
82284
82434
  continue;
82285
82435
  }
82286
82436
  let content;
82287
82437
  try {
82288
- const stat6 = fs69.statSync(fullPath);
82438
+ const stat6 = fs70.statSync(fullPath);
82289
82439
  if (stat6.size > MAX_FILE_SIZE) {
82290
82440
  continue;
82291
82441
  }
82292
- content = fs69.readFileSync(fullPath, "utf-8");
82442
+ content = fs70.readFileSync(fullPath, "utf-8");
82293
82443
  } catch {
82294
82444
  continue;
82295
82445
  }
@@ -82350,8 +82500,8 @@ var placeholder_scan = createSwarmTool({
82350
82500
  }
82351
82501
  });
82352
82502
  // src/tools/pre-check-batch.ts
82353
- import * as fs72 from "node:fs";
82354
- import * as path90 from "node:path";
82503
+ import * as fs73 from "node:fs";
82504
+ import * as path91 from "node:path";
82355
82505
  init_zod();
82356
82506
  init_manager2();
82357
82507
  init_utils();
@@ -82488,8 +82638,8 @@ var quality_budget = createSwarmTool({
82488
82638
  init_zod();
82489
82639
  init_manager2();
82490
82640
  init_detector();
82491
- import * as fs71 from "node:fs";
82492
- import * as path89 from "node:path";
82641
+ import * as fs72 from "node:fs";
82642
+ import * as path90 from "node:path";
82493
82643
  import { extname as extname18 } from "node:path";
82494
82644
 
82495
82645
  // src/sast/rules/c.ts
@@ -83382,25 +83532,25 @@ init_create_tool();
83382
83532
  // src/tools/sast-baseline.ts
83383
83533
  init_utils2();
83384
83534
  import * as crypto8 from "node:crypto";
83385
- import * as fs70 from "node:fs";
83386
- import * as path88 from "node:path";
83535
+ import * as fs71 from "node:fs";
83536
+ import * as path89 from "node:path";
83387
83537
  var BASELINE_SCHEMA_VERSION = "1.0.0";
83388
83538
  var MAX_BASELINE_FINDINGS = 2000;
83389
83539
  var MAX_BASELINE_BYTES = 2 * 1048576;
83390
83540
  var LOCK_RETRY_DELAYS_MS = [50, 100, 200, 400, 800];
83391
83541
  function normalizeFindingPath(directory, file3) {
83392
- const resolved = path88.isAbsolute(file3) ? file3 : path88.resolve(directory, file3);
83393
- 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);
83394
83544
  return rel.replace(/\\/g, "/");
83395
83545
  }
83396
83546
  function baselineRelPath(phase) {
83397
- return path88.join("evidence", String(phase), "sast-baseline.json");
83547
+ return path89.join("evidence", String(phase), "sast-baseline.json");
83398
83548
  }
83399
83549
  function tempRelPath(phase) {
83400
- 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}`);
83401
83551
  }
83402
83552
  function lockRelPath(phase) {
83403
- return path88.join("evidence", String(phase), "sast-baseline.json.lock");
83553
+ return path89.join("evidence", String(phase), "sast-baseline.json.lock");
83404
83554
  }
83405
83555
  function getLine(lines, idx) {
83406
83556
  if (idx < 0 || idx >= lines.length)
@@ -83417,7 +83567,7 @@ function fingerprintFinding(finding, directory, occurrenceIndex) {
83417
83567
  }
83418
83568
  const lineNum = finding.location.line;
83419
83569
  try {
83420
- const content = fs70.readFileSync(finding.location.file, "utf-8");
83570
+ const content = fs71.readFileSync(finding.location.file, "utf-8");
83421
83571
  const lines = content.split(`
83422
83572
  `);
83423
83573
  const idx = lineNum - 1;
@@ -83448,7 +83598,7 @@ function assignOccurrenceIndices(findings, directory) {
83448
83598
  try {
83449
83599
  if (relFile.startsWith(".."))
83450
83600
  throw new Error("escapes workspace");
83451
- const content = fs70.readFileSync(finding.location.file, "utf-8");
83601
+ const content = fs71.readFileSync(finding.location.file, "utf-8");
83452
83602
  const lines = content.split(`
83453
83603
  `);
83454
83604
  const idx = lineNum - 1;
@@ -83477,11 +83627,11 @@ function assignOccurrenceIndices(findings, directory) {
83477
83627
  async function acquireLock(lockPath) {
83478
83628
  for (let attempt = 0;attempt <= LOCK_RETRY_DELAYS_MS.length; attempt++) {
83479
83629
  try {
83480
- const fd = fs70.openSync(lockPath, "wx");
83481
- fs70.closeSync(fd);
83630
+ const fd = fs71.openSync(lockPath, "wx");
83631
+ fs71.closeSync(fd);
83482
83632
  return () => {
83483
83633
  try {
83484
- fs70.unlinkSync(lockPath);
83634
+ fs71.unlinkSync(lockPath);
83485
83635
  } catch {}
83486
83636
  };
83487
83637
  } catch {
@@ -83521,13 +83671,13 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83521
83671
  message: e instanceof Error ? e.message : "Path validation failed"
83522
83672
  };
83523
83673
  }
83524
- fs70.mkdirSync(path88.dirname(baselinePath), { recursive: true });
83525
- fs70.mkdirSync(path88.dirname(tempPath), { recursive: true });
83674
+ fs71.mkdirSync(path89.dirname(baselinePath), { recursive: true });
83675
+ fs71.mkdirSync(path89.dirname(tempPath), { recursive: true });
83526
83676
  const releaseLock = await acquireLock(lockPath);
83527
83677
  try {
83528
83678
  let existing = null;
83529
83679
  try {
83530
- const raw = fs70.readFileSync(baselinePath, "utf-8");
83680
+ const raw = fs71.readFileSync(baselinePath, "utf-8");
83531
83681
  const parsed = JSON.parse(raw);
83532
83682
  if (parsed.schema_version === BASELINE_SCHEMA_VERSION) {
83533
83683
  existing = parsed;
@@ -83587,8 +83737,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83587
83737
  message: `Baseline would exceed size cap (${json4.length} bytes > ${MAX_BASELINE_BYTES})`
83588
83738
  };
83589
83739
  }
83590
- fs70.writeFileSync(tempPath, json4, "utf-8");
83591
- fs70.renameSync(tempPath, baselinePath);
83740
+ fs71.writeFileSync(tempPath, json4, "utf-8");
83741
+ fs71.renameSync(tempPath, baselinePath);
83592
83742
  return {
83593
83743
  status: "merged",
83594
83744
  path: baselinePath,
@@ -83619,8 +83769,8 @@ async function captureOrMergeBaseline(directory, phase, findings, engine, scanne
83619
83769
  message: `Baseline would exceed size cap (${json3.length} bytes > ${MAX_BASELINE_BYTES})`
83620
83770
  };
83621
83771
  }
83622
- fs70.writeFileSync(tempPath, json3, "utf-8");
83623
- fs70.renameSync(tempPath, baselinePath);
83772
+ fs71.writeFileSync(tempPath, json3, "utf-8");
83773
+ fs71.renameSync(tempPath, baselinePath);
83624
83774
  return {
83625
83775
  status: "written",
83626
83776
  path: baselinePath,
@@ -83645,7 +83795,7 @@ function loadBaseline(directory, phase) {
83645
83795
  };
83646
83796
  }
83647
83797
  try {
83648
- const raw = fs70.readFileSync(baselinePath, "utf-8");
83798
+ const raw = fs71.readFileSync(baselinePath, "utf-8");
83649
83799
  const parsed = JSON.parse(raw);
83650
83800
  if (parsed.schema_version !== BASELINE_SCHEMA_VERSION) {
83651
83801
  return {
@@ -83687,17 +83837,17 @@ var SEVERITY_ORDER = {
83687
83837
  };
83688
83838
  function shouldSkipFile(filePath) {
83689
83839
  try {
83690
- const stats = fs71.statSync(filePath);
83840
+ const stats = fs72.statSync(filePath);
83691
83841
  if (stats.size > MAX_FILE_SIZE_BYTES8) {
83692
83842
  return { skip: true, reason: "file too large" };
83693
83843
  }
83694
83844
  if (stats.size === 0) {
83695
83845
  return { skip: true, reason: "empty file" };
83696
83846
  }
83697
- const fd = fs71.openSync(filePath, "r");
83847
+ const fd = fs72.openSync(filePath, "r");
83698
83848
  const buffer = Buffer.alloc(8192);
83699
- const bytesRead = fs71.readSync(fd, buffer, 0, 8192, 0);
83700
- fs71.closeSync(fd);
83849
+ const bytesRead = fs72.readSync(fd, buffer, 0, 8192, 0);
83850
+ fs72.closeSync(fd);
83701
83851
  if (bytesRead > 0) {
83702
83852
  let nullCount = 0;
83703
83853
  for (let i2 = 0;i2 < bytesRead; i2++) {
@@ -83736,7 +83886,7 @@ function countBySeverity(findings) {
83736
83886
  }
83737
83887
  function scanFileWithTierA(filePath, language) {
83738
83888
  try {
83739
- const content = fs71.readFileSync(filePath, "utf-8");
83889
+ const content = fs72.readFileSync(filePath, "utf-8");
83740
83890
  const findings = executeRulesSync(filePath, content, language);
83741
83891
  return findings.map((f) => ({
83742
83892
  rule_id: f.rule_id,
@@ -83789,13 +83939,13 @@ async function sastScan(input, directory, config3) {
83789
83939
  _filesSkipped++;
83790
83940
  continue;
83791
83941
  }
83792
- const resolvedPath = path89.isAbsolute(filePath) ? filePath : path89.resolve(directory, filePath);
83793
- const resolvedDirectory = path89.resolve(directory);
83794
- 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) {
83795
83945
  _filesSkipped++;
83796
83946
  continue;
83797
83947
  }
83798
- if (!fs71.existsSync(resolvedPath)) {
83948
+ if (!fs72.existsSync(resolvedPath)) {
83799
83949
  _filesSkipped++;
83800
83950
  continue;
83801
83951
  }
@@ -84102,18 +84252,18 @@ function validatePath(inputPath, baseDir, workspaceDir) {
84102
84252
  let resolved;
84103
84253
  const isWinAbs = isWindowsAbsolutePath(inputPath);
84104
84254
  if (isWinAbs) {
84105
- resolved = path90.win32.resolve(inputPath);
84106
- } else if (path90.isAbsolute(inputPath)) {
84107
- resolved = path90.resolve(inputPath);
84255
+ resolved = path91.win32.resolve(inputPath);
84256
+ } else if (path91.isAbsolute(inputPath)) {
84257
+ resolved = path91.resolve(inputPath);
84108
84258
  } else {
84109
- resolved = path90.resolve(baseDir, inputPath);
84259
+ resolved = path91.resolve(baseDir, inputPath);
84110
84260
  }
84111
- const workspaceResolved = path90.resolve(workspaceDir);
84261
+ const workspaceResolved = path91.resolve(workspaceDir);
84112
84262
  let relative20;
84113
84263
  if (isWinAbs) {
84114
- relative20 = path90.win32.relative(workspaceResolved, resolved);
84264
+ relative20 = path91.win32.relative(workspaceResolved, resolved);
84115
84265
  } else {
84116
- relative20 = path90.relative(workspaceResolved, resolved);
84266
+ relative20 = path91.relative(workspaceResolved, resolved);
84117
84267
  }
84118
84268
  if (relative20.startsWith("..")) {
84119
84269
  return "path traversal detected";
@@ -84178,7 +84328,7 @@ async function runLintOnFiles(linter, files, workspaceDir) {
84178
84328
  if (typeof file3 !== "string") {
84179
84329
  continue;
84180
84330
  }
84181
- const resolvedPath = path90.resolve(file3);
84331
+ const resolvedPath = path91.resolve(file3);
84182
84332
  const validationError = validatePath(resolvedPath, workspaceDir, workspaceDir);
84183
84333
  if (validationError) {
84184
84334
  continue;
@@ -84335,7 +84485,7 @@ async function runSecretscanWithFiles(files, directory) {
84335
84485
  skippedFiles++;
84336
84486
  continue;
84337
84487
  }
84338
- const resolvedPath = path90.resolve(file3);
84488
+ const resolvedPath = path91.resolve(file3);
84339
84489
  const validationError = validatePath(resolvedPath, directory, directory);
84340
84490
  if (validationError) {
84341
84491
  skippedFiles++;
@@ -84353,14 +84503,14 @@ async function runSecretscanWithFiles(files, directory) {
84353
84503
  };
84354
84504
  }
84355
84505
  for (const file3 of validatedFiles) {
84356
- const ext = path90.extname(file3).toLowerCase();
84506
+ const ext = path91.extname(file3).toLowerCase();
84357
84507
  if (DEFAULT_EXCLUDE_EXTENSIONS2.has(ext)) {
84358
84508
  skippedFiles++;
84359
84509
  continue;
84360
84510
  }
84361
84511
  let stat6;
84362
84512
  try {
84363
- stat6 = fs72.statSync(file3);
84513
+ stat6 = fs73.statSync(file3);
84364
84514
  } catch {
84365
84515
  skippedFiles++;
84366
84516
  continue;
@@ -84371,7 +84521,7 @@ async function runSecretscanWithFiles(files, directory) {
84371
84521
  }
84372
84522
  let content;
84373
84523
  try {
84374
- const buffer = fs72.readFileSync(file3);
84524
+ const buffer = fs73.readFileSync(file3);
84375
84525
  if (buffer.includes(0)) {
84376
84526
  skippedFiles++;
84377
84527
  continue;
@@ -84572,7 +84722,7 @@ function classifySastFindings(findings, changedLineRanges, directory) {
84572
84722
  const preexistingFindings = [];
84573
84723
  for (const finding of findings) {
84574
84724
  const filePath = finding.location.file;
84575
- const normalised = path90.relative(directory, filePath).replace(/\\/g, "/");
84725
+ const normalised = path91.relative(directory, filePath).replace(/\\/g, "/");
84576
84726
  const changedLines = changedLineRanges.get(normalised);
84577
84727
  if (changedLines?.has(finding.location.line)) {
84578
84728
  newFindings.push(finding);
@@ -84623,7 +84773,7 @@ async function runPreCheckBatch(input, workspaceDir, contextDir) {
84623
84773
  warn(`pre_check_batch: Invalid file path: ${file3}`);
84624
84774
  continue;
84625
84775
  }
84626
- changedFiles.push(path90.resolve(directory, file3));
84776
+ changedFiles.push(path91.resolve(directory, file3));
84627
84777
  }
84628
84778
  if (changedFiles.length === 0) {
84629
84779
  warn("pre_check_batch: No valid files after validation, skipping all tools (fail-closed)");
@@ -84824,7 +84974,7 @@ var pre_check_batch = createSwarmTool({
84824
84974
  };
84825
84975
  return JSON.stringify(errorResult, null, 2);
84826
84976
  }
84827
- const resolvedDirectory = path90.resolve(typedArgs.directory);
84977
+ const resolvedDirectory = path91.resolve(typedArgs.directory);
84828
84978
  const workspaceAnchor = resolvedDirectory;
84829
84979
  const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
84830
84980
  if (dirError) {
@@ -84865,7 +85015,7 @@ var pre_check_batch = createSwarmTool({
84865
85015
  });
84866
85016
  // src/tools/repo-map.ts
84867
85017
  init_zod();
84868
- import * as path91 from "node:path";
85018
+ import * as path92 from "node:path";
84869
85019
  init_path_security();
84870
85020
  init_create_tool();
84871
85021
  var VALID_ACTIONS = [
@@ -84890,7 +85040,7 @@ function validateFile(p) {
84890
85040
  return "file contains control characters";
84891
85041
  if (containsPathTraversal(p))
84892
85042
  return "file contains path traversal";
84893
- if (path91.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
85043
+ if (path92.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p)) {
84894
85044
  return "file must be a workspace-relative path, not absolute";
84895
85045
  }
84896
85046
  return null;
@@ -84913,8 +85063,8 @@ function ok(action, payload) {
84913
85063
  }
84914
85064
  function toRelativeGraphPath(input, workspaceRoot) {
84915
85065
  const normalized = input.replace(/\\/g, "/");
84916
- if (path91.isAbsolute(normalized)) {
84917
- const rel = path91.relative(workspaceRoot, normalized).replace(/\\/g, "/");
85066
+ if (path92.isAbsolute(normalized)) {
85067
+ const rel = path92.relative(workspaceRoot, normalized).replace(/\\/g, "/");
84918
85068
  return normalizeGraphPath2(rel);
84919
85069
  }
84920
85070
  return normalizeGraphPath2(normalized);
@@ -85058,8 +85208,8 @@ var repo_map = createSwarmTool({
85058
85208
  // src/tools/req-coverage.ts
85059
85209
  init_zod();
85060
85210
  init_create_tool();
85061
- import * as fs73 from "node:fs";
85062
- import * as path92 from "node:path";
85211
+ import * as fs74 from "node:fs";
85212
+ import * as path93 from "node:path";
85063
85213
  var SPEC_FILE = ".swarm/spec.md";
85064
85214
  var EVIDENCE_DIR4 = ".swarm/evidence";
85065
85215
  var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
@@ -85118,19 +85268,19 @@ function extractObligationAndText(id, lineText) {
85118
85268
  var PHASE_TASK_ID_REGEX = /^\d+\.\d+(\.\d+)*$/;
85119
85269
  function readTouchedFiles(evidenceDir, phase, cwd) {
85120
85270
  const touchedFiles = new Set;
85121
- if (!fs73.existsSync(evidenceDir) || !fs73.statSync(evidenceDir).isDirectory()) {
85271
+ if (!fs74.existsSync(evidenceDir) || !fs74.statSync(evidenceDir).isDirectory()) {
85122
85272
  return [];
85123
85273
  }
85124
85274
  let entries;
85125
85275
  try {
85126
- entries = fs73.readdirSync(evidenceDir);
85276
+ entries = fs74.readdirSync(evidenceDir);
85127
85277
  } catch {
85128
85278
  return [];
85129
85279
  }
85130
85280
  for (const entry of entries) {
85131
- const entryPath = path92.join(evidenceDir, entry);
85281
+ const entryPath = path93.join(evidenceDir, entry);
85132
85282
  try {
85133
- const stat6 = fs73.statSync(entryPath);
85283
+ const stat6 = fs74.statSync(entryPath);
85134
85284
  if (!stat6.isDirectory()) {
85135
85285
  continue;
85136
85286
  }
@@ -85144,14 +85294,14 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85144
85294
  if (entryPhase !== String(phase)) {
85145
85295
  continue;
85146
85296
  }
85147
- const evidenceFilePath = path92.join(entryPath, "evidence.json");
85297
+ const evidenceFilePath = path93.join(entryPath, "evidence.json");
85148
85298
  try {
85149
- const resolvedPath = path92.resolve(evidenceFilePath);
85150
- const evidenceDirResolved = path92.resolve(evidenceDir);
85151
- 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)) {
85152
85302
  continue;
85153
85303
  }
85154
- const stat6 = fs73.lstatSync(evidenceFilePath);
85304
+ const stat6 = fs74.lstatSync(evidenceFilePath);
85155
85305
  if (!stat6.isFile()) {
85156
85306
  continue;
85157
85307
  }
@@ -85163,7 +85313,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85163
85313
  }
85164
85314
  let content;
85165
85315
  try {
85166
- content = fs73.readFileSync(evidenceFilePath, "utf-8");
85316
+ content = fs74.readFileSync(evidenceFilePath, "utf-8");
85167
85317
  } catch {
85168
85318
  continue;
85169
85319
  }
@@ -85182,7 +85332,7 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85182
85332
  if (Array.isArray(diffEntry.files_changed)) {
85183
85333
  for (const file3 of diffEntry.files_changed) {
85184
85334
  if (typeof file3 === "string") {
85185
- touchedFiles.add(path92.resolve(cwd, file3));
85335
+ touchedFiles.add(path93.resolve(cwd, file3));
85186
85336
  }
85187
85337
  }
85188
85338
  }
@@ -85195,12 +85345,12 @@ function readTouchedFiles(evidenceDir, phase, cwd) {
85195
85345
  }
85196
85346
  function searchFileForKeywords(filePath, keywords, cwd) {
85197
85347
  try {
85198
- const resolvedPath = path92.resolve(filePath);
85199
- const cwdResolved = path92.resolve(cwd);
85348
+ const resolvedPath = path93.resolve(filePath);
85349
+ const cwdResolved = path93.resolve(cwd);
85200
85350
  if (!resolvedPath.startsWith(cwdResolved)) {
85201
85351
  return false;
85202
85352
  }
85203
- const content = fs73.readFileSync(resolvedPath, "utf-8");
85353
+ const content = fs74.readFileSync(resolvedPath, "utf-8");
85204
85354
  for (const keyword of keywords) {
85205
85355
  const regex = new RegExp(`\\b${keyword}\\b`, "i");
85206
85356
  if (regex.test(content)) {
@@ -85330,10 +85480,10 @@ var req_coverage = createSwarmTool({
85330
85480
  }, null, 2);
85331
85481
  }
85332
85482
  const cwd = inputDirectory || directory;
85333
- const specPath = path92.join(cwd, SPEC_FILE);
85483
+ const specPath = path93.join(cwd, SPEC_FILE);
85334
85484
  let specContent;
85335
85485
  try {
85336
- specContent = fs73.readFileSync(specPath, "utf-8");
85486
+ specContent = fs74.readFileSync(specPath, "utf-8");
85337
85487
  } catch (readError) {
85338
85488
  return JSON.stringify({
85339
85489
  success: false,
@@ -85357,7 +85507,7 @@ var req_coverage = createSwarmTool({
85357
85507
  message: "No FR requirements found in spec.md"
85358
85508
  }, null, 2);
85359
85509
  }
85360
- const evidenceDir = path92.join(cwd, EVIDENCE_DIR4);
85510
+ const evidenceDir = path93.join(cwd, EVIDENCE_DIR4);
85361
85511
  const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
85362
85512
  const analyzedRequirements = [];
85363
85513
  let coveredCount = 0;
@@ -85383,12 +85533,12 @@ var req_coverage = createSwarmTool({
85383
85533
  requirements: analyzedRequirements
85384
85534
  };
85385
85535
  const reportFilename = `req-coverage-phase-${phase}.json`;
85386
- const reportPath = path92.join(evidenceDir, reportFilename);
85536
+ const reportPath = path93.join(evidenceDir, reportFilename);
85387
85537
  try {
85388
- if (!fs73.existsSync(evidenceDir)) {
85389
- fs73.mkdirSync(evidenceDir, { recursive: true });
85538
+ if (!fs74.existsSync(evidenceDir)) {
85539
+ fs74.mkdirSync(evidenceDir, { recursive: true });
85390
85540
  }
85391
- fs73.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
85541
+ fs74.writeFileSync(reportPath, JSON.stringify(result, null, 2), "utf-8");
85392
85542
  } catch (writeError) {
85393
85543
  console.warn(`Failed to write coverage report: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
85394
85544
  }
@@ -85470,8 +85620,8 @@ init_plan_schema();
85470
85620
  init_qa_gate_profile();
85471
85621
  init_file_locks();
85472
85622
  import * as crypto9 from "node:crypto";
85473
- import * as fs74 from "node:fs";
85474
- import * as path93 from "node:path";
85623
+ import * as fs75 from "node:fs";
85624
+ import * as path94 from "node:path";
85475
85625
  init_ledger();
85476
85626
  init_manager();
85477
85627
  init_state();
@@ -85549,17 +85699,17 @@ async function executeSavePlan(args2, fallbackDir) {
85549
85699
  };
85550
85700
  }
85551
85701
  if (args2.working_directory && fallbackDir) {
85552
- const resolvedTarget = path93.resolve(args2.working_directory);
85553
- const resolvedRoot = path93.resolve(fallbackDir);
85702
+ const resolvedTarget = path94.resolve(args2.working_directory);
85703
+ const resolvedRoot = path94.resolve(fallbackDir);
85554
85704
  let fallbackExists = false;
85555
85705
  try {
85556
- fs74.accessSync(resolvedRoot, fs74.constants.F_OK);
85706
+ fs75.accessSync(resolvedRoot, fs75.constants.F_OK);
85557
85707
  fallbackExists = true;
85558
85708
  } catch {
85559
85709
  fallbackExists = false;
85560
85710
  }
85561
85711
  if (fallbackExists) {
85562
- const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path93.sep);
85712
+ const isSubdirectory = resolvedTarget.startsWith(resolvedRoot + path94.sep);
85563
85713
  if (isSubdirectory) {
85564
85714
  return {
85565
85715
  success: false,
@@ -85575,11 +85725,11 @@ async function executeSavePlan(args2, fallbackDir) {
85575
85725
  let specMtime;
85576
85726
  let specHash;
85577
85727
  if (process.env.SWARM_SKIP_SPEC_GATE !== "1") {
85578
- const specPath = path93.join(targetWorkspace, ".swarm", "spec.md");
85728
+ const specPath = path94.join(targetWorkspace, ".swarm", "spec.md");
85579
85729
  try {
85580
- const stat6 = await fs74.promises.stat(specPath);
85730
+ const stat6 = await fs75.promises.stat(specPath);
85581
85731
  specMtime = stat6.mtime.toISOString();
85582
- const content = await fs74.promises.readFile(specPath, "utf8");
85732
+ const content = await fs75.promises.readFile(specPath, "utf8");
85583
85733
  specHash = crypto9.createHash("sha256").update(content).digest("hex");
85584
85734
  } catch {
85585
85735
  return {
@@ -85591,10 +85741,10 @@ async function executeSavePlan(args2, fallbackDir) {
85591
85741
  }
85592
85742
  }
85593
85743
  if (process.env.SWARM_SKIP_GATE_SELECTION !== "1") {
85594
- const contextPath = path93.join(targetWorkspace, ".swarm", "context.md");
85744
+ const contextPath = path94.join(targetWorkspace, ".swarm", "context.md");
85595
85745
  let contextContent = "";
85596
85746
  try {
85597
- contextContent = await fs74.promises.readFile(contextPath, "utf8");
85747
+ contextContent = await fs75.promises.readFile(contextPath, "utf8");
85598
85748
  } catch {}
85599
85749
  const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
85600
85750
  if (!hasPendingSection) {
@@ -85741,14 +85891,14 @@ async function executeSavePlan(args2, fallbackDir) {
85741
85891
  }
85742
85892
  await writeCheckpoint(dir).catch(() => {});
85743
85893
  try {
85744
- const markerPath = path93.join(dir, ".swarm", ".plan-write-marker");
85894
+ const markerPath = path94.join(dir, ".swarm", ".plan-write-marker");
85745
85895
  const marker = JSON.stringify({
85746
85896
  source: "save_plan",
85747
85897
  timestamp: new Date().toISOString(),
85748
85898
  phases_count: plan.phases.length,
85749
85899
  tasks_count: tasksCount
85750
85900
  });
85751
- await fs74.promises.writeFile(markerPath, marker, "utf8");
85901
+ await fs75.promises.writeFile(markerPath, marker, "utf8");
85752
85902
  } catch {}
85753
85903
  const warnings = [];
85754
85904
  let criticReviewFound = false;
@@ -85764,7 +85914,7 @@ async function executeSavePlan(args2, fallbackDir) {
85764
85914
  return {
85765
85915
  success: true,
85766
85916
  message: "Plan saved successfully",
85767
- plan_path: path93.join(dir, ".swarm", "plan.json"),
85917
+ plan_path: path94.join(dir, ".swarm", "plan.json"),
85768
85918
  phases_count: plan.phases.length,
85769
85919
  tasks_count: tasksCount,
85770
85920
  ...resolvedProfile !== undefined ? { execution_profile: resolvedProfile } : {},
@@ -85816,8 +85966,8 @@ var save_plan = createSwarmTool({
85816
85966
  // src/tools/sbom-generate.ts
85817
85967
  init_zod();
85818
85968
  init_manager2();
85819
- import * as fs75 from "node:fs";
85820
- import * as path94 from "node:path";
85969
+ import * as fs76 from "node:fs";
85970
+ import * as path95 from "node:path";
85821
85971
 
85822
85972
  // src/sbom/detectors/index.ts
85823
85973
  init_utils();
@@ -86665,9 +86815,9 @@ function findManifestFiles(rootDir) {
86665
86815
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
86666
86816
  function searchDir(dir) {
86667
86817
  try {
86668
- const entries = fs75.readdirSync(dir, { withFileTypes: true });
86818
+ const entries = fs76.readdirSync(dir, { withFileTypes: true });
86669
86819
  for (const entry of entries) {
86670
- const fullPath = path94.join(dir, entry.name);
86820
+ const fullPath = path95.join(dir, entry.name);
86671
86821
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
86672
86822
  continue;
86673
86823
  }
@@ -86676,7 +86826,7 @@ function findManifestFiles(rootDir) {
86676
86826
  } else if (entry.isFile()) {
86677
86827
  for (const pattern of patterns) {
86678
86828
  if (simpleGlobToRegex(pattern).test(entry.name)) {
86679
- manifestFiles.push(path94.relative(rootDir, fullPath));
86829
+ manifestFiles.push(path95.relative(rootDir, fullPath));
86680
86830
  break;
86681
86831
  }
86682
86832
  }
@@ -86692,13 +86842,13 @@ function findManifestFilesInDirs(directories, workingDir) {
86692
86842
  const patterns = [...new Set(allDetectors.flatMap((d) => d.patterns))];
86693
86843
  for (const dir of directories) {
86694
86844
  try {
86695
- const entries = fs75.readdirSync(dir, { withFileTypes: true });
86845
+ const entries = fs76.readdirSync(dir, { withFileTypes: true });
86696
86846
  for (const entry of entries) {
86697
- const fullPath = path94.join(dir, entry.name);
86847
+ const fullPath = path95.join(dir, entry.name);
86698
86848
  if (entry.isFile()) {
86699
86849
  for (const pattern of patterns) {
86700
86850
  if (simpleGlobToRegex(pattern).test(entry.name)) {
86701
- found.push(path94.relative(workingDir, fullPath));
86851
+ found.push(path95.relative(workingDir, fullPath));
86702
86852
  break;
86703
86853
  }
86704
86854
  }
@@ -86711,11 +86861,11 @@ function findManifestFilesInDirs(directories, workingDir) {
86711
86861
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
86712
86862
  const dirs = new Set;
86713
86863
  for (const file3 of changedFiles) {
86714
- let currentDir = path94.dirname(file3);
86864
+ let currentDir = path95.dirname(file3);
86715
86865
  while (true) {
86716
- if (currentDir && currentDir !== "." && currentDir !== path94.sep) {
86717
- dirs.add(path94.join(workingDir, currentDir));
86718
- 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);
86719
86869
  if (parent === currentDir)
86720
86870
  break;
86721
86871
  currentDir = parent;
@@ -86729,7 +86879,7 @@ function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
86729
86879
  }
86730
86880
  function ensureOutputDir(outputDir) {
86731
86881
  try {
86732
- fs75.mkdirSync(outputDir, { recursive: true });
86882
+ fs76.mkdirSync(outputDir, { recursive: true });
86733
86883
  } catch (error93) {
86734
86884
  if (!error93 || error93.code !== "EEXIST") {
86735
86885
  throw error93;
@@ -86799,7 +86949,7 @@ var sbom_generate = createSwarmTool({
86799
86949
  const changedFiles = obj.changed_files;
86800
86950
  const relativeOutputDir = obj.output_dir || DEFAULT_OUTPUT_DIR;
86801
86951
  const workingDir = directory;
86802
- const outputDir = path94.isAbsolute(relativeOutputDir) ? relativeOutputDir : path94.join(workingDir, relativeOutputDir);
86952
+ const outputDir = path95.isAbsolute(relativeOutputDir) ? relativeOutputDir : path95.join(workingDir, relativeOutputDir);
86803
86953
  let manifestFiles = [];
86804
86954
  if (scope === "all") {
86805
86955
  manifestFiles = findManifestFiles(workingDir);
@@ -86822,11 +86972,11 @@ var sbom_generate = createSwarmTool({
86822
86972
  const processedFiles = [];
86823
86973
  for (const manifestFile of manifestFiles) {
86824
86974
  try {
86825
- const fullPath = path94.isAbsolute(manifestFile) ? manifestFile : path94.join(workingDir, manifestFile);
86826
- if (!fs75.existsSync(fullPath)) {
86975
+ const fullPath = path95.isAbsolute(manifestFile) ? manifestFile : path95.join(workingDir, manifestFile);
86976
+ if (!fs76.existsSync(fullPath)) {
86827
86977
  continue;
86828
86978
  }
86829
- const content = fs75.readFileSync(fullPath, "utf-8");
86979
+ const content = fs76.readFileSync(fullPath, "utf-8");
86830
86980
  const components = detectComponents(manifestFile, content);
86831
86981
  processedFiles.push(manifestFile);
86832
86982
  if (components.length > 0) {
@@ -86839,8 +86989,8 @@ var sbom_generate = createSwarmTool({
86839
86989
  const bom = generateCycloneDX(allComponents);
86840
86990
  const bomJson = serializeCycloneDX(bom);
86841
86991
  const filename = generateSbomFilename();
86842
- const outputPath = path94.join(outputDir, filename);
86843
- fs75.writeFileSync(outputPath, bomJson, "utf-8");
86992
+ const outputPath = path95.join(outputDir, filename);
86993
+ fs76.writeFileSync(outputPath, bomJson, "utf-8");
86844
86994
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
86845
86995
  try {
86846
86996
  const timestamp = new Date().toISOString();
@@ -86882,8 +87032,8 @@ var sbom_generate = createSwarmTool({
86882
87032
  // src/tools/schema-drift.ts
86883
87033
  init_zod();
86884
87034
  init_create_tool();
86885
- import * as fs76 from "node:fs";
86886
- import * as path95 from "node:path";
87035
+ import * as fs77 from "node:fs";
87036
+ import * as path96 from "node:path";
86887
87037
  var SPEC_CANDIDATES = [
86888
87038
  "openapi.json",
86889
87039
  "openapi.yaml",
@@ -86915,28 +87065,28 @@ function normalizePath3(p) {
86915
87065
  }
86916
87066
  function discoverSpecFile(cwd, specFileArg) {
86917
87067
  if (specFileArg) {
86918
- const resolvedPath = path95.resolve(cwd, specFileArg);
86919
- 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;
86920
87070
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
86921
87071
  throw new Error("Invalid spec_file: path traversal detected");
86922
87072
  }
86923
- const ext = path95.extname(resolvedPath).toLowerCase();
87073
+ const ext = path96.extname(resolvedPath).toLowerCase();
86924
87074
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
86925
87075
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
86926
87076
  }
86927
- const stats = fs76.statSync(resolvedPath);
87077
+ const stats = fs77.statSync(resolvedPath);
86928
87078
  if (stats.size > MAX_SPEC_SIZE) {
86929
87079
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
86930
87080
  }
86931
- if (!fs76.existsSync(resolvedPath)) {
87081
+ if (!fs77.existsSync(resolvedPath)) {
86932
87082
  throw new Error(`Spec file not found: ${resolvedPath}`);
86933
87083
  }
86934
87084
  return resolvedPath;
86935
87085
  }
86936
87086
  for (const candidate of SPEC_CANDIDATES) {
86937
- const candidatePath = path95.resolve(cwd, candidate);
86938
- if (fs76.existsSync(candidatePath)) {
86939
- const stats = fs76.statSync(candidatePath);
87087
+ const candidatePath = path96.resolve(cwd, candidate);
87088
+ if (fs77.existsSync(candidatePath)) {
87089
+ const stats = fs77.statSync(candidatePath);
86940
87090
  if (stats.size <= MAX_SPEC_SIZE) {
86941
87091
  return candidatePath;
86942
87092
  }
@@ -86945,8 +87095,8 @@ function discoverSpecFile(cwd, specFileArg) {
86945
87095
  return null;
86946
87096
  }
86947
87097
  function parseSpec(specFile) {
86948
- const content = fs76.readFileSync(specFile, "utf-8");
86949
- const ext = path95.extname(specFile).toLowerCase();
87098
+ const content = fs77.readFileSync(specFile, "utf-8");
87099
+ const ext = path96.extname(specFile).toLowerCase();
86950
87100
  if (ext === ".json") {
86951
87101
  return parseJsonSpec(content);
86952
87102
  }
@@ -87017,12 +87167,12 @@ function extractRoutes(cwd) {
87017
87167
  function walkDir(dir) {
87018
87168
  let entries;
87019
87169
  try {
87020
- entries = fs76.readdirSync(dir, { withFileTypes: true });
87170
+ entries = fs77.readdirSync(dir, { withFileTypes: true });
87021
87171
  } catch {
87022
87172
  return;
87023
87173
  }
87024
87174
  for (const entry of entries) {
87025
- const fullPath = path95.join(dir, entry.name);
87175
+ const fullPath = path96.join(dir, entry.name);
87026
87176
  if (entry.isSymbolicLink()) {
87027
87177
  continue;
87028
87178
  }
@@ -87032,7 +87182,7 @@ function extractRoutes(cwd) {
87032
87182
  }
87033
87183
  walkDir(fullPath);
87034
87184
  } else if (entry.isFile()) {
87035
- const ext = path95.extname(entry.name).toLowerCase();
87185
+ const ext = path96.extname(entry.name).toLowerCase();
87036
87186
  const baseName = entry.name.toLowerCase();
87037
87187
  if (![".ts", ".js", ".mjs"].includes(ext)) {
87038
87188
  continue;
@@ -87050,7 +87200,7 @@ function extractRoutes(cwd) {
87050
87200
  }
87051
87201
  function extractRoutesFromFile(filePath) {
87052
87202
  const routes = [];
87053
- const content = fs76.readFileSync(filePath, "utf-8");
87203
+ const content = fs77.readFileSync(filePath, "utf-8");
87054
87204
  const lines = content.split(/\r?\n/);
87055
87205
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
87056
87206
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -87199,8 +87349,8 @@ init_zod();
87199
87349
  init_bun_compat();
87200
87350
  init_path_security();
87201
87351
  init_create_tool();
87202
- import * as fs77 from "node:fs";
87203
- import * as path96 from "node:path";
87352
+ import * as fs78 from "node:fs";
87353
+ import * as path97 from "node:path";
87204
87354
  var DEFAULT_MAX_RESULTS = 100;
87205
87355
  var DEFAULT_MAX_LINES = 200;
87206
87356
  var REGEX_TIMEOUT_MS = 5000;
@@ -87236,11 +87386,11 @@ function containsWindowsAttacks3(str) {
87236
87386
  }
87237
87387
  function isPathInWorkspace3(filePath, workspace) {
87238
87388
  try {
87239
- const resolvedPath = path96.resolve(workspace, filePath);
87240
- const realWorkspace = fs77.realpathSync(workspace);
87241
- const realResolvedPath = fs77.realpathSync(resolvedPath);
87242
- const relativePath = path96.relative(realWorkspace, realResolvedPath);
87243
- 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)) {
87244
87394
  return false;
87245
87395
  }
87246
87396
  return true;
@@ -87253,12 +87403,12 @@ function validatePathForRead2(filePath, workspace) {
87253
87403
  }
87254
87404
  function findRgInEnvPath() {
87255
87405
  const searchPath = process.env.PATH ?? "";
87256
- for (const dir of searchPath.split(path96.delimiter)) {
87406
+ for (const dir of searchPath.split(path97.delimiter)) {
87257
87407
  if (!dir)
87258
87408
  continue;
87259
87409
  const isWindows = process.platform === "win32";
87260
- const candidate = path96.join(dir, isWindows ? "rg.exe" : "rg");
87261
- if (fs77.existsSync(candidate))
87410
+ const candidate = path97.join(dir, isWindows ? "rg.exe" : "rg");
87411
+ if (fs78.existsSync(candidate))
87262
87412
  return candidate;
87263
87413
  }
87264
87414
  return null;
@@ -87385,10 +87535,10 @@ function collectFiles(dir, workspace, includeGlobs, excludeGlobs) {
87385
87535
  return files;
87386
87536
  }
87387
87537
  try {
87388
- const entries = fs77.readdirSync(dir, { withFileTypes: true });
87538
+ const entries = fs78.readdirSync(dir, { withFileTypes: true });
87389
87539
  for (const entry of entries) {
87390
- const fullPath = path96.join(dir, entry.name);
87391
- const relativePath = path96.relative(workspace, fullPath);
87540
+ const fullPath = path97.join(dir, entry.name);
87541
+ const relativePath = path97.relative(workspace, fullPath);
87392
87542
  if (!validatePathForRead2(fullPath, workspace)) {
87393
87543
  continue;
87394
87544
  }
@@ -87429,13 +87579,13 @@ async function fallbackSearch(opts) {
87429
87579
  const matches = [];
87430
87580
  let total = 0;
87431
87581
  for (const file3 of files) {
87432
- const fullPath = path96.join(opts.workspace, file3);
87582
+ const fullPath = path97.join(opts.workspace, file3);
87433
87583
  if (!validatePathForRead2(fullPath, opts.workspace)) {
87434
87584
  continue;
87435
87585
  }
87436
87586
  let stats;
87437
87587
  try {
87438
- stats = fs77.statSync(fullPath);
87588
+ stats = fs78.statSync(fullPath);
87439
87589
  if (stats.size > MAX_FILE_SIZE_BYTES10) {
87440
87590
  continue;
87441
87591
  }
@@ -87444,7 +87594,7 @@ async function fallbackSearch(opts) {
87444
87594
  }
87445
87595
  let content;
87446
87596
  try {
87447
- content = fs77.readFileSync(fullPath, "utf-8");
87597
+ content = fs78.readFileSync(fullPath, "utf-8");
87448
87598
  } catch {
87449
87599
  continue;
87450
87600
  }
@@ -87556,7 +87706,7 @@ var search = createSwarmTool({
87556
87706
  message: "Exclude pattern contains invalid Windows-specific sequence"
87557
87707
  }, null, 2);
87558
87708
  }
87559
- if (!fs77.existsSync(directory)) {
87709
+ if (!fs78.existsSync(directory)) {
87560
87710
  return JSON.stringify({
87561
87711
  error: true,
87562
87712
  type: "unknown",
@@ -87681,12 +87831,136 @@ var set_qa_gates = createSwarmTool({
87681
87831
  return JSON.stringify(await executeSetQaGates(typedArgs, directory), null, 2);
87682
87832
  }
87683
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
+ });
87684
87958
  // src/tools/suggest-patch.ts
87685
87959
  init_zod();
87686
87960
  init_path_security();
87687
87961
  init_create_tool();
87688
- import * as fs78 from "node:fs";
87689
- import * as path97 from "node:path";
87962
+ import * as fs79 from "node:fs";
87963
+ import * as path98 from "node:path";
87690
87964
  var WINDOWS_RESERVED_NAMES4 = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
87691
87965
  function containsWindowsAttacks4(str) {
87692
87966
  if (/:[^\\/]/.test(str))
@@ -87700,14 +87974,14 @@ function containsWindowsAttacks4(str) {
87700
87974
  }
87701
87975
  function isPathInWorkspace4(filePath, workspace) {
87702
87976
  try {
87703
- const resolvedPath = path97.resolve(workspace, filePath);
87704
- if (!fs78.existsSync(resolvedPath)) {
87977
+ const resolvedPath = path98.resolve(workspace, filePath);
87978
+ if (!fs79.existsSync(resolvedPath)) {
87705
87979
  return true;
87706
87980
  }
87707
- const realWorkspace = fs78.realpathSync(workspace);
87708
- const realResolvedPath = fs78.realpathSync(resolvedPath);
87709
- const relativePath = path97.relative(realWorkspace, realResolvedPath);
87710
- 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)) {
87711
87985
  return false;
87712
87986
  }
87713
87987
  return true;
@@ -87879,7 +88153,7 @@ var suggestPatch = createSwarmTool({
87879
88153
  message: "changes cannot be empty"
87880
88154
  }, null, 2);
87881
88155
  }
87882
- if (!fs78.existsSync(directory)) {
88156
+ if (!fs79.existsSync(directory)) {
87883
88157
  return JSON.stringify({
87884
88158
  success: false,
87885
88159
  error: true,
@@ -87915,8 +88189,8 @@ var suggestPatch = createSwarmTool({
87915
88189
  });
87916
88190
  continue;
87917
88191
  }
87918
- const fullPath = path97.resolve(directory, change.file);
87919
- if (!fs78.existsSync(fullPath)) {
88192
+ const fullPath = path98.resolve(directory, change.file);
88193
+ if (!fs79.existsSync(fullPath)) {
87920
88194
  errors5.push({
87921
88195
  success: false,
87922
88196
  error: true,
@@ -87930,7 +88204,7 @@ var suggestPatch = createSwarmTool({
87930
88204
  }
87931
88205
  let content;
87932
88206
  try {
87933
- content = fs78.readFileSync(fullPath, "utf-8");
88207
+ content = fs79.readFileSync(fullPath, "utf-8");
87934
88208
  } catch (err3) {
87935
88209
  errors5.push({
87936
88210
  success: false,
@@ -88177,8 +88451,8 @@ var generate_mutants = createSwarmTool({
88177
88451
  // src/tools/lint-spec.ts
88178
88452
  init_spec_schema();
88179
88453
  init_create_tool();
88180
- import * as fs79 from "node:fs";
88181
- import * as path98 from "node:path";
88454
+ import * as fs80 from "node:fs";
88455
+ import * as path99 from "node:path";
88182
88456
  var SPEC_FILE_NAME = "spec.md";
88183
88457
  var SWARM_DIR2 = ".swarm";
88184
88458
  var OBLIGATION_KEYWORDS2 = ["MUST", "SHALL", "SHOULD", "MAY"];
@@ -88231,8 +88505,8 @@ var lint_spec = createSwarmTool({
88231
88505
  async execute(_args, directory) {
88232
88506
  const errors5 = [];
88233
88507
  const warnings = [];
88234
- const specPath = path98.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
88235
- if (!fs79.existsSync(specPath)) {
88508
+ const specPath = path99.join(directory, SWARM_DIR2, SPEC_FILE_NAME);
88509
+ if (!fs80.existsSync(specPath)) {
88236
88510
  const result2 = {
88237
88511
  valid: false,
88238
88512
  specMtime: null,
@@ -88251,12 +88525,12 @@ var lint_spec = createSwarmTool({
88251
88525
  }
88252
88526
  let specMtime = null;
88253
88527
  try {
88254
- const stats = fs79.statSync(specPath);
88528
+ const stats = fs80.statSync(specPath);
88255
88529
  specMtime = stats.mtime.toISOString();
88256
88530
  } catch {}
88257
88531
  let content;
88258
88532
  try {
88259
- content = fs79.readFileSync(specPath, "utf-8");
88533
+ content = fs80.readFileSync(specPath, "utf-8");
88260
88534
  } catch (e) {
88261
88535
  const result2 = {
88262
88536
  valid: false,
@@ -88301,13 +88575,13 @@ var lint_spec = createSwarmTool({
88301
88575
  });
88302
88576
  // src/tools/mutation-test.ts
88303
88577
  init_zod();
88304
- import * as fs80 from "node:fs";
88305
- import * as path100 from "node:path";
88578
+ import * as fs81 from "node:fs";
88579
+ import * as path101 from "node:path";
88306
88580
 
88307
88581
  // src/mutation/engine.ts
88308
88582
  import { spawnSync as spawnSync3 } from "node:child_process";
88309
88583
  import { unlinkSync as unlinkSync13, writeFileSync as writeFileSync20 } from "node:fs";
88310
- import * as path99 from "node:path";
88584
+ import * as path100 from "node:path";
88311
88585
 
88312
88586
  // src/mutation/equivalence.ts
88313
88587
  function isStaticallyEquivalent(originalCode, mutatedCode) {
@@ -88442,7 +88716,7 @@ async function executeMutation(patch, testCommand, _testFiles, workingDir) {
88442
88716
  let patchFile;
88443
88717
  try {
88444
88718
  const safeId2 = patch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
88445
- patchFile = path99.join(workingDir, `.mutation_patch_${safeId2}.diff`);
88719
+ patchFile = path100.join(workingDir, `.mutation_patch_${safeId2}.diff`);
88446
88720
  try {
88447
88721
  writeFileSync20(patchFile, patch.patch);
88448
88722
  } catch (writeErr) {
@@ -88836,8 +89110,8 @@ var mutation_test = createSwarmTool({
88836
89110
  ];
88837
89111
  for (const filePath of uniquePaths) {
88838
89112
  try {
88839
- const resolvedPath = path100.resolve(cwd, filePath);
88840
- sourceFiles.set(filePath, fs80.readFileSync(resolvedPath, "utf-8"));
89113
+ const resolvedPath = path101.resolve(cwd, filePath);
89114
+ sourceFiles.set(filePath, fs81.readFileSync(resolvedPath, "utf-8"));
88841
89115
  } catch {}
88842
89116
  }
88843
89117
  const report = await executeMutationSuite(typedArgs.patches, typedArgs.test_command, typedArgs.files, cwd, undefined, undefined, sourceFiles.size > 0 ? sourceFiles : undefined);
@@ -88855,8 +89129,8 @@ var mutation_test = createSwarmTool({
88855
89129
  init_zod();
88856
89130
  init_manager2();
88857
89131
  init_detector();
88858
- import * as fs81 from "node:fs";
88859
- import * as path101 from "node:path";
89132
+ import * as fs82 from "node:fs";
89133
+ import * as path102 from "node:path";
88860
89134
  init_create_tool();
88861
89135
  var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
88862
89136
  var BINARY_CHECK_BYTES = 8192;
@@ -88922,7 +89196,7 @@ async function syntaxCheck(input, directory, config3) {
88922
89196
  if (languages?.length) {
88923
89197
  const lowerLangs = languages.map((l) => l.toLowerCase());
88924
89198
  filesToCheck = filesToCheck.filter((file3) => {
88925
- const ext = path101.extname(file3.path).toLowerCase();
89199
+ const ext = path102.extname(file3.path).toLowerCase();
88926
89200
  const langDef = getLanguageForExtension(ext);
88927
89201
  const fileProfile = getProfileForFile(file3.path);
88928
89202
  const langId = fileProfile?.id || langDef?.id;
@@ -88935,7 +89209,7 @@ async function syntaxCheck(input, directory, config3) {
88935
89209
  let skippedCount = 0;
88936
89210
  for (const fileInfo of filesToCheck) {
88937
89211
  const { path: filePath } = fileInfo;
88938
- const fullPath = path101.isAbsolute(filePath) ? filePath : path101.join(directory, filePath);
89212
+ const fullPath = path102.isAbsolute(filePath) ? filePath : path102.join(directory, filePath);
88939
89213
  const result = {
88940
89214
  path: filePath,
88941
89215
  language: "",
@@ -88965,7 +89239,7 @@ async function syntaxCheck(input, directory, config3) {
88965
89239
  }
88966
89240
  let content;
88967
89241
  try {
88968
- content = fs81.readFileSync(fullPath, "utf8");
89242
+ content = fs82.readFileSync(fullPath, "utf8");
88969
89243
  } catch {
88970
89244
  result.skipped_reason = "file_read_error";
88971
89245
  skippedCount++;
@@ -88984,7 +89258,7 @@ async function syntaxCheck(input, directory, config3) {
88984
89258
  results.push(result);
88985
89259
  continue;
88986
89260
  }
88987
- const ext = path101.extname(filePath).toLowerCase();
89261
+ const ext = path102.extname(filePath).toLowerCase();
88988
89262
  const langDef = getLanguageForExtension(ext);
88989
89263
  result.language = profile?.id || langDef?.id || "unknown";
88990
89264
  const errors5 = extractSyntaxErrors(parser, content);
@@ -89076,8 +89350,8 @@ init_zod();
89076
89350
  init_utils();
89077
89351
  init_create_tool();
89078
89352
  init_path_security();
89079
- import * as fs82 from "node:fs";
89080
- import * as path102 from "node:path";
89353
+ import * as fs83 from "node:fs";
89354
+ import * as path103 from "node:path";
89081
89355
  var MAX_TEXT_LENGTH = 200;
89082
89356
  var MAX_FILE_SIZE_BYTES11 = 1024 * 1024;
89083
89357
  var SUPPORTED_EXTENSIONS4 = new Set([
@@ -89143,9 +89417,9 @@ function validatePathsInput(paths, cwd) {
89143
89417
  return { error: "paths contains path traversal", resolvedPath: null };
89144
89418
  }
89145
89419
  try {
89146
- const resolvedPath = path102.resolve(paths);
89147
- const normalizedCwd = path102.resolve(cwd);
89148
- const normalizedResolved = path102.resolve(resolvedPath);
89420
+ const resolvedPath = path103.resolve(paths);
89421
+ const normalizedCwd = path103.resolve(cwd);
89422
+ const normalizedResolved = path103.resolve(resolvedPath);
89149
89423
  if (!normalizedResolved.startsWith(normalizedCwd)) {
89150
89424
  return {
89151
89425
  error: "paths must be within the current working directory",
@@ -89161,13 +89435,13 @@ function validatePathsInput(paths, cwd) {
89161
89435
  }
89162
89436
  }
89163
89437
  function isSupportedExtension(filePath) {
89164
- const ext = path102.extname(filePath).toLowerCase();
89438
+ const ext = path103.extname(filePath).toLowerCase();
89165
89439
  return SUPPORTED_EXTENSIONS4.has(ext);
89166
89440
  }
89167
89441
  function findSourceFiles3(dir, files = []) {
89168
89442
  let entries;
89169
89443
  try {
89170
- entries = fs82.readdirSync(dir);
89444
+ entries = fs83.readdirSync(dir);
89171
89445
  } catch {
89172
89446
  return files;
89173
89447
  }
@@ -89176,10 +89450,10 @@ function findSourceFiles3(dir, files = []) {
89176
89450
  if (SKIP_DIRECTORIES5.has(entry)) {
89177
89451
  continue;
89178
89452
  }
89179
- const fullPath = path102.join(dir, entry);
89453
+ const fullPath = path103.join(dir, entry);
89180
89454
  let stat6;
89181
89455
  try {
89182
- stat6 = fs82.statSync(fullPath);
89456
+ stat6 = fs83.statSync(fullPath);
89183
89457
  } catch {
89184
89458
  continue;
89185
89459
  }
@@ -89272,7 +89546,7 @@ var todo_extract = createSwarmTool({
89272
89546
  return JSON.stringify(errorResult, null, 2);
89273
89547
  }
89274
89548
  const scanPath = resolvedPath;
89275
- if (!fs82.existsSync(scanPath)) {
89549
+ if (!fs83.existsSync(scanPath)) {
89276
89550
  const errorResult = {
89277
89551
  error: `path not found: ${pathsInput}`,
89278
89552
  total: 0,
@@ -89282,13 +89556,13 @@ var todo_extract = createSwarmTool({
89282
89556
  return JSON.stringify(errorResult, null, 2);
89283
89557
  }
89284
89558
  const filesToScan = [];
89285
- const stat6 = fs82.statSync(scanPath);
89559
+ const stat6 = fs83.statSync(scanPath);
89286
89560
  if (stat6.isFile()) {
89287
89561
  if (isSupportedExtension(scanPath)) {
89288
89562
  filesToScan.push(scanPath);
89289
89563
  } else {
89290
89564
  const errorResult = {
89291
- error: `unsupported file extension: ${path102.extname(scanPath)}`,
89565
+ error: `unsupported file extension: ${path103.extname(scanPath)}`,
89292
89566
  total: 0,
89293
89567
  byPriority: { high: 0, medium: 0, low: 0 },
89294
89568
  entries: []
@@ -89301,11 +89575,11 @@ var todo_extract = createSwarmTool({
89301
89575
  const allEntries = [];
89302
89576
  for (const filePath of filesToScan) {
89303
89577
  try {
89304
- const fileStat = fs82.statSync(filePath);
89578
+ const fileStat = fs83.statSync(filePath);
89305
89579
  if (fileStat.size > MAX_FILE_SIZE_BYTES11) {
89306
89580
  continue;
89307
89581
  }
89308
- const content = fs82.readFileSync(filePath, "utf-8");
89582
+ const content = fs83.readFileSync(filePath, "utf-8");
89309
89583
  const entries = parseTodoComments(content, filePath, tagsSet);
89310
89584
  allEntries.push(...entries);
89311
89585
  } catch {}
@@ -89336,19 +89610,19 @@ init_loader();
89336
89610
  init_schema();
89337
89611
  init_qa_gate_profile();
89338
89612
  init_gate_evidence();
89339
- import * as fs84 from "node:fs";
89340
- import * as path104 from "node:path";
89613
+ import * as fs85 from "node:fs";
89614
+ import * as path105 from "node:path";
89341
89615
 
89342
89616
  // src/hooks/diff-scope.ts
89343
89617
  init_bun_compat();
89344
- import * as fs83 from "node:fs";
89345
- import * as path103 from "node:path";
89618
+ import * as fs84 from "node:fs";
89619
+ import * as path104 from "node:path";
89346
89620
  function getDeclaredScope(taskId, directory) {
89347
89621
  try {
89348
- const planPath = path103.join(directory, ".swarm", "plan.json");
89349
- if (!fs83.existsSync(planPath))
89622
+ const planPath = path104.join(directory, ".swarm", "plan.json");
89623
+ if (!fs84.existsSync(planPath))
89350
89624
  return null;
89351
- const raw = fs83.readFileSync(planPath, "utf-8");
89625
+ const raw = fs84.readFileSync(planPath, "utf-8");
89352
89626
  const plan = JSON.parse(raw);
89353
89627
  for (const phase of plan.phases ?? []) {
89354
89628
  for (const task of phase.tasks ?? []) {
@@ -89464,7 +89738,7 @@ var TIER_3_PATTERNS = [
89464
89738
  ];
89465
89739
  function matchesTier3Pattern(files) {
89466
89740
  for (const file3 of files) {
89467
- const fileName = path104.basename(file3);
89741
+ const fileName = path105.basename(file3);
89468
89742
  for (const pattern of TIER_3_PATTERNS) {
89469
89743
  if (pattern.test(fileName)) {
89470
89744
  return true;
@@ -89478,8 +89752,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
89478
89752
  if (hasActiveTurboMode()) {
89479
89753
  const resolvedDir2 = workingDirectory;
89480
89754
  try {
89481
- const planPath = path104.join(resolvedDir2, ".swarm", "plan.json");
89482
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89755
+ const planPath = path105.join(resolvedDir2, ".swarm", "plan.json");
89756
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89483
89757
  const plan = JSON.parse(planRaw);
89484
89758
  for (const planPhase of plan.phases ?? []) {
89485
89759
  for (const task of planPhase.tasks ?? []) {
@@ -89548,8 +89822,8 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
89548
89822
  }
89549
89823
  try {
89550
89824
  const resolvedDir2 = workingDirectory;
89551
- const planPath = path104.join(resolvedDir2, ".swarm", "plan.json");
89552
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89825
+ const planPath = path105.join(resolvedDir2, ".swarm", "plan.json");
89826
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89553
89827
  const plan = JSON.parse(planRaw);
89554
89828
  for (const planPhase of plan.phases ?? []) {
89555
89829
  for (const task of planPhase.tasks ?? []) {
@@ -89692,65 +89966,6 @@ function recoverTaskStateFromDelegations(taskId) {
89692
89966
  }
89693
89967
  }
89694
89968
  }
89695
- function checkCouncilGate(workingDirectory, taskId) {
89696
- let councilEnabled = false;
89697
- let effectiveMinimum = 3;
89698
- try {
89699
- const config3 = loadPluginConfig(workingDirectory);
89700
- councilEnabled = config3.council?.enabled === true;
89701
- effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
89702
- } catch {
89703
- return { blocked: false, reason: "" };
89704
- }
89705
- if (!councilEnabled) {
89706
- return { blocked: false, reason: "" };
89707
- }
89708
- try {
89709
- const planPath = path104.join(workingDirectory, ".swarm", "plan.json");
89710
- const planRaw = fs84.readFileSync(planPath, "utf-8");
89711
- const planObj = JSON.parse(planRaw);
89712
- if (planObj.swarm && planObj.title) {
89713
- const planId = `${planObj.swarm}-${planObj.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
89714
- const profile = getProfile(workingDirectory, planId);
89715
- if (!profile || !profile.gates.council_mode) {
89716
- return { blocked: false, reason: "" };
89717
- }
89718
- }
89719
- } catch {
89720
- return { blocked: false, reason: "" };
89721
- }
89722
- let evidence;
89723
- try {
89724
- evidence = readTaskEvidenceRaw(workingDirectory, taskId);
89725
- } catch {
89726
- return {
89727
- blocked: true,
89728
- reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task"
89729
- };
89730
- }
89731
- const councilGate = evidence?.gates?.council;
89732
- if (!councilGate) {
89733
- return {
89734
- blocked: true,
89735
- reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task"
89736
- };
89737
- }
89738
- if (councilGate.verdict === "REJECT") {
89739
- return {
89740
- blocked: true,
89741
- reason: "council gate blocked advancement — resolve requiredFixes and re-run submit_council_verdicts"
89742
- };
89743
- }
89744
- const rawQuorumSize = councilGate.quorumSize;
89745
- const quorumSize = typeof rawQuorumSize === "number" && Number.isFinite(rawQuorumSize) && rawQuorumSize >= 1 ? rawQuorumSize : 1;
89746
- if (quorumSize < effectiveMinimum) {
89747
- return {
89748
- blocked: true,
89749
- 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.`
89750
- };
89751
- }
89752
- return { blocked: false, reason: "" };
89753
- }
89754
89969
  async function executeUpdateTaskStatus(args2, fallbackDir) {
89755
89970
  const statusError = validateStatus(args2.status);
89756
89971
  if (statusError) {
@@ -89797,8 +90012,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89797
90012
  };
89798
90013
  }
89799
90014
  }
89800
- normalizedDir = path104.normalize(args2.working_directory);
89801
- const pathParts = normalizedDir.split(path104.sep);
90015
+ normalizedDir = path105.normalize(args2.working_directory);
90016
+ const pathParts = normalizedDir.split(path105.sep);
89802
90017
  if (pathParts.includes("..")) {
89803
90018
  return {
89804
90019
  success: false,
@@ -89808,11 +90023,11 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89808
90023
  ]
89809
90024
  };
89810
90025
  }
89811
- const resolvedDir = path104.resolve(normalizedDir);
90026
+ const resolvedDir = path105.resolve(normalizedDir);
89812
90027
  try {
89813
- const realPath = fs84.realpathSync(resolvedDir);
89814
- const planPath = path104.join(realPath, ".swarm", "plan.json");
89815
- if (!fs84.existsSync(planPath)) {
90028
+ const realPath = fs85.realpathSync(resolvedDir);
90029
+ const planPath = path105.join(realPath, ".swarm", "plan.json");
90030
+ if (!fs85.existsSync(planPath)) {
89816
90031
  return {
89817
90032
  success: false,
89818
90033
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -89843,22 +90058,22 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89843
90058
  }
89844
90059
  if (args2.status === "in_progress") {
89845
90060
  try {
89846
- const evidencePath = path104.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
89847
- fs84.mkdirSync(path104.dirname(evidencePath), { recursive: true });
89848
- const fd = fs84.openSync(evidencePath, "wx");
90061
+ const evidencePath = path105.join(directory, ".swarm", "evidence", `${args2.task_id}.json`);
90062
+ fs85.mkdirSync(path105.dirname(evidencePath), { recursive: true });
90063
+ const fd = fs85.openSync(evidencePath, "wx");
89849
90064
  let writeOk = false;
89850
90065
  try {
89851
- fs84.writeSync(fd, JSON.stringify({
90066
+ fs85.writeSync(fd, JSON.stringify({
89852
90067
  taskId: args2.task_id,
89853
90068
  required_gates: ["reviewer", "test_engineer"],
89854
90069
  gates: {}
89855
90070
  }, null, 2));
89856
90071
  writeOk = true;
89857
90072
  } finally {
89858
- fs84.closeSync(fd);
90073
+ fs85.closeSync(fd);
89859
90074
  if (!writeOk) {
89860
90075
  try {
89861
- fs84.unlinkSync(evidencePath);
90076
+ fs85.unlinkSync(evidencePath);
89862
90077
  } catch {}
89863
90078
  }
89864
90079
  }
@@ -89868,8 +90083,8 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89868
90083
  recoverTaskStateFromDelegations(args2.task_id);
89869
90084
  let phaseRequiresReviewer = true;
89870
90085
  try {
89871
- const planPath = path104.join(directory, ".swarm", "plan.json");
89872
- const planRaw = fs84.readFileSync(planPath, "utf-8");
90086
+ const planPath = path105.join(directory, ".swarm", "plan.json");
90087
+ const planRaw = fs85.readFileSync(planPath, "utf-8");
89873
90088
  const plan = JSON.parse(planRaw);
89874
90089
  const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
89875
90090
  if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
@@ -89886,14 +90101,6 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
89886
90101
  };
89887
90102
  }
89888
90103
  }
89889
- const councilCheck = checkCouncilGate(directory, args2.task_id);
89890
- if (councilCheck.blocked) {
89891
- return {
89892
- success: false,
89893
- message: councilCheck.reason,
89894
- errors: [councilCheck.reason]
89895
- };
89896
- }
89897
90104
  }
89898
90105
  const lockTaskId = `update-task-status-${args2.task_id}-${Date.now()}`;
89899
90106
  const planFilePath = "plan.json";
@@ -90103,7 +90310,7 @@ function createWebSearchProvider(config3) {
90103
90310
  init_create_tool();
90104
90311
  init_resolve_working_directory();
90105
90312
  var MAX_RESULTS_HARD_CAP = 10;
90106
- var ArgsSchema4 = exports_external.object({
90313
+ var ArgsSchema5 = exports_external.object({
90107
90314
  query: exports_external.string().min(1).max(500),
90108
90315
  max_results: exports_external.number().int().min(1).max(20).optional(),
90109
90316
  working_directory: exports_external.string().optional()
@@ -90116,7 +90323,7 @@ var web_search = createSwarmTool({
90116
90323
  working_directory: exports_external.string().optional().describe("Project root for config resolution. Optional.")
90117
90324
  },
90118
90325
  execute: async (args2, directory) => {
90119
- const parsed = ArgsSchema4.safeParse(args2);
90326
+ const parsed = ArgsSchema5.safeParse(args2);
90120
90327
  if (!parsed.success) {
90121
90328
  const fail = {
90122
90329
  success: false,
@@ -90187,8 +90394,8 @@ init_utils2();
90187
90394
  init_ledger();
90188
90395
  init_manager();
90189
90396
  init_create_tool();
90190
- import fs85 from "node:fs";
90191
- import path105 from "node:path";
90397
+ import fs86 from "node:fs";
90398
+ import path106 from "node:path";
90192
90399
  function derivePlanId5(plan) {
90193
90400
  return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
90194
90401
  }
@@ -90239,7 +90446,7 @@ async function executeWriteDriftEvidence(args2, directory) {
90239
90446
  entries: [evidenceEntry]
90240
90447
  };
90241
90448
  const filename = "drift-verifier.json";
90242
- const relativePath = path105.join("evidence", String(phase), filename);
90449
+ const relativePath = path106.join("evidence", String(phase), filename);
90243
90450
  let validatedPath;
90244
90451
  try {
90245
90452
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90250,12 +90457,12 @@ async function executeWriteDriftEvidence(args2, directory) {
90250
90457
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90251
90458
  }, null, 2);
90252
90459
  }
90253
- const evidenceDir = path105.dirname(validatedPath);
90460
+ const evidenceDir = path106.dirname(validatedPath);
90254
90461
  try {
90255
- await fs85.promises.mkdir(evidenceDir, { recursive: true });
90256
- const tempPath = path105.join(evidenceDir, `.${filename}.tmp`);
90257
- await fs85.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90258
- await fs85.promises.rename(tempPath, validatedPath);
90462
+ await fs86.promises.mkdir(evidenceDir, { recursive: true });
90463
+ const tempPath = path106.join(evidenceDir, `.${filename}.tmp`);
90464
+ await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90465
+ await fs86.promises.rename(tempPath, validatedPath);
90259
90466
  let snapshotInfo;
90260
90467
  let snapshotError;
90261
90468
  let qaProfileLocked;
@@ -90349,8 +90556,8 @@ var write_drift_evidence = createSwarmTool({
90349
90556
  init_zod();
90350
90557
  init_utils2();
90351
90558
  init_create_tool();
90352
- import fs86 from "node:fs";
90353
- import path106 from "node:path";
90559
+ import fs87 from "node:fs";
90560
+ import path107 from "node:path";
90354
90561
  function normalizeVerdict2(verdict) {
90355
90562
  switch (verdict) {
90356
90563
  case "APPROVED":
@@ -90398,7 +90605,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
90398
90605
  entries: [evidenceEntry]
90399
90606
  };
90400
90607
  const filename = "hallucination-guard.json";
90401
- const relativePath = path106.join("evidence", String(phase), filename);
90608
+ const relativePath = path107.join("evidence", String(phase), filename);
90402
90609
  let validatedPath;
90403
90610
  try {
90404
90611
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90409,12 +90616,12 @@ async function executeWriteHallucinationEvidence(args2, directory) {
90409
90616
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90410
90617
  }, null, 2);
90411
90618
  }
90412
- const evidenceDir = path106.dirname(validatedPath);
90619
+ const evidenceDir = path107.dirname(validatedPath);
90413
90620
  try {
90414
- await fs86.promises.mkdir(evidenceDir, { recursive: true });
90415
- const tempPath = path106.join(evidenceDir, `.${filename}.tmp`);
90416
- await fs86.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90417
- await fs86.promises.rename(tempPath, validatedPath);
90621
+ await fs87.promises.mkdir(evidenceDir, { recursive: true });
90622
+ const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
90623
+ await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90624
+ await fs87.promises.rename(tempPath, validatedPath);
90418
90625
  return JSON.stringify({
90419
90626
  success: true,
90420
90627
  phase,
@@ -90460,8 +90667,8 @@ var write_hallucination_evidence = createSwarmTool({
90460
90667
  init_zod();
90461
90668
  init_utils2();
90462
90669
  init_create_tool();
90463
- import fs87 from "node:fs";
90464
- import path107 from "node:path";
90670
+ import fs88 from "node:fs";
90671
+ import path108 from "node:path";
90465
90672
  function normalizeVerdict3(verdict) {
90466
90673
  switch (verdict) {
90467
90674
  case "PASS":
@@ -90535,7 +90742,7 @@ async function executeWriteMutationEvidence(args2, directory) {
90535
90742
  entries: [evidenceEntry]
90536
90743
  };
90537
90744
  const filename = "mutation-gate.json";
90538
- const relativePath = path107.join("evidence", String(phase), filename);
90745
+ const relativePath = path108.join("evidence", String(phase), filename);
90539
90746
  let validatedPath;
90540
90747
  try {
90541
90748
  validatedPath = validateSwarmPath(directory, relativePath);
@@ -90546,12 +90753,12 @@ async function executeWriteMutationEvidence(args2, directory) {
90546
90753
  message: error93 instanceof Error ? error93.message : "Failed to validate path"
90547
90754
  }, null, 2);
90548
90755
  }
90549
- const evidenceDir = path107.dirname(validatedPath);
90756
+ const evidenceDir = path108.dirname(validatedPath);
90550
90757
  try {
90551
- await fs87.promises.mkdir(evidenceDir, { recursive: true });
90552
- const tempPath = path107.join(evidenceDir, `.${filename}.tmp`);
90553
- await fs87.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90554
- await fs87.promises.rename(tempPath, validatedPath);
90758
+ await fs88.promises.mkdir(evidenceDir, { recursive: true });
90759
+ const tempPath = path108.join(evidenceDir, `.${filename}.tmp`);
90760
+ await fs88.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
90761
+ await fs88.promises.rename(tempPath, validatedPath);
90555
90762
  return JSON.stringify({
90556
90763
  success: true,
90557
90764
  phase,
@@ -90605,20 +90812,20 @@ init_write_retro();
90605
90812
  init_utils();
90606
90813
 
90607
90814
  // src/utils/gitignore-warning.ts
90608
- import * as fs88 from "node:fs";
90609
- import * as path108 from "node:path";
90815
+ import * as fs89 from "node:fs";
90816
+ import * as path109 from "node:path";
90610
90817
  var _gitignoreWarningEmitted = false;
90611
90818
  function findGitRoot(startDir) {
90612
90819
  let current = startDir;
90613
90820
  while (true) {
90614
90821
  try {
90615
- const gitPath = path108.join(current, ".git");
90616
- const stat6 = fs88.statSync(gitPath);
90822
+ const gitPath = path109.join(current, ".git");
90823
+ const stat6 = fs89.statSync(gitPath);
90617
90824
  if (stat6.isDirectory()) {
90618
90825
  return current;
90619
90826
  }
90620
90827
  } catch {}
90621
- const parent = path108.dirname(current);
90828
+ const parent = path109.dirname(current);
90622
90829
  if (parent === current) {
90623
90830
  return null;
90624
90831
  }
@@ -90638,7 +90845,7 @@ function fileCoversSwarm(content) {
90638
90845
  }
90639
90846
  function readFileSafe(filePath) {
90640
90847
  try {
90641
- return fs88.readFileSync(filePath, "utf8");
90848
+ return fs89.readFileSync(filePath, "utf8");
90642
90849
  } catch {
90643
90850
  return null;
90644
90851
  }
@@ -90650,12 +90857,12 @@ function warnIfSwarmNotGitignored(directory, quiet = false) {
90650
90857
  const gitRoot = findGitRoot(directory);
90651
90858
  if (!gitRoot)
90652
90859
  return;
90653
- const gitignoreContent = readFileSafe(path108.join(gitRoot, ".gitignore"));
90860
+ const gitignoreContent = readFileSafe(path109.join(gitRoot, ".gitignore"));
90654
90861
  if (gitignoreContent !== null && fileCoversSwarm(gitignoreContent)) {
90655
90862
  _gitignoreWarningEmitted = true;
90656
90863
  return;
90657
90864
  }
90658
- const excludeContent = readFileSafe(path108.join(gitRoot, ".git", "info", "exclude"));
90865
+ const excludeContent = readFileSafe(path109.join(gitRoot, ".git", "info", "exclude"));
90659
90866
  if (excludeContent !== null && fileCoversSwarm(excludeContent)) {
90660
90867
  _gitignoreWarningEmitted = true;
90661
90868
  return;
@@ -90900,7 +91107,7 @@ async function initializeOpenCodeSwarm(ctx) {
90900
91107
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
90901
91108
  preflightTriggerManager = new PTM(automationConfig);
90902
91109
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
90903
- const swarmDir = path109.resolve(ctx.directory, ".swarm");
91110
+ const swarmDir = path110.resolve(ctx.directory, ".swarm");
90904
91111
  statusArtifact = new ASA(swarmDir);
90905
91112
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
90906
91113
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {