opencode-swarm 6.65.0 → 6.66.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/agents/architect.d.ts +18 -1
- package/dist/cli/index.js +21 -1
- package/dist/config/schema.d.ts +17 -0
- package/dist/council/council-advisory.d.ts +46 -0
- package/dist/council/council-evidence-writer.d.ts +18 -0
- package/dist/council/council-service.d.ts +12 -0
- package/dist/council/criteria-store.d.ts +9 -0
- package/dist/council/types.d.ts +77 -0
- package/dist/index.js +576 -13
- package/dist/tools/convene-council.d.ts +48 -0
- package/dist/tools/declare-council-criteria.d.ts +14 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/tool-names.d.ts +1 -1
- package/dist/tools/update-task-status.d.ts +19 -0
- package/package.json +1 -1
|
@@ -8,4 +8,21 @@ export interface AdversarialTestingConfig {
|
|
|
8
8
|
enabled: boolean;
|
|
9
9
|
scope: 'all' | 'security-only';
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Subset of PluginConfig.council needed to gate the Work Complete Council
|
|
13
|
+
* workflow block in the architect prompt. Only `enabled` is consumed here —
|
|
14
|
+
* runtime behavior (maxRounds, timeout, veto priority) is enforced elsewhere
|
|
15
|
+
* via the council tools and config. Keeping this shape narrow avoids pulling
|
|
16
|
+
* the full PluginConfig type into the agent-prompt layer.
|
|
17
|
+
*/
|
|
18
|
+
export interface CouncilWorkflowConfig {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build the Work Complete Council four-phase workflow block. Returns the full
|
|
23
|
+
* block text when council.enabled === true, otherwise the empty string. The
|
|
24
|
+
* empty-string return path guarantees byte-for-byte non-regression when the
|
|
25
|
+
* council feature is off or the config key is absent.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCouncilWorkflow(council?: CouncilWorkflowConfig): string;
|
|
28
|
+
export declare function createArchitectAgent(model: string, customPrompt?: string, customAppendPrompt?: string, adversarialTesting?: AdversarialTestingConfig, council?: CouncilWorkflowConfig): AgentDefinition;
|
package/dist/cli/index.js
CHANGED
|
@@ -14825,7 +14825,14 @@ async function savePlan(directory, plan, options) {
|
|
|
14825
14825
|
const planId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
14826
14826
|
const planHashForInit = computePlanHash(validated);
|
|
14827
14827
|
if (!await ledgerExists(directory)) {
|
|
14828
|
-
|
|
14828
|
+
try {
|
|
14829
|
+
await initLedger(directory, planId, planHashForInit, validated);
|
|
14830
|
+
} catch (initErr) {
|
|
14831
|
+
const msg = initErr instanceof Error ? initErr.message : String(initErr);
|
|
14832
|
+
if (!/already initialized/i.test(msg)) {
|
|
14833
|
+
throw initErr;
|
|
14834
|
+
}
|
|
14835
|
+
}
|
|
14829
14836
|
} else {
|
|
14830
14837
|
const existingEvents = await readLedgerEvents(directory);
|
|
14831
14838
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
@@ -18470,6 +18477,8 @@ var TOOL_NAMES = [
|
|
|
18470
18477
|
"evidence_check",
|
|
18471
18478
|
"check_gate_status",
|
|
18472
18479
|
"completion_verify",
|
|
18480
|
+
"convene_council",
|
|
18481
|
+
"declare_council_criteria",
|
|
18473
18482
|
"sbom_generate",
|
|
18474
18483
|
"checkpoint",
|
|
18475
18484
|
"pkg_audit",
|
|
@@ -18526,6 +18535,8 @@ var AGENT_TOOL_MAP = {
|
|
|
18526
18535
|
"checkpoint",
|
|
18527
18536
|
"check_gate_status",
|
|
18528
18537
|
"completion_verify",
|
|
18538
|
+
"convene_council",
|
|
18539
|
+
"declare_council_criteria",
|
|
18529
18540
|
"complexity_hotspots",
|
|
18530
18541
|
"detect_domains",
|
|
18531
18542
|
"evidence_check",
|
|
@@ -19182,6 +19193,14 @@ var AuthorityConfigSchema = exports_external.object({
|
|
|
19182
19193
|
enabled: exports_external.boolean().default(true),
|
|
19183
19194
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
|
|
19184
19195
|
});
|
|
19196
|
+
var CouncilConfigSchema = exports_external.object({
|
|
19197
|
+
enabled: exports_external.boolean().default(false),
|
|
19198
|
+
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
19199
|
+
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
19200
|
+
vetoPriority: exports_external.boolean().default(true),
|
|
19201
|
+
requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
|
|
19202
|
+
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet.")
|
|
19203
|
+
}).strict();
|
|
19185
19204
|
var PluginConfigSchema = exports_external.object({
|
|
19186
19205
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
19187
19206
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
@@ -19229,6 +19248,7 @@ var PluginConfigSchema = exports_external.object({
|
|
|
19229
19248
|
}).optional(),
|
|
19230
19249
|
incremental_verify: IncrementalVerifyConfigSchema.optional(),
|
|
19231
19250
|
compaction_service: CompactionConfigSchema.optional(),
|
|
19251
|
+
council: CouncilConfigSchema.optional(),
|
|
19232
19252
|
turbo_mode: exports_external.boolean().default(false).optional(),
|
|
19233
19253
|
full_auto: exports_external.object({
|
|
19234
19254
|
enabled: exports_external.boolean().default(false),
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -511,6 +511,15 @@ export declare const AuthorityConfigSchema: z.ZodObject<{
|
|
|
511
511
|
}, z.core.$strip>>>;
|
|
512
512
|
}, z.core.$strip>;
|
|
513
513
|
export type AuthorityConfig = z.infer<typeof AuthorityConfigSchema>;
|
|
514
|
+
export declare const CouncilConfigSchema: z.ZodObject<{
|
|
515
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
516
|
+
maxRounds: z.ZodDefault<z.ZodNumber>;
|
|
517
|
+
parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
518
|
+
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
519
|
+
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
520
|
+
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
521
|
+
}, z.core.$strict>;
|
|
522
|
+
export type CouncilConfig = z.infer<typeof CouncilConfigSchema>;
|
|
514
523
|
export declare const PluginConfigSchema: z.ZodObject<{
|
|
515
524
|
agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
516
525
|
model: z.ZodOptional<z.ZodString>;
|
|
@@ -854,6 +863,14 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
854
863
|
emergencyThreshold: z.ZodDefault<z.ZodNumber>;
|
|
855
864
|
preserveLastNTurns: z.ZodDefault<z.ZodNumber>;
|
|
856
865
|
}, z.core.$strip>>;
|
|
866
|
+
council: z.ZodOptional<z.ZodObject<{
|
|
867
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
868
|
+
maxRounds: z.ZodDefault<z.ZodNumber>;
|
|
869
|
+
parallelTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
870
|
+
vetoPriority: z.ZodDefault<z.ZodBoolean>;
|
|
871
|
+
requireAllMembers: z.ZodDefault<z.ZodBoolean>;
|
|
872
|
+
escalateOnMaxRounds: z.ZodOptional<z.ZodString>;
|
|
873
|
+
}, z.core.$strict>>;
|
|
857
874
|
turbo_mode: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
858
875
|
full_auto: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
859
876
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — advisory routing helper.
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* Routes a CouncilSynthesis (unifiedFeedbackMd + verdict metadata) into the
|
|
6
|
+
* architect's non-blocking advisory queue (`session.pendingAdvisoryMessages`).
|
|
7
|
+
* The guardrails `messagesTransform` hook drains that queue into an
|
|
8
|
+
* [ADVISORIES] block prepended to the architect's first SYSTEM message on
|
|
9
|
+
* the next turn.
|
|
10
|
+
*
|
|
11
|
+
* Runtime call site:
|
|
12
|
+
* `src/tools/convene-council.ts` invokes this helper after writing evidence,
|
|
13
|
+
* guarded by `ctx?.sessionID` and `getAgentSession`. Missing session, missing
|
|
14
|
+
* sessionID, or any thrown error silently skip — the advisory is never
|
|
15
|
+
* critical-path. The helper is also re-exported for direct use by any future
|
|
16
|
+
* caller that wants to push synthesis output into an advisory queue.
|
|
17
|
+
*
|
|
18
|
+
* Scope and known limitation:
|
|
19
|
+
* The advisory queue is READ by the architect session on its next turn (self
|
|
20
|
+
* echo). It is NOT a programmatic architect→coder delivery channel — the
|
|
21
|
+
* architect still has to render `unifiedFeedbackMd` into the coder's
|
|
22
|
+
* delegation payload manually, per the prompt's four-phase workflow. A
|
|
23
|
+
* dedicated architect→coder advisory primitive is future work.
|
|
24
|
+
*
|
|
25
|
+
* Dedup semantics:
|
|
26
|
+
* Dedup key is `council:${taskId}:${roundNumber}`. If the queue already
|
|
27
|
+
* contains a string whose content includes that key, the push is a no-op.
|
|
28
|
+
* Different rounds or tasks push distinct entries.
|
|
29
|
+
*
|
|
30
|
+
* Blocking signal (metadata only):
|
|
31
|
+
* - REJECT → header declares `blocking=true`. Council vetoed the candidate.
|
|
32
|
+
* - CONCERNS→ `blocking=false`. Architect should weigh fixes but is not vetoed.
|
|
33
|
+
* - APPROVE → `blocking=false`. Helper skips push entirely when there are no
|
|
34
|
+
* advisoryFindings (nothing useful to surface).
|
|
35
|
+
*/
|
|
36
|
+
import type { AgentSessionState } from '../state';
|
|
37
|
+
import type { CouncilSynthesis } from './types';
|
|
38
|
+
/**
|
|
39
|
+
* Push a CouncilSynthesis into the given session's advisory queue so the
|
|
40
|
+
* architect will see it as an [ADVISORIES] block on the next messagesTransform.
|
|
41
|
+
*
|
|
42
|
+
* Idempotent per (taskId, roundNumber): repeated calls with the same key
|
|
43
|
+
* leave the queue unchanged. Safe to call on APPROVE — it is a no-op when
|
|
44
|
+
* there are no advisoryFindings.
|
|
45
|
+
*/
|
|
46
|
+
export declare function pushCouncilAdvisory(session: Pick<AgentSessionState, 'pendingAdvisoryMessages'>, synthesis: CouncilSynthesis): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — evidence writer.
|
|
3
|
+
*
|
|
4
|
+
* Stamps the council synthesis result into `.swarm/evidence/{taskId}.json`
|
|
5
|
+
* under `gates.council`, matching the shape other gate writers use and the
|
|
6
|
+
* shape that `check_gate_status` and `update_task_status` consume (they read
|
|
7
|
+
* `evidence.gates[gateName]`). Council-specific fields (verdict, vetoedBy,
|
|
8
|
+
* roundNumber, allCriteriaMet) are stored alongside the standard GateInfo
|
|
9
|
+
* fields (sessionId, timestamp, agent); existing consumers only check
|
|
10
|
+
* `gates.council != null`, so the extras are compatible.
|
|
11
|
+
*
|
|
12
|
+
* Existing fields in the evidence file — top-level keys AND other `gates[*]`
|
|
13
|
+
* entries — are preserved across the write. The raw taskId is used as the
|
|
14
|
+
* filename; defense-in-depth regex validation rejects malformed IDs before
|
|
15
|
+
* any filesystem op.
|
|
16
|
+
*/
|
|
17
|
+
import type { CouncilSynthesis } from './types';
|
|
18
|
+
export declare function writeCouncilEvidence(workingDir: string, synthesis: CouncilSynthesis): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — pure synthesis service.
|
|
3
|
+
*
|
|
4
|
+
* Given the verdicts of council members (critic, reviewer, sme, test_engineer),
|
|
5
|
+
* compute the overall verdict, classify findings, detect conflicts, and build a
|
|
6
|
+
* single unified feedback document for the coder.
|
|
7
|
+
*
|
|
8
|
+
* No I/O — fully unit-testable with mock inputs. All file reads/writes happen in
|
|
9
|
+
* sibling modules (criteria-store, council-evidence-writer).
|
|
10
|
+
*/
|
|
11
|
+
import type { CouncilConfig, CouncilCriteria, CouncilMemberVerdict, CouncilSynthesis } from './types';
|
|
12
|
+
export declare function synthesizeCouncilVerdicts(taskId: string, swarmId: string, verdicts: CouncilMemberVerdict[], criteria: CouncilCriteria | null, roundNumber: number, config?: Partial<CouncilConfig>): CouncilSynthesis;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — pre-declaration criteria writer/reader.
|
|
3
|
+
*
|
|
4
|
+
* Stores acceptance criteria under .swarm/council/{safeId}.json so they can be
|
|
5
|
+
* read back during council evaluation.
|
|
6
|
+
*/
|
|
7
|
+
import type { CouncilCriteria, CouncilCriteriaItem } from './types';
|
|
8
|
+
export declare function writeCriteria(workingDir: string, taskId: string, criteria: CouncilCriteriaItem[]): void;
|
|
9
|
+
export declare function readCriteria(workingDir: string, taskId: string): CouncilCriteria | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — data contracts.
|
|
3
|
+
*
|
|
4
|
+
* Flat, stable schema — no nested generics. Designed for reliable LLM output.
|
|
5
|
+
* No business logic, no I/O. Only types, interfaces, and defaults.
|
|
6
|
+
*/
|
|
7
|
+
export type CouncilVerdict = 'APPROVE' | 'CONCERNS' | 'REJECT';
|
|
8
|
+
export type CouncilFindingSeverity = 'HIGH' | 'MEDIUM' | 'LOW';
|
|
9
|
+
export type CouncilFindingCategory = 'logic' | 'edge_case' | 'error_handling' | 'spec_compliance' | 'security' | 'maintainability' | 'naming' | 'domain' | 'test_gap' | 'test_quality' | 'mutation_gap' | 'adversarial_gap' | 'slop_pattern' | 'hallucinated_api' | 'lazy_abstraction' | 'cargo_cult' | 'spec_drift' | 'other';
|
|
10
|
+
export type CouncilAgent = 'critic' | 'reviewer' | 'sme' | 'test_engineer' | 'explorer';
|
|
11
|
+
export interface CouncilFinding {
|
|
12
|
+
severity: CouncilFindingSeverity;
|
|
13
|
+
category: CouncilFindingCategory;
|
|
14
|
+
/** e.g. "src/tools/convene-council.ts:42" */
|
|
15
|
+
location: string;
|
|
16
|
+
/** Human-readable explanation */
|
|
17
|
+
detail: string;
|
|
18
|
+
/** Concrete quote or line reference */
|
|
19
|
+
evidence: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CouncilMemberVerdict {
|
|
22
|
+
agent: CouncilAgent;
|
|
23
|
+
verdict: CouncilVerdict;
|
|
24
|
+
/** Confidence 0.0–1.0 */
|
|
25
|
+
confidence: number;
|
|
26
|
+
findings: CouncilFinding[];
|
|
27
|
+
/** Criteria IDs from pre-declaration (e.g. ["C1","C3"]) */
|
|
28
|
+
criteriaAssessed: string[];
|
|
29
|
+
/** Criteria IDs that failed */
|
|
30
|
+
criteriaUnmet: string[];
|
|
31
|
+
durationMs: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CouncilSynthesis {
|
|
34
|
+
taskId: string;
|
|
35
|
+
swarmId: string;
|
|
36
|
+
/** ISO 8601 */
|
|
37
|
+
timestamp: string;
|
|
38
|
+
overallVerdict: CouncilVerdict;
|
|
39
|
+
vetoedBy: CouncilAgent[] | null;
|
|
40
|
+
memberVerdicts: CouncilMemberVerdict[];
|
|
41
|
+
unresolvedConflicts: string[];
|
|
42
|
+
/** Severity HIGH + MEDIUM from veto members */
|
|
43
|
+
requiredFixes: CouncilFinding[];
|
|
44
|
+
/** Severity LOW or from non-veto members */
|
|
45
|
+
advisoryFindings: CouncilFinding[];
|
|
46
|
+
/** Single markdown document sent to coder */
|
|
47
|
+
unifiedFeedbackMd: string;
|
|
48
|
+
/** 1-indexed */
|
|
49
|
+
roundNumber: number;
|
|
50
|
+
allCriteriaMet: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface CouncilCriteriaItem {
|
|
53
|
+
id: string;
|
|
54
|
+
description: string;
|
|
55
|
+
mandatory: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface CouncilCriteria {
|
|
58
|
+
taskId: string;
|
|
59
|
+
criteria: CouncilCriteriaItem[];
|
|
60
|
+
/** ISO 8601 */
|
|
61
|
+
declaredAt: string;
|
|
62
|
+
}
|
|
63
|
+
/** Config shape — matched in schema.ts via CouncilConfigSchema. */
|
|
64
|
+
export interface CouncilConfig {
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
/** Default 3 */
|
|
67
|
+
maxRounds: number;
|
|
68
|
+
/** Default 30_000 */
|
|
69
|
+
parallelTimeoutMs: number;
|
|
70
|
+
/** Default true — any REJECT blocks */
|
|
71
|
+
vetoPriority: boolean;
|
|
72
|
+
/** Default false — when true, convene_council rejects unless all 5 member verdicts are provided */
|
|
73
|
+
requireAllMembers: boolean;
|
|
74
|
+
/** Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet. */
|
|
75
|
+
escalateOnMaxRounds?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare const COUNCIL_DEFAULTS: CouncilConfig;
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,8 @@ var init_tool_names = __esm(() => {
|
|
|
49
49
|
"evidence_check",
|
|
50
50
|
"check_gate_status",
|
|
51
51
|
"completion_verify",
|
|
52
|
+
"convene_council",
|
|
53
|
+
"declare_council_criteria",
|
|
52
54
|
"sbom_generate",
|
|
53
55
|
"checkpoint",
|
|
54
56
|
"pkg_audit",
|
|
@@ -173,6 +175,8 @@ var init_constants = __esm(() => {
|
|
|
173
175
|
"checkpoint",
|
|
174
176
|
"check_gate_status",
|
|
175
177
|
"completion_verify",
|
|
178
|
+
"convene_council",
|
|
179
|
+
"declare_council_criteria",
|
|
176
180
|
"complexity_hotspots",
|
|
177
181
|
"detect_domains",
|
|
178
182
|
"evidence_check",
|
|
@@ -394,6 +398,8 @@ var init_constants = __esm(() => {
|
|
|
394
398
|
co_change_analyzer: "detect hidden couplings by analyzing git history",
|
|
395
399
|
check_gate_status: "check the gate status of a specific task",
|
|
396
400
|
completion_verify: "verify completed tasks have required evidence",
|
|
401
|
+
convene_council: "convene the Work Complete Council \u2014 parallel veto-aware verification gate across critic, reviewer, sme, test_engineer, and explorer verdicts",
|
|
402
|
+
declare_council_criteria: "pre-declare acceptance criteria for a task before the coder starts work; criteria are read back during council evaluation",
|
|
397
403
|
detect_domains: "detect which SME domains are relevant for a given text",
|
|
398
404
|
extract_code_blocks: "extract code blocks from text content and save them to files",
|
|
399
405
|
gitingest: "fetch a GitHub repository full content via gitingest.com",
|
|
@@ -14628,7 +14634,7 @@ function resolveGuardrailsConfig(config2, agentName) {
|
|
|
14628
14634
|
};
|
|
14629
14635
|
return resolved;
|
|
14630
14636
|
}
|
|
14631
|
-
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, PluginConfigSchema;
|
|
14637
|
+
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, CouncilConfigSchema, PluginConfigSchema;
|
|
14632
14638
|
var init_schema = __esm(() => {
|
|
14633
14639
|
init_zod();
|
|
14634
14640
|
init_constants();
|
|
@@ -15120,6 +15126,14 @@ var init_schema = __esm(() => {
|
|
|
15120
15126
|
enabled: exports_external.boolean().default(true),
|
|
15121
15127
|
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
|
|
15122
15128
|
});
|
|
15129
|
+
CouncilConfigSchema = exports_external.object({
|
|
15130
|
+
enabled: exports_external.boolean().default(false),
|
|
15131
|
+
maxRounds: exports_external.number().int().min(1).max(10).default(3),
|
|
15132
|
+
parallelTimeoutMs: exports_external.number().int().min(5000).max(120000).default(30000),
|
|
15133
|
+
vetoPriority: exports_external.boolean().default(true),
|
|
15134
|
+
requireAllMembers: exports_external.boolean().default(false).describe("When true, convene_council rejects if fewer than 5 member verdicts are provided."),
|
|
15135
|
+
escalateOnMaxRounds: exports_external.string().optional().describe("Optional webhook URL or handler name invoked when maxRounds is reached without APPROVE. Declared for forward compatibility; no behavior is implemented yet.")
|
|
15136
|
+
}).strict();
|
|
15123
15137
|
PluginConfigSchema = exports_external.object({
|
|
15124
15138
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
15125
15139
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
@@ -15167,6 +15181,7 @@ var init_schema = __esm(() => {
|
|
|
15167
15181
|
}).optional(),
|
|
15168
15182
|
incremental_verify: IncrementalVerifyConfigSchema.optional(),
|
|
15169
15183
|
compaction_service: CompactionConfigSchema.optional(),
|
|
15184
|
+
council: CouncilConfigSchema.optional(),
|
|
15170
15185
|
turbo_mode: exports_external.boolean().default(false).optional(),
|
|
15171
15186
|
full_auto: exports_external.object({
|
|
15172
15187
|
enabled: exports_external.boolean().default(false),
|
|
@@ -16418,7 +16433,14 @@ async function savePlan(directory, plan, options) {
|
|
|
16418
16433
|
const planId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16419
16434
|
const planHashForInit = computePlanHash(validated);
|
|
16420
16435
|
if (!await ledgerExists(directory)) {
|
|
16421
|
-
|
|
16436
|
+
try {
|
|
16437
|
+
await initLedger(directory, planId, planHashForInit, validated);
|
|
16438
|
+
} catch (initErr) {
|
|
16439
|
+
const msg = initErr instanceof Error ? initErr.message : String(initErr);
|
|
16440
|
+
if (!/already initialized/i.test(msg)) {
|
|
16441
|
+
throw initErr;
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16422
16444
|
} else {
|
|
16423
16445
|
const existingEvents = await readLedgerEvents(directory);
|
|
16424
16446
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
@@ -45342,8 +45364,8 @@ async function loadGrammar(languageId) {
|
|
|
45342
45364
|
const parser = new Parser;
|
|
45343
45365
|
const wasmFileName = getWasmFileName(normalizedId);
|
|
45344
45366
|
const wasmPath = path66.join(getGrammarsDirAbsolute(), wasmFileName);
|
|
45345
|
-
const { existsSync:
|
|
45346
|
-
if (!
|
|
45367
|
+
const { existsSync: existsSync39 } = await import("fs");
|
|
45368
|
+
if (!existsSync39(wasmPath)) {
|
|
45347
45369
|
throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
|
|
45348
45370
|
Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
|
|
45349
45371
|
}
|
|
@@ -53684,6 +53706,8 @@ NOT valid completion signals:
|
|
|
53684
53706
|
|
|
53685
53707
|
The ONLY valid completion signal is: all required gate agents returned positive verdicts.
|
|
53686
53708
|
|
|
53709
|
+
{{COUNCIL_WORKFLOW}}
|
|
53710
|
+
|
|
53687
53711
|
Emit 'architect_loop_detected' when triggering sounding board for 3rd time on same impasse.
|
|
53688
53712
|
|
|
53689
53713
|
6g. **META.SUMMARY CONVENTION** \u2014 When emitting state updates to .swarm/ files or events.jsonl, include:
|
|
@@ -54496,6 +54520,68 @@ Swarm: {{SWARM_ID}}
|
|
|
54496
54520
|
\`\`\`
|
|
54497
54521
|
|
|
54498
54522
|
`;
|
|
54523
|
+
function buildCouncilWorkflow(council) {
|
|
54524
|
+
if (council?.enabled !== true)
|
|
54525
|
+
return "";
|
|
54526
|
+
return `## Work Complete Council (when enabled)
|
|
54527
|
+
|
|
54528
|
+
When \`council.enabled\` is true, every task goes through a four-phase verification
|
|
54529
|
+
gate before advancing to \`complete\`. This supplements \u2014 does NOT replace \u2014 the
|
|
54530
|
+
existing precheckbatch / reviewer / test_engineer gate sequence.
|
|
54531
|
+
|
|
54532
|
+
### Phase 0 \u2014 Pre-declare criteria (at plan time, BEFORE dispatching the coder)
|
|
54533
|
+
Call \`declare_council_criteria\` for each task with at least 3 concrete,
|
|
54534
|
+
testable acceptance criteria. Mark functional/correctness criteria
|
|
54535
|
+
\`mandatory: true\`; style/naming criteria \`mandatory: false\`. Criterion ids
|
|
54536
|
+
follow the pattern \`C1\`, \`C2\`, etc. The criteria are persisted to
|
|
54537
|
+
\`.swarm/council/{taskId}.json\` and read back automatically at synthesis time.
|
|
54538
|
+
|
|
54539
|
+
### Phase 1 \u2014 Parallel dispatch (when the coder signals the task is complete)
|
|
54540
|
+
Dispatch all FIVE council members IN PARALLEL \u2014 do not run them sequentially.
|
|
54541
|
+
Each receives ONLY their role-relevant context, not the full conversation:
|
|
54542
|
+
- \`critic\` \u2014 original task spec + acceptance criteria + code diff + test results
|
|
54543
|
+
- \`reviewer\` \u2014 semantic diff summary + blast radius (files importing changed files) + style guide
|
|
54544
|
+
- \`sme\` \u2014 task domain context + relevant knowledge base entries
|
|
54545
|
+
- \`test_engineer\` \u2014 changed test files + coverage delta + known mutation gaps
|
|
54546
|
+
- \`explorer\` \u2014 full diff + original task intent + any prior slop findings
|
|
54547
|
+
(explorer hunts for lazy implementations, hallucinated APIs,
|
|
54548
|
+
cargo-cult patterns, spec drift, lazy abstractions)
|
|
54549
|
+
|
|
54550
|
+
Each member must return a \`CouncilMemberVerdict\` with all fields populated:
|
|
54551
|
+
\`agent\`, \`verdict\` (APPROVE|CONCERNS|REJECT), \`confidence\` (0.0\u20131.0),
|
|
54552
|
+
\`findings[]\`, \`criteriaAssessed[]\`, \`criteriaUnmet[]\`, \`durationMs\`.
|
|
54553
|
+
|
|
54554
|
+
### Phase 2 \u2014 Synthesize
|
|
54555
|
+
Call \`convene_council\` with all 5 verdicts, the task id, swarm id, and the
|
|
54556
|
+
current round number (1-indexed). The tool returns:
|
|
54557
|
+
\`overallVerdict\`, \`vetoedBy\`, \`unifiedFeedbackMd\`, \`requiredFixesCount\`,
|
|
54558
|
+
\`allCriteriaMet\`.
|
|
54559
|
+
|
|
54560
|
+
### Phase 3 \u2014 Act on the result
|
|
54561
|
+
- **APPROVE**: Advance task to complete via \`update_task_status\`. If
|
|
54562
|
+
advisoryFindingsCount > 0, deliver \`unifiedFeedbackMd\` as a
|
|
54563
|
+
single non-blocking note. Otherwise, advance silently.
|
|
54564
|
+
- **CONCERNS**: Send \`unifiedFeedbackMd\` to the coder as ONE coherent document.
|
|
54565
|
+
Do NOT enumerate individual member verdicts. Increment
|
|
54566
|
+
roundNumber on the next council call. CONCERNS does not block
|
|
54567
|
+
advancement at the update_task_status level \u2014 decide per
|
|
54568
|
+
severity whether to advance or retry.
|
|
54569
|
+
- **REJECT**: Block advancement. Send \`unifiedFeedbackMd\` to the coder with
|
|
54570
|
+
the BLOCKING flag. The coder must resolve all \`requiredFixes\`
|
|
54571
|
+
before re-submitting. Maximum \`council.maxRounds\` rounds
|
|
54572
|
+
(default 3). If roundNumber >= maxRounds and verdict is still
|
|
54573
|
+
REJECT, surface \`unifiedFeedbackMd\` to the user and HALT \u2014
|
|
54574
|
+
do NOT auto-advance.
|
|
54575
|
+
|
|
54576
|
+
### Retry protocol
|
|
54577
|
+
On re-submission after REJECT or CONCERNS, the council reads the same
|
|
54578
|
+
pre-declared criteria and receives (a) the previous synthesis findings plus
|
|
54579
|
+
(b) the diff of what changed since the last round. Council members verify
|
|
54580
|
+
prior findings are resolved without re-reviewing unchanged code. The
|
|
54581
|
+
architect resolves any \`unresolvedConflicts\` in \`unifiedFeedbackMd\` BEFORE
|
|
54582
|
+
sending it to the coder \u2014 the coder never sees contradictory instructions
|
|
54583
|
+
from different members.`;
|
|
54584
|
+
}
|
|
54499
54585
|
function buildYourToolsList() {
|
|
54500
54586
|
const tools = AGENT_TOOL_MAP.architect ?? [];
|
|
54501
54587
|
const sorted = [...tools].sort();
|
|
@@ -54636,7 +54722,7 @@ function buildSlashCommandsList() {
|
|
|
54636
54722
|
return lines.join(`
|
|
54637
54723
|
`);
|
|
54638
54724
|
}
|
|
54639
|
-
function createArchitectAgent(model, customPrompt, customAppendPrompt, adversarialTesting) {
|
|
54725
|
+
function createArchitectAgent(model, customPrompt, customAppendPrompt, adversarialTesting, council) {
|
|
54640
54726
|
let prompt = ARCHITECT_PROMPT;
|
|
54641
54727
|
if (customPrompt) {
|
|
54642
54728
|
prompt = customPrompt;
|
|
@@ -54646,6 +54732,18 @@ function createArchitectAgent(model, customPrompt, customAppendPrompt, adversari
|
|
|
54646
54732
|
${customAppendPrompt}`;
|
|
54647
54733
|
}
|
|
54648
54734
|
prompt = prompt?.replace("{{YOUR_TOOLS}}", buildYourToolsList())?.replace("{{AVAILABLE_TOOLS}}", buildAvailableToolsList())?.replace("{{SLASH_COMMANDS}}", buildSlashCommandsList());
|
|
54735
|
+
const councilBlock = buildCouncilWorkflow(council);
|
|
54736
|
+
if (councilBlock === "") {
|
|
54737
|
+
prompt = prompt?.replace(`
|
|
54738
|
+
|
|
54739
|
+
{{COUNCIL_WORKFLOW}}
|
|
54740
|
+
|
|
54741
|
+
`, `
|
|
54742
|
+
|
|
54743
|
+
`);
|
|
54744
|
+
} else {
|
|
54745
|
+
prompt = prompt?.replace("{{COUNCIL_WORKFLOW}}", councilBlock);
|
|
54746
|
+
}
|
|
54649
54747
|
const advEnabled = adversarialTesting?.enabled ?? true;
|
|
54650
54748
|
const advScope = adversarialTesting?.scope ?? "all";
|
|
54651
54749
|
if (!advEnabled) {
|
|
@@ -56241,7 +56339,7 @@ function createSwarmAgents(swarmId, swarmConfig, isDefault, pluginConfig) {
|
|
|
56241
56339
|
const prefixName = (name2) => `${prefix}${name2}`;
|
|
56242
56340
|
if (!isAgentDisabled("architect", swarmAgents, swarmPrefix)) {
|
|
56243
56341
|
const architectPrompts = getPrompts("architect");
|
|
56244
|
-
const architect = createArchitectAgent(getModel("architect"), architectPrompts.prompt, architectPrompts.appendPrompt, pluginConfig?.adversarial_testing);
|
|
56342
|
+
const architect = createArchitectAgent(getModel("architect"), architectPrompts.prompt, architectPrompts.appendPrompt, pluginConfig?.adversarial_testing, pluginConfig?.council);
|
|
56245
56343
|
architect.name = prefixName("architect");
|
|
56246
56344
|
const swarmName = swarmConfig.name || swarmId;
|
|
56247
56345
|
const swarmIdentity = isDefault ? "default" : swarmId;
|
|
@@ -68250,6 +68348,345 @@ var complexity_hotspots = createSwarmTool({
|
|
|
68250
68348
|
}
|
|
68251
68349
|
}
|
|
68252
68350
|
});
|
|
68351
|
+
// src/tools/convene-council.ts
|
|
68352
|
+
init_dist();
|
|
68353
|
+
init_zod();
|
|
68354
|
+
init_loader();
|
|
68355
|
+
|
|
68356
|
+
// src/council/council-advisory.ts
|
|
68357
|
+
function pushCouncilAdvisory(session, synthesis) {
|
|
68358
|
+
const dedupKey = `council:${synthesis.taskId}:${synthesis.roundNumber}`;
|
|
68359
|
+
if (synthesis.overallVerdict === "APPROVE" && synthesis.advisoryFindings.length === 0) {
|
|
68360
|
+
return;
|
|
68361
|
+
}
|
|
68362
|
+
session.pendingAdvisoryMessages ??= [];
|
|
68363
|
+
if (session.pendingAdvisoryMessages.some((m) => m.includes(dedupKey))) {
|
|
68364
|
+
return;
|
|
68365
|
+
}
|
|
68366
|
+
const blocking = synthesis.overallVerdict === "REJECT";
|
|
68367
|
+
const header = `[${dedupKey}] (priority=HIGH, blocking=${blocking})`;
|
|
68368
|
+
const body2 = synthesis.unifiedFeedbackMd;
|
|
68369
|
+
session.pendingAdvisoryMessages.push(`${header}
|
|
68370
|
+
${body2}`);
|
|
68371
|
+
}
|
|
68372
|
+
|
|
68373
|
+
// src/council/council-evidence-writer.ts
|
|
68374
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync16, readFileSync as readFileSync35, writeFileSync as writeFileSync11 } from "fs";
|
|
68375
|
+
import { join as join59 } from "path";
|
|
68376
|
+
var EVIDENCE_DIR2 = ".swarm/evidence";
|
|
68377
|
+
var VALID_TASK_ID = /^\d+\.\d+(\.\d+)*$/;
|
|
68378
|
+
var COUNCIL_GATE_NAME = "council";
|
|
68379
|
+
var COUNCIL_AGENT_ID = "architect";
|
|
68380
|
+
var FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
68381
|
+
function safeAssignOwnProps(target, source) {
|
|
68382
|
+
for (const key of Object.keys(source)) {
|
|
68383
|
+
if (FORBIDDEN_KEYS.has(key))
|
|
68384
|
+
continue;
|
|
68385
|
+
target[key] = source[key];
|
|
68386
|
+
}
|
|
68387
|
+
return target;
|
|
68388
|
+
}
|
|
68389
|
+
function writeCouncilEvidence(workingDir, synthesis) {
|
|
68390
|
+
if (!VALID_TASK_ID.test(synthesis.taskId)) {
|
|
68391
|
+
throw new Error(`writeCouncilEvidence: invalid taskId "${synthesis.taskId}" \u2014 must match N.M or N.M.P format`);
|
|
68392
|
+
}
|
|
68393
|
+
const dir = join59(workingDir, EVIDENCE_DIR2);
|
|
68394
|
+
mkdirSync16(dir, { recursive: true });
|
|
68395
|
+
const filePath = join59(dir, `${synthesis.taskId}.json`);
|
|
68396
|
+
const existingRoot = Object.create(null);
|
|
68397
|
+
if (existsSync36(filePath)) {
|
|
68398
|
+
try {
|
|
68399
|
+
const parsed = JSON.parse(readFileSync35(filePath, "utf-8"));
|
|
68400
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
68401
|
+
safeAssignOwnProps(existingRoot, parsed);
|
|
68402
|
+
}
|
|
68403
|
+
} catch {}
|
|
68404
|
+
}
|
|
68405
|
+
const existingGatesRaw = existingRoot.gates;
|
|
68406
|
+
const mergedGates = Object.create(null);
|
|
68407
|
+
if (existingGatesRaw && typeof existingGatesRaw === "object" && !Array.isArray(existingGatesRaw)) {
|
|
68408
|
+
safeAssignOwnProps(mergedGates, existingGatesRaw);
|
|
68409
|
+
}
|
|
68410
|
+
mergedGates[COUNCIL_GATE_NAME] = {
|
|
68411
|
+
sessionId: synthesis.swarmId,
|
|
68412
|
+
timestamp: synthesis.timestamp,
|
|
68413
|
+
agent: COUNCIL_AGENT_ID,
|
|
68414
|
+
verdict: synthesis.overallVerdict,
|
|
68415
|
+
vetoedBy: synthesis.vetoedBy,
|
|
68416
|
+
roundNumber: synthesis.roundNumber,
|
|
68417
|
+
allCriteriaMet: synthesis.allCriteriaMet
|
|
68418
|
+
};
|
|
68419
|
+
const updated = Object.create(null);
|
|
68420
|
+
safeAssignOwnProps(updated, existingRoot);
|
|
68421
|
+
updated.gates = mergedGates;
|
|
68422
|
+
writeFileSync11(filePath, JSON.stringify(updated, null, 2));
|
|
68423
|
+
}
|
|
68424
|
+
|
|
68425
|
+
// src/council/types.ts
|
|
68426
|
+
var COUNCIL_DEFAULTS = {
|
|
68427
|
+
enabled: false,
|
|
68428
|
+
maxRounds: 3,
|
|
68429
|
+
parallelTimeoutMs: 30000,
|
|
68430
|
+
vetoPriority: true,
|
|
68431
|
+
requireAllMembers: false
|
|
68432
|
+
};
|
|
68433
|
+
|
|
68434
|
+
// src/council/council-service.ts
|
|
68435
|
+
function synthesizeCouncilVerdicts(taskId, swarmId, verdicts, criteria, roundNumber, config3 = {}) {
|
|
68436
|
+
const cfg = { ...COUNCIL_DEFAULTS, ...config3 };
|
|
68437
|
+
const timestamp = new Date().toISOString();
|
|
68438
|
+
const rejectingMembers = verdicts.filter((v) => v.verdict === "REJECT").map((v) => v.agent);
|
|
68439
|
+
let overallVerdict;
|
|
68440
|
+
if (cfg.vetoPriority && rejectingMembers.length > 0) {
|
|
68441
|
+
overallVerdict = "REJECT";
|
|
68442
|
+
} else if (verdicts.some((v) => v.verdict === "CONCERNS") || !cfg.vetoPriority && rejectingMembers.length > 0) {
|
|
68443
|
+
overallVerdict = "CONCERNS";
|
|
68444
|
+
} else {
|
|
68445
|
+
overallVerdict = "APPROVE";
|
|
68446
|
+
}
|
|
68447
|
+
const unresolvedConflicts = detectConflicts(verdicts);
|
|
68448
|
+
const rejectingSet = new Set(rejectingMembers);
|
|
68449
|
+
const vetoFindings = verdicts.filter((v) => rejectingSet.has(v.agent)).flatMap((v) => v.findings);
|
|
68450
|
+
const requiredFixes = vetoFindings.filter((f) => f.severity === "HIGH" || f.severity === "MEDIUM");
|
|
68451
|
+
const advisoryFindings = [
|
|
68452
|
+
...vetoFindings.filter((f) => f.severity === "LOW"),
|
|
68453
|
+
...verdicts.filter((v) => !rejectingSet.has(v.agent)).flatMap((v) => v.findings)
|
|
68454
|
+
];
|
|
68455
|
+
const allAssessedIds = new Set(verdicts.flatMap((v) => v.criteriaAssessed));
|
|
68456
|
+
const allUnmetIds = new Set(verdicts.flatMap((v) => v.criteriaUnmet));
|
|
68457
|
+
const mandatoryIds = new Set((criteria?.criteria ?? []).filter((c) => c.mandatory).map((c) => c.id));
|
|
68458
|
+
const allCriteriaMet = [...mandatoryIds].every((id) => allAssessedIds.has(id) && !allUnmetIds.has(id));
|
|
68459
|
+
const unifiedFeedbackMd = buildUnifiedFeedback(taskId, overallVerdict, rejectingMembers, requiredFixes, advisoryFindings, unresolvedConflicts, roundNumber, cfg.maxRounds);
|
|
68460
|
+
return {
|
|
68461
|
+
taskId,
|
|
68462
|
+
swarmId,
|
|
68463
|
+
timestamp,
|
|
68464
|
+
overallVerdict,
|
|
68465
|
+
vetoedBy: rejectingMembers.length > 0 ? rejectingMembers : null,
|
|
68466
|
+
memberVerdicts: verdicts,
|
|
68467
|
+
unresolvedConflicts,
|
|
68468
|
+
requiredFixes,
|
|
68469
|
+
advisoryFindings,
|
|
68470
|
+
unifiedFeedbackMd,
|
|
68471
|
+
roundNumber,
|
|
68472
|
+
allCriteriaMet
|
|
68473
|
+
};
|
|
68474
|
+
}
|
|
68475
|
+
function detectConflicts(verdicts) {
|
|
68476
|
+
const conflicts = [];
|
|
68477
|
+
const locationMap = new Map;
|
|
68478
|
+
for (const verdict of verdicts) {
|
|
68479
|
+
for (const finding of verdict.findings) {
|
|
68480
|
+
const key = finding.location.toLowerCase();
|
|
68481
|
+
if (!key)
|
|
68482
|
+
continue;
|
|
68483
|
+
const entries = locationMap.get(key);
|
|
68484
|
+
if (entries) {
|
|
68485
|
+
entries.push({ agent: verdict.agent, detail: finding.detail });
|
|
68486
|
+
} else {
|
|
68487
|
+
locationMap.set(key, [
|
|
68488
|
+
{ agent: verdict.agent, detail: finding.detail }
|
|
68489
|
+
]);
|
|
68490
|
+
}
|
|
68491
|
+
}
|
|
68492
|
+
}
|
|
68493
|
+
for (const [location, entries] of locationMap) {
|
|
68494
|
+
if (entries.length < 2)
|
|
68495
|
+
continue;
|
|
68496
|
+
const addDirectives = entries.filter((e) => /\badd\b|\binclude\b|\binsert\b/i.test(e.detail));
|
|
68497
|
+
const removeDirectives = entries.filter((e) => /\bremove\b|\bdelete\b|\beliminate\b/i.test(e.detail));
|
|
68498
|
+
if (addDirectives.length > 0 && removeDirectives.length > 0) {
|
|
68499
|
+
conflicts.push(`Conflict at ${location}: ${addDirectives.map((e) => `${e.agent} says "${e.detail}"`).join(", ")} vs ${removeDirectives.map((e) => `${e.agent} says "${e.detail}"`).join(", ")}`);
|
|
68500
|
+
}
|
|
68501
|
+
}
|
|
68502
|
+
return conflicts;
|
|
68503
|
+
}
|
|
68504
|
+
function buildUnifiedFeedback(taskId, verdict, vetoedBy, requiredFixes, advisoryFindings, conflicts, roundNumber, maxRounds) {
|
|
68505
|
+
const lines = [
|
|
68506
|
+
`## Work Complete Council \u2014 Round ${roundNumber}/${maxRounds}`,
|
|
68507
|
+
`**Task:** ${taskId} **Overall verdict:** ${verdict}`,
|
|
68508
|
+
""
|
|
68509
|
+
];
|
|
68510
|
+
if (vetoedBy.length > 0) {
|
|
68511
|
+
lines.push(`> \u26D4 **BLOCKED** by: ${vetoedBy.join(", ")}`);
|
|
68512
|
+
lines.push("");
|
|
68513
|
+
}
|
|
68514
|
+
if (requiredFixes.length > 0) {
|
|
68515
|
+
lines.push("### Required Fixes (must resolve before re-submission)");
|
|
68516
|
+
for (const f of requiredFixes) {
|
|
68517
|
+
lines.push(`- **[${f.severity}]** \`${f.location}\` \u2014 ${f.detail}`, ` _Evidence:_ ${f.evidence}`);
|
|
68518
|
+
}
|
|
68519
|
+
lines.push("");
|
|
68520
|
+
}
|
|
68521
|
+
if (conflicts.length > 0) {
|
|
68522
|
+
lines.push("### Conflicts to Resolve");
|
|
68523
|
+
lines.push("_The following reviewers gave contradictory instructions. Architect must resolve before sending to coder._");
|
|
68524
|
+
for (const c of conflicts) {
|
|
68525
|
+
lines.push(`- ${c}`);
|
|
68526
|
+
}
|
|
68527
|
+
lines.push("");
|
|
68528
|
+
}
|
|
68529
|
+
if (advisoryFindings.length > 0) {
|
|
68530
|
+
lines.push("### Advisory Findings (non-blocking)");
|
|
68531
|
+
for (const f of advisoryFindings) {
|
|
68532
|
+
lines.push(`- **[${f.severity}]** \`${f.location}\` \u2014 ${f.detail}`);
|
|
68533
|
+
}
|
|
68534
|
+
lines.push("");
|
|
68535
|
+
}
|
|
68536
|
+
if (verdict === "APPROVE") {
|
|
68537
|
+
lines.push("> \u2705 **All council members approved.** Work may advance to `complete`.");
|
|
68538
|
+
} else if (roundNumber >= maxRounds) {
|
|
68539
|
+
lines.push(`> \u26A0\uFE0F **Max rounds (${maxRounds}) reached.** Escalate to user \u2014 do not auto-advance.`);
|
|
68540
|
+
}
|
|
68541
|
+
return lines.join(`
|
|
68542
|
+
`);
|
|
68543
|
+
}
|
|
68544
|
+
|
|
68545
|
+
// src/council/criteria-store.ts
|
|
68546
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync17, readFileSync as readFileSync36, writeFileSync as writeFileSync12 } from "fs";
|
|
68547
|
+
import { join as join60 } from "path";
|
|
68548
|
+
var COUNCIL_DIR = ".swarm/council";
|
|
68549
|
+
function writeCriteria(workingDir, taskId, criteria) {
|
|
68550
|
+
const dir = join60(workingDir, COUNCIL_DIR);
|
|
68551
|
+
mkdirSync17(dir, { recursive: true });
|
|
68552
|
+
const payload = {
|
|
68553
|
+
taskId,
|
|
68554
|
+
criteria,
|
|
68555
|
+
declaredAt: new Date().toISOString()
|
|
68556
|
+
};
|
|
68557
|
+
writeFileSync12(join60(dir, `${safeId(taskId)}.json`), JSON.stringify(payload, null, 2));
|
|
68558
|
+
}
|
|
68559
|
+
function readCriteria(workingDir, taskId) {
|
|
68560
|
+
const filePath = join60(workingDir, COUNCIL_DIR, `${safeId(taskId)}.json`);
|
|
68561
|
+
if (!existsSync37(filePath))
|
|
68562
|
+
return null;
|
|
68563
|
+
try {
|
|
68564
|
+
const parsed = JSON.parse(readFileSync36(filePath, "utf-8"));
|
|
68565
|
+
if (parsed && typeof parsed === "object" && typeof parsed.taskId === "string" && Array.isArray(parsed.criteria)) {
|
|
68566
|
+
return parsed;
|
|
68567
|
+
}
|
|
68568
|
+
return null;
|
|
68569
|
+
} catch {
|
|
68570
|
+
return null;
|
|
68571
|
+
}
|
|
68572
|
+
}
|
|
68573
|
+
function safeId(id) {
|
|
68574
|
+
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
68575
|
+
}
|
|
68576
|
+
|
|
68577
|
+
// src/tools/convene-council.ts
|
|
68578
|
+
init_state();
|
|
68579
|
+
init_create_tool();
|
|
68580
|
+
init_resolve_working_directory();
|
|
68581
|
+
var FindingSchema = exports_external.object({
|
|
68582
|
+
severity: exports_external.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
68583
|
+
category: exports_external.string().min(1),
|
|
68584
|
+
location: exports_external.string(),
|
|
68585
|
+
detail: exports_external.string(),
|
|
68586
|
+
evidence: exports_external.string()
|
|
68587
|
+
});
|
|
68588
|
+
var VerdictSchema = exports_external.object({
|
|
68589
|
+
agent: exports_external.enum(["critic", "reviewer", "sme", "test_engineer", "explorer"]),
|
|
68590
|
+
verdict: exports_external.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
68591
|
+
confidence: exports_external.number().min(0).max(1),
|
|
68592
|
+
findings: exports_external.array(FindingSchema),
|
|
68593
|
+
criteriaAssessed: exports_external.array(exports_external.string()),
|
|
68594
|
+
criteriaUnmet: exports_external.array(exports_external.string()),
|
|
68595
|
+
durationMs: exports_external.number().nonnegative()
|
|
68596
|
+
});
|
|
68597
|
+
var ArgsSchema = exports_external.object({
|
|
68598
|
+
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, 'Task ID must be in N.M or N.M.P format (e.g. "1.1")'),
|
|
68599
|
+
swarmId: exports_external.string().min(1),
|
|
68600
|
+
roundNumber: exports_external.number().int().min(1).max(10).default(1),
|
|
68601
|
+
verdicts: exports_external.array(VerdictSchema).min(1).max(5),
|
|
68602
|
+
working_directory: exports_external.string().optional()
|
|
68603
|
+
});
|
|
68604
|
+
var convene_council = createSwarmTool({
|
|
68605
|
+
description: "Convene the Work Complete Council. Accepts parallel verdicts from critic, " + "reviewer, sme, test_engineer, and explorer (anti-slop specialist). Returns " + "a synthesized assessment with a veto-aware overall verdict, required fixes, " + "and a single unified feedback document. Architect-only. Config-gated via " + "council.enabled.",
|
|
68606
|
+
args: {
|
|
68607
|
+
taskId: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID being evaluated, e.g. "1.1", "1.2.3"'),
|
|
68608
|
+
swarmId: tool.schema.string().min(1).describe('Swarm identifier, e.g. "mega"'),
|
|
68609
|
+
roundNumber: tool.schema.number().int().min(1).max(10).optional().describe("1-indexed round number. Defaults to 1."),
|
|
68610
|
+
verdicts: tool.schema.array(tool.schema.object({
|
|
68611
|
+
agent: tool.schema.enum([
|
|
68612
|
+
"critic",
|
|
68613
|
+
"reviewer",
|
|
68614
|
+
"sme",
|
|
68615
|
+
"test_engineer",
|
|
68616
|
+
"explorer"
|
|
68617
|
+
]),
|
|
68618
|
+
verdict: tool.schema.enum(["APPROVE", "CONCERNS", "REJECT"]),
|
|
68619
|
+
confidence: tool.schema.number().min(0).max(1),
|
|
68620
|
+
findings: tool.schema.array(tool.schema.object({
|
|
68621
|
+
severity: tool.schema.enum(["HIGH", "MEDIUM", "LOW"]),
|
|
68622
|
+
category: tool.schema.string().min(1),
|
|
68623
|
+
location: tool.schema.string(),
|
|
68624
|
+
detail: tool.schema.string(),
|
|
68625
|
+
evidence: tool.schema.string()
|
|
68626
|
+
})),
|
|
68627
|
+
criteriaAssessed: tool.schema.array(tool.schema.string()),
|
|
68628
|
+
criteriaUnmet: tool.schema.array(tool.schema.string()),
|
|
68629
|
+
durationMs: tool.schema.number()
|
|
68630
|
+
})).min(1).max(5).describe("Array of CouncilMemberVerdict objects. Must include between 1 and 5 entries, one per participating member (critic, reviewer, sme, test_engineer, explorer)."),
|
|
68631
|
+
working_directory: tool.schema.string().optional().describe("Explicit project root directory. When provided, .swarm/council/ and .swarm/evidence/ are resolved relative to this path instead of the plugin context directory.")
|
|
68632
|
+
},
|
|
68633
|
+
async execute(args2, directory, ctx) {
|
|
68634
|
+
const parsed = ArgsSchema.safeParse(args2);
|
|
68635
|
+
if (!parsed.success) {
|
|
68636
|
+
return JSON.stringify({
|
|
68637
|
+
success: false,
|
|
68638
|
+
reason: "invalid arguments",
|
|
68639
|
+
errors: parsed.error.issues.map((i2) => ({
|
|
68640
|
+
path: i2.path.join("."),
|
|
68641
|
+
message: i2.message
|
|
68642
|
+
}))
|
|
68643
|
+
}, null, 2);
|
|
68644
|
+
}
|
|
68645
|
+
const input = parsed.data;
|
|
68646
|
+
const dirResult = resolveWorkingDirectory(input.working_directory, directory);
|
|
68647
|
+
if (!dirResult.success) {
|
|
68648
|
+
return JSON.stringify({ success: false, reason: dirResult.message }, null, 2);
|
|
68649
|
+
}
|
|
68650
|
+
const workingDir = dirResult.directory;
|
|
68651
|
+
const config3 = loadPluginConfig(workingDir);
|
|
68652
|
+
if (!config3.council?.enabled) {
|
|
68653
|
+
return JSON.stringify({
|
|
68654
|
+
success: false,
|
|
68655
|
+
reason: "council feature is disabled \u2014 set council.enabled: true in .opencode/opencode-swarm.json to enable"
|
|
68656
|
+
}, null, 2);
|
|
68657
|
+
}
|
|
68658
|
+
if (config3.council?.requireAllMembers && input.verdicts.length < 5) {
|
|
68659
|
+
return JSON.stringify({
|
|
68660
|
+
success: false,
|
|
68661
|
+
reason: `council.requireAllMembers is true but only ${input.verdicts.length} of 5 member verdicts were provided`
|
|
68662
|
+
}, null, 2);
|
|
68663
|
+
}
|
|
68664
|
+
const criteria = readCriteria(workingDir, input.taskId);
|
|
68665
|
+
const verdicts = input.verdicts;
|
|
68666
|
+
const synthesis = synthesizeCouncilVerdicts(input.taskId, input.swarmId, verdicts, criteria, input.roundNumber, config3.council);
|
|
68667
|
+
writeCouncilEvidence(workingDir, synthesis);
|
|
68668
|
+
try {
|
|
68669
|
+
const sessionID = ctx?.sessionID;
|
|
68670
|
+
if (sessionID) {
|
|
68671
|
+
const session = getAgentSession(sessionID);
|
|
68672
|
+
if (session) {
|
|
68673
|
+
pushCouncilAdvisory(session, synthesis);
|
|
68674
|
+
}
|
|
68675
|
+
}
|
|
68676
|
+
} catch {}
|
|
68677
|
+
return JSON.stringify({
|
|
68678
|
+
success: true,
|
|
68679
|
+
overallVerdict: synthesis.overallVerdict,
|
|
68680
|
+
vetoedBy: synthesis.vetoedBy,
|
|
68681
|
+
roundNumber: synthesis.roundNumber,
|
|
68682
|
+
allCriteriaMet: synthesis.allCriteriaMet,
|
|
68683
|
+
requiredFixesCount: synthesis.requiredFixes.length,
|
|
68684
|
+
advisoryFindingsCount: synthesis.advisoryFindings.length,
|
|
68685
|
+
unresolvedConflictsCount: synthesis.unresolvedConflicts.length,
|
|
68686
|
+
unifiedFeedbackMd: synthesis.unifiedFeedbackMd
|
|
68687
|
+
}, null, 2);
|
|
68688
|
+
}
|
|
68689
|
+
});
|
|
68253
68690
|
// src/tools/curator-analyze.ts
|
|
68254
68691
|
init_dist();
|
|
68255
68692
|
init_config();
|
|
@@ -68367,6 +68804,88 @@ var curator_analyze = createSwarmTool({
|
|
|
68367
68804
|
}
|
|
68368
68805
|
}
|
|
68369
68806
|
});
|
|
68807
|
+
// src/tools/declare-council-criteria.ts
|
|
68808
|
+
init_dist();
|
|
68809
|
+
init_zod();
|
|
68810
|
+
init_loader();
|
|
68811
|
+
init_create_tool();
|
|
68812
|
+
init_resolve_working_directory();
|
|
68813
|
+
var CriteriaItemSchema = exports_external.object({
|
|
68814
|
+
id: exports_external.string().min(1).max(20).regex(/^C\d+$/, 'Criterion id must match C\\d+ (e.g. "C1", "C12")'),
|
|
68815
|
+
description: exports_external.string().min(10).max(500),
|
|
68816
|
+
mandatory: exports_external.boolean()
|
|
68817
|
+
});
|
|
68818
|
+
var ArgsSchema2 = exports_external.object({
|
|
68819
|
+
taskId: exports_external.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format"),
|
|
68820
|
+
criteria: exports_external.array(CriteriaItemSchema).min(1).max(20),
|
|
68821
|
+
working_directory: exports_external.string().optional()
|
|
68822
|
+
});
|
|
68823
|
+
var declare_council_criteria = createSwarmTool({
|
|
68824
|
+
description: "Pre-declare acceptance criteria for a task before the coder starts work. " + "Criteria are persisted under .swarm/council/ and read back during council " + "evaluation so reviewers assess a stable, pre-committed contract. " + "Architect-only. Config-gated via council.enabled.",
|
|
68825
|
+
args: {
|
|
68826
|
+
taskId: tool.schema.string().min(1).regex(/^\d+\.\d+(\.\d+)*$/, "Task ID must be in N.M or N.M.P format").describe('Task ID for which criteria are declared, e.g. "1.1", "1.2.3"'),
|
|
68827
|
+
criteria: tool.schema.array(tool.schema.object({
|
|
68828
|
+
id: tool.schema.string().min(1).max(20).regex(/^C\d+$/, "Criterion id must match C\\d+").describe('Criterion identifier, e.g. "C1", "C12"'),
|
|
68829
|
+
description: tool.schema.string().min(10).max(500).describe("Human-readable description of the criterion"),
|
|
68830
|
+
mandatory: tool.schema.boolean().describe("Whether the criterion is mandatory. Mandatory criteria block APPROVE when unmet.")
|
|
68831
|
+
})).min(1).max(20).describe("Array of acceptance criteria items. Must contain between 1 and 20 entries with unique ids."),
|
|
68832
|
+
working_directory: tool.schema.string().optional().describe("Explicit project root directory. When provided, .swarm/council/ is resolved relative to this path instead of the plugin context directory.")
|
|
68833
|
+
},
|
|
68834
|
+
async execute(args2, directory) {
|
|
68835
|
+
const parsed = ArgsSchema2.safeParse(args2);
|
|
68836
|
+
if (!parsed.success) {
|
|
68837
|
+
return JSON.stringify({
|
|
68838
|
+
success: false,
|
|
68839
|
+
reason: "invalid arguments",
|
|
68840
|
+
errors: parsed.error.issues.map((i2) => ({
|
|
68841
|
+
path: i2.path.join("."),
|
|
68842
|
+
message: i2.message
|
|
68843
|
+
}))
|
|
68844
|
+
}, null, 2);
|
|
68845
|
+
}
|
|
68846
|
+
const input = parsed.data;
|
|
68847
|
+
const dirResult = resolveWorkingDirectory(input.working_directory, directory);
|
|
68848
|
+
if (!dirResult.success) {
|
|
68849
|
+
return JSON.stringify({ success: false, reason: dirResult.message }, null, 2);
|
|
68850
|
+
}
|
|
68851
|
+
const workingDir = dirResult.directory;
|
|
68852
|
+
const config3 = loadPluginConfig(workingDir);
|
|
68853
|
+
if (!config3.council?.enabled) {
|
|
68854
|
+
return JSON.stringify({
|
|
68855
|
+
success: false,
|
|
68856
|
+
reason: "council feature is disabled \u2014 set council.enabled: true in .opencode/opencode-swarm.json to enable"
|
|
68857
|
+
}, null, 2);
|
|
68858
|
+
}
|
|
68859
|
+
const ids = input.criteria.map((c) => c.id);
|
|
68860
|
+
const idSet = new Set(ids);
|
|
68861
|
+
if (idSet.size < ids.length) {
|
|
68862
|
+
const seen = new Set;
|
|
68863
|
+
const duplicates = [];
|
|
68864
|
+
for (const id of ids) {
|
|
68865
|
+
if (seen.has(id) && !duplicates.includes(id)) {
|
|
68866
|
+
duplicates.push(id);
|
|
68867
|
+
}
|
|
68868
|
+
seen.add(id);
|
|
68869
|
+
}
|
|
68870
|
+
return JSON.stringify({
|
|
68871
|
+
success: false,
|
|
68872
|
+
reason: "duplicate criterion ids",
|
|
68873
|
+
errors: duplicates
|
|
68874
|
+
}, null, 2);
|
|
68875
|
+
}
|
|
68876
|
+
const existing = readCriteria(workingDir, input.taskId);
|
|
68877
|
+
const replaced = existing !== null;
|
|
68878
|
+
writeCriteria(workingDir, input.taskId, input.criteria);
|
|
68879
|
+
return JSON.stringify({
|
|
68880
|
+
success: true,
|
|
68881
|
+
taskId: input.taskId,
|
|
68882
|
+
criteriaCount: input.criteria.length,
|
|
68883
|
+
mandatoryCount: input.criteria.filter((c) => c.mandatory).length,
|
|
68884
|
+
declaredAt: new Date().toISOString(),
|
|
68885
|
+
replaced
|
|
68886
|
+
}, null, 2);
|
|
68887
|
+
}
|
|
68888
|
+
});
|
|
68370
68889
|
// src/tools/declare-scope.ts
|
|
68371
68890
|
init_tool();
|
|
68372
68891
|
init_state();
|
|
@@ -69247,7 +69766,7 @@ import * as fs52 from "fs";
|
|
|
69247
69766
|
import * as path67 from "path";
|
|
69248
69767
|
var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
|
|
69249
69768
|
var MAX_EVIDENCE_FILES = 1000;
|
|
69250
|
-
var
|
|
69769
|
+
var EVIDENCE_DIR3 = ".swarm/evidence";
|
|
69251
69770
|
var PLAN_FILE = ".swarm/plan.md";
|
|
69252
69771
|
var SHELL_METACHAR_REGEX2 = /[;&|%$`\\]/;
|
|
69253
69772
|
var VALID_EVIDENCE_FILENAME_REGEX = /^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.json$/;
|
|
@@ -69456,7 +69975,7 @@ var evidence_check = createSwarmTool({
|
|
|
69456
69975
|
};
|
|
69457
69976
|
return JSON.stringify(result2, null, 2);
|
|
69458
69977
|
}
|
|
69459
|
-
const evidenceDir = path67.join(cwd,
|
|
69978
|
+
const evidenceDir = path67.join(cwd, EVIDENCE_DIR3);
|
|
69460
69979
|
const evidence = readEvidenceFiles(evidenceDir, cwd);
|
|
69461
69980
|
const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
|
|
69462
69981
|
const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
|
|
@@ -70260,7 +70779,7 @@ init_dist();
|
|
|
70260
70779
|
init_config();
|
|
70261
70780
|
init_knowledge_store();
|
|
70262
70781
|
init_create_tool();
|
|
70263
|
-
import { existsSync as
|
|
70782
|
+
import { existsSync as existsSync42 } from "fs";
|
|
70264
70783
|
var DEFAULT_LIMIT = 10;
|
|
70265
70784
|
var MAX_LESSON_LENGTH = 200;
|
|
70266
70785
|
var VALID_CATEGORIES3 = [
|
|
@@ -70329,14 +70848,14 @@ function validateLimit(limit) {
|
|
|
70329
70848
|
}
|
|
70330
70849
|
async function readSwarmKnowledge(directory) {
|
|
70331
70850
|
const swarmPath = resolveSwarmKnowledgePath(directory);
|
|
70332
|
-
if (!
|
|
70851
|
+
if (!existsSync42(swarmPath)) {
|
|
70333
70852
|
return [];
|
|
70334
70853
|
}
|
|
70335
70854
|
return readKnowledge(swarmPath);
|
|
70336
70855
|
}
|
|
70337
70856
|
async function readHiveKnowledge() {
|
|
70338
70857
|
const hivePath = resolveHiveKnowledgePath();
|
|
70339
|
-
if (!
|
|
70858
|
+
if (!existsSync42(hivePath)) {
|
|
70340
70859
|
return [];
|
|
70341
70860
|
}
|
|
70342
70861
|
return readKnowledge(hivePath);
|
|
@@ -75186,7 +75705,7 @@ init_create_tool();
|
|
|
75186
75705
|
import * as fs60 from "fs";
|
|
75187
75706
|
import * as path76 from "path";
|
|
75188
75707
|
var SPEC_FILE = ".swarm/spec.md";
|
|
75189
|
-
var
|
|
75708
|
+
var EVIDENCE_DIR4 = ".swarm/evidence";
|
|
75190
75709
|
var OBLIGATION_KEYWORDS = ["MUST", "SHOULD", "SHALL"];
|
|
75191
75710
|
var MAX_FILE_SIZE_BYTES9 = 1024 * 1024;
|
|
75192
75711
|
function extractRequirements(specContent) {
|
|
@@ -75482,7 +76001,7 @@ var req_coverage = createSwarmTool({
|
|
|
75482
76001
|
message: "No FR requirements found in spec.md"
|
|
75483
76002
|
}, null, 2);
|
|
75484
76003
|
}
|
|
75485
|
-
const evidenceDir = path76.join(cwd,
|
|
76004
|
+
const evidenceDir = path76.join(cwd, EVIDENCE_DIR4);
|
|
75486
76005
|
const touchedFiles = readTouchedFiles(evidenceDir, phase, cwd);
|
|
75487
76006
|
const analyzedRequirements = [];
|
|
75488
76007
|
let coveredCount = 0;
|
|
@@ -78498,6 +79017,7 @@ var todo_extract = createSwarmTool({
|
|
|
78498
79017
|
});
|
|
78499
79018
|
// src/tools/update-task-status.ts
|
|
78500
79019
|
init_tool();
|
|
79020
|
+
init_loader();
|
|
78501
79021
|
init_schema();
|
|
78502
79022
|
init_gate_evidence();
|
|
78503
79023
|
import * as fs70 from "fs";
|
|
@@ -78844,6 +79364,41 @@ function recoverTaskStateFromDelegations(taskId) {
|
|
|
78844
79364
|
}
|
|
78845
79365
|
}
|
|
78846
79366
|
}
|
|
79367
|
+
function checkCouncilGate(workingDirectory, taskId) {
|
|
79368
|
+
let councilEnabled = false;
|
|
79369
|
+
try {
|
|
79370
|
+
const config3 = loadPluginConfig(workingDirectory);
|
|
79371
|
+
councilEnabled = config3.council?.enabled === true;
|
|
79372
|
+
} catch {
|
|
79373
|
+
return { blocked: false, reason: "" };
|
|
79374
|
+
}
|
|
79375
|
+
if (!councilEnabled) {
|
|
79376
|
+
return { blocked: false, reason: "" };
|
|
79377
|
+
}
|
|
79378
|
+
let evidence;
|
|
79379
|
+
try {
|
|
79380
|
+
evidence = readTaskEvidenceRaw(workingDirectory, taskId);
|
|
79381
|
+
} catch {
|
|
79382
|
+
return {
|
|
79383
|
+
blocked: true,
|
|
79384
|
+
reason: "council gate required but not yet run \u2014 architect must call convene_council before advancing this task"
|
|
79385
|
+
};
|
|
79386
|
+
}
|
|
79387
|
+
const councilGate = evidence?.gates?.council;
|
|
79388
|
+
if (!councilGate) {
|
|
79389
|
+
return {
|
|
79390
|
+
blocked: true,
|
|
79391
|
+
reason: "council gate required but not yet run \u2014 architect must call convene_council before advancing this task"
|
|
79392
|
+
};
|
|
79393
|
+
}
|
|
79394
|
+
if (councilGate.verdict === "REJECT") {
|
|
79395
|
+
return {
|
|
79396
|
+
blocked: true,
|
|
79397
|
+
reason: "council gate blocked advancement \u2014 resolve requiredFixes and re-run convene_council"
|
|
79398
|
+
};
|
|
79399
|
+
}
|
|
79400
|
+
return { blocked: false, reason: "" };
|
|
79401
|
+
}
|
|
78847
79402
|
async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
78848
79403
|
const statusError = validateStatus(args2.status);
|
|
78849
79404
|
if (statusError) {
|
|
@@ -78980,6 +79535,14 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
78980
79535
|
};
|
|
78981
79536
|
}
|
|
78982
79537
|
}
|
|
79538
|
+
const councilCheck = checkCouncilGate(directory, args2.task_id);
|
|
79539
|
+
if (councilCheck.blocked) {
|
|
79540
|
+
return {
|
|
79541
|
+
success: false,
|
|
79542
|
+
message: councilCheck.reason,
|
|
79543
|
+
errors: [councilCheck.reason]
|
|
79544
|
+
};
|
|
79545
|
+
}
|
|
78983
79546
|
}
|
|
78984
79547
|
const lockTaskId = `update-task-status-${args2.task_id}-${Date.now()}`;
|
|
78985
79548
|
const planFilePath = "plan.json";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — architect-only tool.
|
|
3
|
+
*
|
|
4
|
+
* Accepts parallel verdicts from critic, reviewer, sme, and test_engineer,
|
|
5
|
+
* then synthesizes them into a veto-aware overall verdict with required fixes
|
|
6
|
+
* and a single unified feedback document.
|
|
7
|
+
*
|
|
8
|
+
* Config-gated (council.enabled must be true) and architect-only via
|
|
9
|
+
* AGENT_TOOL_MAP. Follows the check-gate-status.ts pattern.
|
|
10
|
+
*/
|
|
11
|
+
import { tool } from '@opencode-ai/plugin';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
export declare const ArgsSchema: z.ZodObject<{
|
|
14
|
+
taskId: z.ZodString;
|
|
15
|
+
swarmId: z.ZodString;
|
|
16
|
+
roundNumber: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
verdicts: z.ZodArray<z.ZodObject<{
|
|
18
|
+
agent: z.ZodEnum<{
|
|
19
|
+
reviewer: "reviewer";
|
|
20
|
+
test_engineer: "test_engineer";
|
|
21
|
+
explorer: "explorer";
|
|
22
|
+
sme: "sme";
|
|
23
|
+
critic: "critic";
|
|
24
|
+
}>;
|
|
25
|
+
verdict: z.ZodEnum<{
|
|
26
|
+
APPROVE: "APPROVE";
|
|
27
|
+
CONCERNS: "CONCERNS";
|
|
28
|
+
REJECT: "REJECT";
|
|
29
|
+
}>;
|
|
30
|
+
confidence: z.ZodNumber;
|
|
31
|
+
findings: z.ZodArray<z.ZodObject<{
|
|
32
|
+
severity: z.ZodEnum<{
|
|
33
|
+
HIGH: "HIGH";
|
|
34
|
+
MEDIUM: "MEDIUM";
|
|
35
|
+
LOW: "LOW";
|
|
36
|
+
}>;
|
|
37
|
+
category: z.ZodString;
|
|
38
|
+
location: z.ZodString;
|
|
39
|
+
detail: z.ZodString;
|
|
40
|
+
evidence: z.ZodString;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
criteriaAssessed: z.ZodArray<z.ZodString>;
|
|
43
|
+
criteriaUnmet: z.ZodArray<z.ZodString>;
|
|
44
|
+
durationMs: z.ZodNumber;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
working_directory: z.ZodOptional<z.ZodString>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
export declare const convene_council: ReturnType<typeof tool>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Complete Council — pre-declaration tool.
|
|
3
|
+
*
|
|
4
|
+
* Lets the architect declare acceptance criteria at plan time, before the
|
|
5
|
+
* coder starts work. Criteria are persisted to .swarm/council/{safeId}.json
|
|
6
|
+
* and later read back during council evaluation (convene_council) so that
|
|
7
|
+
* reviewers assess a stable, pre-committed contract rather than whatever
|
|
8
|
+
* criteria happen to be invented at review time.
|
|
9
|
+
*
|
|
10
|
+
* Config-gated (council.enabled must be true) and architect-only via
|
|
11
|
+
* AGENT_TOOL_MAP. Follows the convene-council.ts pattern.
|
|
12
|
+
*/
|
|
13
|
+
import { tool } from '@opencode-ai/plugin';
|
|
14
|
+
export declare const declare_council_criteria: ReturnType<typeof tool>;
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -5,7 +5,9 @@ export { checkpoint } from './checkpoint';
|
|
|
5
5
|
export { co_change_analyzer } from './co-change-analyzer';
|
|
6
6
|
export { completion_verify } from './completion-verify';
|
|
7
7
|
export { complexity_hotspots } from './complexity-hotspots';
|
|
8
|
+
export { convene_council } from './convene-council';
|
|
8
9
|
export { curator_analyze } from './curator-analyze';
|
|
10
|
+
export { declare_council_criteria } from './declare-council-criteria';
|
|
9
11
|
export { declare_scope } from './declare-scope';
|
|
10
12
|
export { type DiffErrorResult, type DiffResult, diff } from './diff';
|
|
11
13
|
export { doc_extract, doc_scan } from './doc-scan';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Used for constants and agent setup references.
|
|
4
4
|
*/
|
|
5
5
|
/** Union type of all valid tool names */
|
|
6
|
-
export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'check_gate_status' | 'completion_verify' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan' | 'update_task_status' | 'lint_spec' | 'write_retro' | 'write_drift_evidence' | 'declare_scope' | 'knowledge_query' | 'doc_scan' | 'doc_extract' | 'curator_analyze' | 'knowledge_add' | 'knowledge_recall' | 'knowledge_remove' | 'co_change_analyzer' | 'search' | 'batch_symbols' | 'suggest_patch' | 'req_coverage' | 'get_approved_plan' | 'repo_map';
|
|
6
|
+
export type ToolName = 'diff' | 'syntax_check' | 'placeholder_scan' | 'imports' | 'lint' | 'secretscan' | 'sast_scan' | 'build_check' | 'pre_check_batch' | 'quality_budget' | 'symbols' | 'complexity_hotspots' | 'schema_drift' | 'todo_extract' | 'evidence_check' | 'check_gate_status' | 'completion_verify' | 'convene_council' | 'declare_council_criteria' | 'sbom_generate' | 'checkpoint' | 'pkg_audit' | 'test_runner' | 'detect_domains' | 'gitingest' | 'retrieve_summary' | 'extract_code_blocks' | 'phase_complete' | 'save_plan' | 'update_task_status' | 'lint_spec' | 'write_retro' | 'write_drift_evidence' | 'declare_scope' | 'knowledge_query' | 'doc_scan' | 'doc_extract' | 'curator_analyze' | 'knowledge_add' | 'knowledge_recall' | 'knowledge_remove' | 'co_change_analyzer' | 'search' | 'batch_symbols' | 'suggest_patch' | 'req_coverage' | 'get_approved_plan' | 'repo_map';
|
|
7
7
|
/** Readonly array of all tool names */
|
|
8
8
|
export declare const TOOL_NAMES: readonly ToolName[];
|
|
9
9
|
/** Set for O(1) tool name validation */
|
|
@@ -71,6 +71,25 @@ export declare function checkReviewerGateWithScope(taskId: string, workingDirect
|
|
|
71
71
|
* @param taskId - The task ID to recover state for
|
|
72
72
|
*/
|
|
73
73
|
export declare function recoverTaskStateFromDelegations(taskId: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Result of the council-gate check used when transitioning to 'completed'.
|
|
76
|
+
*
|
|
77
|
+
* - When council.enabled is false, {blocked:false} is always returned (no regression).
|
|
78
|
+
* - When council.enabled is true, requires evidence.gates.council to exist and
|
|
79
|
+
* its verdict to be APPROVE or CONCERNS. A missing gate or REJECT verdict blocks.
|
|
80
|
+
*/
|
|
81
|
+
export interface CouncilGateResult {
|
|
82
|
+
blocked: boolean;
|
|
83
|
+
reason: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check the council gate for a completion transition. Pure — reads config and
|
|
87
|
+
* evidence only, no state mutation. Exported for focused unit testing.
|
|
88
|
+
*
|
|
89
|
+
* @param workingDirectory - Validated project root (contains .swarm/evidence/)
|
|
90
|
+
* @param taskId - Task ID in N.M or N.M.P format
|
|
91
|
+
*/
|
|
92
|
+
export declare function checkCouncilGate(workingDirectory: string, taskId: string): CouncilGateResult;
|
|
74
93
|
/**
|
|
75
94
|
* Execute the update_task_status tool.
|
|
76
95
|
* Validates the task_id and status, then updates the task status in the plan.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.66.0",
|
|
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",
|