opencode-swarm 6.35.4 → 6.36.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
  }
@@ -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>"
@@ -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
  }
@@ -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)
@@ -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;
@@ -41187,10 +41378,10 @@ The tool will automatically write the retrospective to \`.swarm/evidence/retro-{
41187
41378
  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
41379
  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
41380
  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.
41381
+ 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
41382
  5.6. **Mandatory gate evidence**: Before calling phase_complete, ensure:
41192
41383
  - \`.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)
41384
+ - \`.swarm/evidence/{phase}/drift-verifier.json\` exists with verdict 'approved' (written by the critic_drift_verifier delegation in step 5.5)
41194
41385
  If either is missing, run the missing gate first. Turbo mode skips both gates automatically.
41195
41386
  6. Summarize to user
41196
41387
  7. Ask: "Ready for Phase [N+1]?"
@@ -41270,7 +41461,7 @@ While Turbo Mode is active:
41270
41461
  - **Stage A gates** (lint, imports, pre_check_batch) are still REQUIRED for ALL tasks
41271
41462
  - **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
41463
  - **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
41464
+ - **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
41465
 
41275
41466
  Classification still determines the pipeline:
41276
41467
  - TIER 0 (metadata): lint + diff only \u2014 no change
@@ -42816,6 +43007,11 @@ If you call @coder instead of @${swarmId}_coder, the call will FAIL or go to the
42816
43007
  critic.name = prefixName("critic_sounding_board");
42817
43008
  agents.push(applyOverrides(critic, swarmAgents, swarmPrefix));
42818
43009
  }
43010
+ if (!isAgentDisabled("critic_drift_verifier", swarmAgents, swarmPrefix)) {
43011
+ const critic = createCriticAgent(swarmAgents?.critic_drift_verifier?.model ?? getModel("critic"), undefined, undefined, "phase_drift_verifier");
43012
+ critic.name = prefixName("critic_drift_verifier");
43013
+ agents.push(applyOverrides(critic, swarmAgents, swarmPrefix));
43014
+ }
42819
43015
  if (!isAgentDisabled("test_engineer", swarmAgents, swarmPrefix)) {
42820
43016
  const testPrompts = getPrompts("test_engineer");
42821
43017
  const testEngineer = createTestEngineerAgent(getModel("test_engineer"), testPrompts.prompt, testPrompts.appendPrompt);
@@ -45533,7 +45729,7 @@ async function handleCurateCommand(directory, _args) {
45533
45729
  if (error93 instanceof Error) {
45534
45730
  return `\u274C Curation failed: ${error93.message}`;
45535
45731
  }
45536
- return `\u274C Curation failed: ${String(error93)}`;
45732
+ return `\u274C Curation failed: ${error93 instanceof Error ? error93.message : String(error93)}`;
45537
45733
  }
45538
45734
  }
45539
45735
  function formatCurationSummary(summary) {
@@ -45923,7 +46119,7 @@ async function handleDarkMatterCommand(directory, args2) {
45923
46119
  [${entries.length} dark matter finding(s) saved to .swarm/knowledge.jsonl]`;
45924
46120
  }
45925
46121
  } catch (err2) {
45926
- console.warn("dark-matter: failed to save knowledge entries:", err2);
46122
+ console.warn("dark-matter: failed to save knowledge entries:", err2 instanceof Error ? err2.message : String(err2));
45927
46123
  return output;
45928
46124
  }
45929
46125
  }
@@ -47697,7 +47893,7 @@ async function handleKnowledgeQuarantineCommand(directory, args2) {
47697
47893
  await quarantineEntry(directory, entryId, reason, "user");
47698
47894
  return `\u2705 Entry ${entryId} quarantined successfully.`;
47699
47895
  } catch (error93) {
47700
- console.warn("[knowledge-command] quarantineEntry error:", error93);
47896
+ console.warn("[knowledge-command] quarantineEntry error:", error93 instanceof Error ? error93.message : String(error93));
47701
47897
  return `\u274C Failed to quarantine entry. Check the entry ID and try again.`;
47702
47898
  }
