opencode-swarm 6.35.4 → 6.37.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/cli/index.js CHANGED
@@ -14422,7 +14422,7 @@ async function loadEvidence(directory, taskId) {
14422
14422
  return { status: "found", bundle: validated };
14423
14423
  } catch (error49) {
14424
14424
  warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14425
- const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [String(error49)];
14425
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error49 instanceof Error ? error49.message : String(error49)];
14426
14426
  return { status: "invalid_schema", errors: errors3 };
14427
14427
  }
14428
14428
  }
@@ -14431,7 +14431,7 @@ async function loadEvidence(directory, taskId) {
14431
14431
  return { status: "found", bundle: validated };
14432
14432
  } catch (error49) {
14433
14433
  warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14434
- const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [String(error49)];
14434
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error49 instanceof Error ? error49.message : String(error49)];
14435
14435
  return { status: "invalid_schema", errors: errors3 };
14436
14436
  }
14437
14437
  }
@@ -17633,6 +17633,7 @@ var ALL_SUBAGENT_NAMES = [
17633
17633
  "docs",
17634
17634
  "designer",
17635
17635
  "critic_sounding_board",
17636
+ "critic_drift_verifier",
17636
17637
  ...QA_AGENTS,
17637
17638
  ...PIPELINE_AGENTS
17638
17639
  ];
@@ -17731,6 +17732,13 @@ var AGENT_TOOL_MAP = {
17731
17732
  "retrieve_summary",
17732
17733
  "symbols"
17733
17734
  ],
17735
+ critic_drift_verifier: [
17736
+ "complexity_hotspots",
17737
+ "detect_domains",
17738
+ "imports",
17739
+ "retrieve_summary",
17740
+ "symbols"
17741
+ ],
17734
17742
  docs: [
17735
17743
  "detect_domains",
17736
17744
  "extract_code_blocks",
@@ -18444,7 +18452,11 @@ var swarmState = {
18444
18452
  function getAgentSession(sessionId) {
18445
18453
  return swarmState.agentSessions.get(sessionId);
18446
18454
  }
18447
- function hasActiveTurboMode() {
18455
+ function hasActiveTurboMode(sessionID) {
18456
+ if (sessionID) {
18457
+ const session = swarmState.agentSessions.get(sessionID);
18458
+ return session?.turboMode === true;
18459
+ }
18448
18460
  for (const [_sessionId, session] of swarmState.agentSessions) {
18449
18461
  if (session.turboMode === true) {
18450
18462
  return true;
@@ -31104,7 +31116,16 @@ function createSwarmTool(opts) {
31104
31116
  args: opts.args,
31105
31117
  execute: async (args, ctx) => {
31106
31118
  const directory = ctx?.directory ?? process.cwd();
31107
- return opts.execute(args, directory, ctx);
31119
+ try {
31120
+ return await opts.execute(args, directory, ctx);
31121
+ } catch (error93) {
31122
+ const message = error93 instanceof Error ? error93.message : String(error93);
31123
+ return JSON.stringify({
31124
+ success: false,
31125
+ message: "Tool execution failed",
31126
+ errors: [message]
31127
+ }, null, 2);
31128
+ }
31108
31129
  }
31109
31130
  });
31110
31131
  }
@@ -32314,7 +32335,7 @@ async function handleCurateCommand(directory, _args) {
32314
32335
  if (error93 instanceof Error) {
32315
32336
  return `\u274C Curation failed: ${error93.message}`;
32316
32337
  }
32317
- return `\u274C Curation failed: ${String(error93)}`;
32338
+ return `\u274C Curation failed: ${error93 instanceof Error ? error93.message : String(error93)}`;
32318
32339
  }
32319
32340
  }
32320
32341
  function formatCurationSummary(summary) {
@@ -32702,7 +32723,7 @@ async function handleDarkMatterCommand(directory, args) {
32702
32723
  [${entries.length} dark matter finding(s) saved to .swarm/knowledge.jsonl]`;
32703
32724
  }
32704
32725
  } catch (err) {
32705
- console.warn("dark-matter: failed to save knowledge entries:", err);
32726
+ console.warn("dark-matter: failed to save knowledge entries:", err instanceof Error ? err.message : String(err));
32706
32727
  return output;
32707
32728
  }
32708
32729
  }
@@ -34461,7 +34482,7 @@ async function handleKnowledgeQuarantineCommand(directory, args) {
34461
34482
  await quarantineEntry(directory, entryId, reason, "user");
34462
34483
  return `\u2705 Entry ${entryId} quarantined successfully.`;
34463
34484
  } catch (error93) {
34464
- console.warn("[knowledge-command] quarantineEntry error:", error93);
34485
+ console.warn("[knowledge-command] quarantineEntry error:", error93 instanceof Error ? error93.message : String(error93));
34465
34486
  return `\u274C Failed to quarantine entry. Check the entry ID and try again.`;
34466
34487
  }
34467
34488
  }
@@ -34477,7 +34498,7 @@ async function handleKnowledgeRestoreCommand(directory, args) {
34477
34498
  await restoreEntry(directory, entryId);
34478
34499
  return `\u2705 Entry ${entryId} restored successfully.`;
34479
34500
  } catch (error93) {
34480
- console.warn("[knowledge-command] restoreEntry error:", error93);
34501
+ console.warn("[knowledge-command] restoreEntry error:", error93 instanceof Error ? error93.message : String(error93));
34481
34502
  return `\u274C Failed to restore entry. Check the entry ID and try again.`;
34482
34503
  }
34483
34504
  }
@@ -34499,7 +34520,7 @@ async function handleKnowledgeMigrateCommand(directory, args) {
34499
34520
  }
34500
34521
  return `\u2705 Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
34501
34522
  } catch (error93) {
34502
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93);
34523
+ console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
34503
34524
  return "\u274C Migration failed. Check .swarm/context.md is readable.";
34504
34525
  }
34505
34526
  }
@@ -34526,7 +34547,7 @@ async function handleKnowledgeListCommand(directory, _args) {
34526
34547
  return lines.join(`
34527
34548
  `);
34528
34549
  } catch (error93) {
34529
- console.warn("[knowledge-command] list error:", error93);
34550
+ console.warn("[knowledge-command] list error:", error93 instanceof Error ? error93.message : String(error93));
34530
34551
  return "\u274C Failed to list knowledge entries. Ensure .swarm/knowledge.jsonl exists.";
34531
34552
  }
34532
34553
  }
@@ -36561,7 +36582,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
36561
36582
  return false;
36562
36583
  }
36563
36584
  function containsControlChars(str) {
36564
- return /[\0\r]/.test(str);
36585
+ return /[\0\t\r\n]/.test(str);
36565
36586
  }
