opencode-swarm 7.5.3 → 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 +27 -21
- package/dist/config/schema.d.ts +0 -10
- package/dist/db/qa-gate-profile.d.ts +2 -1
- package/dist/index.js +494 -168
- 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",
|
|
@@ -518,6 +520,7 @@ var init_constants = __esm(() => {
|
|
|
518
520
|
"imports",
|
|
519
521
|
"retrieve_summary",
|
|
520
522
|
"schema_drift",
|
|
523
|
+
"search",
|
|
521
524
|
"symbols",
|
|
522
525
|
"knowledge_recall"
|
|
523
526
|
],
|
|
@@ -603,6 +606,7 @@ var init_constants = __esm(() => {
|
|
|
603
606
|
"imports",
|
|
604
607
|
"retrieve_summary",
|
|
605
608
|
"schema_drift",
|
|
609
|
+
"search",
|
|
606
610
|
"symbols",
|
|
607
611
|
"todo_extract",
|
|
608
612
|
"knowledge_recall"
|
|
@@ -610,6 +614,7 @@ var init_constants = __esm(() => {
|
|
|
610
614
|
designer: [
|
|
611
615
|
"extract_code_blocks",
|
|
612
616
|
"retrieve_summary",
|
|
617
|
+
"search",
|
|
613
618
|
"symbols",
|
|
614
619
|
"knowledge_recall"
|
|
615
620
|
],
|
|
@@ -659,6 +664,7 @@ var init_constants = __esm(() => {
|
|
|
659
664
|
write_retro: "document phase retrospectives via phase_complete workflow, capture lessons learned",
|
|
660
665
|
write_drift_evidence: "write drift verification evidence for a completed phase",
|
|
661
666
|
write_hallucination_evidence: "write hallucination verification evidence for a completed phase",
|
|
667
|
+
write_final_council_evidence: "write final council evidence for project completion",
|
|
662
668
|
declare_scope: "declare file scope for next coder delegation",
|
|
663
669
|
phase_complete: "mark a phase as complete and track dispatched agents",
|
|
664
670
|
save_plan: "save a structured implementation plan",
|
|
@@ -687,8 +693,8 @@ var init_constants = __esm(() => {
|
|
|
687
693
|
lint_spec: "validate .swarm/spec.md format and required fields",
|
|
688
694
|
get_approved_plan: "retrieve the last critic-approved immutable plan snapshot for baseline drift comparison",
|
|
689
695
|
repo_map: "query the repo code graph: importers, dependencies, blast radius, and localization context for structural awareness before refactoring",
|
|
690
|
-
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.",
|
|
691
|
-
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.",
|
|
692
698
|
req_coverage: "query requirement coverage status for tracked functional requirements"
|
|
693
699
|
};
|
|
694
700
|
for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
|
|
@@ -15484,12 +15490,7 @@ var init_schema = __esm(() => {
|
|
|
15484
15490
|
maxConcurrentTasks: exports_external.number().int().min(1).max(64).default(1),
|
|
15485
15491
|
evidenceLockTimeoutMs: exports_external.number().int().min(1000).max(300000).default(60000),
|
|
15486
15492
|
max_coders: exports_external.number().int().min(1).max(16).default(3),
|
|
15487
|
-
max_reviewers: exports_external.number().int().min(1).max(16).default(2)
|
|
15488
|
-
stageB: exports_external.object({
|
|
15489
|
-
parallel: exports_external.object({
|
|
15490
|
-
enabled: exports_external.boolean().default(false)
|
|
15491
|
-
}).default({ enabled: false })
|
|
15492
|
-
}).default({ parallel: { enabled: false } })
|
|
15493
|
+
max_reviewers: exports_external.number().int().min(1).max(16).default(2)
|
|
15493
15494
|
});
|
|
15494
15495
|
PluginConfigSchema = exports_external.object({
|
|
15495
15496
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
@@ -16748,6 +16749,11 @@ var init_spec_hash = __esm(() => {
|
|
|
16748
16749
|
};
|
|
16749
16750
|
});
|
|
16750
16751
|
|
|
16752
|
+
// src/plan/utils.ts
|
|
16753
|
+
function derivePlanId(plan) {
|
|
16754
|
+
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16755
|
+
}
|
|
16756
|
+
|
|
16751
16757
|
// src/plan/ledger.ts
|
|
16752
16758
|
import * as crypto2 from "node:crypto";
|
|
16753
16759
|
import * as fs4 from "node:fs";
|
|
@@ -16936,7 +16942,7 @@ async function takeSnapshotEvent(directory, plan, options) {
|
|
|
16936
16942
|
if (options?.approvalMetadata) {
|
|
16937
16943
|
snapshotPayload.approval = options.approvalMetadata;
|
|
16938
16944
|
}
|
|
16939
|
-
const planId =
|
|
16945
|
+
const planId = derivePlanId(plan);
|
|
16940
16946
|
return appendLedgerEvent(directory, {
|
|
16941
16947
|
event_type: "snapshot",
|
|
16942
16948
|
source: options?.source ?? "takeSnapshotEvent",
|
|
@@ -17131,7 +17137,7 @@ async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
|
17131
17137
|
continue;
|
|
17132
17138
|
}
|
|
17133
17139
|
if (expectedPlanId !== undefined) {
|
|
17134
|
-
const payloadPlanId =
|
|
17140
|
+
const payloadPlanId = derivePlanId(payload.plan);
|
|
17135
17141
|
if (payloadPlanId !== expectedPlanId) {
|
|
17136
17142
|
continue;
|
|
17137
17143
|
}
|
|
@@ -17332,7 +17338,7 @@ async function loadPlan(directory) {
|
|
|
17332
17338
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
17333
17339
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
17334
17340
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
17335
|
-
const currentPlanId =
|
|
17341
|
+
const currentPlanId = derivePlanId(validated);
|
|
17336
17342
|
const ledgerEvents = await readLedgerEvents(directory);
|
|
17337
17343
|
const firstEvent = ledgerEvents.length > 0 ? ledgerEvents[0] : null;
|
|
17338
17344
|
if (firstEvent && firstEvent.plan_id !== currentPlanId) {
|
|
@@ -17415,7 +17421,7 @@ async function loadPlan(directory) {
|
|
|
17415
17421
|
try {
|
|
17416
17422
|
const rawParsed = JSON.parse(planJsonContent);
|
|
17417
17423
|
if (typeof rawParsed?.swarm === "string" && typeof rawParsed?.title === "string") {
|
|
17418
|
-
rawPlanId =
|
|
17424
|
+
rawPlanId = derivePlanId(rawParsed);
|
|
17419
17425
|
}
|
|
17420
17426
|
} catch {}
|
|
17421
17427
|
if (await ledgerExists(directory)) {
|
|
@@ -17541,7 +17547,7 @@ async function savePlan(directory, plan, options) {
|
|
|
17541
17547
|
}
|
|
17542
17548
|
}
|
|
17543
17549
|
const currentPlan = await _internals6.loadPlanJsonOnly(directory);
|
|
17544
|
-
const planId =
|
|
17550
|
+
const planId = derivePlanId(validated);
|
|
17545
17551
|
const planHashForInit = computePlanHash(validated);
|
|
17546
17552
|
if (!await ledgerExists(directory)) {
|
|
17547
17553
|
try {
|
|
@@ -17642,7 +17648,7 @@ async function savePlan(directory, plan, options) {
|
|
|
17642
17648
|
const oldTask = oldTaskMap.get(task.id);
|
|
17643
17649
|
if (oldTask && oldTask.status !== task.status) {
|
|
17644
17650
|
const eventInput = {
|
|
17645
|
-
plan_id:
|
|
17651
|
+
plan_id: derivePlanId(validated),
|
|
17646
17652
|
event_type: "task_status_changed",
|
|
17647
17653
|
task_id: task.id,
|
|
17648
17654
|
phase_id: phase.id,
|
|
@@ -20545,7 +20551,8 @@ var init_qa_gate_profile = __esm(() => {
|
|
|
20545
20551
|
sast_enabled: true,
|
|
20546
20552
|
mutation_test: false,
|
|
20547
20553
|
council_general_review: false,
|
|
20548
|
-
drift_check: true
|
|
20554
|
+
drift_check: true,
|
|
20555
|
+
final_council: false
|
|
20549
20556
|
};
|
|
20550
20557
|
});
|
|
20551
20558
|
|
|
@@ -25817,7 +25824,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25817
25824
|
hasReviewer = true;
|
|
25818
25825
|
if (targetAgent === "test_engineer")
|
|
25819
25826
|
hasTestEngineer = true;
|
|
25820
|
-
const stageBParallelEnabled =
|
|
25827
|
+
const stageBParallelEnabled = true;
|
|
25821
25828
|
if (stageBParallelEnabled) {
|
|
25822
25829
|
if ((targetAgent === "reviewer" || targetAgent === "test_engineer") && session.taskWorkflowStates) {
|
|
25823
25830
|
const stageBEligibleStates = [
|
|
@@ -25843,6 +25850,20 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25843
25850
|
} catch (err2) {
|
|
25844
25851
|
warn(`[delegation-gate] toolAfter stage-b-parallel: could not advance ${taskId} (${eligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25845
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
|
+
}
|
|
25846
25867
|
}
|
|
25847
25868
|
}
|
|
25848
25869
|
const seedTaskId = getSeedTaskId(session);
|
|
@@ -25872,74 +25893,17 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25872
25893
|
} catch (err2) {
|
|
25873
25894
|
warn(`[delegation-gate] toolAfter cross-session stage-b-parallel: could not advance ${seedTaskId} (${seedEligibleState}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25874
25895
|
}
|
|
25875
|
-
}
|
|
25876
|
-
|
|
25877
|
-
|
|
25878
|
-
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
25882
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
25883
|
-
try {
|
|
25884
|
-
advanceTaskState(session, taskId, "reviewer_run", {
|
|
25885
|
-
telemetrySessionId: input.sessionID
|
|
25886
|
-
});
|
|
25887
|
-
} catch (err2) {
|
|
25888
|
-
warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25889
|
-
}
|
|
25890
|
-
}
|
|
25891
|
-
}
|
|
25892
|
-
}
|
|
25893
|
-
if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
|
|
25894
|
-
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
25895
|
-
if (state === "reviewer_run") {
|
|
25896
|
-
try {
|
|
25897
|
-
advanceTaskState(session, taskId, "tests_run", {
|
|
25898
|
-
telemetrySessionId: input.sessionID
|
|
25899
|
-
});
|
|
25900
|
-
} catch (err2) {
|
|
25901
|
-
warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25902
|
-
}
|
|
25903
|
-
}
|
|
25904
|
-
}
|
|
25905
|
-
}
|
|
25906
|
-
if (targetAgent === "reviewer" || targetAgent === "test_engineer") {
|
|
25907
|
-
for (const [, otherSession] of swarmState.agentSessions) {
|
|
25908
|
-
if (otherSession === session)
|
|
25909
|
-
continue;
|
|
25910
|
-
if (!otherSession.taskWorkflowStates)
|
|
25911
|
-
continue;
|
|
25912
|
-
if (targetAgent === "reviewer") {
|
|
25913
|
-
const seedTaskId = getSeedTaskId(session);
|
|
25914
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
25915
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
|
|
25916
|
-
}
|
|
25917
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
25918
|
-
if (state === "coder_delegated" || state === "pre_check_passed") {
|
|
25919
|
-
try {
|
|
25920
|
-
advanceTaskState(otherSession, taskId, "reviewer_run", {
|
|
25921
|
-
emitTelemetry: false
|
|
25922
|
-
});
|
|
25923
|
-
} catch (err2) {
|
|
25924
|
-
warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25925
|
-
}
|
|
25926
|
-
}
|
|
25927
|
-
}
|
|
25928
|
-
}
|
|
25929
|
-
if (targetAgent === "test_engineer") {
|
|
25930
|
-
const seedTaskId = getSeedTaskId(session);
|
|
25931
|
-
if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
|
|
25932
|
-
otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
|
|
25933
|
-
}
|
|
25934
|
-
for (const [taskId, state] of otherSession.taskWorkflowStates) {
|
|
25935
|
-
if (state === "reviewer_run") {
|
|
25936
|
-
try {
|
|
25937
|
-
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", {
|
|
25938
25902
|
emitTelemetry: false
|
|
25939
25903
|
});
|
|
25940
|
-
} catch (err2) {
|
|
25941
|
-
warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) → tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
25942
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)}`);
|
|
25943
25907
|
}
|
|
25944
25908
|
}
|
|
25945
25909
|
}
|
|
@@ -25950,7 +25914,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
25950
25914
|
if (typeof subagentType === "string") {
|
|
25951
25915
|
try {
|
|
25952
25916
|
const rawTaskId = directArgs?.task_id;
|
|
25953
|
-
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);
|
|
25954
25918
|
if (evidenceTaskId) {
|
|
25955
25919
|
const turbo = hasActiveTurboMode(input.sessionID);
|
|
25956
25920
|
const gateAgents = [
|
|
@@ -26073,7 +26037,7 @@ function createDelegationGateHook(config2, directory) {
|
|
|
26073
26037
|
}
|
|
26074
26038
|
try {
|
|
26075
26039
|
const rawTaskId = directArgs?.task_id;
|
|
26076
|
-
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);
|
|
26077
26041
|
if (evidenceTaskId) {
|
|
26078
26042
|
const turbo = hasActiveTurboMode(input.sessionID);
|
|
26079
26043
|
if (hasReviewer) {
|
|
@@ -26319,6 +26283,7 @@ var init_delegation_gate = __esm(() => {
|
|
|
26319
26283
|
init_state();
|
|
26320
26284
|
init_telemetry();
|
|
26321
26285
|
init_logger();
|
|
26286
|
+
init_task_id();
|
|
26322
26287
|
init_guardrails();
|
|
26323
26288
|
init_normalize_tool_name();
|
|
26324
26289
|
init_utils2();
|
|
@@ -26474,7 +26439,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
|
|
|
26474
26439
|
if (directory) {
|
|
26475
26440
|
let rehydrationPromise;
|
|
26476
26441
|
rehydrationPromise = _internals9.rehydrateSessionFromDisk(directory, sessionState).catch((err2) => {
|
|
26477
|
-
|
|
26442
|
+
warn("[state] Rehydration failed:", err2 instanceof Error ? err2.message : String(err2));
|
|
26478
26443
|
}).finally(() => {
|
|
26479
26444
|
swarmState.pendingRehydrations.delete(rehydrationPromise);
|
|
26480
26445
|
});
|
|
@@ -26760,7 +26725,7 @@ async function advanceTaskStateAndPersist(session, taskId, newState, directory,
|
|
|
26760
26725
|
try {
|
|
26761
26726
|
await updateTaskStatus(directory, taskId, planStatus);
|
|
26762
26727
|
} catch (err2) {
|
|
26763
|
-
|
|
26728
|
+
warn(`[advanceTaskStateAndPersist] persist ${taskId} → ${planStatus} failed (in-memory state still advanced): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
26764
26729
|
}
|
|
26765
26730
|
}
|
|
26766
26731
|
function getTaskState(session, taskId) {
|
|
@@ -26793,9 +26758,6 @@ function hasBothStageBCompletions(session, taskId) {
|
|
|
26793
26758
|
return false;
|
|
26794
26759
|
return completions.has("reviewer") && completions.has("test_engineer");
|
|
26795
26760
|
}
|
|
26796
|
-
function derivePlanIdFromPlan(plan) {
|
|
26797
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
26798
|
-
}
|
|
26799
26761
|
async function isCouncilGateActive(directory, council) {
|
|
26800
26762
|
const enabled = council?.enabled === true;
|
|
26801
26763
|
let plan = null;
|
|
@@ -26807,7 +26769,7 @@ async function isCouncilGateActive(directory, council) {
|
|
|
26807
26769
|
if (!plan) {
|
|
26808
26770
|
return false;
|
|
26809
26771
|
}
|
|
26810
|
-
const planId =
|
|
26772
|
+
const planId = derivePlanId(plan);
|
|
26811
26773
|
let profile = null;
|
|
26812
26774
|
try {
|
|
26813
26775
|
profile = getProfile(directory, planId);
|
|
@@ -26815,7 +26777,7 @@ async function isCouncilGateActive(directory, council) {
|
|
|
26815
26777
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
26816
26778
|
const isBenign = msg.includes("SQLITE_CANTOPEN") || msg.includes("ENOENT");
|
|
26817
26779
|
if (!isBenign) {
|
|
26818
|
-
|
|
26780
|
+
warn(`[isCouncilGateActive] getProfile threw unexpectedly for plan ${planId}: ${msg}. Treating council as inactive.`);
|
|
26819
26781
|
}
|
|
26820
26782
|
profile = null;
|
|
26821
26783
|
}
|
|
@@ -26828,7 +26790,7 @@ async function isCouncilGateActive(directory, council) {
|
|
|
26828
26790
|
}
|
|
26829
26791
|
if (enabled !== councilMode && !_councilDisagreementWarned.has(planId)) {
|
|
26830
26792
|
_councilDisagreementWarned.add(planId);
|
|
26831
|
-
|
|
26793
|
+
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.");
|
|
26832
26794
|
}
|
|
26833
26795
|
return false;
|
|
26834
26796
|
}
|
|
@@ -27038,6 +27000,7 @@ var init_state = __esm(() => {
|
|
|
27038
27000
|
init_delegation_gate();
|
|
27039
27001
|
init_manager();
|
|
27040
27002
|
init_telemetry();
|
|
27003
|
+
init_logger();
|
|
27041
27004
|
_councilDisagreementWarned = new Set;
|
|
27042
27005
|
_toolAggregates = new Map;
|
|
27043
27006
|
defaultRunContext = new AgentRunContext("default", _toolAggregates);
|
|
@@ -53952,9 +53915,6 @@ var init_promote = __esm(() => {
|
|
|
53952
53915
|
});
|
|
53953
53916
|
|
|
53954
53917
|
// src/commands/qa-gates.ts
|
|
53955
|
-
function derivePlanId(plan) {
|
|
53956
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
53957
|
-
}
|
|
53958
53918
|
function isGateName(name2) {
|
|
53959
53919
|
return ALL_GATE_NAMES.includes(name2);
|
|
53960
53920
|
}
|
|
@@ -54080,7 +54040,8 @@ var init_qa_gates = __esm(() => {
|
|
|
54080
54040
|
"sast_enabled",
|
|
54081
54041
|
"mutation_test",
|
|
54082
54042
|
"council_general_review",
|
|
54083
|
-
"drift_check"
|
|
54043
|
+
"drift_check",
|
|
54044
|
+
"final_council"
|
|
54084
54045
|
];
|
|
54085
54046
|
});
|
|
54086
54047
|
|
|
@@ -55144,7 +55105,7 @@ async function handleRollbackCommand(directory, args2) {
|
|
|
55144
55105
|
if (fs27.existsSync(planJsonPath)) {
|
|
55145
55106
|
const planRaw = fs27.readFileSync(planJsonPath, "utf-8");
|
|
55146
55107
|
const plan = PlanSchema.parse(JSON.parse(planRaw));
|
|
55147
|
-
const planId =
|
|
55108
|
+
const planId = derivePlanId(plan);
|
|
55148
55109
|
const planHash = computePlanHash(plan);
|
|
55149
55110
|
await initLedger(directory, planId, planHash, plan);
|
|
55150
55111
|
await appendLedgerEvent(directory, {
|
|
@@ -56676,7 +56637,7 @@ function buildQaGateSelectionDialogue(modeLabel) {
|
|
|
56676
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.";
|
|
56677
56638
|
return `${leadIn}
|
|
56678
56639
|
|
|
56679
|
-
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:
|
|
56680
56641
|
- reviewer (default: ON) — code review of coder output
|
|
56681
56642
|
- test_engineer (default: ON) — test verification of coder output
|
|
56682
56643
|
- sme_enabled (default: ON) — SME consultation during planning/clarification
|
|
@@ -56687,8 +56648,20 @@ Present the ten gates with their defaults (DEFAULT_QA_GATES) as a single user-fa
|
|
|
56687
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)
|
|
56688
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.
|
|
56689
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.
|
|
56690
56654
|
|
|
56691
|
-
|
|
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.`;
|
|
56692
56665
|
}
|
|
56693
56666
|
function buildAvailableToolsList(council) {
|
|
56694
56667
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
@@ -57252,6 +57225,63 @@ SECURITY_KEYWORDS: password, secret, token, credential, auth, login, encryption,
|
|
|
57252
57225
|
{{AGENT_PREFIX}}docs - Documentation updates (README, API docs, guides — NOT .swarm/ files)
|
|
57253
57226
|
{{AGENT_PREFIX}}designer - UI/UX design specs (scaffold generation for UI components — runs BEFORE coder on UI tasks)
|
|
57254
57227
|
|
|
57228
|
+
## SKILLS PROPAGATION
|
|
57229
|
+
|
|
57230
|
+
Subagents run in isolated contexts. Any project-specific skill constraints loaded into your session (e.g. \`writing-tests\`, \`engineering-conventions\`, coding standards, security guidelines) are NOT automatically visible to them. Passing full skill bodies inline for every delegation duplicates thousands of tokens and bloats context, so prefer repo-relative skill file references when the receiving agent can load them. Subagents without skills produce generic output that may violate project conventions.
|
|
57231
|
+
|
|
57232
|
+
### Step 1 — Discover available skills (once per session)
|
|
57233
|
+
|
|
57234
|
+
At session start, before your first delegation:
|
|
57235
|
+
1. Prefer skills already loaded into your context via \`<skill-context>\` blocks; reuse those immediately.
|
|
57236
|
+
2. When you need to inspect on-disk skills, use the \`search\` tool with \`include\` patterns like \`.opencode/skills/*/SKILL.md,.claude/skills/*/SKILL.md\` and frontmatter queries such as \`^name:\` / \`^description:\` so you only read the YAML lines you need.
|
|
57237
|
+
3. Write a brief skill index to \`.swarm/context.md\` under \`## Available Skills\`:
|
|
57238
|
+
- writing-tests: Guidelines for writing tests (bun:test, mock isolation, CI) → test_engineer, coder
|
|
57239
|
+
- engineering-conventions: Engineering invariants for this repo → coder, reviewer, test_engineer
|
|
57240
|
+
- [name]: [description] → [applicable agents]
|
|
57241
|
+
4. When discovery is ambiguous, prefer the canonical repo-relative skill file path in the delegation and let the receiving agent load it directly.
|
|
57242
|
+
|
|
57243
|
+
### Step 2 — Route skills to agents
|
|
57244
|
+
|
|
57245
|
+
Include a skill in a delegation when ANY of the following match:
|
|
57246
|
+
|
|
57247
|
+
| Skill description / name contains… | Pass to agents… |
|
|
57248
|
+
|---------------------------------------------------|---------------------------------------|
|
|
57249
|
+
| "test", "testing", "test files", "writing tests" | test_engineer, coder |
|
|
57250
|
+
| "engineering", "conventions", "invariants", "rules" | coder, reviewer, test_engineer |
|
|
57251
|
+
| "code", "implementation", "coding standards" | coder, reviewer |
|
|
57252
|
+
| "review", "security audit", "security guidelines" | reviewer |
|
|
57253
|
+
| "documentation", "docs", "writing docs" | docs |
|
|
57254
|
+
| "architecture", "design patterns", "ui" | designer, sme |
|
|
57255
|
+
| domain-specific (database, cloud, mobile, etc.) | sme |
|
|
57256
|
+
|
|
57257
|
+
When uncertain: pass the skill. Subagents ignore irrelevant content. A missing applicable skill degrades output quality.
|
|
57258
|
+
|
|
57259
|
+
### Step 3 — Include skill references in delegations
|
|
57260
|
+
|
|
57261
|
+
Add a \`SKILLS:\` field to every delegation that goes to an implementation or review agent (coder, reviewer, test_engineer, sme, docs, designer). Use one of:
|
|
57262
|
+
|
|
57263
|
+
- \`SKILLS: none\` — only when no project-specific skill applies to that delegation
|
|
57264
|
+
- \`SKILLS: file:.claude/skills/writing-tests/SKILL.md\` — preferred for skills that exist on disk; use repo-relative \`file:\` references, comma-separated when multiple skills apply
|
|
57265
|
+
- Inline block fallback:
|
|
57266
|
+
SKILLS:
|
|
57267
|
+
--- [skill-name] ---
|
|
57268
|
+
[full SKILL.md body content pasted here]
|
|
57269
|
+
--- [skill-name-2] ---
|
|
57270
|
+
[full SKILL.md body content pasted here]
|
|
57271
|
+
|
|
57272
|
+
Default to repo-relative \`file:\` references for coder, reviewer, test_engineer, and sme. Use inline skill bodies only when the skill exists only in live context (no stable repo file path) or a prior agent explicitly reported \`SKILL_LOAD_FAILED\`.
|
|
57273
|
+
|
|
57274
|
+
**SKILL_LOAD_FAILED recovery:** If a subagent reports SKILL_LOAD_FAILED for a \`file:\` reference, do NOT retry with the same reference. Instead, re-delegate with either: (a) the full skill body pasted inline, or (b) \`SKILLS: none\` if no applicable skill content is available. Never re-use a file: reference that has already failed.
|
|
57275
|
+
|
|
57276
|
+
**Mandatory for coding tasks:** Always provide \`writing-tests\` to test_engineer and \`engineering-conventions\` to coder + reviewer when those skills are present in the project. Prefer \`file:\` references when the files exist.
|
|
57277
|
+
|
|
57278
|
+
### ANTI-RATIONALIZATION
|
|
57279
|
+
- ✗ "The coder already knows these conventions" → Skills contain project-specific rules the model cannot know from training. Always pass.
|
|
57280
|
+
- ✗ "It's a simple task, skills aren't needed" → A short \`file:\` reference is cheap. Missing skill constraints cause convention drift. Always pass.
|
|
57281
|
+
- ✗ "I don't know which skill is relevant" → When uncertain, pass ALL discovered skills. Subagents discard inapplicable content.
|
|
57282
|
+
- ✗ "The skill was loaded earlier so the agent knows it" → Each subagent Task call is a fresh context. Skills do NOT persist across Task boundaries.
|
|
57283
|
+
- ✗ "I'll paste the whole skill body every time just to be safe" → Inline bodies are fallback only. Prefer \`file:\` references to avoid unnecessary context bloat.
|
|
57284
|
+
|
|
57255
57285
|
## SLASH COMMANDS
|
|
57256
57286
|
{{SLASH_COMMANDS}}
|
|
57257
57287
|
Commands above are documented with args and behavioral details. Run commands via /swarm <command> [args].
|
|
@@ -57265,15 +57295,13 @@ Available Tools: {{AVAILABLE_TOOLS}}
|
|
|
57265
57295
|
|
|
57266
57296
|
Delegations are performed ONLY by calling the **Task** tool. Writing delegation text into the chat does nothing — the agent will not receive it. Every delegation below is the content you pass to the Task tool, not text you output to the conversation.
|
|
57267
57297
|
|
|
57268
|
-
All delegations MUST
|
|
57298
|
+
All delegations MUST follow the receiving agent's INPUT FORMAT exactly. Do NOT invent fields, omit required fields, or force one agent's schema onto another. Every delegation MUST begin with the agent name, include \`TASK:\`, and include \`SKILLS:\` when that agent prompt supports skills.
|
|
57269
57299
|
Do NOT add conversational preamble before the agent prefix. Begin directly with the agent name.
|
|
57270
57300
|
|
|
57271
57301
|
{{AGENT_PREFIX}}[agent]
|
|
57272
57302
|
TASK: [single objective]
|
|
57273
|
-
|
|
57274
|
-
|
|
57275
|
-
OUTPUT: [expected deliverable format]
|
|
57276
|
-
CONSTRAINT: [what NOT to do]
|
|
57303
|
+
[agent-specific fields required by that agent's INPUT FORMAT]
|
|
57304
|
+
SKILLS: [either "none", repo-relative file: references, or inline skill bodies — see SKILLS PROPAGATION; use "none" only when no project-specific skill applies]
|
|
57277
57305
|
|
|
57278
57306
|
Examples:
|
|
57279
57307
|
|
|
@@ -57281,6 +57309,7 @@ Examples:
|
|
|
57281
57309
|
TASK: Analyze codebase for auth implementation
|
|
57282
57310
|
INPUT: Focus on src/auth/, src/middleware/
|
|
57283
57311
|
OUTPUT: Structure, frameworks, key files, relevant domains
|
|
57312
|
+
SKILLS: none
|
|
57284
57313
|
|
|
57285
57314
|
{{AGENT_PREFIX}}sme
|
|
57286
57315
|
TASK: Review auth token patterns
|
|
@@ -57288,12 +57317,14 @@ DOMAIN: security
|
|
|
57288
57317
|
INPUT: src/auth/login.ts uses JWT with RS256
|
|
57289
57318
|
OUTPUT: Security considerations, recommended patterns
|
|
57290
57319
|
CONSTRAINT: Focus on auth only, not general code style
|
|
57320
|
+
SKILLS: none
|
|
57291
57321
|
|
|
57292
57322
|
{{AGENT_PREFIX}}sme
|
|
57293
57323
|
TASK: Advise on state management approach
|
|
57294
57324
|
DOMAIN: ios
|
|
57295
57325
|
INPUT: Building a SwiftUI app with offline-first sync
|
|
57296
57326
|
OUTPUT: Recommended patterns, frameworks, gotchas
|
|
57327
|
+
SKILLS: none
|
|
57297
57328
|
|
|
57298
57329
|
PRE-STEP (required): call \`declare_scope({ taskId, files })\` BEFORE writing any {{AGENT_PREFIX}}coder delegation. See Rule 1a.
|
|
57299
57330
|
|
|
@@ -57303,6 +57334,7 @@ FILE: src/auth/login.ts
|
|
|
57303
57334
|
INPUT: Validate email format, password >= 8 chars
|
|
57304
57335
|
OUTPUT: Modified file
|
|
57305
57336
|
CONSTRAINT: Do not modify other functions
|
|
57337
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
57306
57338
|
|
|
57307
57339
|
{{AGENT_PREFIX}}reviewer
|
|
57308
57340
|
TASK: Review login validation
|
|
@@ -57310,17 +57342,20 @@ FILE: src/auth/login.ts
|
|
|
57310
57342
|
CHECK: [security, correctness, edge-cases]
|
|
57311
57343
|
GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
|
|
57312
57344
|
OUTPUT: VERDICT + RISK + ISSUES
|
|
57345
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
57313
57346
|
|
|
57314
57347
|
{{AGENT_PREFIX}}test_engineer
|
|
57315
57348
|
TASK: Generate and run login validation tests
|
|
57316
57349
|
FILE: src/auth/login.ts
|
|
57317
57350
|
OUTPUT: Test file at src/auth/login.test.ts + VERDICT: PASS/FAIL with failure details
|
|
57351
|
+
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
57318
57352
|
|
|
57319
57353
|
{{AGENT_PREFIX}}critic
|
|
57320
57354
|
TASK: Review plan for user authentication feature
|
|
57321
57355
|
PLAN: [paste the plan.md content]
|
|
57322
57356
|
CONTEXT: [codebase summary from explorer]
|
|
57323
57357
|
OUTPUT: VERDICT + CONFIDENCE + ISSUES + SUMMARY
|
|
57358
|
+
SKILLS: none
|
|
57324
57359
|
|
|
57325
57360
|
{{AGENT_PREFIX}}reviewer
|
|
57326
57361
|
TASK: Security-only review of login validation
|
|
@@ -57328,18 +57363,21 @@ FILE: src/auth/login.ts
|
|
|
57328
57363
|
CHECK: [security-only] — evaluate against OWASP Top 10, scan for hardcoded secrets, injection vectors, insecure crypto, missing input validation
|
|
57329
57364
|
GATES: lint=PASS, sast_scan=PASS, secretscan=PASS
|
|
57330
57365
|
OUTPUT: VERDICT + RISK + SECURITY ISSUES ONLY
|
|
57366
|
+
SKILLS: file:.claude/skills/engineering-conventions/SKILL.md
|
|
57331
57367
|
|
|
57332
57368
|
{{AGENT_PREFIX}}test_engineer
|
|
57333
57369
|
TASK: Adversarial security testing
|
|
57334
57370
|
FILE: src/auth/login.ts
|
|
57335
57371
|
CONSTRAINT: ONLY attack vectors — malformed inputs, oversized payloads, injection attempts, auth bypass, boundary violations
|
|
57336
57372
|
OUTPUT: Test file + VERDICT: PASS/FAIL
|
|
57373
|
+
SKILLS: file:.claude/skills/writing-tests/SKILL.md
|
|
57337
57374
|
|
|
57338
57375
|
{{AGENT_PREFIX}}explorer
|
|
57339
57376
|
TASK: Integration impact analysis
|
|
57340
57377
|
INPUT: Contract changes detected: [list from diff tool]
|
|
57341
57378
|
OUTPUT: BREAKING_CHANGES + COMPATIBLE_CHANGES + CONSUMERS_AFFECTED + COMPATIBILITY SIGNALS: [COMPATIBLE | INCOMPATIBLE | UNCERTAIN] + MIGRATION_SURFACE: [yes — list of affected call signatures | no]
|
|
57342
57379
|
CONSTRAINT: Read-only. use search to find imports/usages of changed exports.
|
|
57380
|
+
SKILLS: none
|
|
57343
57381
|
|
|
57344
57382
|
{{AGENT_PREFIX}}docs
|
|
57345
57383
|
TASK: Update documentation for Phase 2 changes
|
|
@@ -57350,6 +57388,7 @@ CHANGES SUMMARY:
|
|
|
57350
57388
|
- Added UserSession interface with refreshToken field
|
|
57351
57389
|
DOC FILES: README.md, docs/api.md, docs/installation.md
|
|
57352
57390
|
OUTPUT: Updated doc files + SUMMARY
|
|
57391
|
+
SKILLS: none
|
|
57353
57392
|
|
|
57354
57393
|
{{AGENT_PREFIX}}designer
|
|
57355
57394
|
TASK: Design specification for user settings page
|
|
@@ -57357,6 +57396,7 @@ CONTEXT: Users need to update profile info, change password, manage notification
|
|
|
57357
57396
|
FRAMEWORK: React (TSX)
|
|
57358
57397
|
EXISTING PATTERNS: All forms use react-hook-form, validation with zod, toast notifications for success/error
|
|
57359
57398
|
OUTPUT: Code scaffold for src/pages/Settings.tsx with component tree, typed props, layout, and accessibility
|
|
57399
|
+
SKILLS: none
|
|
57360
57400
|
|
|
57361
57401
|
## WORKFLOW
|
|
57362
57402
|
|
|
@@ -57458,6 +57498,7 @@ Do NOT call \`set_qa_gates\` yet — \`plan.json\` does not exist at this point.
|
|
|
57458
57498
|
- mutation_test: <true|false>
|
|
57459
57499
|
- council_general_review: <true|false>
|
|
57460
57500
|
- drift_check: <true|false>
|
|
57501
|
+
- final_council: <true|false>
|
|
57461
57502
|
- recorded_at: <ISO timestamp>
|
|
57462
57503
|
\`\`\`
|
|
57463
57504
|
MODE: PLAN applies these after \`save_plan\` succeeds via \`set_qa_gates\`.
|
|
@@ -57536,6 +57577,7 @@ Do NOT call \`set_qa_gates\` yet — \`plan.json\` does not exist at this point.
|
|
|
57536
57577
|
- mutation_test: <true|false>
|
|
57537
57578
|
- council_general_review: <true|false>
|
|
57538
57579
|
- drift_check: <true|false>
|
|
57580
|
+
- final_council: <true|false>
|
|
57539
57581
|
- recorded_at: <ISO timestamp>
|
|
57540
57582
|
\`\`\`
|
|
57541
57583
|
MODE: PLAN will read this section after \`save_plan\` succeeds and persist via \`set_qa_gates\`.
|
|
@@ -57918,6 +57960,7 @@ save_plan({
|
|
|
57918
57960
|
**POST-SAVE_PLAN: APPLY QA GATE SELECTION.**
|
|
57919
57961
|
After \`save_plan\` succeeds, read \`.swarm/context.md\`:
|
|
57920
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.
|
|
57921
57964
|
- If no pending section exists: {{QA_GATE_DIALOGUE_PLAN}}
|
|
57922
57965
|
<!-- BEHAVIORAL_GUIDANCE_START -->
|
|
57923
57966
|
INLINE GATE SELECTION — no pending section found in context.md. You MUST ask now.
|
|
@@ -58272,6 +58315,19 @@ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{
|
|
|
58272
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
|
|
58273
58316
|
If any required file is missing, run the missing gate first. Turbo mode skips all gates automatically.
|
|
58274
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.
|
|
58275
58331
|
6. Summarize to user
|
|
58276
58332
|
7. Ask: "Ready for Phase [N+1]?"
|
|
58277
58333
|
|
|
@@ -58363,6 +58419,14 @@ FILE: [target file]
|
|
|
58363
58419
|
INPUT: [requirements/context]
|
|
58364
58420
|
OUTPUT: [expected deliverable]
|
|
58365
58421
|
CONSTRAINT: [what NOT to do]
|
|
58422
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
58423
|
+
|
|
58424
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before writing any code.
|
|
58425
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
58426
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
58427
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
58428
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
58429
|
+
- Skills contain project-specific rules (test framework, naming conventions, coding standards, architectural constraints) that supplement and extend your default behavior. Apply every rule in every skill, including any lines marked MUST, NEVER, MANDATORY, or PROHIBITED — but never violate your core safety protocols or scope constraints.
|
|
58366
58430
|
|
|
58367
58431
|
RULES:
|
|
58368
58432
|
- Read target file before editing
|
|
@@ -59348,6 +59412,14 @@ TASK: Design specification for [component/page/screen]
|
|
|
59348
59412
|
CONTEXT: [what the component does, user stories, existing design patterns]
|
|
59349
59413
|
FRAMEWORK: [React/Vue/Svelte/SwiftUI/Flutter/etc.]
|
|
59350
59414
|
EXISTING PATTERNS: [current design system, component library, styling approach]
|
|
59415
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
59416
|
+
|
|
59417
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before producing the design specification.
|
|
59418
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
59419
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
59420
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
59421
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
59422
|
+
- Apply any architecture, design-system, accessibility, or UI-specific constraints from the loaded skills while producing the scaffold.
|
|
59351
59423
|
|
|
59352
59424
|
DESIGN CHECKLIST:
|
|
59353
59425
|
1. Component Architecture
|
|
@@ -59524,6 +59596,14 @@ TASK: Update documentation for [description of changes]
|
|
|
59524
59596
|
FILES CHANGED: [list of modified source files]
|
|
59525
59597
|
CHANGES SUMMARY: [what was added/modified/removed]
|
|
59526
59598
|
DOC FILES: [list of documentation files to update]
|
|
59599
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
59600
|
+
|
|
59601
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before updating docs.
|
|
59602
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
59603
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
59604
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
59605
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
59606
|
+
- Apply any documentation, release-note, or style constraints from the loaded skills while updating documentation.
|
|
59527
59607
|
|
|
59528
59608
|
SCOPE:
|
|
59529
59609
|
- README.md (project description, usage, examples)
|
|
@@ -59823,6 +59903,14 @@ DIFF: [changed files/functions, or "infer from FILE" if omitted]
|
|
|
59823
59903
|
AFFECTS: [callers/consumers/dependents to inspect, or "infer from diff"]
|
|
59824
59904
|
CHECK: [list of dimensions to evaluate]
|
|
59825
59905
|
GATES: [pre-completed gate results (lint, SAST, secretscan, etc.), or "none" if unavailable]
|
|
59906
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
59907
|
+
|
|
59908
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before beginning your review.
|
|
59909
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
59910
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
59911
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
59912
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
59913
|
+
- Skills contain project-specific constraints (coding standards, architectural invariants, security requirements) that supplement and may extend your normal review dimensions. Flag any violation of a skill rule at the same severity as a logic error.
|
|
59826
59914
|
|
|
59827
59915
|
PROCESSING: If GATES is provided and includes passing results for lint, SAST, placeholder-scan, or secret-scan: skip the corresponding Tier 2 checks that those gates already cover. Focus Tier 2 time on checks NOT covered by automated gates.
|
|
59828
59916
|
|
|
@@ -59930,6 +60018,14 @@ Match response length to confidence and complexity. HIGH confidence on simple lo
|
|
|
59930
60018
|
TASK: [what guidance is needed]
|
|
59931
60019
|
DOMAIN: [the domain - e.g., security, ios, android, rust, kubernetes]
|
|
59932
60020
|
INPUT: [context/requirements]
|
|
60021
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
60022
|
+
|
|
60023
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before formulating your recommendation.
|
|
60024
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
60025
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
60026
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
60027
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
60028
|
+
- Skills may contain project-specific constraints relevant to your domain (e.g. security rules, platform requirements, coding standards). Where skills add constraints to your recommendation, list them explicitly in your APPROACH and GOTCHAS.
|
|
59933
60029
|
|
|
59934
60030
|
## OUTPUT FORMAT (MANDATORY — deviations will be rejected)
|
|
59935
60031
|
Begin directly with CONFIDENCE. Do NOT prepend "Here's my research..." or any conversational preamble.
|
|
@@ -60049,6 +60145,14 @@ INPUT FORMAT:
|
|
|
60049
60145
|
TASK: Generate tests for [description]
|
|
60050
60146
|
FILE: [source file path]
|
|
60051
60147
|
OUTPUT: [test file path]
|
|
60148
|
+
SKILLS: [optional — either "none", repo-relative file: references (preferred), or inline skill content pasted by architect]
|
|
60149
|
+
|
|
60150
|
+
SKILLS HANDLING: If SKILLS is present and not "none", load EVERY referenced skill before writing any test code.
|
|
60151
|
+
- For \`file:\` entries, use the search tool to read the referenced \`SKILL.md\` file with \`include\` set to that exact repo-relative path, \`mode: regex\`, \`query: .*\`, \`max_results: 1000\`, and \`max_lines: 1000\`.
|
|
60152
|
+
- After running search, inspect the result: if \`total === 0\` (file does not exist or is empty) OR \`truncated\` is \`true\` (file was too large and content was cut off), stop and report \`SKILL_LOAD_FAILED: <path>\`. Do NOT continue without the complete skill.
|
|
60153
|
+
- If the search result has \`total > 0\` and \`truncated\` is \`false\`, reconstruct the full skill content from the line-by-line matches and apply it.
|
|
60154
|
+
- If inline \`--- skill-name ---\` sections are present, read them directly.
|
|
60155
|
+
- Skills override your default framework choices, mock patterns, file placement conventions, and CI rules. Apply every MUST, NEVER, MANDATORY, and PROHIBITED rule precisely.
|
|
60052
60156
|
|
|
60053
60157
|
COVERAGE:
|
|
60054
60158
|
- Happy path: normal inputs
|
|
@@ -65543,7 +65647,7 @@ var init_curator_drift = __esm(() => {
|
|
|
65543
65647
|
init_package();
|
|
65544
65648
|
init_agents2();
|
|
65545
65649
|
init_critic();
|
|
65546
|
-
import * as
|
|
65650
|
+
import * as path116 from "node:path";
|
|
65547
65651
|
|
|
65548
65652
|
// src/background/index.ts
|
|
65549
65653
|
init_event_bus();
|
|
@@ -78736,7 +78840,7 @@ var diff = createSwarmTool({
|
|
|
78736
78840
|
encoding: "utf-8",
|
|
78737
78841
|
timeout: 3000,
|
|
78738
78842
|
cwd: directory,
|
|
78739
|
-
stdio: "pipe"
|
|
78843
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78740
78844
|
});
|
|
78741
78845
|
return true;
|
|
78742
78846
|
} catch (e) {
|
|
@@ -78750,9 +78854,9 @@ var diff = createSwarmTool({
|
|
|
78750
78854
|
}, getContentFromRef = function(refPath) {
|
|
78751
78855
|
return child_process7.execFileSync("git", ["show", refPath], {
|
|
78752
78856
|
encoding: "utf-8",
|
|
78753
|
-
timeout:
|
|
78857
|
+
timeout: 15000,
|
|
78754
78858
|
cwd: directory,
|
|
78755
|
-
stdio: "pipe"
|
|
78859
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78756
78860
|
});
|
|
78757
78861
|
};
|
|
78758
78862
|
if (!directory || typeof directory !== "string" || directory.trim() === "") {
|
|
@@ -78803,13 +78907,15 @@ var diff = createSwarmTool({
|
|
|
78803
78907
|
encoding: "utf-8",
|
|
78804
78908
|
timeout: DIFF_TIMEOUT_MS,
|
|
78805
78909
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
78806
|
-
cwd: directory
|
|
78910
|
+
cwd: directory,
|
|
78911
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78807
78912
|
});
|
|
78808
78913
|
const fullDiffOutput = child_process7.execFileSync("git", fullDiffArgs, {
|
|
78809
78914
|
encoding: "utf-8",
|
|
78810
78915
|
timeout: DIFF_TIMEOUT_MS,
|
|
78811
78916
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
78812
|
-
cwd: directory
|
|
78917
|
+
cwd: directory,
|
|
78918
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
78813
78919
|
});
|
|
78814
78920
|
const files = [];
|
|
78815
78921
|
const numstatLines = numstatOutput.split(`
|
|
@@ -79578,9 +79684,6 @@ function summarizePlan(plan) {
|
|
|
79578
79684
|
}))
|
|
79579
79685
|
};
|
|
79580
79686
|
}
|
|
79581
|
-
function derivePlanId2(plan) {
|
|
79582
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
79583
|
-
}
|
|
79584
79687
|
async function executeGetApprovedPlan(args2, directory) {
|
|
79585
79688
|
const currentPlan = await loadPlanJsonOnly(directory);
|
|
79586
79689
|
if (!currentPlan) {
|
|
@@ -79599,7 +79702,7 @@ async function executeGetApprovedPlan(args2, directory) {
|
|
|
79599
79702
|
reason: "no_approved_snapshot"
|
|
79600
79703
|
};
|
|
79601
79704
|
}
|
|
79602
|
-
const expectedPlanId =
|
|
79705
|
+
const expectedPlanId = derivePlanId(currentPlan);
|
|
79603
79706
|
const profile = getProfile(directory, expectedPlanId);
|
|
79604
79707
|
const qaProfileHash = profile ? computeProfileHash(profile) : null;
|
|
79605
79708
|
const approved = await loadLastApprovedPlan(directory, expectedPlanId);
|
|
@@ -79658,9 +79761,6 @@ var get_approved_plan = createSwarmTool({
|
|
|
79658
79761
|
init_qa_gate_profile();
|
|
79659
79762
|
init_manager();
|
|
79660
79763
|
init_create_tool();
|
|
79661
|
-
function derivePlanId3(plan) {
|
|
79662
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
79663
|
-
}
|
|
79664
79764
|
async function executeGetQaGateProfile(_args, directory) {
|
|
79665
79765
|
const plan = await loadPlanJsonOnly(directory);
|
|
79666
79766
|
if (!plan) {
|
|
@@ -79669,7 +79769,7 @@ async function executeGetQaGateProfile(_args, directory) {
|
|
|
79669
79769
|
reason: "plan_json_unavailable"
|
|
79670
79770
|
};
|
|
79671
79771
|
}
|
|
79672
|
-
const planId =
|
|
79772
|
+
const planId = derivePlanId(plan);
|
|
79673
79773
|
const profile = getProfile(directory, planId);
|
|
79674
79774
|
if (!profile) {
|
|
79675
79775
|
return {
|
|
@@ -80841,7 +80941,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
80841
80941
|
}, null, 2);
|
|
80842
80942
|
}
|
|
80843
80943
|
if (hasActiveTurboMode(sessionID)) {
|
|
80844
|
-
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}`);
|
|
80845
80945
|
} else {
|
|
80846
80946
|
try {
|
|
80847
80947
|
const completionResultRaw = await executeCompletionVerify({ phase }, dir);
|
|
@@ -80870,7 +80970,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
80870
80970
|
driftHasSpecMd = fs69.existsSync(specMdPath);
|
|
80871
80971
|
const gatePlan = await loadPlan(dir);
|
|
80872
80972
|
if (gatePlan) {
|
|
80873
|
-
const gatePlanId =
|
|
80973
|
+
const gatePlanId = derivePlanId(gatePlan);
|
|
80874
80974
|
const gateProfile = getProfile(dir, gatePlanId);
|
|
80875
80975
|
if (gateProfile) {
|
|
80876
80976
|
const gateSession = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -80999,7 +81099,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
80999
81099
|
try {
|
|
81000
81100
|
const plan = await loadPlan(dir);
|
|
81001
81101
|
if (plan) {
|
|
81002
|
-
const planId =
|
|
81102
|
+
const planId = derivePlanId(plan);
|
|
81003
81103
|
const profile = getProfile(dir, planId);
|
|
81004
81104
|
if (profile) {
|
|
81005
81105
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81071,7 +81171,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
81071
81171
|
try {
|
|
81072
81172
|
const plan = await loadPlan(dir);
|
|
81073
81173
|
if (plan) {
|
|
81074
|
-
const planId =
|
|
81174
|
+
const planId = derivePlanId(plan);
|
|
81075
81175
|
const profile = getProfile(dir, planId);
|
|
81076
81176
|
if (profile) {
|
|
81077
81177
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81144,7 +81244,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
|
|
|
81144
81244
|
try {
|
|
81145
81245
|
const plan = await loadPlan(dir);
|
|
81146
81246
|
if (plan) {
|
|
81147
|
-
const planId =
|
|
81247
|
+
const planId = derivePlanId(plan);
|
|
81148
81248
|
const profile = getProfile(dir, planId);
|
|
81149
81249
|
if (profile) {
|
|
81150
81250
|
const session2 = sessionID ? swarmState.agentSessions.get(sessionID) : undefined;
|
|
@@ -81345,6 +81445,127 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
81345
81445
|
}
|
|
81346
81446
|
}
|
|
81347
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
|
+
}
|
|
81348
81569
|
let knowledgeConfig;
|
|
81349
81570
|
try {
|
|
81350
81571
|
knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
|
|
@@ -86681,7 +86902,10 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
86681
86902
|
} catch {}
|
|
86682
86903
|
const hasPendingSection = contextContent.includes("## Pending QA Gate Selection");
|
|
86683
86904
|
if (!hasPendingSection) {
|
|
86684
|
-
const candidatePlanId =
|
|
86905
|
+
const candidatePlanId = derivePlanId({
|
|
86906
|
+
swarm: args2.swarm_id,
|
|
86907
|
+
title: args2.title
|
|
86908
|
+
});
|
|
86685
86909
|
let existingProfile = null;
|
|
86686
86910
|
try {
|
|
86687
86911
|
existingProfile = getProfile(targetWorkspace, candidatePlanId);
|
|
@@ -86802,7 +87026,7 @@ async function executeSavePlan(args2, fallbackDir) {
|
|
|
86802
87026
|
await takeSnapshotEvent(dir, savedPlan).catch(() => {});
|
|
86803
87027
|
}
|
|
86804
87028
|
if (resolvedProfile !== undefined && savedPlan) {
|
|
86805
|
-
const planId =
|
|
87029
|
+
const planId = derivePlanId(plan);
|
|
86806
87030
|
const planHashAfter = computePlanHash(savedPlan);
|
|
86807
87031
|
const profileChanged = JSON.stringify(resolvedProfile) !== JSON.stringify(preservedExecutionProfile);
|
|
86808
87032
|
if (profileChanged) {
|
|
@@ -88684,9 +88908,6 @@ init_zod();
|
|
|
88684
88908
|
init_qa_gate_profile();
|
|
88685
88909
|
init_manager();
|
|
88686
88910
|
init_create_tool();
|
|
88687
|
-
function derivePlanId4(plan) {
|
|
88688
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
88689
|
-
}
|
|
88690
88911
|
async function executeSetQaGates(args2, directory) {
|
|
88691
88912
|
const plan = await loadPlanJsonOnly(directory);
|
|
88692
88913
|
if (!plan) {
|
|
@@ -88696,7 +88917,7 @@ async function executeSetQaGates(args2, directory) {
|
|
|
88696
88917
|
message: "Cannot configure QA gates: plan.json is missing or invalid. " + "Create a plan first (e.g. via /swarm specify or save_plan)."
|
|
88697
88918
|
};
|
|
88698
88919
|
}
|
|
88699
|
-
const planId =
|
|
88920
|
+
const planId = derivePlanId(plan);
|
|
88700
88921
|
getOrCreateProfile(directory, planId, args2.project_type);
|
|
88701
88922
|
const partial3 = {};
|
|
88702
88923
|
for (const key of [
|
|
@@ -88709,7 +88930,8 @@ async function executeSetQaGates(args2, directory) {
|
|
|
88709
88930
|
"sast_enabled",
|
|
88710
88931
|
"mutation_test",
|
|
88711
88932
|
"council_general_review",
|
|
88712
|
-
"drift_check"
|
|
88933
|
+
"drift_check",
|
|
88934
|
+
"final_council"
|
|
88713
88935
|
]) {
|
|
88714
88936
|
if (args2[key] !== undefined)
|
|
88715
88937
|
partial3[key] = args2[key];
|
|
@@ -88757,6 +88979,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
88757
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."),
|
|
88758
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."),
|
|
88759
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."),
|
|
88760
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.')
|
|
88761
88984
|
},
|
|
88762
88985
|
execute: async (args2, directory) => {
|
|
@@ -90855,6 +91078,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
90855
91078
|
} catch {}
|
|
90856
91079
|
}
|
|
90857
91080
|
const resolvedDir = workingDirectory ?? process.cwd();
|
|
91081
|
+
let evidenceIncompleteReason = null;
|
|
90858
91082
|
try {
|
|
90859
91083
|
const evidence = readTaskEvidenceRaw(resolvedDir, taskId);
|
|
90860
91084
|
if (evidence === null) {} else if (evidence.required_gates && Array.isArray(evidence.required_gates) && evidence.gates) {
|
|
@@ -90863,11 +91087,7 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
90863
91087
|
return { blocked: false, reason: "" };
|
|
90864
91088
|
}
|
|
90865
91089
|
const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
|
|
90866
|
-
|
|
90867
|
-
return {
|
|
90868
|
-
blocked: true,
|
|
90869
|
-
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.`
|
|
90870
|
-
};
|
|
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.`;
|
|
90871
91091
|
}
|
|
90872
91092
|
} catch (error93) {
|
|
90873
91093
|
console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
|
|
@@ -90957,23 +91177,18 @@ function checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled = fal
|
|
|
90957
91177
|
}
|
|
90958
91178
|
}
|
|
90959
91179
|
const currentStateStr = stateEntries.length > 0 ? stateEntries.join(", ") : "no active sessions";
|
|
90960
|
-
|
|
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`);
|
|
90961
91182
|
return {
|
|
90962
91183
|
blocked: true,
|
|
90963
|
-
reason:
|
|
91184
|
+
reason: finalReason
|
|
90964
91185
|
};
|
|
90965
91186
|
} catch {
|
|
90966
91187
|
return { blocked: false, reason: "" };
|
|
90967
91188
|
}
|
|
90968
91189
|
}
|
|
90969
91190
|
async function checkReviewerGateWithScope(taskId, workingDirectory) {
|
|
90970
|
-
|
|
90971
|
-
if (workingDirectory) {
|
|
90972
|
-
try {
|
|
90973
|
-
const cfg = await loadPluginConfig(workingDirectory);
|
|
90974
|
-
stageBParallelEnabled = cfg.parallelization?.stageB?.parallel?.enabled === true;
|
|
90975
|
-
} catch {}
|
|
90976
|
-
}
|
|
91191
|
+
const stageBParallelEnabled = true;
|
|
90977
91192
|
const result = checkReviewerGate(taskId, workingDirectory, stageBParallelEnabled);
|
|
90978
91193
|
const scopeWarning = await validateDiffScope(taskId, workingDirectory).catch(() => null);
|
|
90979
91194
|
if (!scopeWarning)
|
|
@@ -91480,9 +91695,6 @@ init_manager();
|
|
|
91480
91695
|
init_create_tool();
|
|
91481
91696
|
import fs89 from "node:fs";
|
|
91482
91697
|
import path112 from "node:path";
|
|
91483
|
-
function derivePlanId5(plan) {
|
|
91484
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
91485
|
-
}
|
|
91486
91698
|
function normalizeVerdict(verdict) {
|
|
91487
91699
|
switch (verdict) {
|
|
91488
91700
|
case "APPROVED":
|
|
@@ -91569,7 +91781,7 @@ async function executeWriteDriftEvidence(args2, directory) {
|
|
|
91569
91781
|
timestamp: snapshotEvent.timestamp
|
|
91570
91782
|
};
|
|
91571
91783
|
try {
|
|
91572
|
-
const planId =
|
|
91784
|
+
const planId = derivePlanId(currentPlan);
|
|
91573
91785
|
const locked = lockProfile(directory, planId, snapshotEvent.seq);
|
|
91574
91786
|
qaProfileLocked = {
|
|
91575
91787
|
plan_id: planId,
|
|
@@ -91636,9 +91848,10 @@ var write_drift_evidence = createSwarmTool({
|
|
|
91636
91848
|
}
|
|
91637
91849
|
}
|
|
91638
91850
|
});
|
|
91639
|
-
// src/tools/write-
|
|
91851
|
+
// src/tools/write-final-council-evidence.ts
|
|
91640
91852
|
init_zod();
|
|
91641
91853
|
init_utils2();
|
|
91854
|
+
init_manager();
|
|
91642
91855
|
init_create_tool();
|
|
91643
91856
|
import fs90 from "node:fs";
|
|
91644
91857
|
import path113 from "node:path";
|
|
@@ -91652,7 +91865,7 @@ function normalizeVerdict2(verdict) {
|
|
|
91652
91865
|
throw new Error(`Invalid verdict: must be 'APPROVED' or 'NEEDS_REVISION', got '${verdict}'`);
|
|
91653
91866
|
}
|
|
91654
91867
|
}
|
|
91655
|
-
async function
|
|
91868
|
+
async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
91656
91869
|
const phase = args2.phase;
|
|
91657
91870
|
if (!Number.isInteger(phase) || phase < 1) {
|
|
91658
91871
|
return JSON.stringify({
|
|
@@ -91678,18 +91891,21 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
91678
91891
|
}, null, 2);
|
|
91679
91892
|
}
|
|
91680
91893
|
const normalizedVerdict = normalizeVerdict2(args2.verdict);
|
|
91894
|
+
const plan = await loadPlan(directory);
|
|
91895
|
+
const planId = plan ? derivePlanId(plan) : "unknown";
|
|
91681
91896
|
const evidenceEntry = {
|
|
91682
|
-
type: "
|
|
91897
|
+
type: "final-council",
|
|
91898
|
+
phase,
|
|
91899
|
+
plan_id: planId,
|
|
91683
91900
|
verdict: normalizedVerdict,
|
|
91684
91901
|
summary: summary.trim(),
|
|
91685
|
-
timestamp: new Date().toISOString()
|
|
91686
|
-
findings: args2.findings
|
|
91902
|
+
timestamp: new Date().toISOString()
|
|
91687
91903
|
};
|
|
91688
91904
|
const evidenceContent = {
|
|
91689
91905
|
entries: [evidenceEntry]
|
|
91690
91906
|
};
|
|
91691
|
-
const filename = "
|
|
91692
|
-
const relativePath = path113.join("evidence",
|
|
91907
|
+
const filename = "final-council.json";
|
|
91908
|
+
const relativePath = path113.join("evidence", filename);
|
|
91693
91909
|
let validatedPath;
|
|
91694
91910
|
try {
|
|
91695
91911
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -91706,6 +91922,115 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
91706
91922
|
const tempPath = path113.join(evidenceDir, `.${filename}.tmp`);
|
|
91707
91923
|
await fs90.promises.writeFile(tempPath, JSON.stringify(evidenceContent, null, 2), "utf-8");
|
|
91708
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);
|
|
91709
92034
|
return JSON.stringify({
|
|
91710
92035
|
success: true,
|
|
91711
92036
|
phase,
|
|
@@ -91751,9 +92076,9 @@ var write_hallucination_evidence = createSwarmTool({
|
|
|
91751
92076
|
init_zod();
|
|
91752
92077
|
init_utils2();
|
|
91753
92078
|
init_create_tool();
|
|
91754
|
-
import
|
|
91755
|
-
import
|
|
91756
|
-
function
|
|
92079
|
+
import fs92 from "node:fs";
|
|
92080
|
+
import path115 from "node:path";
|
|
92081
|
+
function normalizeVerdict4(verdict) {
|
|
91757
92082
|
switch (verdict) {
|
|
91758
92083
|
case "PASS":
|
|
91759
92084
|
return "pass";
|
|
@@ -91810,7 +92135,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91810
92135
|
message: "Invalid summary: must be a non-empty string"
|
|
91811
92136
|
}, null, 2);
|
|
91812
92137
|
}
|
|
91813
|
-
const normalizedVerdict =
|
|
92138
|
+
const normalizedVerdict = normalizeVerdict4(args2.verdict);
|
|
91814
92139
|
const evidenceEntry = {
|
|
91815
92140
|
type: "mutation-gate",
|
|
91816
92141
|
verdict: normalizedVerdict,
|
|
@@ -91826,7 +92151,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91826
92151
|
entries: [evidenceEntry]
|
|
91827
92152
|
};
|
|
91828
92153
|
const filename = "mutation-gate.json";
|
|
91829
|
-
const relativePath =
|
|
92154
|
+
const relativePath = path115.join("evidence", String(phase), filename);
|
|
91830
92155
|
let validatedPath;
|
|
91831
92156
|
try {
|
|
91832
92157
|
validatedPath = validateSwarmPath(directory, relativePath);
|
|
@@ -91837,12 +92162,12 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
91837
92162
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
91838
92163
|
}, null, 2);
|
|
91839
92164
|
}
|
|
91840
|
-
const evidenceDir =
|
|
92165
|
+
const evidenceDir = path115.dirname(validatedPath);
|
|
91841
92166
|
try {
|
|
91842
|
-
await
|
|
91843
|
-
const tempPath =
|
|
91844
|
-
await
|
|
91845
|
-
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);
|
|
91846
92171
|
return JSON.stringify({
|
|
91847
92172
|
success: true,
|
|
91848
92173
|
phase,
|
|
@@ -92134,7 +92459,7 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
92134
92459
|
const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
|
|
92135
92460
|
preflightTriggerManager = new PTM(automationConfig);
|
|
92136
92461
|
const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
|
|
92137
|
-
const swarmDir =
|
|
92462
|
+
const swarmDir = path116.resolve(ctx.directory, ".swarm");
|
|
92138
92463
|
statusArtifact = new ASA(swarmDir);
|
|
92139
92464
|
statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
|
|
92140
92465
|
if (automationConfig.capabilities?.evidence_auto_summaries === true) {
|
|
@@ -92293,6 +92618,7 @@ async function initializeOpenCodeSwarm(ctx) {
|
|
|
92293
92618
|
write_drift_evidence,
|
|
92294
92619
|
write_hallucination_evidence,
|
|
92295
92620
|
write_mutation_evidence,
|
|
92621
|
+
write_final_council_evidence,
|
|
92296
92622
|
declare_scope
|
|
92297
92623
|
},
|
|
92298
92624
|
config: async (opencodeConfig) => {
|