47703
47899
  }
@@ -47713,7 +47909,7 @@ async function handleKnowledgeRestoreCommand(directory, args2) {
47713
47909
  await restoreEntry(directory, entryId);
47714
47910
  return `\u2705 Entry ${entryId} restored successfully.`;
47715
47911
  } catch (error93) {
47716
- console.warn("[knowledge-command] restoreEntry error:", error93);
47912
+ console.warn("[knowledge-command] restoreEntry error:", error93 instanceof Error ? error93.message : String(error93));
47717
47913
  return `\u274C Failed to restore entry. Check the entry ID and try again.`;
47718
47914
  }
47719
47915
  }
@@ -47735,7 +47931,7 @@ async function handleKnowledgeMigrateCommand(directory, args2) {
47735
47931
  }
47736
47932
  return `\u2705 Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
47737
47933
  } catch (error93) {
47738
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93);
47934
+ console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
47739
47935
  return "\u274C Migration failed. Check .swarm/context.md is readable.";
47740
47936
  }
47741
47937
  }
@@ -47762,7 +47958,7 @@ async function handleKnowledgeListCommand(directory, _args) {
47762
47958
  return lines.join(`
47763
47959
  `);
47764
47960
  } catch (error93) {
47765
- console.warn("[knowledge-command] list error:", error93);
47961
+ console.warn("[knowledge-command] list error:", error93 instanceof Error ? error93.message : String(error93));
47766
47962
  return "\u274C Failed to list knowledge entries. Ensure .swarm/knowledge.jsonl exists.";
47767
47963
  }
47768
47964
  }
@@ -47953,7 +48149,7 @@ async function handlePromoteCommand(directory, args2) {
47953
48149
  if (error93 instanceof Error) {
47954
48150
  return error93.message;
47955
48151
  }
47956
- return `Failed to promote lesson: ${String(error93)}`;
48152
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
47957
48153
  }
47958
48154
  }
47959
48155
  try {
@@ -47962,7 +48158,7 @@ async function handlePromoteCommand(directory, args2) {
47962
48158
  if (error93 instanceof Error) {
47963
48159
  return error93.message;
47964
48160
  }
47965
- return `Failed to promote lesson: ${String(error93)}`;
48161
+ return `Failed to promote lesson: ${error93 instanceof Error ? error93.message : String(error93)}`;
47966
48162
  }
47967
48163
  }
47968
48164
 
@@ -48248,7 +48444,7 @@ async function handleRollbackCommand(directory, args2) {
48248
48444
  fs18.appendFileSync(eventsPath, `${JSON.stringify(rollbackEvent)}