36566
36587
  function validateDirectoryInput(dir) {
36567
36588
  if (!dir || dir.length === 0) {
@@ -38632,7 +38653,7 @@ async function handlePromoteCommand(directory, args) {
38632
38653
  if (error93 instanceof Error) {
38633
38654
  return error93.message;
38634
38655
  }
38635
- return `Failed to promote lesson: ${String(error93)}`;
38656
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
38636
38657
  }
38637
38658
  }
38638
38659
  try {
@@ -38641,7 +38662,7 @@ async function handlePromoteCommand(directory, args) {
38641
38662
  if (error93 instanceof Error) {
38642
38663
  return error93.message;
38643
38664
  }
38644
- return `Failed to promote lesson: ${String(error93)}`;
38665
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
38645
38666
  }
38646
38667
  }
38647
38668
 
@@ -39575,7 +39596,7 @@ async function handleRollbackCommand(directory, args) {
39575
39596
  fs12.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
39576
39597
  `);
39577
39598
  } catch (error93) {
39578
- console.error("Failed to write rollback event:", error93);
39599
+ console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
39579
39600
  }
39580
39601
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
39581
39602
  }
@@ -2,8 +2,8 @@ import type { ToolName } from '../tools/tool-names';
2
2
  export declare const QA_AGENTS: readonly ["reviewer", "critic"];
3
3
  export declare const PIPELINE_AGENTS: readonly ["explorer", "coder", "test_engineer"];
4
4
  export declare const ORCHESTRATOR_NAME: "architect";
5
- export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "docs", "designer", "critic_sounding_board", "reviewer", "critic", "explorer", "coder", "test_engineer"];
6
- export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "docs", "designer", "critic_sounding_board", "reviewer", "critic", "explorer", "coder", "test_engineer"];
5
+ export declare const ALL_SUBAGENT_NAMES: readonly ["sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "reviewer", "critic", "explorer", "coder", "test_engineer"];
6
+ export declare const ALL_AGENT_NAMES: readonly ["architect", "sme", "docs", "designer", "critic_sounding_board", "critic_drift_verifier", "reviewer", "critic", "explorer", "coder", "test_engineer"];
7
7
  export type QAAgentName = (typeof QA_AGENTS)[number];
8
8
  export type PipelineAgentName = (typeof PIPELINE_AGENTS)[number];
9
9
  export type AgentName = (typeof ALL_AGENT_NAMES)[number];
@@ -12,14 +12,14 @@ export declare function readPriorDriftReports(directory: string): Promise<DriftR
12
12
  */
13
13
  export declare function writeDriftReport(directory: string, report: DriftReport): Promise<string>;
14
14
  /**
15
- * Run the critic drift check for the given phase.
15
+ * Deterministic drift check for the given phase.
16
16
  * Builds a structured DriftReport from curator data, plan, spec, and prior reports.
17
17
  * Writes the report to .swarm/drift-report-phase-N.json.
18
18
  * Emits 'curator.drift.completed' event on success.
19
19
  * On any error: emits 'curator.error' event and returns a safe default result.
20
20
  * NEVER throws — drift failures must not block phase_complete.
21
21
  */
22
- export declare function runCriticDriftCheck(directory: string, phase: number, curatorResult: CuratorPhaseResult, config: CuratorConfig, injectAdvisory?: (message: string) => void): Promise<CriticDriftResult>;
22
+ export declare function runDeterministicDriftCheck(directory: string, phase: number, curatorResult: CuratorPhaseResult, config: CuratorConfig, injectAdvisory?: (message: string) => void): Promise<CriticDriftResult>;
23
23
  /**
24
24
  * Build a truncated summary suitable for architect context injection.
25
25
  * Format: "<drift_report>Phase N: {alignment} ({drift_score}) — {key finding}. {correction if any}.</drift_report>"
@@ -50,6 +50,13 @@ export declare function createDelegationGateHook(config: PluginConfig, directory
50
50
  messagesTransform: (input: Record<string, never>, output: {
51
51
  messages?: MessageWithParts[];
52
52
  }) => Promise<void>;
53
+ toolBefore: (input: {
54
+ tool: string;
55
+ sessionID: string;
56
+ callID: string;
57
+ }, output: {
58
+ args: unknown;
59
+ }) => Promise<void>;
53
60
  toolAfter: (input: {
54
61
  tool: string;
55
62
  sessionID: string;
@@ -7,10 +7,15 @@ import type { KnowledgeConfig } from './knowledge-types.js';
7
7
  export declare function isWriteToEvidenceFile(input: unknown): boolean;
8
8
  /**
9
9
  * Curate and store swarm knowledge entries from lessons.
10
+ * @returns Promise resolving to an object with counts of stored, skipped, and rejected lessons.
10
11
  */
11
12
  export declare function curateAndStoreSwarm(lessons: string[], projectName: string, phaseInfo: {
12
13
  phase_number: number;
13
- }, directory: string, config: KnowledgeConfig): Promise<void>;
14
+ }, directory: string, config: KnowledgeConfig): Promise<{
15
+ stored: number;
16
+ skipped: number;
17
+ rejected: number;
18
+ }>;
14
19
  /**
15
20
  * Auto-promote swarm entries based on phase confirmations and age.
16
21
  */
@@ -24,7 +24,7 @@ export interface KnowledgeEntryBase {
24
24
  tags: string[];
25
25
  scope: string;
26
26
  confidence: number;
27
- status: 'candidate' | 'established' | 'promoted';
27
+ status: 'candidate' | 'established' | 'promoted' | 'archived';
28
28
  confirmed_by: PhaseConfirmationRecord[] | ProjectConfirmationRecord[];
29
29
  retrieval_outcomes: RetrievalOutcome;
30
30
  schema_version: number;
package/dist/index.js CHANGED
@@ -134,6 +134,7 @@ var init_constants = __esm(() => {
134
134
  "docs",
135
135
  "designer",
136
136
  "critic_sounding_board",
137
+ "critic_drift_verifier",
137
138
  ...QA_AGENTS,
138
139
  ...PIPELINE_AGENTS
139
140
  ];
@@ -232,6 +233,13 @@ var init_constants = __esm(() => {
232
233
  "retrieve_summary",
233
234
  "symbols"
234
235
  ],
236
+ critic_drift_verifier: [
237
+ "complexity_hotspots",
238
+ "detect_domains",
239
+ "imports",
240
+ "retrieve_summary",
241
+ "symbols"
242
+ ],
235
243
  docs: [
236
244
  "detect_domains",
237
245
  "extract_code_blocks",
@@ -258,6 +266,7 @@ var init_constants = __esm(() => {
258
266
  sme: "opencode/trinity-large-preview-free",
259
267
  critic: "opencode/trinity-large-preview-free",
260
268
  critic_sounding_board: "opencode/trinity-large-preview-free",
269
+ critic_drift_verifier: "opencode/trinity-large-preview-free",
261
270
  docs: "opencode/trinity-large-preview-free",
262
271
  designer: "opencode/trinity-large-preview-free",
263
272
  default: "opencode/trinity-large-preview-free"
@@ -15689,7 +15698,7 @@ async function loadEvidence(directory, taskId) {
15689
15698
  return { status: "found", bundle: validated };
15690
15699
  } catch (error49) {
15691
15700
  warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
15692
- const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [String(error49)];
15701
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error49 instanceof Error ? error49.message : String(error49)];
15693
15702
  return { status: "invalid_schema", errors: errors3 };
15694
15703
  }
15695
15704
  }
@@ -15698,7 +15707,7 @@ async function loadEvidence(directory, taskId) {
15698
15707
  return { status: "found", bundle: validated };
15699
15708
  } catch (error49) {
15700
15709
  warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
15701
- const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [String(error49)];
15710
+ const errors3 = error49 instanceof ZodError ? error49.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error49 instanceof Error ? error49.message : String(error49)];
15702
15711
  return { status: "invalid_schema", errors: errors3 };
15703
15712
  }
15704
15713
  }
@@ -30024,7 +30033,16 @@ function createSwarmTool(opts) {
30024
30033
  args: opts.args,
30025
30034
  execute: async (args2, ctx) => {
30026
30035
  const directory = ctx?.directory ?? process.cwd();
30027
- return opts.execute(args2, directory, ctx);
30036
+ try {
30037
+ return await opts.execute(args2, directory, ctx);
30038
+ } catch (error93) {
30039
+ const message = error93 instanceof Error ? error93.message : String(error93);
30040
+ return JSON.stringify({
30041
+ success: false,
30042
+ message: "Tool execution failed",
30043
+ errors: [message]
30044
+ }, null, 2);
30045
+ }
30028
30046
  }
30029
30047
  });
30030
30048
  }
@@ -34177,7 +34195,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
34177
34195
  return false;
34178
34196
  }
34179
34197
  function containsControlChars(str) {
34180
- return /[\0\r]/.test(str);
34198
+ return /[\0\t\r\n]/.test(str);
34181
34199
  }
34182
34200
  function validateDirectoryInput(dir) {
34183
34201
  if (!dir || dir.length === 0) {
@@ -36621,6 +36639,175 @@ var init_preflight_integration = __esm(() => {
36621
36639
  init_preflight_service();
36622
36640
  });
36623
36641
 
36642
+ // src/hooks/curator-drift.ts
36643
+ var exports_curator_drift = {};
36644
+ __export(exports_curator_drift, {
36645
+ writeDriftReport: () => writeDriftReport,
36646
+ runDeterministicDriftCheck: () => runDeterministicDriftCheck,
36647
+ readPriorDriftReports: () => readPriorDriftReports,
36648
+ buildDriftInjectionText: () => buildDriftInjectionText
36649
+ });
36650
+ import * as fs26 from "fs";
36651
+ import * as path37 from "path";
36652
+ async function readPriorDriftReports(directory) {
36653
+ const swarmDir = path37.join(directory, ".swarm");
36654
+ const entries = await fs26.promises.readdir(swarmDir).catch(() => null);
36655
+ if (entries === null)
36656
+ return [];
36657
+ const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
36658
+ const reports = [];
36659
+ for (const filename of reportFiles) {
36660
+ const content = await readSwarmFileAsync(directory, filename);
36661
+ if (content === null)
36662
+ continue;
36663
+ try {
36664
+ const report = JSON.parse(content);
36665
+ if (typeof report.phase !== "number" || typeof report.alignment !== "string") {
36666
+ console.warn(`[curator-drift] Skipping corrupt drift report: ${filename}`);
36667
+ continue;
36668
+ }
36669
+ reports.push(report);
36670
+ } catch {
36671
+ console.warn(`[curator-drift] Skipping unreadable drift report: ${filename}`);
36672
+ }
36673
+ }
36674
+ reports.sort((a, b) => a.phase - b.phase);
36675
+ return reports;
36676
+ }
36677
+ async function writeDriftReport(directory, report) {
36678
+ const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
36679
+ const filePath = validateSwarmPath(directory, filename);
36680
+ const swarmDir = path37.dirname(filePath);
36681
+ await fs26.promises.mkdir(swarmDir, { recursive: true });
36682
+ try {
36683
+ await fs26.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
36684
+ } catch (err2) {
36685
+ throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
36686
+ }
36687
+ return filePath;
36688
+ }
36689
+ async function runDeterministicDriftCheck(directory, phase, curatorResult, config3, injectAdvisory) {
36690
+ try {
36691
+ const planMd = await readSwarmFileAsync(directory, "plan.md");
36692
+ const specMd = await readSwarmFileAsync(directory, "spec.md");
36693
+ const priorReports = await readPriorDriftReports(directory);
36694
+ const complianceCount = curatorResult.compliance.length;
36695
+ const warningCompliance = curatorResult.compliance.filter((obs) => obs.severity === "warning");
36696
+ let alignment = "ALIGNED";
36697
+ let driftScore = 0;
36698
+ if (!planMd) {
36699
+ alignment = "MINOR_DRIFT";
36700
+ driftScore = 0.3;
36701
+ } else if (warningCompliance.length >= 3) {
36702
+ alignment = "MAJOR_DRIFT";
36703
+ driftScore = Math.min(0.9, 0.5 + warningCompliance.length * 0.1);
36704
+ } else if (warningCompliance.length >= 1 || complianceCount >= 3) {
36705
+ alignment = "MINOR_DRIFT";
36706
+ driftScore = Math.min(0.49, 0.2 + complianceCount * 0.05);
36707
+ }
36708
+ const priorSummaries = priorReports.map((r) => r.injection_summary).filter(Boolean);
36709
+ const keyCorrections = warningCompliance.map((obs) => obs.description);
36710
+ const firstDeviation = warningCompliance.length > 0 ? {
36711
+ phase,
36712
+ task: "unknown",
36713
+ description: warningCompliance[0]?.description ?? ""
36714
+ } : null;
36715
+ const payloadLines = [
36716
+ `CURATOR_DIGEST: ${JSON.stringify(curatorResult.digest)}`,
36717
+ `CURATOR_COMPLIANCE: ${JSON.stringify(curatorResult.compliance)}`,
36718
+ `PLAN: ${planMd ?? "none"}`,
36719
+ `SPEC: ${specMd ?? "none"}`,
36720
+ `PRIOR_DRIFT_REPORTS: ${JSON.stringify(priorSummaries)}`
36721
+ ];
36722
+ const payload = payloadLines.join(`
36723
+ `);
36724
+ const requirementsChecked = curatorResult.digest.tasks_total;
36725
+ const requirementsSatisfied = curatorResult.digest.tasks_completed;
36726
+ const injectionSummaryRaw = `Phase ${phase}: ${alignment} (${driftScore.toFixed(2)}) \u2014 ${firstDeviation ? firstDeviation.description : "all requirements on track"}.${keyCorrections.length > 0 ? `Correction: ${keyCorrections[0] ?? ""}.` : ""}`;
36727
+ const injectionSummary = injectionSummaryRaw.slice(0, config3.drift_inject_max_chars);
36728
+ const report = {
36729
+ schema_version: 1,
36730
+ phase,
36731
+ timestamp: new Date().toISOString(),
36732
+ alignment,
36733
+ drift_score: driftScore,
36734
+ first_deviation: firstDeviation,
36735
+ compounding_effects: priorReports.filter((r) => r.alignment !== "ALIGNED").map((r) => `Phase ${r.phase}: ${r.alignment}`).slice(0, 5),
36736
+ corrections: keyCorrections.slice(0, 5),
36737
+ requirements_checked: requirementsChecked,
36738
+ requirements_satisfied: requirementsSatisfied,
36739
+ scope_additions: [],
36740
+ injection_summary: injectionSummary
36741
+ };
36742
+ const reportPath = await writeDriftReport(directory, report);
36743
+ getGlobalEventBus().publish("curator.drift.completed", {
36744
+ phase,
36745
+ alignment,
36746
+ drift_score: driftScore,
36747
+ report_path: reportPath
36748
+ });
36749
+ if (injectAdvisory && alignment !== "ALIGNED" && driftScore > 0) {
36750
+ try {
36751
+ const advisoryText = `CURATOR DRIFT DETECTED (phase ${phase}, score ${driftScore.toFixed(2)}): ${injectionSummary.slice(0, 300)}. Review .swarm/${DRIFT_REPORT_PREFIX}${phase}.json and address spec alignment before proceeding.`;
36752
+ injectAdvisory(advisoryText);
36753
+ } catch {}
36754
+ }
36755
+ const injectionText = injectionSummary;
36756
+ return {
36757
+ phase,
36758
+ report,
36759
+ report_path: reportPath,
36760
+ injection_text: injectionText
36761
+ };
36762
+ } catch (err2) {
36763
+ getGlobalEventBus().publish("curator.error", {
36764
+ operation: "drift",
36765
+ phase,
36766
+ error: String(err2)
36767
+ });
36768
+ const defaultReport = {
36769
+ schema_version: 1,
36770
+ phase,
36771
+ timestamp: new Date().toISOString(),
36772
+ alignment: "ALIGNED",
36773
+ drift_score: 0,
36774
+ first_deviation: null,
36775
+ compounding_effects: [],
36776
+ corrections: [],
36777
+ requirements_checked: 0,
36778
+ requirements_satisfied: 0,
36779
+ scope_additions: [],
36780
+ injection_summary: `Phase ${phase}: drift analysis unavailable (${String(err2)})`
36781
+ };
36782
+ return {
36783
+ phase,
36784
+ report: defaultReport,
36785
+ report_path: "",
36786
+ injection_text: ""
36787
+ };
36788
+ }
36789
+ }
36790
+ function buildDriftInjectionText(report, maxChars) {
36791
+ if (maxChars <= 0) {
36792
+ return "";
36793
+ }
36794
+ let text;
36795
+ if (report.alignment === "ALIGNED" && report.drift_score < 0.1) {
36796
+ text = `<drift_report>Phase ${report.phase}: ALIGNED, all requirements on track.</drift_report>`;
36797
+ } else {
36798
+ const keyFinding = report.first_deviation?.description ?? "no deviation recorded";
36799
+ const score = report.drift_score ?? 0;
36800
+ const correctionClause = report.corrections?.[0] ? `Correction: ${report.corrections[0]}.` : "";
36801
+ text = `<drift_report>Phase ${report.phase}: ${report.alignment} (${score.toFixed(2)}) \u2014 ${keyFinding}. ${correctionClause}</drift_report>`;
36802
+ }
36803
+ return text.slice(0, maxChars);
36804
+ }
36805
+ var DRIFT_REPORT_PREFIX = "drift-report-phase-";
36806
+ var init_curator_drift = __esm(() => {
36807
+ init_event_bus();
36808
+ init_utils2();
36809
+ });
36810
+
36624
36811
  // node_modules/web-tree-sitter/tree-sitter.js
36625
36812
  function assertInternal(x) {
36626
36813
  if (x !== INTERNAL)
@@ -40199,7 +40386,7 @@ async function readPlanFromDisk(directory) {
40199
40386
  return null;
40200
40387
  }
40201
40388
  }
40202
- async function readEvidenceFromDisk(directory) {
40389
+ async function readGateEvidenceFromDisk(directory) {
40203
40390
  const evidenceMap = new Map;
40204
40391
  try {
40205
40392
  const evidenceDir = path3.join(directory, ".swarm", "evidence");
@@ -40234,7 +40421,7 @@ async function buildRehydrationCache(directory) {
40234
40421
  }
40235
40422
  }
40236
40423
  }
40237
- const evidenceMap = await readEvidenceFromDisk(directory);
40424
+ const evidenceMap = await readGateEvidenceFromDisk(directory);
40238
40425
  _rehydrationCache = { planTaskStates, evidenceMap };
40239
40426
  }
40240
40427
  function applyRehydrationCache(session) {
@@ -40272,7 +40459,11 @@ async function rehydrateSessionFromDisk(directory, session) {
40272
40459
  await buildRehydrationCache(directory);
40273
40460
  applyRehydrationCache(session);
40274
40461
  }
40275
- function hasActiveTurboMode() {
40462
+ function hasActiveTurboMode(sessionID) {
40463
+ if (sessionID) {
40464
+ const session = swarmState.agentSessions.get(sessionID);
40465
+ return session?.turboMode === true;
40466
+ }
40276
40467
  for (const [_sessionId, session] of swarmState.agentSessions) {
40277
40468
  if (session.turboMode === true) {
40278
40469
  return true;
@@ -40339,7 +40530,9 @@ Do not re-trigger DISCOVER or CONSULT because you noticed a project phase bounda
40339
40530
  Output to .swarm/plan.md MUST use "## Phase N" headers. Do not write MODE labels into plan.md.
40340
40531
 
40341
40532
  1. DELEGATE all coding to {{AGENT_PREFIX}}coder. You do NOT write code.
40342
- YOUR TOOLS: Task (delegation), 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, sbom_generate, checkpoint, pkg_audit, test_runner.
40533
+ // IMPORTANT: This list MUST match AGENT_TOOL_MAP['architect'] in src/config/constants.ts
40534
+ // If you add a tool to the map, add it here. If you remove it from the map, remove it here.
40535
+ YOUR TOOLS: Task (delegation), checkpoint, check_gate_status, complexity_hotspots, declare_scope, detect_domains, diff, evidence_check, extract_code_blocks, gitingest, imports, knowledge_query, lint, pkg_audit, pre_check_batch, retrieve_summary, save_plan, schema_drift, secretscan, symbols, test_runner, todo_extract, update_task_status, write_retro.
40343
40536
  CODER'S TOOLS: write, edit, patch, apply_patch, create_file, insert, replace \u2014 any tool that modifies file contents.
40344
40537
  If a tool modifies a file, it is a CODER tool. Delegate.
40345
40538
  2. ONE agent per message. Send, STOP, wait for response.
@@ -41187,10 +41380,10 @@ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{
41187
41380
  4. Write retrospective evidence: record phase, total_tool_calls, coder_revisions, reviewer_rejections, test_failures, security_findings, integration_issues, task_count, task_complexity, top_rejection_reasons, lessons_learned to .swarm/evidence/ via write_retro. Reset Phase Metrics in context.md to 0.
41188
41381
  4.5. Run \`evidence_check\` to verify all completed tasks have required evidence (review + test). If gaps found, note in retrospective lessons_learned. Optionally run \`pkg_audit\` if dependencies were modified during this phase. Optionally run \`schema_drift\` if API routes were modified during this phase.
41189
41382
  5. Run \`sbom_generate\` with scope='changed' to capture post-implementation dependency snapshot (saved to \`.swarm/evidence/sbom/\`). This is a non-blocking step - always proceeds to summary.
41190
- 5.5. **Defense-in-depth drift check**: The \`phase_complete\` tool now enforces two mandatory gates automatically \u2014 (1) completion-verify (deterministic identifier check) and (2) drift verification gate evidence check. If either gate fails, \`phase_complete\` returns status 'blocked'. As defense-in-depth, delegate to {{AGENT_PREFIX}}critic with drift-check context BEFORE calling phase_complete to get early feedback on drift issues and write the required evidence. If spec.md does not exist: skip the critic delegation.
41383
+ 5.5. **Drift verification**: Delegate to {{AGENT_PREFIX}}critic_drift_verifier for phase drift review BEFORE calling phase_complete. The critic_drift_verifier will read every target file, verify described changes exist, and produce drift evidence. Persist critic drift evidence at \`.swarm/evidence/{phase}/drift-verifier.json\`. Only then call phase_complete. If the critic returns needs_revision, address the missing items before retrying phase_complete. If spec.md does not exist: skip the critic delegation. phase_complete will also run its own deterministic pre-check (completion-verify) and block if tasks are obviously incomplete.
41191
41384
  5.6. **Mandatory gate evidence**: Before calling phase_complete, ensure:
41192
41385
  - \`.swarm/evidence/{phase}/completion-verify.json\` exists (written automatically by the completion-verify gate)
41193
- - \`.swarm/evidence/{phase}/drift-verifier.json\` exists with verdict 'approved' (written by the curator drift check or critic drift verification delegation in step 5.5)
41386
+ - \`.swarm/evidence/{phase}/drift-verifier.json\` exists with verdict 'approved' (written by the critic_drift_verifier delegation in step 5.5)
41194
41387
  If either is missing, run the missing gate first. Turbo mode skips both gates automatically.
41195
41388
  6. Summarize to user
41196
41389
  7. Ask: "Ready for Phase [N+1]?"
@@ -41270,7 +41463,7 @@ While Turbo Mode is active:
41270
41463
  - **Stage A gates** (lint, imports, pre_check_batch) are still REQUIRED for ALL tasks
41271
41464
  - **Tier 3 tasks** (security-sensitive files matching: architect*.ts, delegation*.ts, guardrails*.ts, adversarial*.ts, sanitiz*.ts, auth*, permission*, crypto*, secret*, security) still require FULL review (Stage B)
41272
41465
  - **Tier 0-2 tasks** can skip Stage B (reviewer, test_engineer) to speed up execution
41273
- - **Phase completion gates** (completion-verify and drift verification gate) are automatically bypassed \u2014 phase_complete will succeed without drift verification evidence when turbo is active
41466
+ - **Phase completion gates** (completion-verify and drift verification gate) are automatically bypassed \u2014 phase_complete will succeed without drift verification evidence when turbo is active. Note: turbo bypass is session-scoped; one session's turbo does not affect other sessions.
41274
41467
 
41275
41468
  Classification still determines the pipeline:
41276
41469
  - TIER 0 (metadata): lint + diff only \u2014 no change
@@ -42816,6 +43009,11 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
42816
43009
  critic.name = prefixName("critic_sounding_board");
42817
43010
  agents.push(applyOverrides(critic, swarmAgents, swarmPrefix));
42818
43011
  }
43012
+ if (!isAgentDisabled("critic_drift_verifier", swarmAgents, swarmPrefix)) {
43013
+ const critic = createCriticAgent(swarmAgents?.critic_drift_verifier?.model ?? getModel("critic"), undefined, undefined, "phase_drift_verifier");
43014
+ critic.name = prefixName("critic_drift_verifier");
43015
+ agents.push(applyOverrides(critic, swarmAgents, swarmPrefix));
43016
+ }
42819
43017
  if (!isAgentDisabled("test_engineer", swarmAgents, swarmPrefix)) {
42820
43018
  const testPrompts = getPrompts("test_engineer");
42821
43019
  const testEngineer = createTestEngineerAgent(getModel("test_engineer"), testPrompts.prompt, testPrompts.appendPrompt);
@@ -45533,7 +45731,7 @@ async function handleCurateCommand(directory, _args) {
45533
45731
  if (error93 instanceof Error) {
45534
45732
  return `\u274C Curation failed: ${error93.message}`;
45535
45733
  }
45536
- return `\u274C Curation failed: ${String(error93)}`;
45734
+ return `\u274C Curation failed: ${error93 instanceof Error ? error93.message : String(error93)}`;
45537
45735
  }
45538
45736
  }
45539
45737
  function formatCurationSummary(summary) {
@@ -45923,7 +46121,7 @@ async function handleDarkMatterCommand(directory, args2) {
45923
46121
  [${entries.length} dark matter finding(s) saved to .swarm/knowledge.jsonl]`;
45924
46122
  }
45925
46123
  } catch (err2) {
45926
- console.warn("dark-matter: failed to save knowledge entries:", err2);
46124
+ console.warn("dark-matter: failed to save knowledge entries:", err2 instanceof Error ? err2.message : String(err2));
45927
46125
  return output;
45928
46126
  }
