opencode-swarm 7.17.0 → 7.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +27 -6
- package/dist/council/council-service.d.ts +7 -1
- package/dist/council/types.d.ts +32 -0
- package/dist/hooks/knowledge-injector.d.ts +2 -1
- package/dist/hooks/knowledge-types.d.ts +1 -1
- package/dist/hooks/knowledge-validator.d.ts +1 -0
- package/dist/index.js +295 -91
- package/dist/tools/write-final-council-evidence.d.ts +51 -14
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.17.
|
|
37
|
+
version: "7.17.2",
|
|
38
38
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
39
39
|
main: "dist/index.js",
|
|
40
40
|
types: "dist/index.d.ts",
|
|
@@ -15935,7 +15935,6 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
15935
15935
|
return "Spec staleness file was corrupted. It has been removed.";
|
|
15936
15936
|
}
|
|
15937
15937
|
const { planTitle, phase } = stalenessData;
|
|
15938
|
-
await fsPromises3.unlink(specStalenessPath);
|
|
15939
15938
|
let currentHash = null;
|
|
15940
15939
|
let planUpdateSkipped = false;
|
|
15941
15940
|
try {
|
|
@@ -15949,6 +15948,9 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
15949
15948
|
console.error("[acknowledge-spec-drift] Failed to update plan specHash:", planError instanceof Error ? planError.message : String(planError));
|
|
15950
15949
|
planUpdateSkipped = true;
|
|
15951
15950
|
}
|
|
15951
|
+
if (!planUpdateSkipped) {
|
|
15952
|
+
await fsPromises3.unlink(specStalenessPath);
|
|
15953
|
+
}
|
|
15952
15954
|
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
15953
15955
|
const acknowledgmentEvent = {
|
|
15954
15956
|
type: "spec_drift_acknowledged",
|
|
@@ -35412,6 +35414,8 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
35412
35414
|
const remaining = entries.filter((e) => e.id !== entryId);
|
|
35413
35415
|
const quarantined = {
|
|
35414
35416
|
...entry,
|
|
35417
|
+
status: "quarantined",
|
|
35418
|
+
original_status: entry.status,
|
|
35415
35419
|
quarantine_reason: sanitizedReason,
|
|
35416
35420
|
quarantined_at: new Date().toISOString(),
|
|
35417
35421
|
reported_by: reportedBy
|
|
@@ -35470,7 +35474,24 @@ async function restoreEntry(directory, entryId) {
|
|
|
35470
35474
|
return;
|
|
35471
35475
|
}
|
|
35472
35476
|
const remaining = quarantinedEntries.filter((e) => e.id !== entryId);
|
|
35473
|
-
const {
|
|
35477
|
+
const {
|
|
35478
|
+
quarantine_reason,
|
|
35479
|
+
quarantined_at,
|
|
35480
|
+
reported_by,
|
|
35481
|
+
original_status,
|
|
35482
|
+
status: _quarantineStatus,
|
|
35483
|
+
...rest
|
|
35484
|
+
} = entryToRestore;
|
|
35485
|
+
const original = { ...rest, status: original_status ?? "candidate" };
|
|
35486
|
+
const validation = validateLesson(original.lesson, [], {
|
|
35487
|
+
category: original.category,
|
|
35488
|
+
scope: original.scope,
|
|
35489
|
+
confidence: original.confidence
|
|
35490
|
+
});
|
|
35491
|
+
if (!validation.valid) {
|
|
35492
|
+
warn(`[knowledge-validator] restoreEntry: entry ${entryId} failed re-validation: ${validation.reason}`);
|
|
35493
|
+
return;
|
|
35494
|
+
}
|
|
35474
35495
|
const jsonlContent = remaining.length > 0 ? `${remaining.map((e) => JSON.stringify(e)).join(`
|
|
35475
35496
|
`)}
|
|
35476
35497
|
` : "";
|
|
@@ -46325,7 +46346,7 @@ async function selectEntryPoints2(dir) {
|
|
|
46325
46346
|
`)) {
|
|
46326
46347
|
const m = line.match(/=\s*['"]([^'":]+)/);
|
|
46327
46348
|
if (m) {
|
|
46328
|
-
const modPath = m[1].replace(/\./g, "/")
|
|
46349
|
+
const modPath = `${m[1].replace(/\./g, "/")}.py`;
|
|
46329
46350
|
points.add(modPath);
|
|
46330
46351
|
}
|
|
46331
46352
|
}
|
|
@@ -47508,7 +47529,7 @@ function clearDispatchCache() {
|
|
|
47508
47529
|
manifestRootCache.clear();
|
|
47509
47530
|
insertCounter = 0;
|
|
47510
47531
|
}
|
|
47511
|
-
var _internals22, cache, insertCounter = 0, MANIFEST_FILES,
|
|
47532
|
+
var _internals22, cache, insertCounter = 0, MANIFEST_FILES, _MANIFEST_SET, manifestRootCache;
|
|
47512
47533
|
var init_dispatch = __esm(() => {
|
|
47513
47534
|
init_backends();
|
|
47514
47535
|
init_detector();
|
|
@@ -47540,7 +47561,7 @@ var init_dispatch = __esm(() => {
|
|
|
47540
47561
|
"Gemfile",
|
|
47541
47562
|
"composer.json"
|
|
47542
47563
|
];
|
|
47543
|
-
|
|
47564
|
+
_MANIFEST_SET = new Set(MANIFEST_FILES);
|
|
47544
47565
|
manifestRootCache = new Map;
|
|
47545
47566
|
});
|
|
47546
47567
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* No I/O — fully unit-testable with mock inputs. All file reads/writes happen in
|
|
9
9
|
* sibling modules (criteria-store, council-evidence-writer).
|
|
10
10
|
*/
|
|
11
|
-
import type { CouncilConfig, CouncilCriteria, CouncilMemberVerdict, CouncilSynthesis, PhaseCouncilSynthesis } from './types';
|
|
11
|
+
import type { CouncilConfig, CouncilCriteria, CouncilMemberVerdict, CouncilSynthesis, FinalCouncilSynthesis, PhaseCouncilSynthesis } from './types';
|
|
12
12
|
export declare function synthesizeCouncilVerdicts(taskId: string, swarmId: string, verdicts: CouncilMemberVerdict[], criteria: CouncilCriteria | null, roundNumber: number, config?: Partial<CouncilConfig>): CouncilSynthesis;
|
|
13
13
|
/**
|
|
14
14
|
* Synthesize phase-level council verdicts into a PhaseCouncilSynthesis.
|
|
@@ -19,3 +19,9 @@ export declare function synthesizeCouncilVerdicts(taskId: string, swarmId: strin
|
|
|
19
19
|
* (see writePhaseCouncilEvidence in submit-phase-council-verdicts.ts).
|
|
20
20
|
*/
|
|
21
21
|
export declare function synthesizePhaseCouncilAdvisory(phaseNumber: number, phaseSummary: string, verdicts: CouncilMemberVerdict[], roundNumber: number, config?: Partial<CouncilConfig>, _workingDir?: string): PhaseCouncilSynthesis;
|
|
22
|
+
/**
|
|
23
|
+
* Synthesize project-level final council verdicts into a FinalCouncilSynthesis.
|
|
24
|
+
* This uses the same five-member verdict semantics as phase council, but the
|
|
25
|
+
* output is scoped to completed-project review and the final_council gate.
|
|
26
|
+
*/
|
|
27
|
+
export declare function synthesizeFinalCouncilAdvisory(projectSummary: string, verdicts: CouncilMemberVerdict[], roundNumber: number, config?: Partial<CouncilConfig>): FinalCouncilSynthesis;
|
package/dist/council/types.d.ts
CHANGED
|
@@ -87,6 +87,38 @@ export interface PhaseCouncilSynthesis {
|
|
|
87
87
|
/** Summary of the phase being reviewed */
|
|
88
88
|
phaseSummary?: string;
|
|
89
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Project-level final council synthesis result.
|
|
92
|
+
* Distinct from task-level and phase-level council results: this is the
|
|
93
|
+
* final project close gate and writes to .swarm/evidence/final-council.json.
|
|
94
|
+
*/
|
|
95
|
+
export interface FinalCouncilSynthesis {
|
|
96
|
+
/** Always 'project' - distinguishes final council from task/phase councils */
|
|
97
|
+
scope: 'project';
|
|
98
|
+
/** ISO 8601 */
|
|
99
|
+
timestamp: string;
|
|
100
|
+
overallVerdict: CouncilVerdict;
|
|
101
|
+
vetoedBy: CouncilAgent[] | null;
|
|
102
|
+
memberVerdicts: CouncilMemberVerdict[];
|
|
103
|
+
unresolvedConflicts: string[];
|
|
104
|
+
/** Severity HIGH + MEDIUM from veto members */
|
|
105
|
+
requiredFixes: CouncilFinding[];
|
|
106
|
+
/** Severity LOW or from non-veto members */
|
|
107
|
+
advisoryFindings: CouncilFinding[];
|
|
108
|
+
/** Project-level advisory notes for the architect */
|
|
109
|
+
advisoryNotes: string[];
|
|
110
|
+
/** Single markdown document for final project review */
|
|
111
|
+
unifiedFeedbackMd: string;
|
|
112
|
+
/** 1-indexed */
|
|
113
|
+
roundNumber: number;
|
|
114
|
+
allCriteriaMet: boolean;
|
|
115
|
+
/** Distinct council members that produced verdicts */
|
|
116
|
+
quorumSize: number;
|
|
117
|
+
/** Path where evidence was written */
|
|
118
|
+
evidencePath: '.swarm/evidence/final-council.json';
|
|
119
|
+
/** Summary of the completed project being reviewed */
|
|
120
|
+
projectSummary: string;
|
|
121
|
+
}
|
|
90
122
|
export interface CouncilCriteriaItem {
|
|
91
123
|
id: string;
|
|
92
124
|
description: string;
|
|
@@ -9,7 +9,8 @@ import type { KnowledgeConfig, MessageWithParts } from './knowledge-types.js';
|
|
|
9
9
|
/**
|
|
10
10
|
* Creates a knowledge injection hook that injects relevant knowledge into the
|
|
11
11
|
* architect's message context at phase start. Supports caching for re-injection
|
|
12
|
-
* after compaction.
|
|
12
|
+
* after compaction. Cache is per-instance (bound to the returned hook closure),
|
|
13
|
+
* ensuring no cross-test pollution in Bun's shared test-runner process.
|
|
13
14
|
*
|
|
14
15
|
* @param directory - The project directory containing .swarm/
|
|
15
16
|
* @param config - Knowledge system configuration
|
|
@@ -81,7 +81,7 @@ export interface KnowledgeEntryBase extends ActionableDirectiveFields {
|
|
|
81
81
|
tags: string[];
|
|
82
82
|
scope: string;
|
|
83
83
|
confidence: number;
|
|
84
|
-
status: 'candidate' | 'established' | 'promoted' | 'archived';
|
|
84
|
+
status: 'candidate' | 'established' | 'promoted' | 'archived' | 'quarantined';
|
|
85
85
|
confirmed_by: PhaseConfirmationRecord[] | ProjectConfirmationRecord[];
|
|
86
86
|
retrieval_outcomes: RetrievalOutcome;
|
|
87
87
|
schema_version: number;
|
|
@@ -31,6 +31,7 @@ export declare function validateSkillPath(p: unknown): boolean;
|
|
|
31
31
|
export declare function validateActionableFields(fields: ActionableDirectiveFields | undefined): ActionableValidationResult;
|
|
32
32
|
export type { ActionableDirectiveFields, DirectivePriority };
|
|
33
33
|
export interface QuarantinedEntry extends KnowledgeEntryBase {
|
|
34
|
+
original_status: string;
|
|
34
35
|
quarantine_reason: string;
|
|
35
36
|
quarantined_at: string;
|
|
36
37
|
reported_by: 'architect' | 'user' | 'auto';
|
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.17.
|
|
36
|
+
version: "7.17.2",
|
|
37
37
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
38
38
|
main: "dist/index.js",
|
|
39
39
|
types: "dist/index.d.ts",
|
|
@@ -18445,7 +18445,6 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
18445
18445
|
return "Spec staleness file was corrupted. It has been removed.";
|
|
18446
18446
|
}
|
|
18447
18447
|
const { planTitle, phase } = stalenessData;
|
|
18448
|
-
await fsPromises4.unlink(specStalenessPath);
|
|
18449
18448
|
let currentHash = null;
|
|
18450
18449
|
let planUpdateSkipped = false;
|
|
18451
18450
|
try {
|
|
@@ -18459,6 +18458,9 @@ async function handleAcknowledgeSpecDriftCommand(directory, _args) {
|
|
|
18459
18458
|
console.error("[acknowledge-spec-drift] Failed to update plan specHash:", planError instanceof Error ? planError.message : String(planError));
|
|
18460
18459
|
planUpdateSkipped = true;
|
|
18461
18460
|
}
|
|
18461
|
+
if (!planUpdateSkipped) {
|
|
18462
|
+
await fsPromises4.unlink(specStalenessPath);
|
|
18463
|
+
}
|
|
18462
18464
|
const eventsPath = validateSwarmPath(directory, "events.jsonl");
|
|
18463
18465
|
const acknowledgmentEvent = {
|
|
18464
18466
|
type: "spec_drift_acknowledged",
|
|
@@ -42470,6 +42472,8 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
|
|
|
42470
42472
|
const remaining = entries.filter((e) => e.id !== entryId);
|
|
42471
42473
|
const quarantined = {
|
|
42472
42474
|
...entry,
|
|
42475
|
+
status: "quarantined",
|
|
42476
|
+
original_status: entry.status,
|
|
42473
42477
|
quarantine_reason: sanitizedReason,
|
|
42474
42478
|
quarantined_at: new Date().toISOString(),
|
|
42475
42479
|
reported_by: reportedBy
|
|
@@ -42528,7 +42532,24 @@ async function restoreEntry(directory, entryId) {
|
|
|
42528
42532
|
return;
|
|
42529
42533
|
}
|
|
42530
42534
|
const remaining = quarantinedEntries.filter((e) => e.id !== entryId);
|
|
42531
|
-
const {
|
|
42535
|
+
const {
|
|
42536
|
+
quarantine_reason,
|
|
42537
|
+
quarantined_at,
|
|
42538
|
+
reported_by,
|
|
42539
|
+
original_status,
|
|
42540
|
+
status: _quarantineStatus,
|
|
42541
|
+
...rest
|
|
42542
|
+
} = entryToRestore;
|
|
42543
|
+
const original = { ...rest, status: original_status ?? "candidate" };
|
|
42544
|
+
const validation = validateLesson(original.lesson, [], {
|
|
42545
|
+
category: original.category,
|
|
42546
|
+
scope: original.scope,
|
|
42547
|
+
confidence: original.confidence
|
|
42548
|
+
});
|
|
42549
|
+
if (!validation.valid) {
|
|
42550
|
+
warn(`[knowledge-validator] restoreEntry: entry ${entryId} failed re-validation: ${validation.reason}`);
|
|
42551
|
+
return;
|
|
42552
|
+
}
|
|
42532
42553
|
const jsonlContent = remaining.length > 0 ? `${remaining.map((e) => JSON.stringify(e)).join(`
|
|
42533
42554
|
`)}
|
|
42534
42555
|
` : "";
|
|
@@ -44359,7 +44380,9 @@ async function readMergedKnowledge(directory, config3, context) {
|
|
|
44359
44380
|
const maxInject = config3.max_inject_count ?? 5;
|
|
44360
44381
|
const topN = ranked.slice(0, maxInject);
|
|
44361
44382
|
if (topN.length > 0 && context?.currentPhase) {
|
|
44362
|
-
recordLessonsShown(directory, topN.map((e) => e.id), context.currentPhase).catch(() => {
|
|
44383
|
+
recordLessonsShown(directory, topN.map((e) => e.id), context.currentPhase).catch((err2) => {
|
|
44384
|
+
warn("[knowledge-reader] recordLessonsShown unexpected rejection:", err2);
|
|
44385
|
+
});
|
|
44363
44386
|
}
|
|
44364
44387
|
return topN;
|
|
44365
44388
|
}
|
|
@@ -54848,7 +54871,7 @@ async function selectEntryPoints2(dir) {
|
|
|
54848
54871
|
`)) {
|
|
54849
54872
|
const m = line.match(/=\s*['"]([^'":]+)/);
|
|
54850
54873
|
if (m) {
|
|
54851
|
-
const modPath = m[1].replace(/\./g, "/")
|
|
54874
|
+
const modPath = `${m[1].replace(/\./g, "/")}.py`;
|
|
54852
54875
|
points.add(modPath);
|
|
54853
54876
|
}
|
|
54854
54877
|
}
|
|
@@ -56031,7 +56054,7 @@ function clearDispatchCache() {
|
|
|
56031
56054
|
manifestRootCache.clear();
|
|
56032
56055
|
insertCounter = 0;
|
|
56033
56056
|
}
|
|
56034
|
-
var _internals28, cache, insertCounter = 0, MANIFEST_FILES,
|
|
56057
|
+
var _internals28, cache, insertCounter = 0, MANIFEST_FILES, _MANIFEST_SET, manifestRootCache;
|
|
56035
56058
|
var init_dispatch = __esm(() => {
|
|
56036
56059
|
init_backends();
|
|
56037
56060
|
init_detector();
|
|
@@ -56063,7 +56086,7 @@ var init_dispatch = __esm(() => {
|
|
|
56063
56086
|
"Gemfile",
|
|
56064
56087
|
"composer.json"
|
|
56065
56088
|
];
|
|
56066
|
-
|
|
56089
|
+
_MANIFEST_SET = new Set(MANIFEST_FILES);
|
|
56067
56090
|
manifestRootCache = new Map;
|
|
56068
56091
|
});
|
|
56069
56092
|
|
|
@@ -61416,7 +61439,7 @@ Present the eleven gates with their defaults (DEFAULT_QA_GATES) as a single user
|
|
|
61416
61439
|
- 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)
|
|
61417
61440
|
- 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.
|
|
61418
61441
|
- 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)
|
|
61419
|
-
- final_council (default: OFF)
|
|
61442
|
+
- final_council (default: OFF) - when enabled, after all phases complete the architect dispatches the same five phase-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\`.
|
|
61420
61443
|
|
|
61421
61444
|
One question, one message, defaults pre-stated. Wait for the user's answer.
|
|
61422
61445
|
|
|
@@ -63256,19 +63279,15 @@ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{
|
|
|
63256
63279
|
- \`.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
|
|
63257
63280
|
If any required file is missing, run the missing gate first. Turbo mode skips all gates automatically.
|
|
63258
63281
|
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.
|
|
63259
|
-
5.7. **Final Council (conditional on QA gate
|
|
63282
|
+
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.
|
|
63260
63283
|
If enabled AND this is the LAST phase in the plan (all other phases have status 'complete' and no more phases remain):
|
|
63261
|
-
|
|
63262
|
-
2.
|
|
63263
|
-
3.
|
|
63264
|
-
4.
|
|
63265
|
-
5.
|
|
63266
|
-
6.
|
|
63267
|
-
|
|
63268
|
-
8. Present the final synthesis to the user as a project-close summary.
|
|
63269
|
-
9. Write the final council result to \`.swarm/evidence/final-council.json\`.
|
|
63270
|
-
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.
|
|
63271
|
-
If enabled but NOT the last phase, skip silently — final council only runs once, after all phases.
|
|
63284
|
+
1. Build a PROJECT DOSSIER from the completed plan, all phase summaries, changed-file summaries, and all relevant evidence artifacts. This is a completed-project review, not General Council mode.
|
|
63285
|
+
2. Dispatch \`{{AGENT_PREFIX}}critic\`, \`{{AGENT_PREFIX}}reviewer\`, \`{{AGENT_PREFIX}}sme\`, \`{{AGENT_PREFIX}}test_engineer\`, and \`{{AGENT_PREFIX}}explorer\` in PARALLEL with project-scoped context. Each member must review the entire completed body of work and return a \`CouncilMemberVerdict\` JSON object using \`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT), \`confidence\`, \`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`, and \`durationMs\`.
|
|
63286
|
+
3. Collect the five returned verdict objects. Do NOT fabricate, infer, or substitute verdicts. If a member does not return valid JSON, re-dispatch that member.
|
|
63287
|
+
4. Call \`write_final_council_evidence\` with \`phase\`, \`projectSummary\`, \`roundNumber\`, and the collected \`verdicts\` array. This writes \`.swarm/evidence/final-council.json\` with plan binding, member verdicts, and quorum metadata.
|
|
63288
|
+
5. Do NOT call \`convene_general_council\`, do NOT dispatch \`council_generalist\`, \`council_skeptic\`, or \`council_domain_expert\`, and do NOT require \`council.general.enabled\` for this gate. \`final_council\` is the same five-member phase council rerun at project scope.
|
|
63289
|
+
6. Do NOT call \`phase_complete\` or \`/swarm close\` until \`.swarm/evidence/final-council.json\` exists with an approved, plan-bound, quorumed final-council verdict. When \`final_council\` is enabled, \`phase_complete\` will block until that evidence exists.
|
|
63290
|
+
If enabled but NOT the last phase, skip silently - final council only runs once, after all phases.
|
|
63272
63291
|
6. Summarize to user
|
|
63273
63292
|
7. Ask: "Ready for Phase [N+1]?"
|
|
63274
63293
|
|
|
@@ -71420,7 +71439,7 @@ function decisionFromVerdict(verdict, escalationNeeded) {
|
|
|
71420
71439
|
if (verdict === "APPROVED" || verdict === "ANSWER")
|
|
71421
71440
|
return "allow";
|
|
71422
71441
|
if (verdict === "BLOCKED")
|
|
71423
|
-
return "
|
|
71442
|
+
return "deny";
|
|
71424
71443
|
if (verdict === "PENDING")
|
|
71425
71444
|
return "pending";
|
|
71426
71445
|
return "deny";
|
|
@@ -80646,7 +80665,7 @@ function createFullAutoPermissionHook(options) {
|
|
|
80646
80665
|
phase: effectivePhase,
|
|
80647
80666
|
taskID: taskId ?? undefined,
|
|
80648
80667
|
planID: runState.planID,
|
|
80649
|
-
architectOutput: undefined,
|
|
80668
|
+
architectOutput: output.args ? JSON.stringify(output.args) : undefined,
|
|
80650
80669
|
actionContext: {
|
|
80651
80670
|
tool: toolName,
|
|
80652
80671
|
...decision.context ?? {}
|
|
@@ -81352,6 +81371,7 @@ init_extractors();
|
|
|
81352
81371
|
init_knowledge_reader();
|
|
81353
81372
|
init_knowledge_store();
|
|
81354
81373
|
init_utils2();
|
|
81374
|
+
var INJECTION_SENTINEL = "[[KNOWLEDGE-INJECTED]]";
|
|
81355
81375
|
function buildKnowledgeBlock(entries, charBudget, cfg, currentProject) {
|
|
81356
81376
|
if (entries.length === 0)
|
|
81357
81377
|
return null;
|
|
@@ -81444,7 +81464,7 @@ function isOrchestratorAgent(agentName) {
|
|
|
81444
81464
|
function injectKnowledgeMessage(output, text) {
|
|
81445
81465
|
if (!output.messages)
|
|
81446
81466
|
return;
|
|
81447
|
-
const alreadyInjected = output.messages.some((m) => m.parts?.some((p) => p.text?.includes(
|
|
81467
|
+
const alreadyInjected = output.messages.some((m) => m.parts?.some((p) => p.text?.includes(INJECTION_SENTINEL)));
|
|
81448
81468
|
if (alreadyInjected)
|
|
81449
81469
|
return;
|
|
81450
81470
|
let insertIdx = output.messages.length - 1;
|
|
@@ -81456,14 +81476,11 @@ function injectKnowledgeMessage(output, text) {
|
|
|
81456
81476
|
}
|
|
81457
81477
|
const knowledgeMessage = {
|
|
81458
81478
|
info: { role: "system" },
|
|
81459
|
-
parts: [{ type: "text", text }]
|
|
81479
|
+
parts: [{ type: "text", text: `${INJECTION_SENTINEL}${text}` }]
|
|
81460
81480
|
};
|
|
81461
81481
|
output.messages.splice(insertIdx, 0, knowledgeMessage);
|
|
81462
81482
|
}
|
|
81463
81483
|
function createKnowledgeInjectorHook(directory, config3) {
|
|
81464
|
-
let lastSeenCacheKey = null;
|
|
81465
|
-
let cachedInjectionText = null;
|
|
81466
|
-
let cachedShownIds = [];
|
|
81467
81484
|
function buildContextCacheKey(phase, ctx) {
|
|
81468
81485
|
const parts2 = [
|
|
81469
81486
|
String(phase),
|
|
@@ -81475,6 +81492,9 @@ function createKnowledgeInjectorHook(directory, config3) {
|
|
|
81475
81492
|
].join("|");
|
|
81476
81493
|
return createHash7("sha1").update(parts2).digest("hex").slice(0, 16);
|
|
81477
81494
|
}
|
|
81495
|
+
let lastSeenCacheKey = null;
|
|
81496
|
+
let cachedInjectionText = null;
|
|
81497
|
+
let cachedShownIds = [];
|
|
81478
81498
|
return safeHook(async (_input, output) => {
|
|
81479
81499
|
if (!output.messages || output.messages.length === 0)
|
|
81480
81500
|
return;
|
|
@@ -85332,6 +85352,99 @@ function buildPhaseCouncilFeedback(phaseNumber, phaseSummary, verdict, vetoedBy,
|
|
|
85332
85352
|
return lines.join(`
|
|
85333
85353
|
`);
|
|
85334
85354
|
}
|
|
85355
|
+
function synthesizeFinalCouncilAdvisory(projectSummary, verdicts, roundNumber, config3 = {}) {
|
|
85356
|
+
const cfg = { ...COUNCIL_DEFAULTS, ...config3 };
|
|
85357
|
+
const timestamp = new Date().toISOString();
|
|
85358
|
+
const quorumSize = new Set(verdicts.map((v) => v.agent)).size;
|
|
85359
|
+
const rejectingMembers = verdicts.filter((v) => v.verdict === "REJECT").map((v) => v.agent);
|
|
85360
|
+
let overallVerdict;
|
|
85361
|
+
if (cfg.vetoPriority && rejectingMembers.length > 0) {
|
|
85362
|
+
overallVerdict = "REJECT";
|
|
85363
|
+
} else if (verdicts.some((v) => v.verdict === "CONCERNS") || !cfg.vetoPriority && rejectingMembers.length > 0) {
|
|
85364
|
+
overallVerdict = "CONCERNS";
|
|
85365
|
+
} else {
|
|
85366
|
+
overallVerdict = "APPROVE";
|
|
85367
|
+
}
|
|
85368
|
+
const unresolvedConflicts = detectConflicts(verdicts);
|
|
85369
|
+
const rejectingSet = new Set(rejectingMembers);
|
|
85370
|
+
const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
85371
|
+
const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
85372
|
+
const advisoryFindings = [
|
|
85373
|
+
...vetoFindings.filter((f) => f.severity === "LOW"),
|
|
85374
|
+
...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
|
|
85375
|
+
];
|
|
85376
|
+
const advisoryNotes = [];
|
|
85377
|
+
if (advisoryFindings.length > 0) {
|
|
85378
|
+
advisoryNotes.push(`Final council found ${advisoryFindings.length} advisory finding(s). Review before project close.`);
|
|
85379
|
+
}
|
|
85380
|
+
if (quorumSize < 3) {
|
|
85381
|
+
advisoryNotes.push(`Final council quorum is ${quorumSize} members - dispatch additional project-scoped council members before closing the project.`);
|
|
85382
|
+
}
|
|
85383
|
+
const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
|
|
85384
|
+
const allCriteriaMet = allUnmetIds.size === 0 && verdicts.length > 0;
|
|
85385
|
+
const unifiedFeedbackMd = buildFinalCouncilFeedback(projectSummary, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
|
|
85386
|
+
return {
|
|
85387
|
+
scope: "project",
|
|
85388
|
+
timestamp,
|
|
85389
|
+
overallVerdict,
|
|
85390
|
+
vetoedBy: rejectingMembers.length > 0 ? rejectingMembers : null,
|
|
85391
|
+
memberVerdicts: verdicts,
|
|
85392
|
+
unresolvedConflicts,
|
|
85393
|
+
requiredFixes,
|
|
85394
|
+
advisoryFindings,
|
|
85395
|
+
advisoryNotes,
|
|
85396
|
+
unifiedFeedbackMd,
|
|
85397
|
+
roundNumber,
|
|
85398
|
+
allCriteriaMet,
|
|
85399
|
+
quorumSize,
|
|
85400
|
+
evidencePath: ".swarm/evidence/final-council.json",
|
|
85401
|
+
projectSummary
|
|
85402
|
+
};
|
|
85403
|
+
}
|
|
85404
|
+
function buildFinalCouncilFeedback(projectSummary, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
|
|
85405
|
+
const lines = [
|
|
85406
|
+
`## Final Council Review - Round ${roundNumber}/${maxRounds}`,
|
|
85407
|
+
`**Scope:** completed project **Overall verdict:** ${verdict}`,
|
|
85408
|
+
""
|
|
85409
|
+
];
|
|
85410
|
+
if (projectSummary) {
|
|
85411
|
+
lines.push(`**Project Summary:** ${projectSummary}`);
|
|
85412
|
+
lines.push("");
|
|
85413
|
+
}
|
|
85414
|
+
if (vetoedBy.length > 0) {
|
|
85415
|
+
lines.push(`> BLOCKED: project close is blocked by ${vetoedBy.join(", ")}`);
|
|
85416
|
+
lines.push("");
|
|
85417
|
+
}
|
|
85418
|
+
if (requiredFixes.length > 0) {
|
|
85419
|
+
lines.push("### Required Fixes (must resolve before project close)");
|
|
85420
|
+
for (const f of requiredFixes) {
|
|
85421
|
+
lines.push(`- **[${f.severity}]** \`${f.location}\` - ${f.detail}`, ` _Evidence:_ ${f.evidence}`);
|
|
85422
|
+
}
|
|
85423
|
+
lines.push("");
|
|
85424
|
+
}
|
|
85425
|
+
if (conflicts.length > 0) {
|
|
85426
|
+
lines.push("### Conflicts to Resolve");
|
|
85427
|
+
lines.push("_The following council members gave contradictory project-close instructions. Architect must resolve before closing the project._");
|
|
85428
|
+
for (const c of conflicts) {
|
|
85429
|
+
lines.push(`- ${c}`);
|
|
85430
|
+
}
|
|
85431
|
+
lines.push("");
|
|
85432
|
+
}
|
|
85433
|
+
if (advisoryFindings.length > 0) {
|
|
85434
|
+
lines.push("### Advisory Findings (non-blocking)");
|
|
85435
|
+
for (const f of advisoryFindings) {
|
|
85436
|
+
lines.push(`- **[${f.severity}]** \`${f.location}\` - ${f.detail}`);
|
|
85437
|
+
}
|
|
85438
|
+
lines.push("");
|
|
85439
|
+
}
|
|
85440
|
+
if (verdict === "APPROVE") {
|
|
85441
|
+
lines.push("> Final council approved. Project may proceed to close.");
|
|
85442
|
+
} else if (roundNumber >= maxRounds) {
|
|
85443
|
+
lines.push(`> Max rounds (${maxRounds}) reached. Escalate to user - do not close the project automatically.`);
|
|
85444
|
+
}
|
|
85445
|
+
return lines.join(`
|
|
85446
|
+
`);
|
|
85447
|
+
}
|
|
85335
85448
|
|
|
85336
85449
|
// src/council/criteria-store.ts
|
|
85337
85450
|
import { existsSync as existsSync50, mkdirSync as mkdirSync23, readFileSync as readFileSync42, writeFileSync as writeFileSync16 } from "node:fs";
|
|
@@ -87862,7 +87975,9 @@ var imports = createSwarmTool({
|
|
|
87862
87975
|
});
|
|
87863
87976
|
// src/tools/knowledge-ack.ts
|
|
87864
87977
|
init_zod();
|
|
87978
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
87865
87979
|
init_state();
|
|
87980
|
+
init_logger();
|
|
87866
87981
|
init_create_tool();
|
|
87867
87982
|
var knowledge_ack = createSwarmTool({
|
|
87868
87983
|
description: "Record an acknowledgment outcome (applied/ignored/violated) for a previously-injected knowledge directive. Updates retrieval-outcome counters and appends a record to .swarm/knowledge-application.jsonl.",
|
|
@@ -87890,7 +88005,11 @@ var knowledge_ack = createSwarmTool({
|
|
|
87890
88005
|
result: a.result,
|
|
87891
88006
|
reason: a.reason
|
|
87892
88007
|
};
|
|
87893
|
-
|
|
88008
|
+
let sessionId = ctx?.sessionID;
|
|
88009
|
+
if (!sessionId) {
|
|
88010
|
+
warn("[knowledge-ack] No sessionID in tool context — dedup disabled for this acknowledgment");
|
|
88011
|
+
sessionId = randomUUID7();
|
|
88012
|
+
}
|
|
87894
88013
|
const dedupKey = buildAckDedupKey(sessionId, a.id, a.result);
|
|
87895
88014
|
if (swarmState.knowledgeAckDedup.has(dedupKey)) {
|
|
87896
88015
|
return JSON.stringify({
|
|
@@ -87919,7 +88038,7 @@ init_knowledge_store();
|
|
|
87919
88038
|
init_knowledge_validator();
|
|
87920
88039
|
init_manager();
|
|
87921
88040
|
init_create_tool();
|
|
87922
|
-
import { randomUUID as
|
|
88041
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
87923
88042
|
var VALID_CATEGORIES2 = [
|
|
87924
88043
|
"process",
|
|
87925
88044
|
"architecture",
|
|
@@ -87993,7 +88112,7 @@ var knowledge_add = createSwarmTool({
|
|
|
87993
88112
|
project_name = plan?.title ?? "";
|
|
87994
88113
|
} catch {}
|
|
87995
88114
|
const entry = {
|
|
87996
|
-
id:
|
|
88115
|
+
id: randomUUID8(),
|
|
87997
88116
|
tier: "swarm",
|
|
87998
88117
|
lesson,
|
|
87999
88118
|
category,
|
|
@@ -89793,6 +89912,41 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
89793
89912
|
}, null, 2);
|
|
89794
89913
|
}
|
|
89795
89914
|
}
|
|
89915
|
+
if (typeof entry.quorumSize !== "number" || !Number.isFinite(entry.quorumSize) || entry.quorumSize < 5) {
|
|
89916
|
+
return JSON.stringify({
|
|
89917
|
+
success: false,
|
|
89918
|
+
phase,
|
|
89919
|
+
status: "blocked",
|
|
89920
|
+
reason: "FINAL_COUNCIL_MISSING_QUORUM",
|
|
89921
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council evidence is missing valid quorum metadata. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate quorumed evidence.`,
|
|
89922
|
+
agentsDispatched,
|
|
89923
|
+
agentsMissing: [],
|
|
89924
|
+
warnings: []
|
|
89925
|
+
}, null, 2);
|
|
89926
|
+
}
|
|
89927
|
+
const requiredFinalCouncilMembers = [
|
|
89928
|
+
"critic",
|
|
89929
|
+
"reviewer",
|
|
89930
|
+
"sme",
|
|
89931
|
+
"test_engineer",
|
|
89932
|
+
"explorer"
|
|
89933
|
+
];
|
|
89934
|
+
const membersVoted = Array.isArray(entry.membersVoted) ? entry.membersVoted.filter((member) => typeof member === "string") : [];
|
|
89935
|
+
const membersAbsent = Array.isArray(entry.membersAbsent) ? entry.membersAbsent.filter((member) => typeof member === "string") : [];
|
|
89936
|
+
const distinctMembersVoted = new Set(membersVoted);
|
|
89937
|
+
const hasAllRequiredMembers = requiredFinalCouncilMembers.every((member) => distinctMembersVoted.has(member)) && distinctMembersVoted.size === requiredFinalCouncilMembers.length && membersAbsent.length === 0;
|
|
89938
|
+
if (!hasAllRequiredMembers) {
|
|
89939
|
+
return JSON.stringify({
|
|
89940
|
+
success: false,
|
|
89941
|
+
phase,
|
|
89942
|
+
status: "blocked",
|
|
89943
|
+
reason: "FINAL_COUNCIL_MISSING_QUORUM",
|
|
89944
|
+
message: `Phase ${phase} (last phase) cannot be completed: final council evidence does not prove all five required members voted. Re-run the project-scoped five-member final council and call write_final_council_evidence to generate complete evidence.`,
|
|
89945
|
+
agentsDispatched,
|
|
89946
|
+
agentsMissing: [],
|
|
89947
|
+
warnings: []
|
|
89948
|
+
}, null, 2);
|
|
89949
|
+
}
|
|
89796
89950
|
if (entry.verdict === "rejected" || entry.verdict === "REJECTED") {
|
|
89797
89951
|
return JSON.stringify({
|
|
89798
89952
|
success: false,
|
|
@@ -89832,11 +89986,11 @@ Advisory notes: ${advisoryNotes.join("; ")}` : "";
|
|
|
89832
89986
|
status: "blocked",
|
|
89833
89987
|
reason: "FINAL_COUNCIL_REQUIRED",
|
|
89834
89988
|
final_council_required: true,
|
|
89835
|
-
message: `Phase ${phase} (last phase) cannot be completed: final_council is enabled and final council evidence not found at .swarm/evidence/final-council.json.
|
|
89989
|
+
message: `Phase ${phase} (last phase) cannot be completed: final_council is enabled and final council evidence not found at .swarm/evidence/final-council.json. Dispatch critic, reviewer, sme, test_engineer, and explorer with project-scoped context, collect their CouncilMemberVerdict JSON, and call write_final_council_evidence before completing the project. Do not use convene_general_council for this gate.`,
|
|
89836
89990
|
agentsDispatched,
|
|
89837
89991
|
agentsMissing: [],
|
|
89838
89992
|
warnings: [
|
|
89839
|
-
`Final council required
|
|
89993
|
+
`Final council required - dispatch the five project-scoped council members, then call write_final_council_evidence to persist quorumed evidence.`
|
|
89840
89994
|
]
|
|
89841
89995
|
}, null, 2);
|
|
89842
89996
|
}
|
|
@@ -97313,7 +97467,7 @@ var set_qa_gates = createSwarmTool({
|
|
|
97313
97467
|
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."),
|
|
97314
97468
|
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."),
|
|
97315
97469
|
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."),
|
|
97316
|
-
final_council: exports_external.boolean().optional().describe("Enable the final_council gate (default: off). When on, " + "after all phases complete the architect
|
|
97470
|
+
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."),
|
|
97317
97471
|
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.')
|
|
97318
97472
|
},
|
|
97319
97473
|
execute: async (args2, directory) => {
|
|
@@ -99561,7 +99715,7 @@ ${fileList}
|
|
|
99561
99715
|
async _withStateLock(fn) {
|
|
99562
99716
|
const timeoutMs = 1e4;
|
|
99563
99717
|
let timeoutId;
|
|
99564
|
-
const withTimeout2 = new Promise((
|
|
99718
|
+
const withTimeout2 = new Promise((_resolve, reject) => {
|
|
99565
99719
|
timeoutId = setTimeout(() => {
|
|
99566
99720
|
reject(new Error(`_withStateLock timed out after ${timeoutMs}ms — state update will not block subsequent operations`));
|
|
99567
99721
|
}, timeoutMs);
|
|
@@ -102271,59 +102425,94 @@ var write_drift_evidence = createSwarmTool({
|
|
|
102271
102425
|
});
|
|
102272
102426
|
// src/tools/write-final-council-evidence.ts
|
|
102273
102427
|
init_zod();
|
|
102428
|
+
init_loader();
|
|
102429
|
+
import fs107 from "node:fs";
|
|
102430
|
+
import path135 from "node:path";
|
|
102274
102431
|
init_utils2();
|
|
102275
102432
|
init_manager();
|
|
102276
102433
|
init_create_tool();
|
|
102277
|
-
|
|
102278
|
-
|
|
102279
|
-
|
|
102280
|
-
|
|
102281
|
-
|
|
102282
|
-
|
|
102283
|
-
|
|
102284
|
-
|
|
102285
|
-
|
|
102286
|
-
|
|
102287
|
-
|
|
102434
|
+
var FINAL_COUNCIL_MEMBERS = [
|
|
102435
|
+
"critic",
|
|
102436
|
+
"reviewer",
|
|
102437
|
+
"sme",
|
|
102438
|
+
"test_engineer",
|
|
102439
|
+
"explorer"
|
|
102440
|
+
];
|
|
102441
|
+
var VerdictSchema3 = exports_external.object({
|
|
102442
|
+
agent: exports_external.enum(FINAL_COUNCIL_MEMBERS),
|
|
102443
|
+
verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
102444
|
+
confidence: exports_external.number().min(0).max(1),
|
|
102445
|
+
findings: exports_external.array(exports_external.object({
|
|
102446
|
+
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
102447
|
+
category: exports_external.string().min(1),
|
|
102448
|
+
location: exports_external.string(),
|
|
102449
|
+
detail: exports_external.string(),
|
|
102450
|
+
evidence: exports_external.string()
|
|
102451
|
+
})),
|
|
102452
|
+
criteriaAssessed: exports_external.array(exports_external.string()),
|
|
102453
|
+
criteriaUnmet: exports_external.array(exports_external.string()),
|
|
102454
|
+
durationMs: exports_external.number().nonnegative()
|
|
102455
|
+
});
|
|
102456
|
+
var ArgsSchema6 = exports_external.object({
|
|
102457
|
+
phase: exports_external.number().int().min(1),
|
|
102458
|
+
projectSummary: exports_external.string().min(1),
|
|
102459
|
+
roundNumber: exports_external.number().int().min(1).max(10).optional(),
|
|
102460
|
+
verdicts: exports_external.array(VerdictSchema3).min(1).max(5)
|
|
102461
|
+
});
|
|
102462
|
+
function normalizeFinalVerdict(verdict) {
|
|
102463
|
+
return verdict === "APPROVE" ? "approved" : "rejected";
|
|
102288
102464
|
}
|
|
102289
102465
|
async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
102290
|
-
const
|
|
102291
|
-
if (!
|
|
102292
|
-
return JSON.stringify({
|
|
102293
|
-
success: false,
|
|
102294
|
-
phase,
|
|
102295
|
-
message: "Invalid phase: must be a positive integer"
|
|
102296
|
-
}, null, 2);
|
|
102297
|
-
}
|
|
102298
|
-
const validVerdicts = ["APPROVED", "NEEDS_REVISION"];
|
|
102299
|
-
if (!validVerdicts.includes(args2.verdict)) {
|
|
102466
|
+
const parsed = ArgsSchema6.safeParse(args2);
|
|
102467
|
+
if (!parsed.success) {
|
|
102300
102468
|
return JSON.stringify({
|
|
102301
102469
|
success: false,
|
|
102302
|
-
|
|
102303
|
-
|
|
102470
|
+
reason: "invalid arguments",
|
|
102471
|
+
errors: parsed.error.issues.map((i2) => ({
|
|
102472
|
+
path: i2.path.join("."),
|
|
102473
|
+
message: i2.message
|
|
102474
|
+
}))
|
|
102304
102475
|
}, null, 2);
|
|
102305
102476
|
}
|
|
102306
|
-
const
|
|
102307
|
-
|
|
102477
|
+
const input = parsed.data;
|
|
102478
|
+
const config3 = loadPluginConfig(directory);
|
|
102479
|
+
const requiredMembers = FINAL_COUNCIL_MEMBERS.length;
|
|
102480
|
+
const distinctMembers = new Set(input.verdicts.map((v) => v.agent));
|
|
102481
|
+
const membersVoted = [...distinctMembers];
|
|
102482
|
+
const membersAbsent = FINAL_COUNCIL_MEMBERS.filter((m) => !distinctMembers.has(m));
|
|
102483
|
+
if (membersVoted.length < requiredMembers) {
|
|
102308
102484
|
return JSON.stringify({
|
|
102309
102485
|
success: false,
|
|
102310
|
-
|
|
102311
|
-
message: "
|
|
102486
|
+
reason: "insufficient_quorum",
|
|
102487
|
+
message: `Final council quorum not met: ${membersVoted.length} of ${requiredMembers} required members provided verdicts. ` + `Members voted: [${membersVoted.join(", ")}]. ` + `Members absent: [${membersAbsent.join(", ")}]. ` + `Dispatch the absent council members with project-scoped context and collect their verdicts before calling write_final_council_evidence.`,
|
|
102488
|
+
membersVoted,
|
|
102489
|
+
membersAbsent,
|
|
102490
|
+
quorumRequired: requiredMembers
|
|
102312
102491
|
}, null, 2);
|
|
102313
102492
|
}
|
|
102314
|
-
const
|
|
102493
|
+
const synthesis = synthesizeFinalCouncilAdvisory(input.projectSummary.trim(), input.verdicts, input.roundNumber ?? 1, config3.council);
|
|
102315
102494
|
const plan = await loadPlan(directory);
|
|
102316
102495
|
const planId = plan ? derivePlanId(plan) : "unknown";
|
|
102496
|
+
const normalizedVerdict = normalizeFinalVerdict(synthesis.overallVerdict);
|
|
102317
102497
|
const evidenceEntry = {
|
|
102318
102498
|
type: "final-council",
|
|
102319
|
-
phase,
|
|
102499
|
+
phase: input.phase,
|
|
102320
102500
|
plan_id: planId,
|
|
102321
102501
|
verdict: normalizedVerdict,
|
|
102322
|
-
|
|
102323
|
-
|
|
102324
|
-
|
|
102325
|
-
|
|
102326
|
-
|
|
102502
|
+
rawCouncilVerdict: synthesis.overallVerdict,
|
|
102503
|
+
quorumSize: synthesis.quorumSize,
|
|
102504
|
+
membersVoted,
|
|
102505
|
+
membersAbsent,
|
|
102506
|
+
requiredFixes: synthesis.requiredFixes,
|
|
102507
|
+
advisoryFindings: synthesis.advisoryFindings,
|
|
102508
|
+
advisoryNotes: synthesis.advisoryNotes,
|
|
102509
|
+
unresolvedConflicts: synthesis.unresolvedConflicts,
|
|
102510
|
+
roundNumber: synthesis.roundNumber,
|
|
102511
|
+
allCriteriaMet: synthesis.allCriteriaMet,
|
|
102512
|
+
memberVerdicts: synthesis.memberVerdicts,
|
|
102513
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd,
|
|
102514
|
+
projectSummary: synthesis.projectSummary,
|
|
102515
|
+
timestamp: synthesis.timestamp
|
|
102327
102516
|
};
|
|
102328
102517
|
const filename = "final-council.json";
|
|
102329
102518
|
const relativePath = path135.join("evidence", filename);
|
|
@@ -102333,10 +102522,13 @@ async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
|
102333
102522
|
} catch (error93) {
|
|
102334
102523
|
return JSON.stringify({
|
|
102335
102524
|
success: false,
|
|
102336
|
-
phase,
|
|
102525
|
+
phase: input.phase,
|
|
102337
102526
|
message: error93 instanceof Error ? error93.message : "Failed to validate path"
|
|
102338
102527
|
}, null, 2);
|
|
102339
102528
|
}
|
|
102529
|
+
const evidenceContent = {
|
|
102530
|
+
entries: [evidenceEntry]
|
|
102531
|
+
};
|
|
102340
102532
|
const evidenceDir = path135.dirname(validatedPath);
|
|
102341
102533
|
try {
|
|
102342
102534
|
await fs107.promises.mkdir(evidenceDir, { recursive: true });
|
|
@@ -102345,41 +102537,53 @@ async function executeWriteFinalCouncilEvidence(args2, directory) {
|
|
|
102345
102537
|
await fs107.promises.rename(tempPath, validatedPath);
|
|
102346
102538
|
return JSON.stringify({
|
|
102347
102539
|
success: true,
|
|
102348
|
-
phase,
|
|
102540
|
+
phase: input.phase,
|
|
102541
|
+
overallVerdict: synthesis.overallVerdict,
|
|
102349
102542
|
verdict: normalizedVerdict,
|
|
102350
|
-
|
|
102543
|
+
vetoedBy: synthesis.vetoedBy,
|
|
102544
|
+
roundNumber: synthesis.roundNumber,
|
|
102545
|
+
allCriteriaMet: synthesis.allCriteriaMet,
|
|
102546
|
+
requiredFixesCount: synthesis.requiredFixes.length,
|
|
102547
|
+
advisoryFindingsCount: synthesis.advisoryFindings.length,
|
|
102548
|
+
unresolvedConflictsCount: synthesis.unresolvedConflicts.length,
|
|
102549
|
+
advisoryNotes: synthesis.advisoryNotes,
|
|
102550
|
+
membersVoted,
|
|
102551
|
+
membersAbsent,
|
|
102552
|
+
quorumSize: synthesis.quorumSize,
|
|
102553
|
+
quorumMet: true,
|
|
102554
|
+
evidencePath: synthesis.evidencePath,
|
|
102555
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd,
|
|
102556
|
+
message: "Final council evidence written to .swarm/evidence/final-council.json"
|
|
102351
102557
|
}, null, 2);
|
|
102352
102558
|
} catch (error93) {
|
|
102353
102559
|
return JSON.stringify({
|
|
102354
102560
|
success: false,
|
|
102355
|
-
phase,
|
|
102561
|
+
phase: input.phase,
|
|
102356
102562
|
message: error93 instanceof Error ? error93.message : String(error93)
|
|
102357
102563
|
}, null, 2);
|
|
102358
102564
|
}
|
|
102359
102565
|
}
|
|
102360
102566
|
var write_final_council_evidence = createSwarmTool({
|
|
102361
|
-
description: "Write final council evidence for a completed project.
|
|
102567
|
+
description: "Write final council evidence for a completed project. This is not General Council mode and does not use convene_general_council. PREREQUISITE: dispatch critic, reviewer, sme, test_engineer, and explorer as project-scoped Agent tasks, collect their CouncilMemberVerdict JSON, then call this tool to synthesize and persist .swarm/evidence/final-council.json.",
|
|
102362
102568
|
args: {
|
|
102363
|
-
phase: exports_external.number().int().min(1).describe("The phase number for the
|
|
102364
|
-
|
|
102365
|
-
|
|
102569
|
+
phase: exports_external.number().int().min(1).describe("The final phase number for the project being reviewed"),
|
|
102570
|
+
projectSummary: exports_external.string().min(1).describe("Summary of the completed project and total work reviewed"),
|
|
102571
|
+
roundNumber: exports_external.number().int().min(1).max(10).optional().describe("1-indexed final council round number. Defaults to 1."),
|
|
102572
|
+
verdicts: exports_external.array(VerdictSchema3).min(1).max(5).describe("Collected CouncilMemberVerdict objects from critic, reviewer, sme, test_engineer, and explorer.")
|
|
102366
102573
|
},
|
|
102367
102574
|
execute: async (args2, directory) => {
|
|
102368
|
-
const
|
|
102369
|
-
|
|
102370
|
-
const writeFinalCouncilEvidenceArgs = {
|
|
102371
|
-
phase: Number(args2.phase),
|
|
102372
|
-
verdict: String(args2.verdict),
|
|
102373
|
-
summary: String(args2.summary ?? "")
|
|
102374
|
-
};
|
|
102375
|
-
return await executeWriteFinalCouncilEvidence(writeFinalCouncilEvidenceArgs, directory);
|
|
102376
|
-
} catch (error93) {
|
|
102575
|
+
const parsed = ArgsSchema6.safeParse(args2);
|
|
102576
|
+
if (!parsed.success) {
|
|
102377
102577
|
return JSON.stringify({
|
|
102378
102578
|
success: false,
|
|
102379
|
-
|
|
102380
|
-
|
|
102579
|
+
reason: "invalid arguments",
|
|
102580
|
+
errors: parsed.error.issues.map((i2) => ({
|
|
102581
|
+
path: i2.path.join("."),
|
|
102582
|
+
message: i2.message
|
|
102583
|
+
}))
|
|
102381
102584
|
}, null, 2);
|
|
102382
102585
|
}
|
|
102586
|
+
return await executeWriteFinalCouncilEvidence(parsed.data, directory);
|
|
102383
102587
|
}
|
|
102384
102588
|
});
|
|
102385
102589
|
// src/tools/write-hallucination-evidence.ts
|
|
@@ -102388,7 +102592,7 @@ init_utils2();
|
|
|
102388
102592
|
init_create_tool();
|
|
102389
102593
|
import fs108 from "node:fs";
|
|
102390
102594
|
import path136 from "node:path";
|
|
102391
|
-
function
|
|
102595
|
+
function normalizeVerdict2(verdict) {
|
|
102392
102596
|
switch (verdict) {
|
|
102393
102597
|
case "APPROVED":
|
|
102394
102598
|
return "approved";
|
|
@@ -102423,7 +102627,7 @@ async function executeWriteHallucinationEvidence(args2, directory) {
|
|
|
102423
102627
|
message: "Invalid summary: must be a non-empty string"
|
|
102424
102628
|
}, null, 2);
|
|
102425
102629
|
}
|
|
102426
|
-
const normalizedVerdict =
|
|
102630
|
+
const normalizedVerdict = normalizeVerdict2(args2.verdict);
|
|
102427
102631
|
const evidenceEntry = {
|
|
102428
102632
|
type: "hallucination-verification",
|
|
102429
102633
|
verdict: normalizedVerdict,
|
|
@@ -102499,7 +102703,7 @@ init_utils2();
|
|
|
102499
102703
|
init_create_tool();
|
|
102500
102704
|
import fs109 from "node:fs";
|
|
102501
102705
|
import path137 from "node:path";
|
|
102502
|
-
function
|
|
102706
|
+
function normalizeVerdict3(verdict) {
|
|
102503
102707
|
switch (verdict) {
|
|
102504
102708
|
case "PASS":
|
|
102505
102709
|
return "pass";
|
|
@@ -102556,7 +102760,7 @@ async function executeWriteMutationEvidence(args2, directory) {
|
|
|
102556
102760
|
message: "Invalid summary: must be a non-empty string"
|
|
102557
102761
|
}, null, 2);
|
|
102558
102762
|
}
|
|
102559
|
-
const normalizedVerdict =
|
|
102763
|
+
const normalizedVerdict = normalizeVerdict3(args2.verdict);
|
|
102560
102764
|
const evidenceEntry = {
|
|
102561
102765
|
type: "mutation-gate",
|
|
102562
102766
|
verdict: normalizedVerdict,
|
|
@@ -1,29 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Write final council evidence
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Write final council evidence for the project-scoped final council gate.
|
|
3
|
+
*
|
|
4
|
+
* The final council is not General Council mode. It accepts the same
|
|
5
|
+
* five-member CouncilMemberVerdict objects used by phase council, synthesized
|
|
6
|
+
* at completed-project scope.
|
|
5
7
|
*/
|
|
6
8
|
import type { ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import type { CouncilMemberVerdict } from '../council/types';
|
|
11
|
+
export declare const ArgsSchema: z.ZodObject<{
|
|
12
|
+
phase: z.ZodNumber;
|
|
13
|
+
projectSummary: z.ZodString;
|
|
14
|
+
roundNumber: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
verdicts: z.ZodArray<z.ZodObject<{
|
|
16
|
+
agent: z.ZodEnum<{
|
|
17
|
+
reviewer: "reviewer";
|
|
18
|
+
test_engineer: "test_engineer";
|
|
19
|
+
explorer: "explorer";
|
|
20
|
+
sme: "sme";
|
|
21
|
+
critic: "critic";
|
|
22
|
+
}>;
|
|
23
|
+
verdict: z.ZodEnum<{
|
|
24
|
+
APPROVE: "APPROVE";
|
|
25
|
+
REJECT: "REJECT";
|
|
26
|
+
CONCERNS: "CONCERNS";
|
|
27
|
+
}>;
|
|
28
|
+
confidence: z.ZodNumber;
|
|
29
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
30
|
+
severity: z.ZodEnum<{
|
|
31
|
+
HIGH: "HIGH";
|
|
32
|
+
MEDIUM: "MEDIUM";
|
|
33
|
+
LOW: "LOW";
|
|
34
|
+
}>;
|
|
35
|
+
category: z.ZodString;
|
|
36
|
+
location: z.ZodString;
|
|
37
|
+
detail: z.ZodString;
|
|
38
|
+
evidence: z.ZodString;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
criteriaAssessed: z.ZodArray<z.ZodString>;
|
|
41
|
+
criteriaUnmet: z.ZodArray<z.ZodString>;
|
|
42
|
+
durationMs: z.ZodNumber;
|
|
43
|
+
}, z.core.$strip>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
7
45
|
/**
|
|
8
|
-
* Arguments for the write_final_council_evidence tool
|
|
46
|
+
* Arguments for the write_final_council_evidence tool.
|
|
9
47
|
*/
|
|
10
48
|
export interface WriteFinalCouncilEvidenceArgs {
|
|
11
49
|
/** The phase number for the final council verdict */
|
|
12
50
|
phase: number;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
51
|
+
/** Summary of the completed project being reviewed */
|
|
52
|
+
projectSummary: string;
|
|
53
|
+
/** 1-indexed final council round number */
|
|
54
|
+
roundNumber?: number;
|
|
55
|
+
/** Collected verdicts from critic, reviewer, sme, test_engineer, explorer */
|
|
56
|
+
verdicts: CouncilMemberVerdict[];
|
|
17
57
|
}
|
|
18
58
|
/**
|
|
19
59
|
* Execute the write_final_council_evidence tool.
|
|
20
|
-
* Validates input,
|
|
21
|
-
* @param args - The write final council evidence arguments
|
|
22
|
-
* @param directory - Working directory
|
|
23
|
-
* @returns JSON string with success status and details
|
|
60
|
+
* Validates input, synthesizes project-scoped council evidence, and writes it.
|
|
24
61
|
*/
|
|
25
|
-
export declare function executeWriteFinalCouncilEvidence(args:
|
|
62
|
+
export declare function executeWriteFinalCouncilEvidence(args: unknown, directory: string): Promise<string>;
|
|
26
63
|
/**
|
|
27
|
-
* Tool definition for write_final_council_evidence
|
|
64
|
+
* Tool definition for write_final_council_evidence.
|
|
28
65
|
*/
|
|
29
66
|
export declare const write_final_council_evidence: ToolDefinition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.17.
|
|
3
|
+
"version": "7.17.2",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|