48249
48445
  `);
48250
48446
  } catch (error93) {
48251
- console.error("Failed to write rollback event:", error93);
48447
+ console.error("Failed to write rollback event:", error93 instanceof Error ? error93.message : String(error93));
48252
48448
  }
48253
48449
  return `Rolled back to phase ${targetPhase}: ${checkpoint2.label || "no label"}`;
48254
48450
  }
@@ -51450,7 +51646,7 @@ function createDelegationGateHook(config3, directory) {
51450
51646
  const rawTaskId = directArgs?.task_id;
51451
51647
  const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && /^\d+\.\d+$/.test(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
51452
51648
  if (evidenceTaskId) {
51453
- const turbo = hasActiveTurboMode();
51649
+ const turbo = hasActiveTurboMode(input.sessionID);
51454
51650
  const gateAgents = [
51455
51651
  "reviewer",
51456
51652
  "test_engineer",
@@ -51569,7 +51765,7 @@ function createDelegationGateHook(config3, directory) {
51569
51765
  const rawTaskId = directArgs?.task_id;
51570
51766
  const evidenceTaskId = typeof rawTaskId === "string" && rawTaskId.length <= 20 && /^\d+\.\d+$/.test(rawTaskId.trim()) ? rawTaskId.trim() : await getEvidenceTaskId(session, directory);
51571
51767
  if (evidenceTaskId) {
51572
- const turbo = hasActiveTurboMode();
51768
+ const turbo = hasActiveTurboMode(input.sessionID);
51573
51769
  if (hasReviewer) {
51574
51770
  const { recordGateEvidence: recordGateEvidence2 } = await Promise.resolve().then(() => (init_gate_evidence(), exports_gate_evidence));
51575
51771
  await recordGateEvidence2(directory, evidenceTaskId, "reviewer", input.sessionID, turbo);
@@ -54395,13 +54591,28 @@ async function processRetractions(retractions, directory) {
54395
54591
  async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, config3) {
54396
54592
  const knowledgePath = resolveSwarmKnowledgePath(directory);
54397
54593
  const existingEntries = await readKnowledge(knowledgePath) ?? [];
54594
+ let stored = 0;
54595
+ let skipped = 0;
54596
+ let rejected = 0;
54597
+ const categoryByTag = new Map([
54598
+ ["process", "process"],
54599
+ ["architecture", "architecture"],
54600
+ ["tooling", "tooling"],
54601
+ ["security", "security"],
54602
+ ["testing", "testing"],
54603
+ ["debugging", "debugging"],
54604
+ ["performance", "performance"],
54605
+ ["integration", "integration"],
54606
+ ["other", "other"]
54607
+ ]);
54398
54608
  for (const lesson of lessons) {
54399
54609
  const tags = inferTags(lesson);
54400
54610
  let category = "process";
54401
- if (tags.includes("security")) {
54402
- category = "security";
54403
- } else if (tags.includes("testing")) {
54404
- category = "testing";
54611
+ for (const tag of tags) {
54612
+ if (categoryByTag.has(tag)) {
54613
+ category = categoryByTag.get(tag);
54614
+ break;
54615
+ }
54405
54616
  }
54406
54617
  const meta3 = {
54407
54618
  category,
@@ -54410,18 +54621,20 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54410
54621
  };
54411
54622
  const result = validateLesson(lesson, existingEntries.map((e) => e.lesson), meta3);
54412
54623
  if (result.valid === false || result.severity === "error") {
54413
- const rejected = {
54624
+ const rejectedLesson = {
54414
54625
  id: crypto.randomUUID(),
54415
54626
  lesson,
54416
54627
  rejection_reason: result.reason ?? "unknown",
54417
54628
  rejected_at: new Date().toISOString(),
54418
54629
  rejection_layer: result.layer ?? 1
54419
54630
  };
54420
- await appendRejectedLesson(directory, rejected);
54631
+ await appendRejectedLesson(directory, rejectedLesson);
54632
+ rejected++;
54421
54633
  continue;
54422
54634
  }
54423
54635
  const duplicate = findNearDuplicate(lesson, existingEntries, config3.dedup_threshold);
54424
54636
  if (duplicate) {
54637
+ skipped++;
54425
54638
  continue;
54426
54639
  }
54427
54640
  const entry = {
@@ -54452,9 +54665,11 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54452
54665
  auto_generated: true
54453
54666
  };
54454
54667
  await appendKnowledge(knowledgePath, entry);
54668
+ stored++;
54455
54669
  existingEntries.push(entry);
54456
54670
  }
54457
54671
  await runAutoPromotion(directory, config3);
54672
+ return { stored, skipped, rejected };
54458
54673
  }
54459
54674
  async function runAutoPromotion(directory, config3) {
54460
54675
  const knowledgePath = resolveSwarmKnowledgePath(directory);
@@ -54670,200 +54885,8 @@ Use this data to avoid repeating known failure patterns.`;
54670
54885
  return prefix + summaryText + suffix;
54671
54886
  }