45929
46127
  }
@@ -47697,7 +47895,7 @@ async function handleKnowledgeQuarantineCommand(directory, args2) {
47697
47895
  await quarantineEntry(directory, entryId, reason, "user");
47698
47896
  return `\u2705 Entry ${entryId} quarantined successfully.`;
47699
47897
  } catch (error93) {
47700
- console.warn("[knowledge-command] quarantineEntry error:", error93);
47898
+ console.warn("[knowledge-command] quarantineEntry error:", error93 instanceof Error ? error93.message : String(error93));
47701
47899
  return `\u274C Failed to quarantine entry. Check the entry ID and try again.`;
47702
47900
  }
47703
47901
  }
@@ -47713,7 +47911,7 @@ async function handleKnowledgeRestoreCommand(directory, args2) {
47713
47911
  await restoreEntry(directory, entryId);
47714
47912
  return `\u2705 Entry ${entryId} restored successfully.`;
47715
47913
  } catch (error93) {
47716
- console.warn("[knowledge-command] restoreEntry error:", error93);
47914
+ console.warn("[knowledge-command] restoreEntry error:", error93 instanceof Error ? error93.message : String(error93));
47717
47915
  return `\u274C Failed to restore entry. Check the entry ID and try again.`;
47718
47916
  }
47719
47917
  }
