opencode-swarm 7.6.0 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/qa-gate-hardening.test.d.ts +1 -1
- package/dist/cli/index.js +23 -21
- package/dist/config/schema.d.ts +0 -10
- package/dist/db/qa-gate-profile.d.ts +2 -1
- package/dist/index.js +366 -159
- package/dist/plan/index.d.ts +1 -0
- package/dist/plan/utils.d.ts +8 -0
- package/dist/state.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/set-qa-gates.d.ts +1 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/update-task-status.d.ts +1 -1
- package/dist/tools/write-final-council-evidence.d.ts +29 -0
- package/package.json +1 -1
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.
|
|
36
|
+
version: "7.7.0",
|
|
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",
|
|
@@ -164,7 +164,8 @@ var init_tool_names = __esm(() => {
|
|
|
164
164
|
"get_qa_gate_profile",
|
|
165
165
|
"set_qa_gates",
|
|
166
166
|
"web_search",
|
|
167
|
-
"convene_general_council"
|
|
167
|
+
"convene_general_council",
|
|
168
|
+
"write_final_council_evidence"
|
|
168
169
|
];
|
|
169
170
|
TOOL_NAME_SET = new Set(TOOL_NAMES);
|
|
170
171
|
});
|
|
@@ -464,7 +465,8 @@ var init_constants = __esm(() => {
|
|
|
464
465
|
"get_qa_gate_profile",
|
|
465
466
|
"set_qa_gates",
|
|
466
467
|
"convene_general_council",
|
|
467
|
-
"web_search"
|
|
468
|
+
"web_search",
|
|
469
|
+
"write_final_council_evidence"
|
|
468
470
|
],
|
|
469
471
|
explorer: [
|
|
470
472
|
"complexity_hotspots",
|
|
@@ -662,6 +664,7 @@ var init_constants = __esm(() => {
|
|
|
662
664
|
write_retro: "document phase retrospectives via phase_complete workflow, capture lessons learned",
|
|
663
665
|
write_drift_evidence: "write drift verification evidence for a completed phase",
|
|
664
666
|
write_hallucination_evidence: "write hallucination verification evidence for a completed phase",
|
|
667
|
+
write_final_council_evidence: "write final council evidence for project completion",
|
|
665
668
|
declare_scope: "declare file scope for next coder delegation",
|
|
666
669
|
phase_complete: "mark a phase as complete and track dispatched agents",
|
|
667
670
|
save_plan: "save a structured implementation plan",
|
|
@@ -690,8 +693,8 @@ var init_constants = __esm(() => {
|
|
|
690
693
|
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
691
694
|
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
|
|
692
695
|
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
|
|
693
|
-
get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review, drift_check), lock state, and profile hash. Read-only.",
|
|
694
|
-
set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only — rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review, drift_check.",
|
|
696
|
+
get_qa_gate_profile: "retrieve the QA gate profile for the current plan: gates (reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review, drift_check, final_council), lock state, and profile hash. Read-only.",
|
|
697
|
+
set_qa_gates: "configure the QA gate profile for the current plan. Architect-only. Ratchet-tighter only — rejected once the profile is locked after critic approval. Supports: reviewer, test_engineer, sme_enabled, critic_pre_plan, sast_enabled, council_mode, hallucination_guard, mutation_test, council_general_review, drift_check, final_council.",
|
|
695
698
|
req_coverage: "query requirement coverage status for tracked functional requirements"
|
|
696
699
|
};
|
|
697
700
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
@@ -15487,12 +15490,7 @@ var init_schema = __esm(() => {
|
|
|
15487
15490
|
maxConcurrentTasks: exports_external.number().int().min(1).max(64).default(1),
|
|
15488
15491
|
evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000),
|
|
15489
15492
|
max_coders: exports_external.number().int().min(1).max(16).default(3),
|
|
15490
|
-
max_reviewers: exports_external.number().int().min(1).max(16).default(2)
|
|
15491
|
-
stageB: exports_external.object({
|
|
15492
|
-
parallel: exports_external.object({
|
|
15493
|
-
enabled: exports_external.boolean().default(false)
|
|
15494
|
-
}).default({ enabled: false })
|
|
15495
|
-
}).default({ parallel: { enabled: false } })
|
|
15493
|
+
max_reviewers: exports_external.number().int().min(1).max(16).default(2)
|
|
15496
15494
|
});
|
|
15497
15495
|
PluginConfigSchema = exports_external.object({
|
|
15498
15496
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
@@ -16751,6 +16749,11 @@ var init_spec_hash = __esm(() => {
|
|
|
16751
16749
|
};
|
|
16752
16750
|
});
|
|
16753
16751
|
|
|
16752
|
+
// src/plan/utils.ts
|
|
16753
|
+
function derivePlanId(plan) {
|
|
16754
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16755
|
+
}
|
|
16756
|
+
|
|
16754
16757
|
// src/plan/ledger.ts
|
|
16755
16758
|
import * as crypto2 from "node:crypto";
|
|
16756
16759
|
import * as fs4 from "node:fs";
|
|
@@ -16939,7 +16942,7 @@ async function takeSnapshotEvent(directory, plan, options) {
|
|
|
16939
16942
|
if (options?.approvalMetadata) {
|
|
16940
16943
|
snapshotPayload.approval = options.approvalMetadata;
|
|
16941
16944
|
}
|
|
16942
|
-
const planId =
|
|
16945
|
+
const planId = derivePlanId(plan);
|
|
16943
16946
|
return appendLedgerEvent(directory, {
|
|
16944
16947
|
event_type: "snapshot",
|
|
16945
16948
|
source: options?.source ?? "takeSnapshotEvent",
|
|
@@ -17134,7 +17137,7 @@ async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
|
17134
17137
|
continue;
|
|
17135
17138
|
}
|
|
17136
17139
|
if (expectedPlanId !== undefined) {
|
|
17137
|
-
const payloadPlanId =
|
|
17140
|
+
const payloadPlanId = derivePlanId(payload.plan);
|
|
17138
17141
|
if (payloadPlanId !== expectedPlanId) {
|
|
17139
17142
|
continue;
|
|
17140
17143
|
}
|
|
@@ -17335,7 +17338,7 @@ async function loadPlan(directory) {
|
|
|
17335
17338
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
17336
17339
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
17337
17340
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
17338
|
-
const currentPlanId =
|
|
17341
|
+
const currentPlanId = derivePlanId(validated);
|
|
17339
17342
|
const ledgerEvents = await readLedgerEvents(directory);
|
|
17340
17343
|
const firstEvent = ledgerEvents.length > 0 ? ledgerEvents[0] : null;
|
|
17341
17344
|
if (firstEvent && firstEvent.plan_id !== currentPlanId) {
|
|
@@ -17418,7 +17421,7 @@ async function loadPlan(directory) {
|
|
|
17418
17421
|
try {
|
|
17419
17422
|
const rawParsed = JSON.parse(planJsonContent);
|
|
17420
17423
|
if (typeof rawParsed?.swarm === "string" && typeof rawParsed?.title === "string") {
|
|
17421
|
-
rawPlanId =
|
|
17424
|
+
rawPlanId = derivePlanId(rawParsed);
|
|
17422
17425
|
}
|
|
17423
17426
|
} catch {}
|
|
17424
17427
|
if (await ledgerExists(directory)) {
|
|
@@ -17544,7 +17547,7 @@ async function savePlan(directory, plan, options) {
|
|
|
17544
17547
|
}
|
|
17545
17548
|
}
|
|
17546
17549
|
const currentPlan = await _internals6.loadPlanJsonOnly(directory);
|
|
17547
|
-
const planId =
|
|
17550
|
+
const planId = derivePlanId(validated);
|
|
17548
17551
|
const planHashForInit = computePlanHash(validated);
|
|
17549
17552
|
if (!await ledgerExists(directory)) {
|
|
17550
17553
|
try {
|
|
@@ -17645,7 +17648,7 @@ async function savePlan(directory, plan, options) {
|
|
|
17645
17648
|
const oldTask = oldTaskMap.get(task.id);
|
|
17646
17649
|
if (oldTask && oldTask.status !== task.status) {
|
|
17647
17650
|
const eventInput = {
|
|
17648
|
-
plan_id:
|
|
17651
|
+
plan_id: derivePlanId(validated),
|
|
17649
17652
|
event_type: "task_status_changed",
|
|
17650
17653
|
task_id: task.id,
|
|
17651
17654
|
phase_id: phase.id,
|
|
@@ -20548,7 +20551,8 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
20548
20551
|
sast_enabled: true,
|
|
20549
20552
|
mutation_test: false,
|
|
20550
20553
|
council_general_review: false,
|
|
20551
|
-
drift_check: true
|
|
20554
|
+
drift_check: true,
|
|
20555
|
+
final_council: false
|
|
20552
20556
|
};
|
|
20553
20557
|
});
|
|
20554
20558
|
|
|
@@ -25820,7 +25824,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25820
25824
|
hasReviewer = true;
|
|
25821
25825
|
if (targetAgent === "test_engineer")
|
|
25822
25826
|
hasTestEngineer = true;
|
|
25823
|
-
const stageBParallelEnabled =
|
|
25827
|
+
const stageBParallelEnabled = true;
|
|
25824
25828
|
if (stageBParallelEnabled) {
|
|
25825
25829
|
if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
|
|
25826
25830
|
const stageBEligibleStates = [
|
|
@@ -25846,6 +25850,20 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25846
25850
|
} catch (err2) {
|
|
25847
25851
|
warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25848
25852
|
}
|
|
25853
|
+
} else {
|
|
25854
|
+
try {
|
|
25855
|
+
if (targetAgent === "reviewer" && (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed")) {
|
|
25856
|
+
advanceTaskState(session, taskId, "reviewer_run", {
|
|
25857
|
+
telemetrySessionId: input.sessionID
|
|
25858
|
+
});
|
|
25859
|
+
} else if (targetAgent === "test_engineer" && eligibleState === "reviewer_run") {
|
|
25860
|
+
advanceTaskState(session, taskId, "tests_run", {
|
|
25861
|
+
telemetrySessionId: input.sessionID
|
|
25862
|
+
});
|
|
25863
|
+
}
|
|
25864
|
+
} catch (err2) {
|
|
25865
|
+
warn(`[delegation-gate] toolAfter stage-b-parallel intermediate: could not advance ${taskId} (${eligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25866
|
+
}
|
|
25849
25867
|
}
|
|
25850
25868
|
}
|
|
25851
25869
|
const seedTaskId = getSeedTaskId(session);
|
|
@@ -25875,74 +25893,17 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25875
25893
|
} catch (err2) {
|
|
25876
25894
|
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25877
25895
|
}
|
|
25878
|
-
}
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
25884
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
25885
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
25886
|
-
try {
|
|
25887
|
-
advanceTaskState(session, taskId, "reviewer_run", {
|
|
25888
|
-
telemetrySessionId: input.sessionID
|
|
25889
|
-
});
|
|
25890
|
-
} catch (err2) {
|
|
25891
|
-
warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25892
|
-
}
|
|
25893
|
-
}
|
|
25894
|
-
}
|
|
25895
|
-
}
|
|
25896
|
-
if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
|
|
25897
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
25898
|
-
if (state === "reviewer_run") {
|
|
25899
|
-
try {
|
|
25900
|
-
advanceTaskState(session, taskId, "tests_run", {
|
|
25901
|
-
telemetrySessionId: input.sessionID
|
|
25902
|
-
});
|
|
25903
|
-
} catch (err2) {
|
|
25904
|
-
warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25905
|
-
}
|
|
25906
|
-
}
|
|
25907
|
-
}
|
|
25908
|
-
}
|
|
25909
|
-
if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
|
|
25910
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
25911
|
-
if (otherSession === session)
|
|
25912
|
-
continue;
|
|
25913
|
-
if (!otherSession.taskWorkflowStates)
|
|
25914
|
-
continue;
|
|
25915
|
-
if (targetAgent === "reviewer") {
|
|
25916
|
-
const seedTaskId = getSeedTaskId(session);
|
|
25917
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
25918
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
25919
|
-
}
|
|
25920
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
25921
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
25922
|
-
try {
|
|
25923
|
-
advanceTaskState(otherSession, taskId, "reviewer_run", {
|
|
25924
|
-
emitTelemetry: false
|
|
25925
|
-
});
|
|
25926
|
-
} catch (err2) {
|
|
25927
|
-
warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25928
|
-
}
|
|
25929
|
-
}
|
|
25930
|
-
}
|
|
25931
|
-
}
|
|
25932
|
-
if (targetAgent === "test_engineer") {
|
|
25933
|
-
const seedTaskId = getSeedTaskId(session);
|
|
25934
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
25935
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
25936
|
-
}
|
|
25937
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
25938
|
-
if (state === "reviewer_run") {
|
|
25939
|
-
try {
|
|
25940
|
-
advanceTaskState(otherSession, taskId, "tests_run", {
|
|
25896
|
+
} else {
|
|
25897
|
+
try {
|
|
25898
|
+
if (targetAgent === "reviewer" && (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed")) {
|
|
25899
|
+
advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
|
|
25900
|
+
} else if (targetAgent === "test_engineer" && seedEligibleState === "reviewer_run") {
|
|
25901
|
+
advanceTaskState(otherSession, seedTaskId, "tests_run", {
|
|
25941
25902
|
emitTelemetry: false
|
|
25942
25903
|
});
|
|
25943
|
-
} catch (err2) {
|
|
25944
|
-
warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25945
25904
|
}
|
|
25905
|
+
} catch (err2) {
|
|
25906
|
+
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel intermediate: could not advance ${seedTaskId} (${seedEligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25946
25907
|
}
|
|
25947
25908
|
}
|
|
25948
25909
|
}
|
|
@@ -25953,7 +25914,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25953
25914
|
if (typeof subagentType === "string") {
|
|
25954
25915
|
try {
|
|
25955
25916
|
const rawTaskId = directArgs?.task_id;
|
|
25956
|
-
const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 &&
|
|
25917
|
+
const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && isStrictTaskId(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
|
|
25957
25918
|
if (evidenceTaskId) {
|
|
25958
25919
|
const turbo = hasActiveTurboMode(input.sessionID);
|
|
25959
25920
|
const gateAgents = [
|
|
@@ -26076,7 +26037,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
26076
26037
|
}
|
|
26077
26038
|
try {
|
|
26078
26039
|
const rawTaskId = directArgs?.task_id;
|
|
26079
|
-
const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 &&
|
|
26040
|
+
const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && isStrictTaskId(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
|
|
26080
26041
|
if (evidenceTaskId) {
|
|
26081
26042
|
const turbo = hasActiveTurboMode(input.sessionID);
|
|
26082
26043
|
if (hasReviewer) {
|
|
@@ -26322,6 +26283,7 @@ var init_delegation_gate = __esm(() => {
|
|
|
26322
26283
|
init_state();
|
|
26323
26284
|
init_telemetry();
|
|
26324
26285
|
init_logger();
|
|
26286
|
+
init_task_id();
|
|
26325
26287
|
init_guardrails();
|
|
26326
26288
|
init_normalize_tool_name();
|
|
26327
26289
|
init_utils2();
|
|
@@ -26796,9 +26758,6 @@ function hasBothStageBCompletions(session, taskId) {
|
|
|
26796
26758
|
return false;
|
|
26797
26759
|
return completions.has("reviewer") && completions.has("test_engineer");
|
|
26798
26760
|
}
|
|
26799
|
-
function derivePlanIdFromPlan(plan) {
|
|
26800
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
26801
|
-
}
|
|
26802
26761
|
async function isCouncilGateActive(directory, council) {
|
|
26803
26762
|
const enabled = council?.enabled === true;
|
|
26804
26763
|
let plan = null;
|
|
@@ -26810,7 +26769,7 @@ async function isCouncilGateActive(directory, council) {
|
|
|
26810
26769
|
if (!plan) {
|
|
26811
26770
|
return false;
|
|
26812
26771
|
}
|
|
26813
|
-
const planId =
|
|
26772
|
+
const planId = derivePlanId(plan);
|
|
26814
26773
|
let profile = null;
|
|
26815
26774
|
try {
|
|
26816
26775
|
profile = getProfile(directory, planId);
|
|
@@ -53956,9 +53915,6 @@ var init_promote = __esm(() => {
|
|
|
53956
53915
|
});
|
|
53957
53916
|
|
|
53958
53917
|
// src/commands/qa-gates.ts
|
|
53959
|
-
function derivePlanId(plan) {
|
|
53960
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
53961
|
-
}
|
|
53962
53918
|
function isGateName(name2) {
|
|
53963
53919
|
return ALL_GATE_NAMES.includes(name2);
|
|
53964
53920
|
}
|
|
@@ -54084,7 +54040,8 @@ var init_qa_gates = __esm(() => {
|
|
|
54084
54040
|
"sast_enabled",
|
|
54085
54041
|
"mutation_test",
|
|
54086
54042
|
"council_general_review",
|
|
54087
|
-
"drift_check"
|
|
54043
|
+
"drift_check",
|
|
54044
|
+
"final_council"
|
|
54088
54045
|
];
|
|
54089
54046
|
});
|
|
54090
54047
|
|
|
@@ -55148,7 +55105,7 @@ async function handleRollbackCommand(directory, args2) {
|
|
|
55148
55105
|
if (fs27.existsSync(planJsonPath)) {
|
|
55149
55106
|
const planRaw = fs27.readFileSync(planJsonPath, "utf-8");
|
|
55150
55107
|
const plan = PlanSchema.parse(JSON.parse(planRaw));
|
|
55151
|
-
const planId =
|
|
55108
|
+
const planId = derivePlanId(plan);
|
|
55152
55109
|
const planHash = computePlanHash(plan);
|
|
55153
55110
|
await initLedger(directory, planId, planHash, plan);
|
|
55154
55111
|
await appendLedgerEvent(directory, {
|
|
@@ -56680,7 +56637,7 @@ function buildQaGateSelectionDialogue(modeLabel) {
|
|
|
56680
56637
|
const leadIn = modeLabel === "BRAINSTORM" ? "Now ask the user which QA gates to enable for this plan — do not select on their behalf." : modeLabel === "SPECIFY" ? "Ask the user which QA gates to enable for this plan before suggesting the next step." : "No pending gate selection found in `.swarm/context.md`. Ask the user inline now.";
|
|
56681
56638
|
return `${leadIn}
|
|
56682
56639
|
|
|
56683
|
-
Present the
|
|
56640
|
+
Present the eleven gates with their defaults (DEFAULT_QA_GATES) as a single user-facing question. Offer the user a one-shot choice: accept defaults, or customize. The eleven gates are:
|
|
56684
56641
|
- reviewer (default: ON) — code review of coder output
|
|
56685
56642
|
- test_engineer (default: ON) — test verification of coder output
|
|
56686
56643
|
- sme_enabled (default: ON) — SME consultation during planning/clarification
|
|
@@ -56691,8 +56648,20 @@ Present the ten gates with their defaults (DEFAULT_QA_GATES) as a single user-fa
|
|
|
56691
56648
|
- mutation_test (default: OFF) — when enabled, runs mutation testing on source files touched this phase via generate_mutants + mutation_test + write_mutation_evidence at PHASE-WRAP; FAIL verdict blocks phase_complete; WARN is non-blocking (recommended for projects with coverage gaps or safety-critical code)
|
|
56692
56649
|
- council_general_review (default: OFF) — when enabled, MODE: SPECIFY runs convene_general_council on the draft spec before the critic-gate; the architect runs a curated web_search pass, dispatches council_generalist / council_skeptic / council_domain_expert in parallel with a shared RESEARCH CONTEXT block, deliberates on disagreements, and synthesizes the result directly into the spec (recommended for novel architecture, unclear best practices, or high-risk design decisions). Requires council.general.enabled: true and a configured search API key.
|
|
56693
56650
|
- drift_check (default: ON) — when enabled, mandatory per-phase drift verification via critic_drift_verifier at PHASE-WRAP; compares implemented changes against spec.md intent; hard-blocks phase_complete when spec.md exists and drift evidence is missing or REJECTED; advisory-only when no spec.md exists (recommended for all projects with a specification)
|
|
56651
|
+
- final_council (default: OFF) — when enabled, after all phases complete the architect convenes a holistic general council review of the entire body of work before project close. Requires council.general.enabled: true in plugin config. Recommended for multi-phase projects with high architectural complexity.
|
|
56652
|
+
|
|
56653
|
+
One question, one message, defaults pre-stated. Wait for the user's answer.
|
|
56694
56654
|
|
|
56695
|
-
|
|
56655
|
+
If the user answered the gate question, immediately follow up with ONE more question: "How many coders should run in parallel? (default: 1, range: 1-4)" — if the user says a number > 1, also write a \`## Pending Parallelization Config\` section to \`.swarm/context.md\` alongside the gate selection:
|
|
56656
|
+
\`\`\`
|
|
56657
|
+
## Pending Parallelization Config
|
|
56658
|
+
- parallelization_enabled: true
|
|
56659
|
+
- max_concurrent_tasks: <user's number>
|
|
56660
|
+
- council_parallel: false
|
|
56661
|
+
- locked: true
|
|
56662
|
+
- recorded_at: <ISO timestamp>
|
|
56663
|
+
\`\`\`
|
|
56664
|
+
If the user accepts the default (1), skip writing this section entirely — serial execution is the default and needs no config.`;
|
|
56696
56665
|
}
|
|
56697
56666
|
function buildAvailableToolsList(council) {
|
|
56698
56667
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
@@ -57529,6 +57498,7 @@ Do NOT call \`set_qa_gates\` yet — \`plan.json\` does not exist at this point.
|
|
|
57529
57498
|
- mutation_test: <true|false>
|
|
57530
57499
|
- council_general_review: <true|false>
|
|
57531
57500
|
- drift_check: <true|false>
|
|
57501
|
+
- final_council: <true|false>
|
|
57532
57502
|
- recorded_at: <ISO timestamp>
|
|
57533
57503
|
\`\`\`
|
|
57534
57504
|
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
@@ -57607,6 +57577,7 @@ Do NOT call \`set_qa_gates\` yet — \`plan.json\` does not exist at this point.
|
|
|
57607
57577
|
- mutation_test: <true|false>
|
|
57608
57578
|
- council_general_review: <true|false>
|
|
57609
57579
|
- drift_check: <true|false>
|
|
57580
|
+
- final_council: <true|false>
|
|
57610
57581
|
- recorded_at: <ISO timestamp>
|
|
57611
57582
|
\`\`\`
|
|
57612
57583
|
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
@@ -57989,6 +57960,7 @@ save_plan({
|
|
|
57989
57960
|
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
57990
57961
|
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
57991
57962
|
- If a \`## Pending QA Gate Selection\` section exists: parse the gate values, call \`set_qa_gates\` with those flags, confirm with the user ("QA gates applied: <list>"), then remove the section from context.md.
|
|
57963
|
+
- If a \`## Pending Parallelization Config\` section also exists: parse the values and call \`save_plan\` again with \`execution_profile\` set to \`{ parallelization_enabled: <parsed>, max_concurrent_tasks: <parsed>, council_parallel: false, locked: true }\`. Then remove the section from context.md. If the plan already had \`execution_profile.locked: true\`, skip this step — the profile is already locked and immutable.
|
|
57992
57964
|
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
57993
57965
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
57994
57966
|
INLINE GATE SELECTION — no pending section found in context.md. You MUST ask now.
|
|
@@ -58343,6 +58315,19 @@ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{
|
|
|
58343
58315
|
- \`.swarm/evidence/{phase}/mutation-gate.json\` exists with verdict 'pass' or 'warn' (written by YOU via the \`write_mutation_evidence\` tool after step 5.56) — ONLY required when \`mutation_test\` is enabled in the QA gate profile
|
|
58344
58316
|
If any required file is missing, run the missing gate first. Turbo mode skips all gates automatically.
|
|
58345
58317
|
NOTE: Steps 5.5, 5.55, and 5.56 are enforced by runtime hooks. If \`hallucination_guard\` is enabled and you skip the critic_hallucination_verifier delegation (or fail to call \`write_hallucination_evidence\`), phase_complete will be BLOCKED by the plugin. Similarly, if \`mutation_test\` is enabled and you skip step 5.56 (or fail to call \`write_mutation_evidence\`), phase_complete will be BLOCKED. These are not suggestions — they are hard enforcement mechanisms.
|
|
58318
|
+
5.7. **Final Council (conditional on QA gate — last phase only)**: Check whether \`final_council\` is enabled in the effective QA gate profile (visible via \`get_qa_gate_profile\`). If disabled, skip silently and proceed to step 6.
|
|
58319
|
+
If enabled AND this is the LAST phase in the plan (all other phases have status 'complete' and no more phases remain):
|
|
58320
|
+
1. Verify \`council.general.enabled: true\` in plugin config. If not enabled, warn the user: "final_council gate is enabled but General Council is not configured. Skipping final council." Then proceed to step 6. Check that \`convene_general_council\` is available in your tool list. If the tool is unavailable (filtered by config), warn the user and skip.
|
|
58321
|
+
2. Run 1-3 targeted \`web_search\` queries relevant to the project domain.
|
|
58322
|
+
3. Compile a RESEARCH CONTEXT block from search results.
|
|
58323
|
+
4. Dispatch \`{{AGENT_PREFIX}}council_generalist\`, \`{{AGENT_PREFIX}}council_skeptic\`, and \`{{AGENT_PREFIX}}council_domain_expert\` in PARALLEL. Pass: the full body of work (all phase summaries, all evidence artifacts), the RESEARCH CONTEXT block, round number 1. Instruction: "Review the entire body of work holistically. Identify cross-cutting issues, architectural coherence, and overall quality."
|
|
58324
|
+
5. Collect all three JSON responses.
|
|
58325
|
+
6. Call \`convene_general_council\` with mode: 'general', the project summary as question, and the collected round1Responses.
|
|
58326
|
+
7. If disagreements exist, re-dispute as in MODE: COUNCIL step 5-6.
|
|
58327
|
+
8. Present the final synthesis to the user as a project-close summary.
|
|
58328
|
+
9. Write the final council result to \`.swarm/evidence/final-council.json\`.
|
|
58329
|
+
10. Do NOT call \`/swarm close\` until the final council completes (if enabled). The evidence file \`.swarm/evidence/final-council.json\` must exist with an APPROVED verdict before \`/swarm close\` is permitted when final_council is enabled.
|
|
58330
|
+
If enabled but NOT the last phase, skip silently — final council only runs once, after all phases.
|
|
58346
58331
|
6. Summarize to user
|
|
58347
58332
|
7. Ask: "Ready for Phase [N+1]?"
|
|
58348
58333
|
|
|
@@ -65662,7 +65647,7 @@ var init_curator_drift = __esm(() => {
|
|
|
65662
65647
|
init_package();
|
|
65663
65648
|
init_agents2();
|
|
65664
65649
|
init_critic();
|
|
65665
|
-
import * as
|
|
65650
|
+
import * as path116 from "node:path";
|
|
65666
65651
|
|
|
65667
65652
|
// src/background/index.ts
|
|
65668
65653
|
init_event_bus();
|
|
@@ -78855,7 +78840,7 @@ var diff = createSwarmTool({
|
|
|
78855
78840
|
encoding: "utf-8",
|
|
78856
78841
|
timeout: 3000,
|
|
78857
78842
|
cwd: directory,
|
|
78858
|
-
stdio: "pipe"
|
|
78843
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78859
78844
|
});
|
|
78860
78845
|
return true;
|
|
78861
78846
|
} catch (e) {
|
|
@@ -78869,9 +78854,9 @@ var diff = createSwarmTool({
|
|
|
78869
78854
|
}, getContentFromRef = function(refPath) {
|
|
78870
78855
|
return child_process7.execFileSync("git", ["show", refPath], {
|
|
78871
78856
|
encoding: "utf-8",
|
|
78872
|
-
timeout:
|
|
78857
|
+
timeout: 15000,
|
|
78873
78858
|
cwd: directory,
|
|
78874
|
-
stdio: "pipe"
|
|
78859
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78875
78860
|
});
|
|
78876
78861
|
};
|
|
78877
78862
|
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
@@ -78922,13 +78907,15 @@ var diff = createSwarmTool({
|
|
|
78922
78907
|
encoding: "utf-8",
|
|
78923
78908
|
timeout: DIFF_TIMEOUT_MS,
|
|
78924
78909
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
78925
|
-
cwd: directory
|
|
78910
|
+
cwd: directory,
|
|
78911
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78926
78912
|
});
|
|
78927
78913
|
const fullDiffOutput = child_process7.execFileSync("git", fullDiffArgs, {
|
|
78928
78914
|
encoding: "utf-8",
|
|
78929
78915
|
timeout: DIFF_TIMEOUT_MS,
|
|
78930
78916
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
78931
|
-
cwd: directory
|
|
78917
|
+
cwd: directory,
|
|
78918
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78932
78919
|
});
|
|
78933
78920
|
const files = [];
|
|
78934
78921
|
const numstatLines = numstatOutput.split(`
|
|
@@ -79697,9 +79684,6 @@ function summarizePlan(plan) {
|
|
|
79697
79684
|
}))
|
|
79698
79685
|
};
|
|
79699
79686
|
}
|
|
79700
|
-
function derivePlanId2(plan) {
|
|
79701
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
79702
|
-
}
|
|
79703
79687
|
async function executeGetApprovedPlan(args2, directory) {
|
|
79704
79688
|
const currentPlan = await loadPlanJsonOnly(directory);
|
|
79705
79689
|
if (!currentPlan) {
|
|
@@ -79718,7 +79702,7 @@ async function executeGetApprovedPlan(args2, directory) {
|
|
|
79718
79702
|
reason: "no_approved_snapshot"
|
|
79719
79703
|
};
|
|
79720
79704
|
}
|
|
79721
|
-
const expectedPlanId =
|
|
79705
|
+
const expectedPlanId = derivePlanId(currentPlan);
|
|
79722
79706
|
const profile = getProfile(directory, expectedPlanId);
|
|
79723
79707
|
const qaProfileHash = profile ? computeProfileHash(profile) : null;
|
|
79724
79708
|
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
@@ -79777,9 +79761,6 @@ var get_approved_plan = createSwarmTool({
|
|
|
79777
79761
|
init_qa_gate_profile();
|
|
79778
79762
|
init_manager();
|
|
79779
79763
|
init_create_tool();
|
|
79780
|
-
function derivePlanId3(plan) {
|
|
79781
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
79782
|
-
}
|
|
79783
79764
|
async function executeGetQaGateProfile(_args, directory) {
|
|
79784
79765
|
const plan = await loadPlanJsonOnly(directory);
|
|
79785
79766
|
if (!plan) {
|
|
@@ -79788,7 +79769,7 @@ async function executeGetQaGateProfile(_args, directory) {
|
|
|
79788
79769
|
reason: "plan_json_unavailable"
|
|
79789
79770
|
};
|
|
79790
79771
|
}
|
|
79791
|
-
const planId =
|
|
79772
|
+
const planId = derivePlanId(plan);
|
|
79792
79773
|
const profile = getProfile(directory, planId);
|
|
79793
79774
|
if (!profile) {
|
|
79794
79775
|
return {
|
|
@@ -80960,7 +80941,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
80960
80941
|
}, null, 2);
|
|
80961
80942
|
}
|
|
80962
80943
|
if (hasActiveTurboMode(sessionID)) {
|
|
80963
|
-
console.warn(`[phase_complete] Turbo mode active — skipping completion-verify, drift-verifier, hallucination-guard, mutation-gate, and
|
|
80944
|
+
console.warn(`[phase_complete] Turbo mode active — skipping completion-verify, drift-verifier, hallucination-guard, mutation-gate, phase-council, and final-council gates for phase ${phase}`);
|
|
80964
80945
|
} else {
|
|
80965
80946
|
try {
|
|
80966
80947
|
const completionResultRaw = await executeCompletionVerify({ phase }, dir);
|
|
@@ -80989,7 +80970,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
80989
80970
|
driftHasSpecMd = fs69.existsSync(specMdPath);
|
|
80990
80971
|
const gatePlan = await loadPlan(dir);
|
|
80991
80972
|
if (gatePlan) {
|
|
80992
|
-
const gatePlanId =
|
|
80973
|
+
const gatePlanId = derivePlanId(gatePlan);
|
|
80993
80974
|
const gateProfile = getProfile(dir, gatePlanId);
|
|
80994
80975
|
if (gateProfile) {
|
|
80995
80976
|
const gateSession = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81118,7 +81099,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
81118
81099
|
try {
|
|
81119
81100
|
const plan = await loadPlan(dir);
|
|
81120
81101
|
if (plan) {
|
|
81121
|
-
const planId =
|
|
81102
|
+
const planId = derivePlanId(plan);
|
|
81122
81103
|
const profile = getProfile(dir, planId);
|
|
81123
81104
|
if (profile) {
|
|
81124
81105
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81190,7 +81171,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
81190
81171
|
try {
|
|
81191
81172
|
const plan = await loadPlan(dir);
|
|
81192
81173
|
if (plan) {
|
|
81193
|
-
const planId =
|
|
81174
|
+
const planId = derivePlanId(plan);
|
|
81194
81175
|
const profile = getProfile(dir, planId);
|
|
81195
81176
|
if (profile) {
|
|
81196
81177
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81263,7 +81244,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
81263
81244
|
try {
|
|
81264
81245
|
const plan = await loadPlan(dir);
|
|
81265
81246
|
if (plan) {
|
|
81266
|
-
const planId =
|
|
81247
|
+
const planId = derivePlanId(plan);
|
|
81267
81248
|
const profile = getProfile(dir, planId);
|
|
81268
81249
|
if (profile) {
|
|
81269
81250
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81464,6 +81445,127 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
81464
81445
|
}
|
|
81465
81446
|
}
|
|
81466
81447
|
}
|
|
81448
|
+
if (!hasActiveTurboMode(sessionID)) {
|
|
81449
|
+
let finalCouncilEnabled = false;
|
|
81450
|
+
try {
|
|
81451
|
+
const plan = await loadPlan(dir);
|
|
81452
|
+
if (plan) {
|
|
81453
|
+
const lastPhaseId = plan.phases[plan.phases.length - 1]?.id;
|
|
81454
|
+
if (lastPhaseId !== undefined && phase === lastPhaseId) {
|
|
81455
|
+
const planId = derivePlanId(plan);
|
|
81456
|
+
const profile = getProfile(dir, planId);
|
|
81457
|
+
if (profile) {
|
|
81458
|
+
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
81459
|
+
const overrides = session2?.qaGateSessionOverrides ?? {};
|
|
81460
|
+
const effective = getEffectiveGates(profile, overrides);
|
|
81461
|
+
if (effective.final_council === true) {
|
|
81462
|
+
finalCouncilEnabled = true;
|
|
81463
|
+
const fcPath = path90.join(dir, ".swarm", "evidence", "final-council.json");
|
|
81464
|
+
let fcVerdictFound = false;
|
|
81465
|
+
let _fcVerdict;
|
|
81466
|
+
try {
|
|
81467
|
+
const fcContent = fs69.readFileSync(fcPath, "utf-8");
|
|
81468
|
+
const fcBundle = JSON.parse(fcContent);
|
|
81469
|
+
for (const entry of fcBundle.entries ?? []) {
|
|
81470
|
+
if (typeof entry.type === "string" && entry.type === "final-council" && typeof entry.verdict === "string") {
|
|
81471
|
+
fcVerdictFound = true;
|
|
81472
|
+
_fcVerdict = entry.verdict;
|
|
81473
|
+
if (plan) {
|
|
81474
|
+
const currentPlanId = derivePlanId(plan);
|
|
81475
|
+
if (entry.plan_id && entry.plan_id !== currentPlanId) {
|
|
81476
|
+
return JSON.stringify({
|
|
81477
|
+
success: false,
|
|
81478
|
+
phase,
|
|
81479
|
+
status: "blocked",
|
|
81480
|
+
reason: "final_council_plan_mismatch",
|
|
81481
|
+
message: `Final council evidence belongs to a different plan (evidence: ${entry.plan_id}, current: ${currentPlanId}). Re-run the final council.`,
|
|
81482
|
+
agentsDispatched,
|
|
81483
|
+
agentsMissing: [],
|
|
81484
|
+
warnings: []
|
|
81485
|
+
}, null, 2);
|
|
81486
|
+
}
|
|
81487
|
+
if (!entry.plan_id) {
|
|
81488
|
+
return JSON.stringify({
|
|
81489
|
+
success: false,
|
|
81490
|
+
phase,
|
|
81491
|
+
status: "blocked",
|
|
81492
|
+
reason: "FINAL_COUNCIL_PLAN_ID_REQUIRED",
|
|
81493
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing plan_id binding. Re-run the final council to generate evidence with plan identity.`,
|
|
81494
|
+
agentsDispatched,
|
|
81495
|
+
agentsMissing: [],
|
|
81496
|
+
warnings: []
|
|
81497
|
+
}, null, 2);
|
|
81498
|
+
}
|
|
81499
|
+
}
|
|
81500
|
+
if (entry.verdict === "rejected" || entry.verdict === "REJECTED") {
|
|
81501
|
+
return JSON.stringify({
|
|
81502
|
+
success: false,
|
|
81503
|
+
phase,
|
|
81504
|
+
status: "blocked",
|
|
81505
|
+
reason: "FINAL_COUNCIL_REJECTED",
|
|
81506
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council returned verdict 'REJECTED'. Address the required fixes before completing the project.`,
|
|
81507
|
+
agentsDispatched,
|
|
81508
|
+
agentsMissing: [],
|
|
81509
|
+
warnings: []
|
|
81510
|
+
}, null, 2);
|
|
81511
|
+
}
|
|
81512
|
+
if (entry.verdict !== "approved" && entry.verdict !== "APPROVED") {
|
|
81513
|
+
return JSON.stringify({
|
|
81514
|
+
success: false,
|
|
81515
|
+
phase,
|
|
81516
|
+
status: "blocked",
|
|
81517
|
+
reason: "FINAL_COUNCIL_INVALID_VERDICT",
|
|
81518
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council evidence contains unrecognized verdict '${entry.verdict}'. Expected 'approved'.`,
|
|
81519
|
+
agentsDispatched,
|
|
81520
|
+
agentsMissing: [],
|
|
81521
|
+
warnings: []
|
|
81522
|
+
}, null, 2);
|
|
81523
|
+
}
|
|
81524
|
+
}
|
|
81525
|
+
}
|
|
81526
|
+
} catch (readErr) {
|
|
81527
|
+
if (readErr.code !== "ENOENT") {
|
|
81528
|
+
safeWarn(`[phase_complete] Final council evidence unreadable:`, readErr);
|
|
81529
|
+
}
|
|
81530
|
+
fcVerdictFound = false;
|
|
81531
|
+
}
|
|
81532
|
+
if (!fcVerdictFound) {
|
|
81533
|
+
return JSON.stringify({
|
|
81534
|
+
success: false,
|
|
81535
|
+
phase,
|
|
81536
|
+
status: "blocked",
|
|
81537
|
+
reason: "FINAL_COUNCIL_REQUIRED",
|
|
81538
|
+
final_council_required: true,
|
|
81539
|
+
message: `Phase ${phase} (last phase) cannot be completed: final_council is enabled and final council evidence not found at .swarm/evidence/final-council.json. Convene a final holistic council (use convene_general_council with mode 'general') and call write_final_council_evidence to persist the verdict before completing the project.`,
|
|
81540
|
+
agentsDispatched,
|
|
81541
|
+
agentsMissing: [],
|
|
81542
|
+
warnings: [
|
|
81543
|
+
`Final council required — convene a holistic project review using convene_general_council, then call write_final_council_evidence to persist the verdict.`
|
|
81544
|
+
]
|
|
81545
|
+
}, null, 2);
|
|
81546
|
+
}
|
|
81547
|
+
}
|
|
81548
|
+
}
|
|
81549
|
+
}
|
|
81550
|
+
}
|
|
81551
|
+
} catch (fcError) {
|
|
81552
|
+
if (finalCouncilEnabled) {
|
|
81553
|
+
warnings.push(`FINAL_COUNCIL_ERROR: ${String(fcError)}`);
|
|
81554
|
+
return JSON.stringify({
|
|
81555
|
+
success: false,
|
|
81556
|
+
phase,
|
|
81557
|
+
status: "blocked",
|
|
81558
|
+
reason: "FINAL_COUNCIL_ERROR",
|
|
81559
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council gate encountered an error. Error: ${String(fcError)}`,
|
|
81560
|
+
agentsDispatched,
|
|
81561
|
+
agentsMissing: [],
|
|
81562
|
+
warnings: [`FINAL_COUNCIL_ERROR: ${String(fcError)}`]
|
|
81563
|
+
}, null, 2);
|
|
81564
|
+
} else {
|
|
81565
|
+
safeWarn(`[phase_complete] Final council gate error (non-blocking):`, fcError);
|
|
81566
|
+
}
|
|
81567
|
+
}
|
|
81568
|
+
}
|
|
81467
81569
|
let knowledgeConfig;
|
|
81468
81570
|
try {
|
|
81469
81571
|
knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
|
|
@@ -86800,7 +86902,10 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
86800
86902
|
} catch {}
|
|
86801
86903
|
const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
|
|
86802
86904
|
if (!hasPendingSection) {
|
|
86803
|
-
const candidatePlanId =
|
|
86905
|
+
const candidatePlanId = derivePlanId({
|
|
86906
|
+
swarm: args2.swarm_id,
|
|
86907
|
+
title: args2.title
|
|
86908
|
+
});
|
|
86804
86909
|
let existingProfile = null;
|
|
86805
86910
|
try {
|
|
86806
86911
|
existingProfile = getProfile(targetWorkspace, candidatePlanId);
|
|
@@ -86921,7 +87026,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
86921
87026
|
await takeSnapshotEvent(dir, savedPlan).catch(() => {});
|
|
86922
87027
|
}
|
|
86923
87028
|
if (resolvedProfile !== undefined && savedPlan) {
|
|
86924
|
-
const planId =
|
|
87029
|
+
const planId = derivePlanId(plan);
|
|
86925
87030
|
const planHashAfter = computePlanHash(savedPlan);
|
|
86926
87031
|
const profileChanged = JSON.stringify(resolvedProfile) !== JSON.stringify(preservedExecutionProfile);
|
|
86927
87032
|
if (profileChanged) {
|
|
@@ -88803,9 +88908,6 @@ init_zod();
|
|
|
88803
88908
|
init_qa_gate_profile();
|
|
88804
88909
|
init_manager();
|
|
88805
88910
|
init_create_tool();
|
|
88806
|
-
function derivePlanId4(plan) {
|
|
88807
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
88808
|
-
}
|
|
88809
88911
|
async function executeSetQaGates(args2, directory) {
|
|
88810
88912
|
const plan = await loadPlanJsonOnly(directory);
|
|
88811
88913
|
if (!plan) {
|
|
@@ -88815,7 +88917,7 @@ async function executeSetQaGates(args2, directory) {
|
|
|
88815
88917
|
message: "Cannot configure QA gates: plan.json is missing or invalid. " + "Create a plan first (e.g. via /swarm specify or save_plan)."
|
|
88816
88918
|
};
|
|
88817
88919
|
}
|
|
88818
|
-
const planId =
|
|
88920
|
+
const planId = derivePlanId(plan);
|
|
88819
88921
|
getOrCreateProfile(directory, planId, args2.project_type);
|
|
88820
88922
|
const partial3 = {};
|
|
88821
88923
|
for (const key of [
|
|
@@ -88828,7 +88930,8 @@ async function executeSetQaGates(args2, directory) {
|
|
|
88828
88930
|
"sast_enabled",
|
|
88829
88931
|
"mutation_test",
|
|
88830
88932
|
"council_general_review",
|
|
88831
|
-
"drift_check"
|
|
88933
|
+
"drift_check",
|
|
88934
|
+
"final_council"
|
|
88832
88935
|
]) {
|
|
88833
88936
|
if (args2[key] !== undefined)
|
|
88834
88937
|
partial3[key] = args2[key];
|
|
@@ -88876,6 +88979,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
88876
88979
|
mutation_test: exports_external.boolean().optional().describe("Enable the mutation-testing gate (default: off). Requires mutation " + "tests to achieve a passing kill rate before phase completion; " + "WARN verdict allows advancement, FAIL blocks."),
|
|
88877
88980
|
council_general_review: exports_external.boolean().optional().describe("Enable the council_general_review gate (default: off). When on, " + "MODE: SPECIFY runs convene_general_council on the draft spec " + "before the critic-gate, folding multi-model deliberation into " + "the spec. Requires council.general.enabled and a search API key."),
|
|
88878
88981
|
drift_check: exports_external.boolean().optional().describe("Enable drift verification gate (default: on). Blocks phase_complete " + "until drift-verifier.json has an approved verdict. When disabled, " + "drift verification is skipped entirely."),
|
|
88982
|
+
final_council: exports_external.boolean().optional().describe("Enable the final_council gate (default: off). When on, " + "after all phases complete the architect runs a holistic " + "general council review against the entire body of work. " + "Requires council.general.enabled: true in plugin config."),
|
|
88879
88983
|
project_type: exports_external.string().optional().describe('Project type label (e.g. "ts", "python"). Only applied when the profile is being created for the first time.')
|
|
88880
88984
|
},
|
|
88881
88985
|
execute: async (args2, directory) => {
|
|
@@ -90974,6 +91078,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
90974
91078
|
} catch {}
|
|
90975
91079
|
}
|
|
90976
91080
|
const resolvedDir = workingDirectory ?? process.cwd();
|
|
91081
|
+
let evidenceIncompleteReason = null;
|
|
90977
91082
|
try {
|
|
90978
91083
|
const evidence = readTaskEvidenceRaw(resolvedDir, taskId);
|
|
90979
91084
|
if (evidence === null) {} else if (evidence.required_gates && Array.isArray(evidence.required_gates) && evidence.gates) {
|
|
@@ -90982,11 +91087,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
90982
91087
|
return { blocked: false, reason: "" };
|
|
90983
91088
|
}
|
|
90984
91089
|
const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
|
|
90985
|
-
|
|
90986
|
-
return {
|
|
90987
|
-
blocked: true,
|
|
90988
|
-
reason: `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`
|
|
90989
|
-
};
|
|
91090
|
+
evidenceIncompleteReason = `Task ${taskId} is missing required gates: [${missingGates.join(", ")}]. ` + `Required: [${evidence.required_gates.join(", ")}]. ` + `Completed: [${Object.keys(evidence.gates).join(", ")}]. ` + `Delegate the missing gate agents before marking task as completed.`;
|
|
90990
91091
|
}
|
|
90991
91092
|
} catch (error93) {
|
|
90992
91093
|
console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
|
|
@@ -91076,23 +91177,18 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
91076
91177
|
}
|
|
91077
91178
|
}
|
|
91078
91179
|
const currentStateStr = stateEntries.length > 0 ? stateEntries.join(", ") : "no active sessions";
|
|
91079
|
-
|
|
91180
|
+
const finalReason = evidenceIncompleteReason ?? `Task ${taskId} has not passed QA gates. Current state by session: [${currentStateStr}]. Missing required state: tests_run or complete in at least one valid session. Do not write directly to plan files — use update_task_status after running the reviewer and test_engineer agents.`;
|
|
91181
|
+
telemetry.gateFailed("", "qa_gate", taskId, evidenceIncompleteReason ? `Missing gates: evidence incomplete` : `Missing state: tests_run or complete`);
|
|
91080
91182
|
return {
|
|
91081
91183
|
blocked: true,
|
|
91082
|
-
reason:
|
|
91184
|
+
reason: finalReason
|
|
91083
91185
|
};
|
|
91084
91186
|
} catch {
|
|
91085
91187
|
return { blocked: false, reason: "" };
|
|
91086
91188
|
}
|
|
91087
91189
|
}
|
|
91088
91190
|
async function checkReviewerGateWithScope(taskId, workingDirectory) {
|
|
91089
|
-
|
|
91090
|
-
if (workingDirectory) {
|
|
91091
|
-
try {
|
|
91092
|
-
const cfg = await loadPluginConfig(workingDirectory);
|
|
91093
|
-
stageBParallelEnabled = cfg.parallelization?.stageB?.parallel?.enabled === true;
|
|
91094
|
-
} catch {}
|
|
91095
|
-
}
|
|
91191
|
+
const stageBParallelEnabled = true;
|
|
91096
91192
|
const result = checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled);
|
|
91097
91193
|
const scopeWarning = await validateDiffScope(taskId, workingDirectory).catch(() => null);
|
|
91098
91194
|
if (!scopeWarning)
|
|
@@ -91599,9 +91695,6 @@ init_manager();
|
|
|
91599
91695
|
init_create_tool();
|
|
91600
91696
|
import fs89 from "node:fs";
|
|
91601
91697
|
import path112 from "node:path";
|
|
91602
|
-
function derivePlanId5(plan) {
|
|
91603
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
91604
|
-
}
|
|
91605
91698
|
function normalizeVerdict(verdict) {
|
|
91606
91699
|
switch (verdict) {
|
|
91607
91700
|
case "APPROVED":
|
|
@@ -91688,7 +91781,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
91688
91781
|
timestamp: snapshotEvent.timestamp
|
|
91689
91782
|
};
|
|
91690
91783
|
try {
|
|
91691
|
-
const planId =
|
|
91784
|
+
const planId = derivePlanId(currentPlan);
|
|
91692
91785
|
const locked = lockProfile(directory, planId, snapshotEvent.seq);
|
|
91693
91786
|
qaProfileLocked = {
|
|
91694
91787
|
plan_id: planId,
|
|
@@ -91755,9 +91848,10 @@ var write_drift_evidence = createSwarmTool({
|
|
|
91755
91848
|
}
|
|
91756
91849
|
}
|
|
91757
91850
|
});
|
|
91758
|
-
// src/tools/write-
|
|
91851
|
+
// src/tools/write-final-council-evidence.ts
|
|
91759
91852
|
init_zod();
|
|
91760
91853
|
init_utils2();
|
|
91854
|
+
init_manager();
|
|
91761
91855
|
init_create_tool();
|
|
91762
91856
|
import fs90 from "node:fs";
|
|
91763
91857
|
import path113 from "node:path";
|
|
@@ -91771,7 +91865,7 @@ function normalizeVerdict2(verdict) {
|
|
|
91771
91865
|
throw new Error(`Invalid verdict: must be 'APPROVED' or 'NEEDS_REVISION', got '${verdict}'`);
|
|
91772
91866
|
}
|
|
91773
91867
|
}
|
|
91774
|
-
async function
|
|
91868
|
+
async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
91775
91869
|
const phase = args2.phase;
|
|
91776
91870
|
if (!Number.isInteger(phase) || phase < 1) {
|
|
91777
91871
|
return JSON.stringify({
|
|
@@ -91797,18 +91891,21 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
91797
91891
|
}, null, 2);
|
|
91798
91892
|
}
|
|
91799
91893
|
const normalizedVerdict = normalizeVerdict2(args2.verdict);
|
|
91894
|
+
const plan = await loadPlan(directory);
|
|
91895
|
+
const planId = plan ? derivePlanId(plan) : "unknown";
|
|
91800
91896
|
const evidenceEntry = {
|
|
91801
|
-
type: "
|
|
91897
|
+
type: "final-council",
|
|
91898
|
+
phase,
|
|
91899
|
+
plan_id: planId,
|
|
91802
91900
|
verdict: normalizedVerdict,
|
|
91803
91901
|
summary: summary.trim(),
|
|
91804
|
-
timestamp: new Date().toISOString()
|
|
91805
|
-
findings: args2.findings
|
|
91902
|
+
timestamp: new Date().toISOString()
|
|
91806
91903
|
};
|
|
91807
91904
|
const evidenceContent = {
|
|
91808
91905
|
entries: [evidenceEntry]
|
|
91809
91906
|
};
|
|
91810
|
-
const filename = "
|
|
91811
|
-
const relativePath = path113.join("evidence",
|
|
91907
|
+
const filename = "final-council.json";
|
|
91908
|
+
const relativePath = path113.join("evidence", filename);
|
|
91812
91909
|
let validatedPath;
|
|
91813
91910
|
try {
|
|
91814
91911
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -91825,6 +91922,115 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
91825
91922
|
const tempPath = path113.join(evidenceDir, `.${filename}.tmp`);
|
|
91826
91923
|
await fs90.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
91827
91924
|
await fs90.promises.rename(tempPath, validatedPath);
|
|
91925
|
+
return JSON.stringify({
|
|
91926
|
+
success: true,
|
|
91927
|
+
phase,
|
|
91928
|
+
verdict: normalizedVerdict,
|
|
91929
|
+
message: `Final council evidence written to .swarm/evidence/final-council.json`
|
|
91930
|
+
}, null, 2);
|
|
91931
|
+
} catch (error93) {
|
|
91932
|
+
return JSON.stringify({
|
|
91933
|
+
success: false,
|
|
91934
|
+
phase,
|
|
91935
|
+
message: error93 instanceof Error ? error93.message : String(error93)
|
|
91936
|
+
}, null, 2);
|
|
91937
|
+
}
|
|
91938
|
+
}
|
|
91939
|
+
var write_final_council_evidence = createSwarmTool({
|
|
91940
|
+
description: "Write final council evidence for a completed project. Accepts phase, verdict (APPROVED/NEEDS_REVISION), summary, and writes structured evidence to .swarm/evidence/final-council.json. Normalizes verdict to lowercase. Use this after convening a final holistic council to persist the verdict.",
|
|
91941
|
+
args: {
|
|
91942
|
+
phase: exports_external.number().int().min(1).describe("The phase number for the final council verdict (e.g., 1, 2, 3)"),
|
|
91943
|
+
verdict: exports_external.enum(["APPROVED", "NEEDS_REVISION"]).describe("Verdict of the final council: 'APPROVED' or 'NEEDS_REVISION'"),
|
|
91944
|
+
summary: exports_external.string().describe("Human-readable summary of the final council verdict")
|
|
91945
|
+
},
|
|
91946
|
+
execute: async (args2, directory) => {
|
|
91947
|
+
const rawPhase = args2.phase !== undefined ? Number(args2.phase) : 0;
|
|
91948
|
+
try {
|
|
91949
|
+
const writeFinalCouncilEvidenceArgs = {
|
|
91950
|
+
phase: Number(args2.phase),
|
|
91951
|
+
verdict: String(args2.verdict),
|
|
91952
|
+
summary: String(args2.summary ?? "")
|
|
91953
|
+
};
|
|
91954
|
+
return await executeWriteFinalCouncilEvidence(writeFinalCouncilEvidenceArgs, directory);
|
|
91955
|
+
} catch (error93) {
|
|
91956
|
+
return JSON.stringify({
|
|
91957
|
+
success: false,
|
|
91958
|
+
phase: rawPhase,
|
|
91959
|
+
message: error93 instanceof Error ? error93.message : "Unknown error"
|
|
91960
|
+
}, null, 2);
|
|
91961
|
+
}
|
|
91962
|
+
}
|
|
91963
|
+
});
|
|
91964
|
+
// src/tools/write-hallucination-evidence.ts
|
|
91965
|
+
init_zod();
|
|
91966
|
+
init_utils2();
|
|
91967
|
+
init_create_tool();
|
|
91968
|
+
import fs91 from "node:fs";
|
|
91969
|
+
import path114 from "node:path";
|
|
91970
|
+
function normalizeVerdict3(verdict) {
|
|
91971
|
+
switch (verdict) {
|
|
91972
|
+
case "APPROVED":
|
|
91973
|
+
return "approved";
|
|
91974
|
+
case "NEEDS_REVISION":
|
|
91975
|
+
return "rejected";
|
|
91976
|
+
default:
|
|
91977
|
+
throw new Error(`Invalid verdict: must be 'APPROVED' or 'NEEDS_REVISION', got '${verdict}'`);
|
|
91978
|
+
}
|
|
91979
|
+
}
|
|
91980
|
+
async function executeWriteHallucinationEvidence(args2, directory) {
|
|
91981
|
+
const phase = args2.phase;
|
|
91982
|
+
if (!Number.isInteger(phase) || phase < 1) {
|
|
91983
|
+
return JSON.stringify({
|
|
91984
|
+
success: false,
|
|
91985
|
+
phase,
|
|
91986
|
+
message: "Invalid phase: must be a positive integer"
|
|
91987
|
+
}, null, 2);
|
|
91988
|
+
}
|
|
91989
|
+
const validVerdicts = ["APPROVED", "NEEDS_REVISION"];
|
|
91990
|
+
if (!validVerdicts.includes(args2.verdict)) {
|
|
91991
|
+
return JSON.stringify({
|
|
91992
|
+
success: false,
|
|
91993
|
+
phase,
|
|
91994
|
+
message: "Invalid verdict: must be 'APPROVED' or 'NEEDS_REVISION'"
|
|
91995
|
+
}, null, 2);
|
|
91996
|
+
}
|
|
91997
|
+
const summary = args2.summary;
|
|
91998
|
+
if (typeof summary !== "string" || summary.trim().length === 0) {
|
|
91999
|
+
return JSON.stringify({
|
|
92000
|
+
success: false,
|
|
92001
|
+
phase,
|
|
92002
|
+
message: "Invalid summary: must be a non-empty string"
|
|
92003
|
+
}, null, 2);
|
|
92004
|
+
}
|
|
92005
|
+
const normalizedVerdict = normalizeVerdict3(args2.verdict);
|
|
92006
|
+
const evidenceEntry = {
|
|
92007
|
+
type: "hallucination-verification",
|
|
92008
|
+
verdict: normalizedVerdict,
|
|
92009
|
+
summary: summary.trim(),
|
|
92010
|
+
timestamp: new Date().toISOString(),
|
|
92011
|
+
findings: args2.findings
|
|
92012
|
+
};
|
|
92013
|
+
const evidenceContent = {
|
|
92014
|
+
entries: [evidenceEntry]
|
|
92015
|
+
};
|
|
92016
|
+
const filename = "hallucination-guard.json";
|
|
92017
|
+
const relativePath = path114.join("evidence", String(phase), filename);
|
|
92018
|
+
let validatedPath;
|
|
92019
|
+
try {
|
|
92020
|
+
validatedPath = validateSwarmPath(directory, relativePath);
|
|
92021
|
+
} catch (error93) {
|
|
92022
|
+
return JSON.stringify({
|
|
92023
|
+
success: false,
|
|
92024
|
+
phase,
|
|
92025
|
+
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
92026
|
+
}, null, 2);
|
|
92027
|
+
}
|
|
92028
|
+
const evidenceDir = path114.dirname(validatedPath);
|
|
92029
|
+
try {
|
|
92030
|
+
await fs91.promises.mkdir(evidenceDir, { recursive: true });
|
|
92031
|
+
const tempPath = path114.join(evidenceDir, `.${filename}.tmp`);
|
|
92032
|
+
await fs91.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
92033
|
+
await fs91.promises.rename(tempPath, validatedPath);
|
|
91828
92034
|
return JSON.stringify({
|
|
91829
92035
|
success: true,
|
|
91830
92036
|
phase,
|
|
@@ -91870,9 +92076,9 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
91870
92076
|
init_zod();
|
|
91871
92077
|
init_utils2();
|
|
91872
92078
|
init_create_tool();
|
|
91873
|
-
import
|
|
91874
|
-
import
|
|
91875
|
-
function
|
|
92079
|
+
import fs92 from "node:fs";
|
|
92080
|
+
import path115 from "node:path";
|
|
92081
|
+
function normalizeVerdict4(verdict) {
|
|
91876
92082
|
switch (verdict) {
|
|
91877
92083
|
case "PASS":
|
|
91878
92084
|
return "pass";
|
|
@@ -91929,7 +92135,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91929
92135
|
message: "Invalid summary: must be a non-empty string"
|
|
91930
92136
|
}, null, 2);
|
|
91931
92137
|
}
|
|
91932
|
-
const normalizedVerdict =
|
|
92138
|
+
const normalizedVerdict = normalizeVerdict4(args2.verdict);
|
|
91933
92139
|
const evidenceEntry = {
|
|
91934
92140
|
type: "mutation-gate",
|
|
91935
92141
|
verdict: normalizedVerdict,
|
|
@@ -91945,7 +92151,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91945
92151
|
entries: [evidenceEntry]
|
|
91946
92152
|
};
|
|
91947
92153
|
const filename = "mutation-gate.json";
|
|
91948
|
-
const relativePath =
|
|
92154
|
+
const relativePath = path115.join("evidence", String(phase), filename);
|
|
91949
92155
|
let validatedPath;
|
|
91950
92156
|
try {
|
|
91951
92157
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -91956,12 +92162,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91956
92162
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
91957
92163
|
}, null, 2);
|
|
91958
92164
|
}
|
|
91959
|
-
const evidenceDir =
|
|
92165
|
+
const evidenceDir = path115.dirname(validatedPath);
|
|
91960
92166
|
try {
|
|
91961
|
-
await
|
|
91962
|
-
const tempPath =
|
|
91963
|
-
await
|
|
91964
|
-
await
|
|
92167
|
+
await fs92.promises.mkdir(evidenceDir, { recursive: true });
|
|
92168
|
+
const tempPath = path115.join(evidenceDir, `.${filename}.tmp`);
|
|
92169
|
+
await fs92.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
92170
|
+
await fs92.promises.rename(tempPath, validatedPath);
|
|
91965
92171
|
return JSON.stringify({
|
|
91966
92172
|
success: true,
|
|
91967
92173
|
phase,
|
|
@@ -92253,7 +92459,7 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
92253
92459
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
92254
92460
|
preflightTriggerManager = new PTM(automationConfig);
|
|
92255
92461
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
92256
|
-
const swarmDir =
|
|
92462
|
+
const swarmDir = path116.resolve(ctx.directory, ".swarm");
|
|
92257
92463
|
statusArtifact = new ASA(swarmDir);
|
|
92258
92464
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
92259
92465
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -92412,6 +92618,7 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
92412
92618
|
write_drift_evidence,
|
|
92413
92619
|
write_hallucination_evidence,
|
|
92414
92620
|
write_mutation_evidence,
|
|
92621
|
+
write_final_council_evidence,
|
|
92415
92622
|
declare_scope
|
|
92416
92623
|
},
|
|
92417
92624
|
config: async (opencodeConfig) => {
|