54672
54887
 
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
54888
  // src/hooks/knowledge-injector.ts
54889
+ init_curator_drift();
54867
54890
  init_utils2();
54868
54891
  function formatStars(confidence) {
54869
54892
  if (confidence >= 0.9)
@@ -55745,7 +55768,7 @@ var build_check = createSwarmTool({
55745
55768
  try {
55746
55769
  await saveEvidence(workingDir, "build", evidence);
55747
55770
  } catch (error93) {
55748
- console.error("Failed to save build evidence:", error93);
55771
+ console.error("Failed to save build evidence:", error93 instanceof Error ? error93.message : String(error93));
55749
55772
  }
55750
55773
  return JSON.stringify(result, null, 2);
55751
55774
  }
@@ -55932,14 +55955,6 @@ init_utils2();
55932
55955
  import * as fs30 from "fs";
55933
55956
  import * as path41 from "path";
55934
55957
  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
55958
  function extractMatches(regex, text) {
55944
55959
  return Array.from(text.matchAll(regex));
55945
55960
  }
@@ -56004,7 +56019,7 @@ async function executeCompletionVerify(args2, directory) {
56004
56019
  };
56005
56020
  return JSON.stringify(result2, null, 2);
56006
56021
  }
56007
- if (hasActiveTurboMode2()) {
56022
+ if (hasActiveTurboMode(args2.sessionID)) {
56008
56023
  const result2 = {
56009
56024
  success: true,
56010
56025
  phase,
@@ -56535,7 +56550,7 @@ var curator_analyze = createSwarmTool({
56535
56550
  }, null, 2);
56536
56551
  } catch (error93) {
56537
56552
  return JSON.stringify({
56538
- error: String(error93),
56553
+ error: error93 instanceof Error ? error93.message : String(error93),
56539
56554
  phase: typedArgs.phase
56540
56555
  }, null, 2);
56541
56556
  }
@@ -58759,7 +58774,7 @@ init_telemetry();
58759
58774
  init_create_tool();
58760
58775
  function safeWarn(message, error93) {
58761
58776
  try {
58762
- console.warn(message, error93);
58777
+ console.warn(message, error93 instanceof Error ? error93.message : String(error93));
58763
58778
  } catch {}
58764
58779
  }
58765
58780
  function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSessionId) {
@@ -58949,7 +58964,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
58949
58964
  ]
58950
58965
  }, null, 2);
58951
58966
  }