@@ -47735,7 +47933,7 @@ async function handleKnowledgeMigrateCommand(directory, args2) {
47735
47933
  }
47736
47934
  return `\u2705 Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
47737
47935
  } catch (error93) {
47738
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93);
47936
+ console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
47739
47937
  return "\u274C Migration failed. Check .swarm/context.md is readable.";
47740
47938
  }
47741
47939
  }
@@ -47762,7 +47960,7 @@ async function handleKnowledgeListCommand(directory, _args) {
47762
47960
  return lines.join(`
47763
47961
  `);
47764
47962
  } catch (error93) {
47765
- console.warn("[knowledge-command] list error:", error93);
47963
+ console.warn("[knowledge-command] list error:", error93 instanceof Error ? error93.message : String(error93));
47766
47964
  return "\u274C Failed to list knowledge entries. Ensure .swarm/knowledge.jsonl exists.";
47767
47965
  }
47768
47966
  }
@@ -47953,7 +48151,7 @@ async function handlePromoteCommand(directory, args2) {
47953
48151
  if (error93 instanceof Error) {
47954
48152
  return error93.message;
47955
48153
  }
47956
- return `Failed to promote lesson: ${String(error93)}`;
48154
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
47957
48155
  }
47958
48156
  }
47959
48157
  try {
@@ -47962,7 +48160,7 @@ async function handlePromoteCommand(directory, args2) {
47962
48160
  if (error93 instanceof Error) {
47963
48161
  return error93.message;
47964
48162
  }
47965
- return `Failed to promote lesson: ${String(error93)}`;
48163
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
47966
48164
  }
47967
48165
  }
47968
48166
 
@@ -48248,7 +48446,7 @@ async function handleRollbackCommand(directory, args2) {
48248
48446
  fs18.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
48249
48447
  `);
