opencode-swarm 7.60.0 → 7.61.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/.opencode/skills/brainstorm/SKILL.md +28 -17
- package/.opencode/skills/council/SKILL.md +3 -2
- package/.opencode/skills/execute/SKILL.md +12 -0
- package/.opencode/skills/phase-wrap/SKILL.md +12 -3
- package/.opencode/skills/plan/SKILL.md +9 -7
- package/.opencode/skills/specify/SKILL.md +17 -41
- package/dist/cli/index.js +24 -9
- package/dist/commands/registry.d.ts +1 -1
- package/dist/council/types.d.ts +8 -2
- package/dist/db/qa-gate-profile.d.ts +8 -5
- package/dist/index.js +418 -183
- package/dist/state.d.ts +3 -3
- package/dist/tools/convene-council.d.ts +1 -0
- package/dist/tools/phase-complete/gates/phase-council-gate.d.ts +1 -1
- package/dist/tools/set-qa-gates.d.ts +1 -1
- package/dist/tools/submit-phase-council-verdicts.d.ts +1 -0
- package/dist/tools/update-task-status.d.ts +8 -5
- package/dist/tools/write-final-council-evidence.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ var package_default;
|
|
|
69
69
|
var init_package = __esm(() => {
|
|
70
70
|
package_default = {
|
|
71
71
|
name: "opencode-swarm",
|
|
72
|
-
version: "7.
|
|
72
|
+
version: "7.61.0",
|
|
73
73
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
74
74
|
main: "dist/index.js",
|
|
75
75
|
types: "dist/index.d.ts",
|
|
@@ -577,11 +577,11 @@ var init_tool_metadata = __esm(() => {
|
|
|
577
577
|
]
|
|
578
578
|
},
|
|
579
579
|
get_qa_gate_profile: {
|
|
580
|
-
description: "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,
|
|
580
|
+
description: "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, phase_council, drift_check, final_council), lock state, and profile hash. Read-only.",
|
|
581
581
|
agents: ["architect"]
|
|
582
582
|
},
|
|
583
583
|
set_qa_gates: {
|
|
584
|
-
description: "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,
|
|
584
|
+
description: "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, phase_council, drift_check, final_council.",
|
|
585
585
|
agents: ["architect"]
|
|
586
586
|
},
|
|
587
587
|
web_search: {
|
|
@@ -15906,7 +15906,7 @@ var init_schema = __esm(() => {
|
|
|
15906
15906
|
requireAllMembers: exports_external.boolean().default(false).describe("When true, submit_council_verdicts rejects if fewer than 5 member verdicts are provided. Equivalent to minimumMembers: 5."),
|
|
15907
15907
|
minimumMembers: exports_external.number().int().min(1).max(5).default(3).describe("Minimum distinct council member verdicts required for synthesis. Default 3. Set to 1 to disable quorum enforcement. requireAllMembers: true overrides this to 5 (stricter constraint wins)."),
|
|
15908
15908
|
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet."),
|
|
15909
|
-
phaseConcernsAllowComplete: exports_external.boolean().default(true).describe("When true, a phase-level council CONCERNS verdict does NOT block phase completion — the advisory notes are logged as warnings and the phase proceeds. When false, CONCERNS blocks like REJECT.
|
|
15909
|
+
phaseConcernsAllowComplete: exports_external.boolean().default(true).describe("When true, a phase-level council CONCERNS verdict with only MEDIUM/LOW findings does NOT block phase completion — the advisory notes are logged as warnings and the phase proceeds. When false, CONCERNS blocks like REJECT. Note: HIGH/CRITICAL findings from CONCERNS members are always promoted to requiredFixes and block at the tool level regardless of this setting. Default: true."),
|
|
15910
15910
|
general: GeneralCouncilConfigSchema.optional()
|
|
15911
15911
|
}).strict();
|
|
15912
15912
|
ParallelizationConfigSchema = exports_external.object({
|
|
@@ -21783,11 +21783,26 @@ import { createHash as createHash3 } from "node:crypto";
|
|
|
21783
21783
|
function rowToProfile(row) {
|
|
21784
21784
|
let parsed = {};
|
|
21785
21785
|
try {
|
|
21786
|
-
|
|
21786
|
+
const maybeGates = JSON.parse(row.gates);
|
|
21787
|
+
if (maybeGates && typeof maybeGates === "object" && !Array.isArray(maybeGates)) {
|
|
21788
|
+
parsed = maybeGates;
|
|
21789
|
+
}
|
|
21787
21790
|
} catch {
|
|
21788
21791
|
parsed = {};
|
|
21789
21792
|
}
|
|
21790
|
-
const
|
|
21793
|
+
const raw = parsed;
|
|
21794
|
+
if (raw.council_mode === true && raw.phase_council === undefined) {
|
|
21795
|
+
parsed.phase_council = true;
|
|
21796
|
+
parsed.council_mode = false;
|
|
21797
|
+
}
|
|
21798
|
+
const knownKeys = new Set(Object.keys(DEFAULT_QA_GATES));
|
|
21799
|
+
const filteredParsed = {};
|
|
21800
|
+
for (const key of Object.keys(parsed)) {
|
|
21801
|
+
if (knownKeys.has(key)) {
|
|
21802
|
+
filteredParsed[key] = parsed[key];
|
|
21803
|
+
}
|
|
21804
|
+
}
|
|
21805
|
+
const gates = { ...DEFAULT_QA_GATES, ...filteredParsed };
|
|
21791
21806
|
return {
|
|
21792
21807
|
id: row.id,
|
|
21793
21808
|
plan_id: row.plan_id,
|
|
@@ -21910,7 +21925,7 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
21910
21925
|
hallucination_guard: false,
|
|
21911
21926
|
sast_enabled: true,
|
|
21912
21927
|
mutation_test: false,
|
|
21913
|
-
|
|
21928
|
+
phase_council: false,
|
|
21914
21929
|
drift_check: true,
|
|
21915
21930
|
final_council: false
|
|
21916
21931
|
};
|
|
@@ -41939,7 +41954,8 @@ function createDelegationGateHook(config2, directory) {
|
|
|
41939
41954
|
if (!session)
|
|
41940
41955
|
return;
|
|
41941
41956
|
const normalized = normalizeToolName(input.tool);
|
|
41942
|
-
const
|
|
41957
|
+
const { qaGateSessionOverrides } = ensureAgentSession(input.sessionID);
|
|
41958
|
+
const councilActive = await isCouncilGateActive(directory, config2.council, qaGateSessionOverrides ?? {});
|
|
41943
41959
|
if (normalized === "update_task_status") {
|
|
41944
41960
|
const directArgs = input.args;
|
|
41945
41961
|
const storedArgs = getStoredInputArgs(input.callID);
|
|
@@ -42049,86 +42065,88 @@ function createDelegationGateHook(config2, directory) {
|
|
|
42049
42065
|
hasReviewer = true;
|
|
42050
42066
|
if (targetAgent === "test_engineer")
|
|
42051
42067
|
hasTestEngineer = true;
|
|
42052
|
-
|
|
42053
|
-
|
|
42054
|
-
if (
|
|
42055
|
-
|
|
42056
|
-
|
|
42057
|
-
|
|
42058
|
-
|
|
42059
|
-
|
|
42060
|
-
|
|
42061
|
-
|
|
42062
|
-
|
|
42063
|
-
const eligibleState = state;
|
|
42064
|
-
recordStageBCompletion(session, taskId, targetAgent);
|
|
42065
|
-
if (hasBothStageBCompletions(session, taskId)) {
|
|
42066
|
-
try {
|
|
42067
|
-
if (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed") {
|
|
42068
|
-
advanceTaskState(session, taskId, "reviewer_run", {
|
|
42069
|
-
telemetrySessionId: input.sessionID
|
|
42070
|
-
});
|
|
42071
|
-
}
|
|
42072
|
-
advanceTaskState(session, taskId, "tests_run", {
|
|
42073
|
-
telemetrySessionId: input.sessionID
|
|
42074
|
-
});
|
|
42075
|
-
} catch (err2) {
|
|
42076
|
-
warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42077
|
-
}
|
|
42078
|
-
} else {
|
|
42079
|
-
try {
|
|
42080
|
-
if (targetAgent === "reviewer" && (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed")) {
|
|
42081
|
-
advanceTaskState(session, taskId, "reviewer_run", {
|
|
42082
|
-
telemetrySessionId: input.sessionID
|
|
42083
|
-
});
|
|
42084
|
-
} else if (targetAgent === "test_engineer" && eligibleState === "reviewer_run") {
|
|
42085
|
-
advanceTaskState(session, taskId, "tests_run", {
|
|
42086
|
-
telemetrySessionId: input.sessionID
|
|
42087
|
-
});
|
|
42088
|
-
}
|
|
42089
|
-
} catch (err2) {
|
|
42090
|
-
warn(`[delegation-gate] toolAfter stage-b-parallel intermediate: could not advance ${taskId} (${eligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42091
|
-
}
|
|
42092
|
-
}
|
|
42093
|
-
}
|
|
42094
|
-
const seedTaskId = getSeedTaskId(session);
|
|
42095
|
-
if (seedTaskId) {
|
|
42096
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42097
|
-
if (otherSession === session)
|
|
42098
|
-
continue;
|
|
42099
|
-
if (!otherSession.taskWorkflowStates)
|
|
42100
|
-
continue;
|
|
42101
|
-
if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42102
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
42103
|
-
}
|
|
42104
|
-
const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
|
|
42105
|
-
if (!seedState || !stageBEligibleStates.includes(seedState)) {
|
|
42068
|
+
if (!councilActive) {
|
|
42069
|
+
const stageBParallelEnabled = true;
|
|
42070
|
+
if (stageBParallelEnabled) {
|
|
42071
|
+
if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
|
|
42072
|
+
const stageBEligibleStates = [
|
|
42073
|
+
"coder_delegated",
|
|
42074
|
+
"pre_check_passed",
|
|
42075
|
+
"reviewer_run"
|
|
42076
|
+
];
|
|
42077
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
42078
|
+
if (!stageBEligibleStates.includes(state))
|
|
42106
42079
|
continue;
|
|
42107
|
-
|
|
42108
|
-
|
|
42109
|
-
|
|
42110
|
-
if (hasBothStageBCompletions(otherSession, seedTaskId)) {
|
|
42080
|
+
const eligibleState = state;
|
|
42081
|
+
recordStageBCompletion(session, taskId, targetAgent);
|
|
42082
|
+
if (hasBothStageBCompletions(session, taskId)) {
|
|
42111
42083
|
try {
|
|
42112
|
-
if (
|
|
42113
|
-
advanceTaskState(
|
|
42084
|
+
if (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed") {
|
|
42085
|
+
advanceTaskState(session, taskId, "reviewer_run", {
|
|
42086
|
+
telemetrySessionId: input.sessionID
|
|
42087
|
+
});
|
|
42114
42088
|
}
|
|
42115
|
-
advanceTaskState(
|
|
42116
|
-
|
|
42089
|
+
advanceTaskState(session, taskId, "tests_run", {
|
|
42090
|
+
telemetrySessionId: input.sessionID
|
|
42117
42091
|
});
|
|
42118
42092
|
} catch (err2) {
|
|
42119
|
-
warn(`[delegation-gate] toolAfter
|
|
42093
|
+
warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42120
42094
|
}
|
|
42121
42095
|
} else {
|
|
42122
42096
|
try {
|
|
42123
|
-
if (targetAgent === "reviewer" && (
|
|
42124
|
-
advanceTaskState(
|
|
42125
|
-
|
|
42097
|
+
if (targetAgent === "reviewer" && (eligibleState === "coder_delegated" || eligibleState === "pre_check_passed")) {
|
|
42098
|
+
advanceTaskState(session, taskId, "reviewer_run", {
|
|
42099
|
+
telemetrySessionId: input.sessionID
|
|
42100
|
+
});
|
|
42101
|
+
} else if (targetAgent === "test_engineer" && eligibleState === "reviewer_run") {
|
|
42102
|
+
advanceTaskState(session, taskId, "tests_run", {
|
|
42103
|
+
telemetrySessionId: input.sessionID
|
|
42104
|
+
});
|
|
42105
|
+
}
|
|
42106
|
+
} catch (err2) {
|
|
42107
|
+
warn(`[delegation-gate] toolAfter stage-b-parallel intermediate: could not advance ${taskId} (${eligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42108
|
+
}
|
|
42109
|
+
}
|
|
42110
|
+
}
|
|
42111
|
+
const seedTaskId = getSeedTaskId(session);
|
|
42112
|
+
if (seedTaskId) {
|
|
42113
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42114
|
+
if (otherSession === session)
|
|
42115
|
+
continue;
|
|
42116
|
+
if (!otherSession.taskWorkflowStates)
|
|
42117
|
+
continue;
|
|
42118
|
+
if (!otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42119
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
42120
|
+
}
|
|
42121
|
+
const seedState = otherSession.taskWorkflowStates.get(seedTaskId);
|
|
42122
|
+
if (!seedState || !stageBEligibleStates.includes(seedState)) {
|
|
42123
|
+
continue;
|
|
42124
|
+
}
|
|
42125
|
+
const seedEligibleState = seedState;
|
|
42126
|
+
recordStageBCompletion(otherSession, seedTaskId, targetAgent);
|
|
42127
|
+
if (hasBothStageBCompletions(otherSession, seedTaskId)) {
|
|
42128
|
+
try {
|
|
42129
|
+
if (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed") {
|
|
42130
|
+
advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
|
|
42131
|
+
}
|
|
42126
42132
|
advanceTaskState(otherSession, seedTaskId, "tests_run", {
|
|
42127
42133
|
emitTelemetry: false
|
|
42128
42134
|
});
|
|
42135
|
+
} catch (err2) {
|
|
42136
|
+
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42137
|
+
}
|
|
42138
|
+
} else {
|
|
42139
|
+
try {
|
|
42140
|
+
if (targetAgent === "reviewer" && (seedEligibleState === "coder_delegated" || seedEligibleState === "pre_check_passed")) {
|
|
42141
|
+
advanceTaskState(otherSession, seedTaskId, "reviewer_run", { emitTelemetry: false });
|
|
42142
|
+
} else if (targetAgent === "test_engineer" && seedEligibleState === "reviewer_run") {
|
|
42143
|
+
advanceTaskState(otherSession, seedTaskId, "tests_run", {
|
|
42144
|
+
emitTelemetry: false
|
|
42145
|
+
});
|
|
42146
|
+
}
|
|
42147
|
+
} catch (err2) {
|
|
42148
|
+
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel intermediate: could not advance ${seedTaskId} (${seedEligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42129
42149
|
}
|
|
42130
|
-
} catch (err2) {
|
|
42131
|
-
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel intermediate: could not advance ${seedTaskId} (${seedEligibleState}) after ${targetAgent}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42132
42150
|
}
|
|
42133
42151
|
}
|
|
42134
42152
|
}
|
|
@@ -42167,7 +42185,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
42167
42185
|
if (storedArgs !== undefined) {
|
|
42168
42186
|
deleteStoredInputArgs(input.callID);
|
|
42169
42187
|
}
|
|
42170
|
-
if (!subagentType || !hasReviewer) {
|
|
42188
|
+
if (!subagentType || !hasReviewer || councilActive) {
|
|
42171
42189
|
const delegationChain = swarmState.delegationChains.get(input.sessionID);
|
|
42172
42190
|
if (delegationChain && delegationChain.length > 0) {
|
|
42173
42191
|
let lastCoderIndex = -1;
|
|
@@ -42191,69 +42209,71 @@ function createDelegationGateHook(config2, directory) {
|
|
|
42191
42209
|
session.qaSkipCount = 0;
|
|
42192
42210
|
session.qaSkipTaskIds = [];
|
|
42193
42211
|
}
|
|
42194
|
-
if (
|
|
42195
|
-
|
|
42196
|
-
|
|
42197
|
-
try {
|
|
42198
|
-
advanceTaskState(session, taskId, "reviewer_run");
|
|
42199
|
-
} catch (err2) {
|
|
42200
|
-
warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42201
|
-
}
|
|
42202
|
-
}
|
|
42203
|
-
}
|
|
42204
|
-
}
|
|
42205
|
-
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
|
|
42206
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
42207
|
-
if (state === "reviewer_run") {
|
|
42208
|
-
try {
|
|
42209
|
-
advanceTaskState(session, taskId, "tests_run");
|
|
42210
|
-
} catch (err2) {
|
|
42211
|
-
warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42212
|
-
}
|
|
42213
|
-
}
|
|
42214
|
-
}
|
|
42215
|
-
}
|
|
42216
|
-
if (lastCoderIndex !== -1 && hasReviewer) {
|
|
42217
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42218
|
-
if (otherSession === session)
|
|
42219
|
-
continue;
|
|
42220
|
-
if (!otherSession.taskWorkflowStates)
|
|
42221
|
-
continue;
|
|
42222
|
-
const seedTaskId = getSeedTaskId(session);
|
|
42223
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42224
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
42225
|
-
}
|
|
42226
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
42212
|
+
if (!councilActive) {
|
|
42213
|
+
if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
|
|
42214
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
42227
42215
|
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
42228
42216
|
try {
|
|
42229
|
-
advanceTaskState(
|
|
42230
|
-
emitTelemetry: false
|
|
42231
|
-
});
|
|
42217
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
42232
42218
|
} catch (err2) {
|
|
42233
|
-
warn(`[delegation-gate] fallback
|
|
42219
|
+
warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42234
42220
|
}
|
|
42235
42221
|
}
|
|
42236
42222
|
}
|
|
42237
42223
|
}
|
|
42238
|
-
|
|
42239
|
-
|
|
42240
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42241
|
-
if (otherSession === session)
|
|
42242
|
-
continue;
|
|
42243
|
-
if (!otherSession.taskWorkflowStates)
|
|
42244
|
-
continue;
|
|
42245
|
-
const seedTaskId = getSeedTaskId(session);
|
|
42246
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42247
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
42248
|
-
}
|
|
42249
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
42224
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
|
|
42225
|
+
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
42250
42226
|
if (state === "reviewer_run") {
|
|
42251
42227
|
try {
|
|
42252
|
-
advanceTaskState(
|
|
42253
|
-
emitTelemetry: false
|
|
42254
|
-
});
|
|
42228
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
42255
42229
|
} catch (err2) {
|
|
42256
|
-
warn(`[delegation-gate] fallback
|
|
42230
|
+
warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42231
|
+
}
|
|
42232
|
+
}
|
|
42233
|
+
}
|
|
42234
|
+
}
|
|
42235
|
+
if (lastCoderIndex !== -1 && hasReviewer) {
|
|
42236
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42237
|
+
if (otherSession === session)
|
|
42238
|
+
continue;
|
|
42239
|
+
if (!otherSession.taskWorkflowStates)
|
|
42240
|
+
continue;
|
|
42241
|
+
const seedTaskId = getSeedTaskId(session);
|
|
42242
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42243
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
42244
|
+
}
|
|
42245
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
42246
|
+
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
42247
|
+
try {
|
|
42248
|
+
advanceTaskState(otherSession, taskId, "reviewer_run", {
|
|
42249
|
+
emitTelemetry: false
|
|
42250
|
+
});
|
|
42251
|
+
} catch (err2) {
|
|
42252
|
+
warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42253
|
+
}
|
|
42254
|
+
}
|
|
42255
|
+
}
|
|
42256
|
+
}
|
|
42257
|
+
}
|
|
42258
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
|
|
42259
|
+
for (const [, otherSession] of swarmState.agentSessions) {
|
|
42260
|
+
if (otherSession === session)
|
|
42261
|
+
continue;
|
|
42262
|
+
if (!otherSession.taskWorkflowStates)
|
|
42263
|
+
continue;
|
|
42264
|
+
const seedTaskId = getSeedTaskId(session);
|
|
42265
|
+
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
42266
|
+
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
42267
|
+
}
|
|
42268
|
+
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
42269
|
+
if (state === "reviewer_run") {
|
|
42270
|
+
try {
|
|
42271
|
+
advanceTaskState(otherSession, taskId, "tests_run", {
|
|
42272
|
+
emitTelemetry: false
|
|
42273
|
+
});
|
|
42274
|
+
} catch (err2) {
|
|
42275
|
+
warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42276
|
+
}
|
|
42257
42277
|
}
|
|
42258
42278
|
}
|
|
42259
42279
|
}
|
|
@@ -43077,7 +43097,7 @@ function hasBothStageBCompletions(session, taskId) {
|
|
|
43077
43097
|
return false;
|
|
43078
43098
|
return completions.has("reviewer") && completions.has("test_engineer");
|
|
43079
43099
|
}
|
|
43080
|
-
async function isCouncilGateActive(directory, council) {
|
|
43100
|
+
async function isCouncilGateActive(directory, council, sessionOverrides = {}) {
|
|
43081
43101
|
const enabled = council?.enabled === true;
|
|
43082
43102
|
let plan = null;
|
|
43083
43103
|
try {
|
|
@@ -43103,13 +43123,13 @@ async function isCouncilGateActive(directory, council) {
|
|
|
43103
43123
|
if (!profile) {
|
|
43104
43124
|
return false;
|
|
43105
43125
|
}
|
|
43106
|
-
const councilMode = profile.
|
|
43126
|
+
const councilMode = getEffectiveGates(profile, sessionOverrides).council_mode === true;
|
|
43107
43127
|
if (enabled && councilMode) {
|
|
43108
43128
|
return true;
|
|
43109
43129
|
}
|
|
43110
43130
|
if (enabled !== councilMode && !_councilDisagreementWarned.has(planId)) {
|
|
43111
43131
|
_councilDisagreementWarned.add(planId);
|
|
43112
|
-
warn(`[delegation-gate] Council mode mismatch for plan ${planId}: ` + `pluginConfig.council.enabled=${enabled}, QaGates.council_mode=${councilMode}. ` + "Falling back to Stage B (non-council) advancement.");
|
|
43132
|
+
warn(`[delegation-gate] Council mode mismatch for plan ${planId}: ` + `pluginConfig.council.enabled=${enabled}, QaGates.council_mode=${councilMode}. ` + "Falling back to Stage B (non-council) per-task advancement.");
|
|
43113
43133
|
}
|
|
43114
43134
|
return false;
|
|
43115
43135
|
}
|
|
@@ -80755,7 +80775,7 @@ var init_qa_gates = __esm(() => {
|
|
|
80755
80775
|
"hallucination_guard",
|
|
80756
80776
|
"sast_enabled",
|
|
80757
80777
|
"mutation_test",
|
|
80758
|
-
"
|
|
80778
|
+
"phase_council",
|
|
80759
80779
|
"drift_check",
|
|
80760
80780
|
"final_council"
|
|
80761
80781
|
];
|
|
@@ -84128,7 +84148,7 @@ Subcommands:
|
|
|
84128
84148
|
handler: (ctx) => handleQaGatesCommand(ctx.directory, ctx.args, ctx.sessionID),
|
|
84129
84149
|
description: "View or modify QA gate profile for the current plan [enable|override <gate>...]",
|
|
84130
84150
|
args: "[show|enable|override] <gate>...",
|
|
84131
|
-
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test,
|
|
84151
|
+
details: "show: display spec-level, session-override, and effective QA gates for the current plan. enable: persist gate(s) into the locked-once profile (architect; rejected after critic approval lock). override: session-only ratchet-tighter enable. Valid gates: reviewer, test_engineer, council_mode, sme_enabled, critic_pre_plan, hallucination_guard, sast_enabled, mutation_test, phase_council, drift_check, final_council.",
|
|
84132
84152
|
category: "config"
|
|
84133
84153
|
},
|
|
84134
84154
|
promote: {
|
|
@@ -84371,24 +84391,87 @@ Do NOT dispatch the supervisor yourself as a reviewer of code — it is summary-
|
|
|
84371
84391
|
function buildCouncilWorkflow(council) {
|
|
84372
84392
|
if (council?.enabled !== true)
|
|
84373
84393
|
return "";
|
|
84374
|
-
return `## COUNCIL WORKFLOW
|
|
84394
|
+
return `## COUNCIL WORKFLOW
|
|
84395
|
+
|
|
84396
|
+
ANTI-CONFUSION: Do NOT confuse the three council modes:
|
|
84397
|
+
(1) \`council_mode\` — per-task full council replacing Stage B.
|
|
84398
|
+
(2) \`phase_council\` — phase-level holistic review at \`phase_complete\`.
|
|
84399
|
+
(3) \`final_council\` — project-level final review after all phases.
|
|
84400
|
+
None of these use the General Council (3-agent advisory). The General Council is an early workflow option gated by \`council.general.enabled\`, not a QA gate.
|
|
84401
|
+
|
|
84402
|
+
## A. PER-TASK COUNCIL (when \`council_mode\` is ON)
|
|
84403
|
+
|
|
84404
|
+
When \`council_mode\` is enabled in the QA gate profile, Stage B (reviewer + test_engineer) is **replaced** by the full 5-member council (critic, reviewer, sme, test_engineer, explorer) per task. Stage A (\`pre_check_batch\`) still runs as the pre-review gate.
|
|
84405
|
+
|
|
84406
|
+
### PREREQUISITES
|
|
84407
|
+
- \`declare_council_criteria\` must be called for each task before council dispatch.
|
|
84408
|
+
|
|
84409
|
+
### MANDATORY SEQUENCE — never skip or reorder
|
|
84410
|
+
|
|
84411
|
+
#### STEP 1 — DISPATCH all 5 council members in parallel (task-scoped)
|
|
84412
|
+
After Stage A passes for a task, in a SINGLE message, dispatch \`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`, and \`explorer\` as parallel Agent tasks. Each member receives task-scoped context:
|
|
84413
|
+
- \`critic\` — task diff + task spec + approved-plan baseline (via \`get_approved_plan\`) + spec-intent drift analysis
|
|
84414
|
+
- \`reviewer\` — task semantic diff summary + blast radius across changed files
|
|
84415
|
+
- \`sme\` — task domain context + knowledge base entries relevant to the task
|
|
84416
|
+
- \`test_engineer\` — changed test files for the task + coverage delta + known mutation gaps
|
|
84417
|
+
- \`explorer\` — task diff + original task intent + prior slop findings
|
|
84418
|
+
(hunts for lazy implementations, hallucinated APIs, cargo-cult patterns,
|
|
84419
|
+
spec drift, lazy abstractions)
|
|
84420
|
+
|
|
84421
|
+
Wait for ALL dispatched agents to return their verdict objects before proceeding.
|
|
84422
|
+
|
|
84423
|
+
#### STEP 2 — COLLECT verdicts
|
|
84424
|
+
Read each agent's response and extract their \`CouncilMemberVerdict\` object.
|
|
84425
|
+
Each member must return: \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT),
|
|
84426
|
+
\`confidence\` (0.0–1.0), \`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`,
|
|
84427
|
+
\`durationMs\`.
|
|
84428
|
+
|
|
84429
|
+
Do NOT fabricate, infer, or substitute a verdict. If an agent did not return
|
|
84430
|
+
a valid verdict object, re-dispatch that agent.
|
|
84431
|
+
|
|
84432
|
+
#### STEP 3 — CALL submit_council_verdicts (the per-task tool, NOT submit_phase_council_verdicts)
|
|
84433
|
+
ONLY after collecting real verdicts from all dispatched agents, call
|
|
84434
|
+
\`submit_council_verdicts\` with the collected verdicts. The per-task council
|
|
84435
|
+
verdict replaces the Stage B gate — APPROVE advances the task, REJECT blocks it.
|
|
84436
|
+
|
|
84437
|
+
#### STEP 4 — ACT on the verdict
|
|
84438
|
+
- **APPROVE**: Task passes. Proceed to the next task.
|
|
84439
|
+
If \`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as a single
|
|
84440
|
+
non-blocking advisory note before proceeding.
|
|
84441
|
+
- **CONCERNS with \`success: false\` + \`reason: 'blocking_concerns_unresolved'\`**:
|
|
84442
|
+
The tool blocked because HIGH/CRITICAL findings from CONCERNS members were
|
|
84443
|
+
promoted to \`requiredFixes\`. No evidence was written. Send \`unifiedFeedbackMd\`
|
|
84444
|
+
to the coder — every \`requiredFix\` must be resolved. Increment \`roundNumber\`
|
|
84445
|
+
and re-convene council after fixes. This is tool-enforced: you cannot bypass it.
|
|
84446
|
+
- **CONCERNS with \`success: true\`**: Only MEDIUM/LOW advisory findings remain.
|
|
84447
|
+
Task passes — surface \`unifiedFeedbackMd\` as a non-blocking note.
|
|
84448
|
+
- **REJECT**: Block task advancement. Send \`unifiedFeedbackMd\` to the coder
|
|
84449
|
+
with the BLOCKING flag. The coder must resolve all \`requiredFixes\` before
|
|
84450
|
+
the council is re-convened. Maximum \`council.maxRounds\` rounds (default 3).
|
|
84451
|
+
If \`roundNumber >= maxRounds\` and verdict is still REJECT, surface
|
|
84452
|
+
\`unifiedFeedbackMd\` to the user and HALT — do NOT auto-advance.
|
|
84453
|
+
|
|
84454
|
+
### ANTI-PATTERNS — per-task council bypass violations
|
|
84455
|
+
- ✗ Calling \`submit_council_verdicts\` without first dispatching all 5 members.
|
|
84456
|
+
- ✗ Passing verdicts inferred or fabricated rather than received from dispatched agents.
|
|
84457
|
+
- ✗ Claiming "Council APPROVED" when \`membersAbsent\` is non-empty.
|
|
84458
|
+
- ✗ Falling back to Stage B (reviewer + test_engineer only) when \`council_mode\` is ON — the full council replaces Stage B.
|
|
84459
|
+
- ✗ Skipping \`declare_council_criteria\` before dispatching council members.
|
|
84460
|
+
- ✗ Using \`submit_phase_council_verdicts\` for per-task verdicts — use \`submit_council_verdicts\`.
|
|
84461
|
+
|
|
84462
|
+
## B. PHASE COUNCIL (when \`phase_council\` is ON)
|
|
84375
84463
|
|
|
84376
84464
|
CRITICAL: \`submit_phase_council_verdicts\` does NOT run council members.
|
|
84377
84465
|
It synthesizes verdicts that you must collect BEFORE calling it.
|
|
84378
84466
|
|
|
84379
|
-
When \`
|
|
84380
|
-
profile, a phase-level council review is required before calling \`phase_complete\`.
|
|
84381
|
-
Stage B (reviewer + test_engineer) ALWAYS runs per-task as normal.
|
|
84382
|
-
Stage B always runs per-task — council is an ADDITIONAL verification layer at PHASE LEVEL, never a replacement for Stage B.
|
|
84467
|
+
When \`phase_council\` is enabled in the QA gate profile, a phase-level council review is required before calling \`phase_complete\`. This is additive to whichever per-task mechanism is active — Stage B (reviewer + test_engineer) runs per task by default, or the full 5-member per-task council if \`council_mode\` is ON.
|
|
84383
84468
|
|
|
84384
|
-
### WHEN TO RUN COUNCIL
|
|
84469
|
+
### WHEN TO RUN PHASE COUNCIL
|
|
84385
84470
|
After ALL tasks in the current phase have been marked \`completed\` and their
|
|
84386
|
-
|
|
84471
|
+
per-task gates have passed, and BEFORE calling \`phase_complete\`, convene the
|
|
84387
84472
|
phase council for a Phase Dossier Assembly — a holistic review of cross-cutting concerns,
|
|
84388
84473
|
behavioral cohesion, and the full body of work completed in the phase.
|
|
84389
84474
|
|
|
84390
|
-
## PHASE COUNCIL
|
|
84391
|
-
|
|
84392
84475
|
### MANDATORY SEQUENCE — never skip or reorder
|
|
84393
84476
|
|
|
84394
84477
|
#### STEP 1 — DISPATCH all 5 council members in parallel (phase-scoped)
|
|
@@ -84437,10 +84520,13 @@ and re-call \`submit_phase_council_verdicts\`.
|
|
|
84437
84520
|
- **APPROVE**: Call \`phase_complete\`. Gate 5 will pass.
|
|
84438
84521
|
If \`advisoryFindingsCount > 0\`, deliver \`unifiedFeedbackMd\` as a single
|
|
84439
84522
|
non-blocking advisory note to the team before proceeding.
|
|
84440
|
-
- **CONCERNS
|
|
84441
|
-
|
|
84442
|
-
|
|
84443
|
-
|
|
84523
|
+
- **CONCERNS with \`success: false\` + \`reason: 'blocking_concerns_unresolved'\`**:
|
|
84524
|
+
The tool blocked because HIGH/CRITICAL findings from CONCERNS members were
|
|
84525
|
+
promoted to \`requiredFixes\`. No evidence was written. Send \`unifiedFeedbackMd\`
|
|
84526
|
+
to the coder — every \`requiredFix\` must be resolved. Increment \`roundNumber\`
|
|
84527
|
+
and re-convene council after fixes. This is tool-enforced: you cannot bypass it.
|
|
84528
|
+
- **CONCERNS with \`success: true\`**: Only MEDIUM/LOW advisory findings remain.
|
|
84529
|
+
Call \`phase_complete\` and surface \`unifiedFeedbackMd\` as a non-blocking note.
|
|
84444
84530
|
- **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder
|
|
84445
84531
|
with the BLOCKING flag. The coder must resolve all \`requiredFixes\` before
|
|
84446
84532
|
the phase council is re-convened. Maximum \`council.maxRounds\` rounds (default 3).
|
|
@@ -84451,10 +84537,11 @@ and re-call \`submit_phase_council_verdicts\`.
|
|
|
84451
84537
|
- ✗ Calling \`submit_phase_council_verdicts\` without first dispatching all 5 members.
|
|
84452
84538
|
- ✗ Passing verdicts inferred or fabricated rather than received from dispatched agents.
|
|
84453
84539
|
- ✗ Claiming "Council APPROVED" when \`membersAbsent\` is non-empty.
|
|
84454
|
-
- ✗
|
|
84540
|
+
- ✗ Skipping per-task gates because phase council will catch issues — per-task gates are mandatory regardless.
|
|
84455
84541
|
- ✗ Calling \`phase_complete\` before council evidence has been written (Gate 5 will block you).
|
|
84456
84542
|
- ✗ Treating a prior phase's council verdict as valid for a new phase.
|
|
84457
84543
|
- ✗ Incrementing \`roundNumber\` without re-dispatching members for the new round.
|
|
84544
|
+
- ✗ Using \`submit_council_verdicts\` for phase verdicts — use \`submit_phase_council_verdicts\`.
|
|
84458
84545
|
|
|
84459
84546
|
### ROUND 2 DELIBERATION
|
|
84460
84547
|
If round 1 produces REJECT or CONCERNS requiring re-work, every prior-round
|
|
@@ -84497,16 +84584,25 @@ Present the eleven gates with their defaults (DEFAULT_QA_GATES) as a single user
|
|
|
84497
84584
|
- sme_enabled (default: ON) — SME consultation during planning/clarification
|
|
84498
84585
|
- critic_pre_plan (default: ON) — critic review before plan finalization
|
|
84499
84586
|
- sast_enabled (default: ON) — static security scanning
|
|
84500
|
-
- council_mode (default: OFF) —
|
|
84587
|
+
- council_mode (default: OFF) — replaces per-task Stage B (reviewer + test_engineer) with the full 5-member council (critic, reviewer, sme, test_engineer, explorer). When enabled, Stage A still runs, but after Stage A passes, all 5 council members review the task instead of just reviewer + test_engineer. Requires council.enabled: true in config. (recommended for high-impact architecture, public APIs, schema/data mutation, security-sensitive code)
|
|
84501
84588
|
- hallucination_guard (default: OFF) — when enabled, mandatory per-phase API/signature/claim/citation verification via critic_hallucination_verifier at PHASE-WRAP; phase_complete will REJECT phase completion unless .swarm/evidence/{phase}/hallucination-guard.json exists with an APPROVED verdict (recommended for claim-heavy or research-heavy work)
|
|
84502
84589
|
- 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)
|
|
84503
|
-
-
|
|
84590
|
+
- phase_council (default: OFF) — when enabled, a full 5-member council (critic, reviewer, sme, test_engineer, explorer) reviews all work completed in a phase holistically at phase_complete time. This is additive to whichever per-task mechanism is active — Stage B (reviewer + test_engineer) by default, or the full 5-member per-task council if council_mode is ON. Requires council.enabled: true in config.
|
|
84504
84591
|
- 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)
|
|
84505
|
-
- final_council (default: OFF)
|
|
84592
|
+
- final_council (default: OFF) — when enabled, the full 5-member council (NOT the General Council) reviews the entire project after all phases complete. The architect dispatches the same five council members (\`critic\`, \`reviewer\`, \`sme\`, \`test_engineer\`, \`explorer\`) at project scope, collects \`CouncilMemberVerdict\` objects, and calls \`write_final_council_evidence\`. This is not General Council mode and does not require \`council.general.enabled\`.
|
|
84593
|
+
|
|
84594
|
+
Present all three items together in a single message. One message, defaults pre-stated. Wait for the user's answer to all three:
|
|
84506
84595
|
|
|
84507
|
-
|
|
84596
|
+
**1. QA Gates** — accept defaults or customize (the eleven gates listed above).
|
|
84508
84597
|
|
|
84509
|
-
|
|
84598
|
+
**2. Parallel Coders** — "How many coders should run in parallel? (default: 1, range: 1-4)"
|
|
84599
|
+
|
|
84600
|
+
**3. Commit Frequency** — "Commit frequency for completed tasks? (default: phase-level only; optional per-task checkpoint commit after each task completion)"
|
|
84601
|
+
|
|
84602
|
+
Wait for the user to answer all three in a single reply. Then apply:
|
|
84603
|
+
|
|
84604
|
+
- For gates: record the user's gate selections.
|
|
84605
|
+
- For parallel coders: if the user says a number > 1, write a \`## Pending Parallelization Config\` section to \`.swarm/context.md\` alongside the gate selection:
|
|
84510
84606
|
\`\`\`
|
|
84511
84607
|
## Pending Parallelization Config
|
|
84512
84608
|
- parallelization_enabled: true
|
|
@@ -84517,9 +84613,7 @@ If the user answered the gate question, immediately follow up with ONE more ques
|
|
|
84517
84613
|
\`\`\`
|
|
84518
84614
|
If the user accepts the default (1), skip writing this section entirely — serial execution is the default and needs no config.
|
|
84519
84615
|
|
|
84520
|
-
|
|
84521
|
-
|
|
84522
|
-
If the user chooses per-task commits, write this section to \`.swarm/context.md\`:
|
|
84616
|
+
- For commit frequency: if the user chooses per-task commits, write this section to \`.swarm/context.md\`:
|
|
84523
84617
|
\`\`\`
|
|
84524
84618
|
## Task Completion Commit Policy
|
|
84525
84619
|
- commit_after_each_completed_task: true
|
|
@@ -84971,7 +85065,7 @@ TIER 3 — CRITICAL
|
|
|
84971
85065
|
Pipeline: Full Stage A. Stage B = {{AGENT_PREFIX}}reviewer×2 + {{AGENT_PREFIX}}test_engineer×2.
|
|
84972
85066
|
Rationale: Security paths need adversarial review.
|
|
84973
85067
|
|
|
84974
|
-
|
|
85068
|
+
When \`council_mode\` is enabled, Stage B (reviewer + test_engineer) is replaced by the full 5-member council per task. When \`phase_council\` is enabled, a phase-level council review is additionally required before calling \`phase_complete\`.
|
|
84975
85069
|
|
|
84976
85070
|
CLASSIFICATION RULES:
|
|
84977
85071
|
- Multi-tier → use HIGHEST tier.
|
|
@@ -84998,7 +85092,7 @@ Stage B runs by default for TIER 1-3 classifications. Stage A passing does not s
|
|
|
84998
85092
|
Stage B is where logic errors, security flaws, edge cases, and behavioral bugs are caught.
|
|
84999
85093
|
You MUST delegate to each required Stage B agent. For the standard reviewer + test_engineer pair, dispatch both before waiting so Stage B actually runs in parallel.
|
|
85000
85094
|
|
|
85001
|
-
Stage B (reviewer + test_engineer) **
|
|
85095
|
+
When \`council_mode\` is enabled, Stage B (reviewer + test_engineer) is **replaced** by the full 5-member council (critic, reviewer, sme, test_engineer, explorer) per task. Stage A (\`pre_check_batch\`) still runs as the pre-review gate. When \`phase_council\` is enabled, a phase-level council review is additionally required before calling \`phase_complete\`: dispatch all 5 council members with phase-scoped context, collect their verdicts, call \`submit_phase_council_verdicts\`, then call \`phase_complete\` (Gate 5 validates the resulting \`phase-council.json\` evidence).
|
|
85002
85096
|
|
|
85003
85097
|
A task is complete ONLY when BOTH stages pass.
|
|
85004
85098
|
|
|
@@ -85375,6 +85469,7 @@ HARD CONSTRAINTS:
|
|
|
85375
85469
|
|
|
85376
85470
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
85377
85471
|
- Treat brainstorm output as discovery material until the loaded skill transitions to SPECIFY or PLAN.
|
|
85472
|
+
- When council.general.enabled is true, the brainstorm skill offers the user a General Council advisory input option before spec writing. This is NOT a QA gate — it's an early workflow option. The convene_general_council tool must be available when council.general.enabled is true.
|
|
85378
85473
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
85379
85474
|
|
|
85380
85475
|
### MODE: SPECIFY
|
|
@@ -85391,6 +85486,7 @@ HARD CONSTRAINTS:
|
|
|
85391
85486
|
|
|
85392
85487
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
85393
85488
|
- Follow the loaded skill's spec creation, clarification, and transition rules.
|
|
85489
|
+
- General Council advisory input is available via the /swarm council command at any time. It is NOT offered as a SPECIFY workflow step — it moved to BRAINSTORM Phase 1b as an early option before spec writing.
|
|
85394
85490
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
85395
85491
|
|
|
85396
85492
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
@@ -98130,7 +98226,7 @@ DELEGATION RULES:
|
|
|
98130
98226
|
${complianceHeader}
|
|
98131
98227
|
- Reviewer delegation is MANDATORY for every coder task.
|
|
98132
98228
|
- pre_check_batch is NOT a substitute for reviewer.
|
|
98133
|
-
- Stage A (tools) + Stage B (
|
|
98229
|
+
- Stage A (automated tools) + Stage B/Council (agent review) = BOTH required.
|
|
98134
98230
|
${phaseNumber !== null && phaseNumber >= 4 ? `
|
|
98135
98231
|
⚠️ You are in Phase ${phaseNumber}. Compliance degrades with time. Do not skip reviewer or test_engineer.` : ""}
|
|
98136
98232
|
</swarm_reminder>`;
|
|
@@ -111833,16 +111929,17 @@ function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNum
|
|
|
111833
111929
|
const unresolvedConflicts = detectConflicts2(verdicts);
|
|
111834
111930
|
const rejectingSet = new Set(rejectingMembers);
|
|
111835
111931
|
const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
111836
|
-
const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
111932
|
+
const requiredFixes = vetoFindings.filter((f) => f.severity === "CRITICAL" || f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
111837
111933
|
const advisoryFindings = [
|
|
111838
111934
|
...vetoFindings.filter((f) => f.severity === "LOW"),
|
|
111839
111935
|
...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
|
|
111840
111936
|
];
|
|
111937
|
+
const blockingConcernsCount = promoteBlockingConcerns(verdicts, rejectingSet, requiredFixes, advisoryFindings);
|
|
111841
111938
|
const allAssessedIds = new Set(verdicts.flatMap((v) => v.criteriaAssessed));
|
|
111842
111939
|
const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
|
|
111843
111940
|
const mandatoryIds = new Set((criteria?.criteria ?? []).filter((c) => c.mandatory).map((c) => c.id));
|
|
111844
111941
|
const allCriteriaMet = [...mandatoryIds].every((id) => allAssessedIds.has(id) && !allUnmetIds.has(id));
|
|
111845
|
-
const unifiedFeedbackMd = buildUnifiedFeedback(taskId, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
|
|
111942
|
+
const unifiedFeedbackMd = buildUnifiedFeedback(taskId, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds, blockingConcernsCount);
|
|
111846
111943
|
return {
|
|
111847
111944
|
taskId,
|
|
111848
111945
|
swarmId,
|
|
@@ -111857,9 +111954,24 @@ function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNum
|
|
|
111857
111954
|
roundNumber,
|
|
111858
111955
|
allCriteriaMet,
|
|
111859
111956
|
quorumSize,
|
|
111957
|
+
blockingConcernsCount,
|
|
111860
111958
|
...verdicts.length === 0 && { emptyVerdictsWarning: true }
|
|
111861
111959
|
};
|
|
111862
111960
|
}
|
|
111961
|
+
function promoteBlockingConcerns(verdicts, rejectingSet, requiredFixes, advisoryFindings) {
|
|
111962
|
+
const concernsFindings = verdicts.filter((v) => v.verdict === "CONCERNS" && !rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
111963
|
+
const blocking = concernsFindings.filter((f) => f.severity === "CRITICAL" || f.severity === "HIGH");
|
|
111964
|
+
if (blocking.length === 0)
|
|
111965
|
+
return 0;
|
|
111966
|
+
requiredFixes.push(...blocking);
|
|
111967
|
+
const blockingSet = new Set(blocking);
|
|
111968
|
+
for (let i2 = advisoryFindings.length - 1;i2 >= 0; i2--) {
|
|
111969
|
+
if (blockingSet.has(advisoryFindings[i2])) {
|
|
111970
|
+
advisoryFindings.splice(i2, 1);
|
|
111971
|
+
}
|
|
111972
|
+
}
|
|
111973
|
+
return blocking.length;
|
|
111974
|
+
}
|
|
111863
111975
|
function detectConflicts2(verdicts) {
|
|
111864
111976
|
const conflicts = [];
|
|
111865
111977
|
const locationMap = new Map;
|
|
@@ -111889,7 +112001,7 @@ function detectConflicts2(verdicts) {
|
|
|
111889
112001
|
}
|
|
111890
112002
|
return conflicts;
|
|
111891
112003
|
}
|
|
111892
|
-
function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
|
|
112004
|
+
function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds, blockingConcernsCount = 0) {
|
|
111893
112005
|
const lines = [
|
|
111894
112006
|
`## Work Complete Council — Round ${roundNumber}/${maxRounds}`,
|
|
111895
112007
|
`**Task:** ${taskId} **Overall verdict:** ${verdict}`,
|
|
@@ -111899,6 +112011,10 @@ function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisory
|
|
|
111899
112011
|
lines.push(`> ⛔ **BLOCKED** by: ${vetoedBy.join(", ")}`);
|
|
111900
112012
|
lines.push("");
|
|
111901
112013
|
}
|
|
112014
|
+
if (blockingConcernsCount > 0) {
|
|
112015
|
+
lines.push(`> ⚠️ **BLOCKING CONCERNS**: ${blockingConcernsCount} HIGH/CRITICAL finding(s) from CONCERNS members require investigation and resolution before advancement.`);
|
|
112016
|
+
lines.push("");
|
|
112017
|
+
}
|
|
111902
112018
|
if (requiredFixes.length > 0) {
|
|
111903
112019
|
lines.push("### Required Fixes (must resolve before re-submission)");
|
|
111904
112020
|
for (const f of requiredFixes) {
|
|
@@ -111946,11 +112062,12 @@ function synthesizePhaseCouncilAdvisory(phaseNumber, phaseSummary, verdicts, rou
|
|
|
111946
112062
|
const unresolvedConflicts = detectConflicts2(verdicts);
|
|
111947
112063
|
const rejectingSet = new Set(rejectingMembers);
|
|
111948
112064
|
const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
111949
|
-
const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
112065
|
+
const requiredFixes = vetoFindings.filter((f) => f.severity === "CRITICAL" || f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
111950
112066
|
const advisoryFindings = [
|
|
111951
112067
|
...vetoFindings.filter((f) => f.severity === "LOW"),
|
|
111952
112068
|
...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
|
|
111953
112069
|
];
|
|
112070
|
+
const blockingConcernsCount = promoteBlockingConcerns(verdicts, rejectingSet, requiredFixes, advisoryFindings);
|
|
111954
112071
|
const advisoryNotes = [];
|
|
111955
112072
|
if (advisoryFindings.length > 0) {
|
|
111956
112073
|
advisoryNotes.push(`Phase ${phaseNumber} council found ${advisoryFindings.length} advisory finding(s). Review before proceeding to next phase.`);
|
|
@@ -111960,7 +112077,7 @@ function synthesizePhaseCouncilAdvisory(phaseNumber, phaseSummary, verdicts, rou
|
|
|
111960
112077
|
}
|
|
111961
112078
|
const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
|
|
111962
112079
|
const allCriteriaMet = allUnmetIds.size === 0 && verdicts.length > 0;
|
|
111963
|
-
const unifiedFeedbackMd = buildPhaseCouncilFeedback(phaseNumber, phaseSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
|
|
112080
|
+
const unifiedFeedbackMd = buildPhaseCouncilFeedback(phaseNumber, phaseSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds, blockingConcernsCount);
|
|
111964
112081
|
const evidencePath = `.swarm/evidence/${phaseNumber}/phase-council.json`;
|
|
111965
112082
|
return {
|
|
111966
112083
|
phaseNumber,
|
|
@@ -111977,11 +112094,12 @@ function synthesizePhaseCouncilAdvisory(phaseNumber, phaseSummary, verdicts, rou
|
|
|
111977
112094
|
roundNumber,
|
|
111978
112095
|
allCriteriaMet,
|
|
111979
112096
|
quorumSize,
|
|
112097
|
+
blockingConcernsCount,
|
|
111980
112098
|
evidencePath,
|
|
111981
112099
|
phaseSummary
|
|
111982
112100
|
};
|
|
111983
112101
|
}
|
|
111984
|
-
function buildPhaseCouncilFeedback(phaseNumber, phaseSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
|
|
112102
|
+
function buildPhaseCouncilFeedback(phaseNumber, phaseSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds, blockingConcernsCount = 0) {
|
|
111985
112103
|
const lines = [
|
|
111986
112104
|
`## Phase Council Review — Round ${roundNumber}/${maxRounds}`,
|
|
111987
112105
|
`**Phase:** ${phaseNumber} **Overall verdict:** ${verdict}`,
|
|
@@ -111995,6 +112113,10 @@ function buildPhaseCouncilFeedback(phaseNumber, phaseSummary, verdict, vetoedBy,
|
|
|
111995
112113
|
lines.push(`> ⛔ **BLOCKED** by: ${vetoedBy.join(", ")}`);
|
|
111996
112114
|
lines.push("");
|
|
111997
112115
|
}
|
|
112116
|
+
if (blockingConcernsCount > 0) {
|
|
112117
|
+
lines.push(`> ⚠️ **BLOCKING CONCERNS**: ${blockingConcernsCount} HIGH/CRITICAL finding(s) from CONCERNS members require investigation and resolution before phase completion.`);
|
|
112118
|
+
lines.push("");
|
|
112119
|
+
}
|
|
111998
112120
|
if (requiredFixes.length > 0) {
|
|
111999
112121
|
lines.push("### Required Fixes (must resolve before re-submission)");
|
|
112000
112122
|
for (const f of requiredFixes) {
|
|
@@ -112041,11 +112163,12 @@ function synthesizeFinalCouncilAdvisory(projectSummary, verdicts, roundNumber, c
|
|
|
112041
112163
|
const unresolvedConflicts = detectConflicts2(verdicts);
|
|
112042
112164
|
const rejectingSet = new Set(rejectingMembers);
|
|
112043
112165
|
const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
112044
|
-
const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
112166
|
+
const requiredFixes = vetoFindings.filter((f) => f.severity === "CRITICAL" || f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
112045
112167
|
const advisoryFindings = [
|
|
112046
112168
|
...vetoFindings.filter((f) => f.severity === "LOW"),
|
|
112047
112169
|
...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
|
|
112048
112170
|
];
|
|
112171
|
+
const blockingConcernsCount = promoteBlockingConcerns(verdicts, rejectingSet, requiredFixes, advisoryFindings);
|
|
112049
112172
|
const advisoryNotes = [];
|
|
112050
112173
|
if (advisoryFindings.length > 0) {
|
|
112051
112174
|
advisoryNotes.push(`Final council found ${advisoryFindings.length} advisory finding(s). Review before project close.`);
|
|
@@ -112055,7 +112178,7 @@ function synthesizeFinalCouncilAdvisory(projectSummary, verdicts, roundNumber, c
|
|
|
112055
112178
|
}
|
|
112056
112179
|
const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
|
|
112057
112180
|
const allCriteriaMet = allUnmetIds.size === 0 && verdicts.length > 0;
|
|
112058
|
-
const unifiedFeedbackMd = buildFinalCouncilFeedback(projectSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
|
|
112181
|
+
const unifiedFeedbackMd = buildFinalCouncilFeedback(projectSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds, blockingConcernsCount);
|
|
112059
112182
|
return {
|
|
112060
112183
|
scope: "project",
|
|
112061
112184
|
timestamp,
|
|
@@ -112071,10 +112194,11 @@ function synthesizeFinalCouncilAdvisory(projectSummary, verdicts, roundNumber, c
|
|
|
112071
112194
|
allCriteriaMet,
|
|
112072
112195
|
quorumSize,
|
|
112073
112196
|
evidencePath: ".swarm/evidence/final-council.json",
|
|
112197
|
+
blockingConcernsCount,
|
|
112074
112198
|
projectSummary
|
|
112075
112199
|
};
|
|
112076
112200
|
}
|
|
112077
|
-
function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
|
|
112201
|
+
function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds, blockingConcernsCount = 0) {
|
|
112078
112202
|
const lines = [
|
|
112079
112203
|
`## Final Council Review - Round ${roundNumber}/${maxRounds}`,
|
|
112080
112204
|
`**Scope:** completed project **Overall verdict:** ${verdict}`,
|
|
@@ -112084,6 +112208,10 @@ function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFi
|
|
|
112084
112208
|
lines.push(`**Project Summary:** ${projectSummary}`);
|
|
112085
112209
|
lines.push("");
|
|
112086
112210
|
}
|
|
112211
|
+
if (blockingConcernsCount > 0) {
|
|
112212
|
+
lines.push(`> ⚠️ **BLOCKING CONCERNS**: ${blockingConcernsCount} HIGH/CRITICAL finding(s) from CONCERNS members require investigation and resolution before project close.`);
|
|
112213
|
+
lines.push("");
|
|
112214
|
+
}
|
|
112087
112215
|
if (vetoedBy.length > 0) {
|
|
112088
112216
|
lines.push(`> BLOCKED: project close is blocked by ${vetoedBy.join(", ")}`);
|
|
112089
112217
|
lines.push("");
|
|
@@ -112162,7 +112290,7 @@ init_state();
|
|
|
112162
112290
|
init_create_tool();
|
|
112163
112291
|
init_resolve_working_directory();
|
|
112164
112292
|
var FindingSchema = exports_external.object({
|
|
112165
|
-
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
112293
|
+
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
112166
112294
|
category: exports_external.string().min(1),
|
|
112167
112295
|
location: exports_external.string(),
|
|
112168
112296
|
detail: exports_external.string(),
|
|
@@ -112203,7 +112331,7 @@ var submit_council_verdicts = createSwarmTool({
|
|
|
112203
112331
|
verdictRound: exports_external.number().int().min(1).max(10).optional(),
|
|
112204
112332
|
confidence: exports_external.number().min(0).max(1),
|
|
112205
112333
|
findings: exports_external.array(exports_external.object({
|
|
112206
|
-
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
112334
|
+
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
112207
112335
|
category: exports_external.string().min(1),
|
|
112208
112336
|
location: exports_external.string(),
|
|
112209
112337
|
detail: exports_external.string(),
|
|
@@ -112306,6 +112434,17 @@ var submit_council_verdicts = createSwarmTool({
|
|
|
112306
112434
|
session.pendingCouncilRequirements.set(nextRoundKey, new Set(dissenters));
|
|
112307
112435
|
}
|
|
112308
112436
|
}
|
|
112437
|
+
if (synthesis.overallVerdict === "CONCERNS" && synthesis.blockingConcernsCount > 0) {
|
|
112438
|
+
return JSON.stringify({
|
|
112439
|
+
success: false,
|
|
112440
|
+
reason: "blocking_concerns_unresolved",
|
|
112441
|
+
overallVerdict: synthesis.overallVerdict,
|
|
112442
|
+
blockingConcernsCount: synthesis.blockingConcernsCount,
|
|
112443
|
+
requiredFixes: synthesis.requiredFixes,
|
|
112444
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd,
|
|
112445
|
+
message: `Council returned CONCERNS with ${synthesis.blockingConcernsCount} HIGH/CRITICAL finding(s) promoted to requiredFixes. These must be resolved before the task can advance. Do NOT write evidence or proceed — address every requiredFix and resubmit.`
|
|
112446
|
+
}, null, 2);
|
|
112447
|
+
}
|
|
112309
112448
|
await writeCouncilEvidence(workingDir, synthesis);
|
|
112310
112449
|
try {
|
|
112311
112450
|
if (sessionID) {
|
|
@@ -119886,7 +120025,7 @@ async function runPhaseCouncilGate(ctx) {
|
|
|
119886
120025
|
const session = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
119887
120026
|
const overrides = session?.qaGateSessionOverrides ?? {};
|
|
119888
120027
|
const effective = getEffectiveGates(profile, overrides);
|
|
119889
|
-
if (effective.
|
|
120028
|
+
if (effective.phase_council === true && pluginConfig.council?.enabled === true) {
|
|
119890
120029
|
councilModeEnabled = true;
|
|
119891
120030
|
const pcPath = path148.join(dir, ".swarm", "evidence", String(phase), "phase-council.json");
|
|
119892
120031
|
let pcVerdictFound = false;
|
|
@@ -119990,7 +120129,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
119990
120129
|
blocked: true,
|
|
119991
120130
|
reason: "PHASE_COUNCIL_REQUIRED",
|
|
119992
120131
|
phase_council_required: true,
|
|
119993
|
-
message: `Phase ${phase} cannot be completed:
|
|
120132
|
+
message: `Phase ${phase} cannot be completed: phase_council 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.`,
|
|
119994
120133
|
agentsDispatched,
|
|
119995
120134
|
agentsMissing: [],
|
|
119996
120135
|
warnings: [
|
|
@@ -120046,7 +120185,7 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
120046
120185
|
return {
|
|
120047
120186
|
blocked: true,
|
|
120048
120187
|
reason: "PHASE_COUNCIL_ERROR",
|
|
120049
|
-
message: `Phase ${phase} cannot be completed: phase council gate encountered an error when
|
|
120188
|
+
message: `Phase ${phase} cannot be completed: phase council gate encountered an error when phase_council was enabled. Error: ${String(pcError)}`,
|
|
120050
120189
|
agentsDispatched,
|
|
120051
120190
|
agentsMissing: [],
|
|
120052
120191
|
warnings: [`PHASE_COUNCIL_ERROR: ${String(pcError)}`]
|
|
@@ -128056,7 +128195,7 @@ async function executeSetQaGates(args2, directory) {
|
|
|
128056
128195
|
"hallucination_guard",
|
|
128057
128196
|
"sast_enabled",
|
|
128058
128197
|
"mutation_test",
|
|
128059
|
-
"
|
|
128198
|
+
"phase_council",
|
|
128060
128199
|
"drift_check",
|
|
128061
128200
|
"final_council"
|
|
128062
128201
|
]) {
|
|
@@ -128098,13 +128237,13 @@ var set_qa_gates = createSwarmTool({
|
|
|
128098
128237
|
args: {
|
|
128099
128238
|
reviewer: exports_external.boolean().optional().describe("Enable the reviewer gate (true) — cannot be disabled."),
|
|
128100
128239
|
test_engineer: exports_external.boolean().optional().describe("Enable the test_engineer gate (true) — cannot be disabled once on."),
|
|
128101
|
-
council_mode: exports_external.boolean().optional().describe("Enable council mode
|
|
128240
|
+
council_mode: exports_external.boolean().optional().describe("Enable council mode — replaces per-task Stage B (reviewer + test_engineer) with the full 5-member council (critic, reviewer, sme, test_engineer, explorer) per task."),
|
|
128102
128241
|
sme_enabled: exports_external.boolean().optional().describe("Enable SME consultation."),
|
|
128103
128242
|
critic_pre_plan: exports_external.boolean().optional().describe("Enable critic_pre_plan review before plan approval."),
|
|
128104
128243
|
hallucination_guard: exports_external.boolean().optional().describe("Enable hallucination_guard checks on plan and implementation claims."),
|
|
128105
128244
|
sast_enabled: exports_external.boolean().optional().describe("Enable SAST scanning as a required QA gate."),
|
|
128106
128245
|
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."),
|
|
128107
|
-
|
|
128246
|
+
phase_council: exports_external.boolean().optional().describe("Enable the phase_council gate (default: off). When on, a full " + "5-member council (critic, reviewer, sme, test_engineer, explorer) " + "reviews all work completed in a phase holistically at phase_complete " + "time. Requires council.enabled: true in config."),
|
|
128108
128247
|
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."),
|
|
128109
128248
|
final_council: exports_external.boolean().optional().describe("Enable the final_council gate (default: off). When on, " + "after all phases complete the architect dispatches critic, reviewer, " + "sme, test_engineer, and explorer with project-scoped context, " + "collects their CouncilMemberVerdict objects, and calls " + "write_final_council_evidence. This is not General Council mode " + "and does not require council.general.enabled."),
|
|
128110
128249
|
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.')
|
|
@@ -128363,7 +128502,7 @@ var VerdictSchema2 = exports_external.object({
|
|
|
128363
128502
|
verdictRound: exports_external.number().int().min(1).max(10).optional(),
|
|
128364
128503
|
confidence: exports_external.number().min(0).max(1),
|
|
128365
128504
|
findings: exports_external.array(exports_external.object({
|
|
128366
|
-
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
128505
|
+
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
128367
128506
|
category: exports_external.string().min(1),
|
|
128368
128507
|
location: exports_external.string(),
|
|
128369
128508
|
detail: exports_external.string(),
|
|
@@ -128382,7 +128521,7 @@ var ArgsSchema4 = exports_external.object({
|
|
|
128382
128521
|
working_directory: exports_external.string().optional()
|
|
128383
128522
|
});
|
|
128384
128523
|
var submit_phase_council_verdicts = createSwarmTool({
|
|
128385
|
-
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
|
|
128524
|
+
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 phase_council is enabled. " + "Architect-only. Config-gated via council.enabled.",
|
|
128386
128525
|
args: {
|
|
128387
128526
|
phaseNumber: exports_external.number().int().min(1).describe("Phase number being reviewed (e.g. 1, 2, 3)"),
|
|
128388
128527
|
swarmId: exports_external.string().min(1).describe('Swarm identifier, e.g. "mega"'),
|
|
@@ -128400,7 +128539,7 @@ var submit_phase_council_verdicts = createSwarmTool({
|
|
|
128400
128539
|
verdictRound: exports_external.number().int().min(1).max(10).optional(),
|
|
128401
128540
|
confidence: exports_external.number().min(0).max(1),
|
|
128402
128541
|
findings: exports_external.array(exports_external.object({
|
|
128403
|
-
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
128542
|
+
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
128404
128543
|
category: exports_external.string().min(1),
|
|
128405
128544
|
location: exports_external.string(),
|
|
128406
128545
|
detail: exports_external.string(),
|
|
@@ -128475,6 +128614,20 @@ var submit_phase_council_verdicts = createSwarmTool({
|
|
|
128475
128614
|
const mutationGapFinding = existingMutationGapFinding ? null : getPhaseMutationGapFinding(input.phaseNumber, workingDir);
|
|
128476
128615
|
if (mutationGapFinding) {
|
|
128477
128616
|
addMutationGapFindingToSynthesis(synthesis, mutationGapFinding);
|
|
128617
|
+
if (mutationGapFinding.severity === "CRITICAL" || mutationGapFinding.severity === "HIGH") {
|
|
128618
|
+
synthesis.blockingConcernsCount++;
|
|
128619
|
+
}
|
|
128620
|
+
}
|
|
128621
|
+
if (synthesis.blockingConcernsCount > 0) {
|
|
128622
|
+
return JSON.stringify({
|
|
128623
|
+
success: false,
|
|
128624
|
+
reason: "blocking_concerns_unresolved",
|
|
128625
|
+
overallVerdict: synthesis.overallVerdict,
|
|
128626
|
+
blockingConcernsCount: synthesis.blockingConcernsCount,
|
|
128627
|
+
requiredFixes: synthesis.requiredFixes,
|
|
128628
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd,
|
|
128629
|
+
message: `Phase council returned CONCERNS with ${synthesis.blockingConcernsCount} HIGH/CRITICAL finding(s) promoted to requiredFixes. These must be resolved before the phase can complete. Do NOT write evidence or proceed — address every requiredFix and resubmit.`
|
|
128630
|
+
}, null, 2);
|
|
128478
128631
|
}
|
|
128479
128632
|
writePhaseCouncilEvidence(workingDir, synthesis);
|
|
128480
128633
|
return JSON.stringify({
|
|
@@ -128604,7 +128757,7 @@ function writePhaseCouncilEvidence(workingDir, synthesis) {
|
|
|
128604
128757
|
}
|
|
128605
128758
|
}
|
|
128606
128759
|
function addMutationGapFindingToSynthesis(synthesis, finding) {
|
|
128607
|
-
if (finding.severity === "HIGH" || finding.severity === "MEDIUM") {
|
|
128760
|
+
if (finding.severity === "CRITICAL" || finding.severity === "HIGH" || finding.severity === "MEDIUM") {
|
|
128608
128761
|
synthesis.requiredFixes.push(finding);
|
|
128609
128762
|
} else {
|
|
128610
128763
|
synthesis.advisoryFindings.push(finding);
|
|
@@ -130720,6 +130873,69 @@ function recoverTaskStateFromDelegations(taskId, directory) {
|
|
|
130720
130873
|
}
|
|
130721
130874
|
}
|
|
130722
130875
|
}
|
|
130876
|
+
function checkCouncilGate(workingDirectory, taskId) {
|
|
130877
|
+
let councilEnabled = false;
|
|
130878
|
+
let effectiveMinimum = 3;
|
|
130879
|
+
try {
|
|
130880
|
+
const config3 = loadPluginConfig(workingDirectory);
|
|
130881
|
+
councilEnabled = config3.council?.enabled === true;
|
|
130882
|
+
effectiveMinimum = config3.council?.requireAllMembers ? 5 : config3.council?.minimumMembers ?? 3;
|
|
130883
|
+
} catch {
|
|
130884
|
+
return { blocked: false, reason: "", active: false };
|
|
130885
|
+
}
|
|
130886
|
+
if (!councilEnabled) {
|
|
130887
|
+
return { blocked: false, reason: "", active: false };
|
|
130888
|
+
}
|
|
130889
|
+
try {
|
|
130890
|
+
const planPath = path171.join(workingDirectory, ".swarm", "plan.json");
|
|
130891
|
+
const planRaw = fs128.readFileSync(planPath, "utf-8");
|
|
130892
|
+
const planObj = JSON.parse(planRaw);
|
|
130893
|
+
if (planObj.swarm && planObj.title) {
|
|
130894
|
+
const planId = derivePlanId(planObj);
|
|
130895
|
+
const profile = getProfile(workingDirectory, planId);
|
|
130896
|
+
if (!profile || !profile.gates.council_mode) {
|
|
130897
|
+
return { blocked: false, reason: "", active: false };
|
|
130898
|
+
}
|
|
130899
|
+
}
|
|
130900
|
+
} catch {
|
|
130901
|
+
return { blocked: false, reason: "", active: false };
|
|
130902
|
+
}
|
|
130903
|
+
let evidence;
|
|
130904
|
+
try {
|
|
130905
|
+
evidence = readTaskEvidenceRaw(workingDirectory, taskId);
|
|
130906
|
+
} catch {
|
|
130907
|
+
return {
|
|
130908
|
+
blocked: true,
|
|
130909
|
+
reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task",
|
|
130910
|
+
active: true
|
|
130911
|
+
};
|
|
130912
|
+
}
|
|
130913
|
+
const councilGate = evidence?.gates?.council;
|
|
130914
|
+
if (!councilGate) {
|
|
130915
|
+
return {
|
|
130916
|
+
blocked: true,
|
|
130917
|
+
reason: "council gate required but not yet run — architect must call submit_council_verdicts before advancing this task",
|
|
130918
|
+
active: true
|
|
130919
|
+
};
|
|
130920
|
+
}
|
|
130921
|
+
if (councilGate.verdict === "REJECT") {
|
|
130922
|
+
return {
|
|
130923
|
+
blocked: true,
|
|
130924
|
+
reason: "council gate blocked advancement — resolve requiredFixes and re-run submit_council_verdicts",
|
|
130925
|
+
active: true
|
|
130926
|
+
};
|
|
130927
|
+
}
|
|
130928
|
+
const rawQuorumSize = councilGate.quorumSize;
|
|
130929
|
+
const quorumSize = typeof rawQuorumSize === "number" && Number.isFinite(rawQuorumSize) && rawQuorumSize >= 1 ? rawQuorumSize : 1;
|
|
130930
|
+
if (quorumSize < effectiveMinimum) {
|
|
130931
|
+
return {
|
|
130932
|
+
blocked: true,
|
|
130933
|
+
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.`,
|
|
130934
|
+
active: true
|
|
130935
|
+
};
|
|
130936
|
+
}
|
|
130937
|
+
return { blocked: false, reason: "", active: true };
|
|
130938
|
+
}
|
|
130723
130939
|
async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
|
|
130724
130940
|
const statusError = validateStatus(args2.status);
|
|
130725
130941
|
if (statusError) {
|
|
@@ -130821,7 +131037,15 @@ async function executeUpdateTaskStatus(args2, fallbackDir, ctx) {
|
|
|
130821
131037
|
phaseRequiresReviewer = false;
|
|
130822
131038
|
}
|
|
130823
131039
|
} catch {}
|
|
130824
|
-
|
|
131040
|
+
const councilCheck = checkCouncilGate(directory, args2.task_id);
|
|
131041
|
+
if (councilCheck.blocked) {
|
|
131042
|
+
return {
|
|
131043
|
+
success: false,
|
|
131044
|
+
message: "Gate check failed: council gate not yet satisfied for task " + args2.task_id,
|
|
131045
|
+
errors: [councilCheck.reason]
|
|
131046
|
+
};
|
|
131047
|
+
}
|
|
131048
|
+
if (phaseRequiresReviewer && !councilCheck.active) {
|
|
130825
131049
|
const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory, ctx?.sessionID, fallbackDir);
|
|
130826
131050
|
if (reviewerCheck.blocked) {
|
|
130827
131051
|
return {
|
|
@@ -131601,7 +131825,7 @@ var VerdictSchema3 = exports_external.object({
|
|
|
131601
131825
|
verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
131602
131826
|
confidence: exports_external.number().min(0).max(1),
|
|
131603
131827
|
findings: exports_external.array(exports_external.object({
|
|
131604
|
-
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
131828
|
+
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
131605
131829
|
category: exports_external.string().min(1),
|
|
131606
131830
|
location: exports_external.string(),
|
|
131607
131831
|
detail: exports_external.string(),
|
|
@@ -131655,6 +131879,17 @@ async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
|
131655
131879
|
}, null, 2);
|
|
131656
131880
|
}
|
|
131657
131881
|
const synthesis = synthesizeFinalCouncilAdvisory(input.projectSummary.trim(), input.verdicts, input.roundNumber ?? 1, config3.council);
|
|
131882
|
+
if (synthesis.overallVerdict === "CONCERNS" && synthesis.blockingConcernsCount > 0) {
|
|
131883
|
+
return JSON.stringify({
|
|
131884
|
+
success: false,
|
|
131885
|
+
reason: "blocking_concerns_unresolved",
|
|
131886
|
+
overallVerdict: synthesis.overallVerdict,
|
|
131887
|
+
blockingConcernsCount: synthesis.blockingConcernsCount,
|
|
131888
|
+
requiredFixes: synthesis.requiredFixes,
|
|
131889
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd,
|
|
131890
|
+
message: `Final council returned CONCERNS with ${synthesis.blockingConcernsCount} HIGH/CRITICAL finding(s) promoted to requiredFixes. These must be resolved before the project can close. Do NOT write evidence or proceed — address every requiredFix and resubmit.`
|
|
131891
|
+
}, null, 2);
|
|
131892
|
+
}
|
|
131658
131893
|
const plan = await loadPlan(directory);
|
|
131659
131894
|
const planId = plan ? derivePlanId(plan) : "unknown";
|
|
131660
131895
|
const normalizedVerdict = normalizeFinalVerdict(synthesis.overallVerdict, synthesis.requiredFixes.length);
|