58952
- if (hasActiveTurboMode()) {
58967
+ if (hasActiveTurboMode(sessionID)) {
58953
58968
  console.warn(`[phase_complete] Turbo mode active \u2014 skipping completion-verify and drift-verifier gates for phase ${phase}`);
58954
58969
  } else {
58955
58970
  try {
@@ -59010,7 +59025,23 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59010
59025
  const specPath = path48.join(dir, ".swarm", "spec.md");
59011
59026
  const specExists = fs37.existsSync(specPath);
59012
59027
  if (!specExists) {
59013
- warnings.push(`Drift verifier evidence missing \u2014 no spec.md found, drift check is advisory-only.`);
59028
+ let incompleteTaskCount = 0;
59029
+ let planPhaseFound = false;
59030
+ try {
59031
+ const planPath = validateSwarmPath(dir, "plan.json");
59032
+ const planRaw = fs37.readFileSync(planPath, "utf-8");
59033
+ const plan = JSON.parse(planRaw);
59034
+ const targetPhase = plan.phases.find((p) => p.id === phase);
59035
+ if (targetPhase) {
59036
+ planPhaseFound = true;
59037
+ incompleteTaskCount = targetPhase.tasks.filter((t) => t.status !== "completed").length;
59038
+ }
59039
+ } catch {}
59040
+ if (incompleteTaskCount > 0 || !planPhaseFound) {
59041
+ 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.`);
59042
+ } else {
59043
+ warnings.push(`No spec.md found. Phase ${phase} tasks are all completed in plan.json. Drift verification was skipped.`);
59044
+ }
59014
59045
  } else {
59015
59046
  return JSON.stringify({
59016
59047
  success: false,
@@ -59076,17 +59107,21 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59076
59107
  const curatorConfig = CuratorConfigSchema.parse(config3.curator ?? {});
59077
59108
  if (curatorConfig.enabled && curatorConfig.phase_enabled) {
59078
59109
  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);
59110
+ const knowledgeResult = await applyCuratorKnowledgeUpdates(dir, curatorResult.knowledge_recommendations, knowledgeConfig);
59081
59111
  const callerSessionState = swarmState.agentSessions.get(sessionID);
59082
59112
  if (callerSessionState) {
59083
59113
  callerSessionState.pendingAdvisoryMessages ??= [];
59084
59114
  const digestSummary = curatorResult.digest?.summary ? curatorResult.digest.summary.slice(0, 200) : "Phase analysis complete";
59085
59115
  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
- }
59116
+ 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.`);
59117
+ try {
59118
+ const { readPriorDriftReports: readPriorDriftReports2 } = await Promise.resolve().then(() => (init_curator_drift(), exports_curator_drift));
59119
+ const priorReports = await readPriorDriftReports2(dir);
59120
+ const phaseReport = priorReports.filter((r) => r.phase === phase).pop();
59121
+ if (phaseReport && phaseReport.drift_score > 0) {
59122
+ 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.`);
59123
+ }
59124
+ } catch {}
59090
59125
  }
59091
59126
  if (curatorResult.compliance.length > 0 && !curatorConfig.suppress_warnings) {
59092
59127
  const complianceLines = curatorResult.compliance.map((obs) => `[${obs.severity.toUpperCase()}] ${obs.description}`).slice(0, 5);
@@ -59222,7 +59257,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59222
59257
  return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
59223
59258
  }
59224
59259
  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.",
59260
+ 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
59261
  args: {
59227
59262
  phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
59228
59263
  summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
@@ -63220,7 +63255,7 @@ async function executeSavePlan(args2, fallbackDir) {
63220
63255
  return {
63221
63256
  success: false,
63222
63257
  message: "Failed to save plan: retry with save_plan after resolving the error above",
63223
- errors: [String(error93)],
63258
+ errors: [error93 instanceof Error ? error93.message : String(error93)],
63224
63259
  recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
63225
63260
  };
63226
63261
  }
@@ -65381,17 +65416,9 @@ function matchesTier3Pattern(files) {
65381
65416
  }
65382
65417
  return false;
65383
65418
  }
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
65419
  function checkReviewerGate(taskId, workingDirectory) {
65393
65420
  try {
65394
- if (hasActiveTurboMode3()) {
65421
+ if (hasActiveTurboMode()) {
65395
65422
  const resolvedDir2 = workingDirectory;
65396
65423
  try {
65397
65424
  const planPath = path59.join(resolvedDir2, ".swarm", "plan.json");
@@ -65428,7 +65455,7 @@ function checkReviewerGate(taskId, workingDirectory) {
65428
65455
  };
65429
65456
  }
65430
65457
  } catch (error93) {
65431
- console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93);
65458
+ console.warn(`[gate-evidence] Evidence file for task ${taskId} is corrupt or unreadable:`, error93 instanceof Error ? error93.message : String(error93));
65432
65459
  telemetry.gateFailed("", "qa_gate", taskId, `Evidence file corrupt or unreadable`);
65433
65460
  return {
65434
65461
  blocked: true,
@@ -65721,7 +65748,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
65721
65748
  return {
65722
65749
  success: false,
65723
65750
  message: "Failed to update task status",
65724
- errors: [String(error93)]
65751
+ errors: [error93 instanceof Error ? error93.message : String(error93)]
65725
65752
  };
65726
65753
  }
65727
65754
  }
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.35.4",
3
+ "version": "6.36.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",