48250
48448
  } catch (error93) {
48251
- console.error("Failed to write rollback event:", error93);
48449
+ console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
48252
48450
  }
48253
48451
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
48254
48452
  }
@@ -50948,15 +51146,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
50948
51146
  const errorContent = output.error ?? outputStr;
50949
51147
  if (typeof errorContent === "string" && TRANSIENT_MODEL_ERROR_PATTERN.test(errorContent) && !session.modelFallbackExhausted) {
50950
51148
  session.model_fallback_index++;
50951
- session.modelFallbackExhausted = true;
50952
51149
  const baseAgentName = session.agentName ? session.agentName.replace(/^[^_]+[_]/, "") : "";
50953
- const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, getSwarmAgents());
51150
+ const swarmAgents = getSwarmAgents();
51151
+ const fallbackModels = swarmAgents?.[baseAgentName]?.fallback_models;
51152
+ session.modelFallbackExhausted = !fallbackModels || session.model_fallback_index > fallbackModels.length;
51153
+ const fallbackModel = resolveFallbackModel(baseAgentName, session.model_fallback_index, swarmAgents);
50954
51154
  if (fallbackModel) {
50955
- const swarmAgents = getSwarmAgents();
50956
51155
  const primaryModel = swarmAgents?.[baseAgentName]?.model ?? "default";
51156
+ if (swarmAgents?.[baseAgentName]) {
51157
+ swarmAgents[baseAgentName].model = fallbackModel;
51158
+ }
50957
51159
  telemetry.modelFallback(input.sessionID, session.agentName, primaryModel, fallbackModel, "transient_model_error");
50958
51160
  session.pendingAdvisoryMessages ??= [];
50959
- session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `Configured fallback model: "${fallbackModel}". ` + `Consider retrying with this model or using /swarm handoff to reset.`);
51161
+ session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Applied fallback model "${fallbackModel}" (attempt ${session.model_fallback_index}). ` + `Using /swarm handoff to reset to primary model.`);
50960
51162
  } else {
50961
51163
  session.pendingAdvisoryMessages ??= [];
50962
51164
  session.pendingAdvisoryMessages.push(`MODEL FALLBACK: Transient model error detected (attempt ${session.model_fallback_index}). ` + `No fallback models configured for this agent. Add "fallback_models": ["model-a", "model-b"] ` + `to the agent's config in opencode-swarm.json.`);
@@ -51362,9 +51564,40 @@ function createDelegationGateHook(config3, directory) {
51362
51564
  if (!enabled) {
51363
51565
  return {
51364
51566
  messagesTransform: async (_input, _output) => {},
51567
+ toolBefore: async () => {},
51365
51568
  toolAfter: async () => {}
51366
51569
  };
51367
51570
  }
51571
+ const toolBefore = async (input, output) => {
51572
+ if (!input.sessionID)
51573
+ return;
51574
+ const normalized = input.tool.replace(/^[^:]+[:.]/, "");
51575
+ if (normalized !== "Task" && normalized !== "task")
51576
+ return;
51577
+ const args2 = output.args;
51578
+ if (!args2)
51579
+ return;
51580
+ const subagentType = args2.subagent_type;
51581
+ if (typeof subagentType !== "string")
51582
+ return;
51583
+ const targetAgent = stripKnownSwarmPrefix(subagentType);
51584
+ if (targetAgent !== "coder")
51585
+ return;
51586
+ const session = swarmState.agentSessions.get(input.sessionID);
51587
+ if (!session || !session.taskWorkflowStates)
51588
+ return;
51589
+ for (const [taskId, state2] of session.taskWorkflowStates) {
51590
+ if (state2 !== "coder_delegated")
51591
+ continue;
51592
+ const turbo = hasActiveTurboMode(input.sessionID);
51593
+ if (turbo) {
51594
+ const isTier3 = taskId.startsWith("3.");
51595
+ if (!isTier3)
51596
+ continue;
51597
+ }
51598
+ throw new Error(`REVIEWER_GATE_VIOLATION: Cannot re-delegate to coder without reviewer delegation. ` + `Task ${taskId} state: coder_delegated. Delegate to reviewer first.`);
51599
+ }
51600
+ };
51368
51601
  const toolAfter = async (input, _output) => {
51369
51602
  if (!input.sessionID)
51370
51603
  return;
@@ -51450,7 +51683,7 @@ function createDelegationGateHook(config3, directory) {
51450
51683
  const rawTaskId = directArgs?.task_id;
51451
51684
  const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && /^\d+\.\d+$/.test(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
51452
51685
  if (evidenceTaskId) {
51453
- const turbo = hasActiveTurboMode();
51686
+ const turbo = hasActiveTurboMode(input.sessionID);
51454
51687
  const gateAgents = [
51455
51688
  "reviewer",
51456
51689
  "test_engineer",
@@ -51569,7 +51802,7 @@ function createDelegationGateHook(config3, directory) {
51569
51802
  const rawTaskId = directArgs?.task_id;
51570
51803
  const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && /^\d+\.\d+$/.test(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
51571
51804
  if (evidenceTaskId) {
51572
- const turbo = hasActiveTurboMode();
51805
+ const turbo = hasActiveTurboMode(input.sessionID);
51573
51806
  if (hasReviewer) {
51574
51807
  const { recordGateEvidence: recordGateEvidence2 } = await Promise.resolve().then(() => (init_gate_evidence(), exports_gate_evidence));
51575
51808
  await recordGateEvidence2(directory, evidenceTaskId, "reviewer", input.sessionID, turbo);
@@ -51586,6 +51819,7 @@ function createDelegationGateHook(config3, directory) {
51586
51819
  }
51587
51820
  };
51588
51821
  return {
51822
+ toolBefore,
51589
51823
  messagesTransform: async (_input, output) => {
51590
51824
  const messages = output.messages;
51591
51825
  if (!messages || messages.length === 0)
@@ -54395,13 +54629,28 @@ async function processRetractions(retractions, directory) {
54395
54629
  async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, config3) {
54396
54630
  const knowledgePath = resolveSwarmKnowledgePath(directory);
54397
54631
  const existingEntries = await readKnowledge(knowledgePath) ?? [];
54632
+ let stored = 0;
54633
+ let skipped = 0;
54634
+ let rejected = 0;
54635
+ const categoryByTag = new Map([
54636
+ ["process", "process"],
54637
+ ["architecture", "architecture"],
54638
+ ["tooling", "tooling"],
54639
+ ["security", "security"],
54640
+ ["testing", "testing"],
54641
+ ["debugging", "debugging"],
54642
+ ["performance", "performance"],
54643
+ ["integration", "integration"],
54644
+ ["other", "other"]
54645
+ ]);
54398
54646
  for (const lesson of lessons) {
54399
54647
  const tags = inferTags(lesson);
54400
54648
  let category = "process";
54401
- if (tags.includes("security")) {
54402
- category = "security";
54403
- } else if (tags.includes("testing")) {
54404
- category = "testing";
54649
+ for (const tag of tags) {
54650
+ if (categoryByTag.has(tag)) {
54651
+ category = categoryByTag.get(tag);
54652
+ break;
54653
+ }
54405
54654
  }
54406
54655
  const meta3 = {
54407
54656
  category,
@@ -54410,18 +54659,20 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54410
54659
  };
54411
54660
  const result = validateLesson(lesson, existingEntries.map((e) => e.lesson), meta3);
54412
54661
  if (result.valid === false || result.severity === "error") {
54413
- const rejected = {
54662
+ const rejectedLesson = {
54414
54663
  id: crypto.randomUUID(),
54415
54664
  lesson,
54416
54665
  rejection_reason: result.reason ?? "unknown",
54417
54666
  rejected_at: new Date().toISOString(),
54418
54667
  rejection_layer: result.layer ?? 1
54419
54668
  };
54420
- await appendRejectedLesson(directory, rejected);
54669
+ await appendRejectedLesson(directory, rejectedLesson);
54670
+ rejected++;
54421
54671
  continue;
54422
54672
  }
54423
54673
  const duplicate = findNearDuplicate(lesson, existingEntries, config3.dedup_threshold);
54424
54674
  if (duplicate) {
54675
+ skipped++;
54425
54676
  continue;
54426
54677
  }
54427
54678
  const entry = {
@@ -54452,9 +54703,11 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54452
54703
  auto_generated: true
54453
54704
  };
54454
54705
  await appendKnowledge(knowledgePath, entry);
54706
+ stored++;
54455
54707
  existingEntries.push(entry);
54456
54708
  }
54457
54709
  await runAutoPromotion(directory, config3);
54710
+ return { stored, skipped, rejected };
54458
54711
  }
54459
54712
  async function runAutoPromotion(directory, config3) {
54460
54713
  const knowledgePath = resolveSwarmKnowledgePath(directory);
@@ -54670,200 +54923,8 @@ Use this data to avoid repeating known failure patterns.`;
54670
54923
  return prefix + summaryText + suffix;
54671
54924
  }
54672
54925
 
54673
- // src/hooks/curator-drift.ts
54674
- init_event_bus();
54675
- init_utils2();
54676
- import * as fs26 from "fs";
54677
- import * as fsSync from "fs";
54678
- import * as path37 from "path";
54679
- var DRIFT_REPORT_PREFIX = "drift-report-phase-";
54680
- async function readPriorDriftReports(directory) {
54681
- const swarmDir = path37.join(directory, ".swarm");
54682
- const entries = await fs26.promises.readdir(swarmDir).catch(() => null);
54683
- if (entries === null)
54684
- return [];
54685
- const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
54686
- const reports = [];
54687
- for (const filename of reportFiles) {
54688
- const content = await readSwarmFileAsync(directory, filename);
54689
- if (content === null)
54690
- continue;
54691
- try {
54692
- const report = JSON.parse(content);
54693
- if (typeof report.phase !== "number" || typeof report.alignment !== "string") {
54694
- console.warn(`[curator-drift] Skipping corrupt drift report: ${filename}`);
54695
- continue;
54696
- }
54697
- reports.push(report);
54698
- } catch {
54699
- console.warn(`[curator-drift] Skipping unreadable drift report: ${filename}`);
54700
- }
54701
- }
54702
- reports.sort((a, b) => a.phase - b.phase);
54703
- return reports;
54704
- }
54705
- async function writeDriftReport(directory, report) {
54706
- const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
54707
- const filePath = validateSwarmPath(directory, filename);
54708
- const swarmDir = path37.dirname(filePath);
54709
- await fs26.promises.mkdir(swarmDir, { recursive: true });
54710
- try {
54711
- await fs26.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
54712
- } catch (err2) {
54713
- throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
54714
- }
54715
- return filePath;
54716
- }
54717
- async function runCriticDriftCheck(directory, phase, curatorResult, config3, injectAdvisory) {
54718
- try {
54719
- const planMd = await readSwarmFileAsync(directory, "plan.md");
54720
- const specMd = await readSwarmFileAsync(directory, "spec.md");
54721
- const priorReports = await readPriorDriftReports(directory);
54722
- const complianceCount = curatorResult.compliance.length;
54723
- const warningCompliance = curatorResult.compliance.filter((obs) => obs.severity === "warning");
54724
- let alignment = "ALIGNED";
54725
- let driftScore = 0;
54726
- if (!planMd) {
54727
- alignment = "MINOR_DRIFT";
54728
- driftScore = 0.3;
54729
- } else if (warningCompliance.length >= 3) {
54730
- alignment = "MAJOR_DRIFT";
54731
- driftScore = Math.min(0.9, 0.5 + warningCompliance.length * 0.1);
54732
- } else if (warningCompliance.length >= 1 || complianceCount >= 3) {
54733
- alignment = "MINOR_DRIFT";
54734
- driftScore = Math.min(0.49, 0.2 + complianceCount * 0.05);
54735
- }
54736
- const priorSummaries = priorReports.map((r) => r.injection_summary).filter(Boolean);
54737
- const keyCorrections = warningCompliance.map((obs) => obs.description);
54738
- const firstDeviation = warningCompliance.length > 0 ? {
54739
- phase,
54740
- task: "unknown",
54741
- description: warningCompliance[0]?.description ?? ""
54742
- } : null;
54743
- const payloadLines = [
54744
- `CURATOR_DIGEST: ${JSON.stringify(curatorResult.digest)}`,
54745
- `CURATOR_COMPLIANCE: ${JSON.stringify(curatorResult.compliance)}`,
54746
- `PLAN: ${planMd ?? "none"}`,
54747
- `SPEC: ${specMd ?? "none"}`,
54748
- `PRIOR_DRIFT_REPORTS: ${JSON.stringify(priorSummaries)}`
54749
- ];
54750
- const payload = payloadLines.join(`
54751
- `);
54752
- const requirementsChecked = curatorResult.digest.tasks_total;
54753
- const requirementsSatisfied = curatorResult.digest.tasks_completed;
54754
- const injectionSummaryRaw = `Phase ${phase}: ${alignment} (${driftScore.toFixed(2)}) \u2014 ${firstDeviation ? firstDeviation.description : "all requirements on track"}.${keyCorrections.length > 0 ? `Correction: ${keyCorrections[0] ?? ""}.` : ""}`;
54755
- const injectionSummary = injectionSummaryRaw.slice(0, config3.drift_inject_max_chars);
54756
- const report = {
54757
- schema_version: 1,
54758
- phase,
54759
- timestamp: new Date().toISOString(),
54760
- alignment,
54761
- drift_score: driftScore,
54762
- first_deviation: firstDeviation,
54763
- compounding_effects: priorReports.filter((r) => r.alignment !== "ALIGNED").map((r) => `Phase ${r.phase}: ${r.alignment}`).slice(0, 5),
54764
- corrections: keyCorrections.slice(0, 5),
54765
- requirements_checked: requirementsChecked,
54766
- requirements_satisfied: requirementsSatisfied,
54767
- scope_additions: [],
54768
- injection_summary: injectionSummary
54769
- };
54770
- const reportPath = await writeDriftReport(directory, report);
54771
- try {
54772
- const evidenceDir = path37.join(directory, ".swarm", "evidence", String(phase));
54773
- fsSync.mkdirSync(evidenceDir, { recursive: true });
54774
- const evidencePath = path37.join(evidenceDir, "drift-verifier.json");
54775
- let verdict;
54776
- let summary;
54777
- if (alignment === "MAJOR_DRIFT") {
54778
- verdict = "rejected";
54779
- summary = `Major drift detected (score: ${driftScore.toFixed(2)}): ${firstDeviation ? firstDeviation.description : "alignment issues detected"}`;
54780
- } else if (alignment === "MINOR_DRIFT") {
54781
- verdict = "approved";
54782
- summary = `Minor drift detected (score: ${driftScore.toFixed(2)}): ${firstDeviation ? firstDeviation.description : "minor alignment issues"}`;
54783
- } else {
54784
- verdict = "approved";
54785
- summary = "Drift check passed: all requirements aligned";
54786
- }
54787
- const evidence = {
54788
- entries: [
54789
- {
54790
- type: "drift-verification",
54791
- verdict,
54792
- summary,
54793
- timestamp: new Date().toISOString(),
54794
- agent: "curator-drift",
54795
- session_id: "curator"
54796
- }
54797
- ]
54798
- };
54799
- fsSync.writeFileSync(evidencePath, JSON.stringify(evidence, null, 2), "utf-8");
54800
- } catch (driftWriteErr) {
54801
- console.warn(`[curator-drift] drift-verifier evidence write failed: ${driftWriteErr instanceof Error ? driftWriteErr.message : String(driftWriteErr)}`);
54802
- }
54803
- getGlobalEventBus().publish("curator.drift.completed", {
54804
- phase,
54805
- alignment,
54806
- drift_score: driftScore,
54807
- report_path: reportPath
54808
- });
54809
- if (injectAdvisory && alignment !== "ALIGNED" && driftScore > 0) {
54810
- try {
54811
- const advisoryText = `CURATOR DRIFT DETECTED (phase ${phase}, score ${driftScore.toFixed(2)}): ${injectionSummary.slice(0, 300)}. Review .swarm/${DRIFT_REPORT_PREFIX}${phase}.json and address spec alignment before proceeding.`;
54812
- injectAdvisory(advisoryText);
54813
- } catch {}
54814
- }
54815
- const injectionText = injectionSummary;
54816
- return {
54817
- phase,
54818
- report,
54819
- report_path: reportPath,
54820
- injection_text: injectionText
54821
- };
54822
- } catch (err2) {
54823
- getGlobalEventBus().publish("curator.error", {
54824
- operation: "drift",
54825
- phase,
54826
- error: String(err2)
54827
- });
54828
- const defaultReport = {
54829
- schema_version: 1,
54830
- phase,
54831
- timestamp: new Date().toISOString(),
54832
- alignment: "ALIGNED",
54833
- drift_score: 0,
54834
- first_deviation: null,
54835
- compounding_effects: [],
54836
- corrections: [],
54837
- requirements_checked: 0,
54838
- requirements_satisfied: 0,
54839
- scope_additions: [],
54840
- injection_summary: `Phase ${phase}: drift analysis unavailable (${String(err2)})`
54841
- };
54842
- return {
54843
- phase,
54844
- report: defaultReport,
54845
- report_path: "",
54846
- injection_text: ""
54847
- };
54848
- }
54849
- }
54850
- function buildDriftInjectionText(report, maxChars) {
54851
- if (maxChars <= 0) {
54852
- return "";
54853
- }
54854
- let text;
54855
- if (report.alignment === "ALIGNED" && report.drift_score < 0.1) {
54856
- text = `<drift_report>Phase ${report.phase}: ALIGNED, all requirements on track.</drift_report>`;
54857
- } else {
54858
- const keyFinding = report.first_deviation?.description ?? "no deviation recorded";
54859
- const score = report.drift_score ?? 0;
54860
- const correctionClause = report.corrections?.[0] ? `Correction: ${report.corrections[0]}.` : "";
54861
- text = `<drift_report>Phase ${report.phase}: ${report.alignment} (${score.toFixed(2)}) \u2014 ${keyFinding}. ${correctionClause}</drift_report>`;
54862
- }
54863
- return text.slice(0, maxChars);
54864
- }
54865
-
54866
54926
  // src/hooks/knowledge-injector.ts
54927
+ init_curator_drift();
54867
54928
  init_utils2();
54868
54929
  function formatStars(confidence) {
54869
54930
  if (confidence >= 0.9)
@@ -55745,7 +55806,7 @@ var build_check = createSwarmTool({
55745
55806
  try {
55746
55807
  await saveEvidence(workingDir, "build", evidence);
55747
55808
  } catch (error93) {
55748
- console.error("Failed to save build evidence:", error93);
55809
+ console.error("Failed to save build evidence:", error93 instanceof Error ? error93.message : String(error93));
55749
55810
  }
55750
55811
  return JSON.stringify(result, null, 2);
55751
55812
  }
@@ -55932,14 +55993,6 @@ init_utils2();
55932
55993
  import * as fs30 from "fs";
55933
55994
  import * as path41 from "path";
55934
55995
  init_create_tool();
55935
- function hasActiveTurboMode2() {
55936
- for (const [_sessionId, session] of swarmState.agentSessions) {
55937
- if (session.turboMode === true) {
55938
- return true;
55939
- }
55940
- }
55941
- return false;
55942
- }
55943
55996
  function extractMatches(regex, text) {
55944
55997
  return Array.from(text.matchAll(regex));
55945
55998
  }
@@ -56004,7 +56057,7 @@ async function executeCompletionVerify(args2, directory) {
56004
56057
  };
56005
56058
  return JSON.stringify(result2, null, 2);
56006
56059
  }
56007
- if (hasActiveTurboMode2()) {
56060
+ if (hasActiveTurboMode(args2.sessionID)) {
56008
56061
  const result2 = {
56009
56062
  success: true,
56010
56063
  phase,
@@ -56535,7 +56588,7 @@ var curator_analyze = createSwarmTool({
56535
56588
  }, null, 2);
56536
56589
  } catch (error93) {
56537
56590
  return JSON.stringify({
56538
- error: String(error93),
56591
+ error: error93 instanceof Error ? error93.message : String(error93),
56539
56592
  phase: typedArgs.phase
56540
56593
  }, null, 2);
56541
56594
  }
@@ -58759,7 +58812,7 @@ init_telemetry();
58759
58812
  init_create_tool();
58760
58813
  function safeWarn(message, error93) {
58761
58814
  try {
58762
- console.warn(message, error93);
58815
+ console.warn(message, error93 instanceof Error ? error93.message : String(error93));
58763
58816
  } catch {}
58764
58817
  }
58765
58818
  function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSessionId) {
@@ -58949,7 +59002,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
58949
59002
  ]
58950
59003
  }, null, 2);
58951
59004
  }
58952
- if (hasActiveTurboMode()) {
59005
+ if (hasActiveTurboMode(sessionID)) {
58953
59006
  console.warn(`[phase_complete] Turbo mode active \u2014 skipping completion-verify and drift-verifier gates for phase ${phase}`);
58954
59007
  } else {
58955
59008
  try {
@@ -59010,7 +59063,23 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59010
59063
  const specPath = path48.join(dir, ".swarm", "spec.md");
59011
59064
  const specExists = fs37.existsSync(specPath);
59012
59065
  if (!specExists) {
59013
- warnings.push(`Drift verifier evidence missing \u2014 no spec.md found, drift check is advisory-only.`);
59066
+ let incompleteTaskCount = 0;
59067
+ let planPhaseFound = false;
59068
+ try {
59069
+ const planPath = validateSwarmPath(dir, "plan.json");
59070
+ const planRaw = fs37.readFileSync(planPath, "utf-8");
59071
+ const plan = JSON.parse(planRaw);
59072
+ const targetPhase = plan.phases.find((p) => p.id === phase);
59073
+ if (targetPhase) {
59074
+ planPhaseFound = true;
59075
+ incompleteTaskCount = targetPhase.tasks.filter((t) => t.status !== "completed").length;
59076
+ }
59077
+ } catch {}
59078
+ if (incompleteTaskCount > 0 || !planPhaseFound) {
59079
+ warnings.push(`No spec.md found and drift verification evidence missing. Phase ${phase} has ${incompleteTaskCount} incomplete task(s) in plan.json \u2014 consider running critic_drift_verifier before phase completion.`);
59080
+ } else {
59081
+ warnings.push(`No spec.md found. Phase ${phase} tasks are all completed in plan.json. Drift verification was skipped.`);
59082
+ }
59014
59083
  } else {
59015
59084
  return JSON.stringify({
59016
59085
  success: false,
@@ -59076,17 +59145,21 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59076
59145
  const curatorConfig = CuratorConfigSchema.parse(config3.curator ?? {});
59077
59146
  if (curatorConfig.enabled && curatorConfig.phase_enabled) {
59078
59147
  const curatorResult = await runCuratorPhase(dir, phase, agentsDispatched, curatorConfig, {});
59079
- await applyCuratorKnowledgeUpdates(dir, curatorResult.knowledge_recommendations, knowledgeConfig);
59080
- const driftResult = await runCriticDriftCheck(dir, phase, curatorResult, curatorConfig);
59148
+ const knowledgeResult = await applyCuratorKnowledgeUpdates(dir, curatorResult.knowledge_recommendations, knowledgeConfig);
59081
59149
  const callerSessionState = swarmState.agentSessions.get(sessionID);
59082
59150
  if (callerSessionState) {
59083
59151
  callerSessionState.pendingAdvisoryMessages ??= [];
59084
59152
  const digestSummary = curatorResult.digest?.summary ? curatorResult.digest.summary.slice(0, 200) : "Phase analysis complete";
59085
59153
  const complianceNote = curatorResult.compliance.length > 0 ? ` (${curatorResult.compliance.length} compliance observation(s))` : "";
59086
- callerSessionState.pendingAdvisoryMessages.push(`[CURATOR] Phase ${phase} digest: ${digestSummary}${complianceNote}. Call curator_analyze with recommendations to apply knowledge updates from this phase.`);
59087
- if (driftResult?.report?.drift_score && driftResult.report.drift_score > 0) {
59088
- callerSessionState.pendingAdvisoryMessages.push(`[CURATOR DRIFT DETECTED (phase ${phase}, score ${driftResult.report.drift_score})]: ${(driftResult.injection_text || "").slice(0, 300)}. Review ${driftResult.report_path || "unknown"} and address spec alignment before proceeding.`);
59089
- }
59154
+ callerSessionState.pendingAdvisoryMessages.push(`[CURATOR] Phase ${phase} digest: ${digestSummary}${complianceNote}. Knowledge: ${knowledgeResult.applied} applied, ${knowledgeResult.skipped} skipped. Call curator_analyze with recommendations to apply knowledge updates from this phase.`);
59155
+ try {
59156
+ const { readPriorDriftReports: readPriorDriftReports2 } = await Promise.resolve().then(() => (init_curator_drift(), exports_curator_drift));
59157
+ const priorReports = await readPriorDriftReports2(dir);
59158
+ const phaseReport = priorReports.filter((r) => r.phase === phase).pop();
59159
+ if (phaseReport && phaseReport.drift_score > 0) {
59160
+ callerSessionState.pendingAdvisoryMessages.push(`[CURATOR DRIFT DETECTED (phase ${phase}, score ${phaseReport.drift_score})]: Consider running critic_drift_verifier before phase completion to get a proper drift review. Review drift report for phase ${phase} and address spec alignment if applicable.`);
59161
+ }
59162
+ } catch {}
59090
59163
  }
59091
59164
  if (curatorResult.compliance.length > 0 && !curatorConfig.suppress_warnings) {
59092
59165
  const complianceLines = curatorResult.compliance.map((obs) => `[${obs.severity.toUpperCase()}] ${obs.description}`).slice(0, 5);
@@ -59222,7 +59295,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59222
59295
  return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
59223
59296
  }
59224
59297
  var phase_complete = createSwarmTool({
59225
- description: "Mark a phase as complete and track which agents were dispatched. " + "Used for phase completion gating and tracking. " + "Accepts phase number and optional summary. Returns list of agents that were dispatched.",
59298
+ description: "Mark a phase as complete and track which agents were dispatched. Used for phase completion gating and tracking. Accepts phase number and optional summary. Returns list of agents that were dispatched.",
59226
59299
  args: {
59227
59300
  phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
59228
59301
  summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
@@ -63220,7 +63293,7 @@ async function executeSavePlan(args2, fallbackDir) {
63220
63293
  return {
63221
63294
  success: false,
63222
63295
  message: "Failed to save plan: retry with save_plan after resolving the error above",
63223
- errors: [String(error93)],
63296
+ errors: [error93 instanceof Error ? error93.message : String(error93)],
63224
63297
  recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
63225
63298
  };
63226
63299
  }
@@ -65381,17 +65454,9 @@ function matchesTier3Pattern(files) {
65381
65454
  }
65382
65455
  return false;
65383
65456
  }
65384
- function hasActiveTurboMode3() {
65385
- for (const [_sessionId, session] of swarmState.agentSessions) {
65386
- if (session.turboMode === true) {
65387
- return true;
65388
- }
65389
- }
65390
- return false;
65391
- }
65392
65457
  function checkReviewerGate(taskId, workingDirectory) {
65393
65458
  try {
65394
- if (hasActiveTurboMode3()) {
65459
+ if (hasActiveTurboMode()) {
65395
65460
  const resolvedDir2 = workingDirectory;
65396
65461
  try {
65397
65462
  const planPath = path59.join(resolvedDir2, ".swarm", "plan.json");
@@ -65428,7 +65493,7 @@ function checkReviewerGate(taskId, workingDirectory) {
65428
65493
  };
65429
65494
  }
65430
65495
  } catch (error93) {
65431
- console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93);
65496
+ console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
65432
65497
  telemetry.gateFailed("", "qa_gate", taskId, `Evidence file corrupt or unreadable`);
65433
65498
  return {
65434
65499
  blocked: true,
@@ -65721,7 +65786,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
65721
65786
  return {
65722
65787
  success: false,
65723
65788
  message: "Failed to update task status",
65724
- errors: [String(error93)]
65789
+ errors: [error93 instanceof Error ? error93.message : String(error93)]
65725
65790
  };
65726
65791
  }
65727
65792
  }
@@ -66194,6 +66259,7 @@ var OpenCodeSwarm = async (ctx) => {
66194
66259
  }
66195
66260
  await guardrailsHooks.toolBefore(input, output);
66196
66261
  await scopeGuardHook.toolBefore(input, output);
66262
+ await delegationGateHooks.toolBefore(input, output);
66197
66263
  if (swarmState.lastBudgetPct >= 50) {
66198
66264
  const pressureSession = ensureAgentSession(input.sessionID, swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME);
66199
66265
  if (!pressureSession.contextPressureWarningSent) {
package/dist/state.d.ts CHANGED
@@ -331,7 +331,9 @@ export declare function applyRehydrationCache(session: AgentSessionState): void;
331
331
  */
332
332
  export declare function rehydrateSessionFromDisk(directory: string, session: AgentSessionState): Promise<void>;
333
333
  /**
334
- * Check if ANY active session has Turbo Mode enabled.
335
- * @returns true if any session has turboMode: true
334
+ * Check if Turbo Mode is enabled for a specific session or ANY session.
335
+ * @param sessionID - Optional session ID to check. If provided, checks only that session.
336
+ * If omitted, checks all sessions (backward-compatible global behavior).
337
+ * @returns true if the specified session has turboMode: true, or if any session has turboMode: true when no sessionID provided
336
338
  */
337
- export declare function hasActiveTurboMode(): boolean;
339
+ export declare function hasActiveTurboMode(sessionID?: string): boolean;
@@ -2,6 +2,6 @@
2
2
  * Adversarial security tests for Task 2.15: Session restart rehydration
3
3
  *
4
4
  * ONLY attack vectors - malformed inputs, oversized payloads, injection attempts, boundary violations
5
- * Tests the security hardening of rehydrateSessionFromDisk, readPlanFromDisk, and readEvidenceFromDisk
5
+ * Tests the security hardening of rehydrateSessionFromDisk, readPlanFromDisk, and readGateEvidenceFromDisk
6
6
  */
7
7
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.35.4",
3
+ "version": "6.37.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",