opencode-swarm 6.35.3 → 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;
@@ -40824,7 +41015,7 @@ For complex tasks, make a second explorer call focused on risk/gap analysis:
40824
41015
  After explorer returns:
40825
41016
  - Run \`symbols\` tool on key files identified by explorer to understand public API surfaces
40826
41017
  - Run \`complexity_hotspots\` if not already run in Phase 0 (check context.md for existing analysis). Note modules with recommendation "security_review" or "full_gates" in context.md.
40827
- - Check for project governance files using the \`glob\` tool with patterns \`project-instructions.md\`, \`docs/project-instructions.md\`, and \`INSTRUCTIONS.md\` (checked in that priority order \u2014 first match wins). If a file is found: read it and extract all MUST (mandatory constraints) and SHOULD (recommended practices) rules. Write the extracted rules as a summary to \`.swarm/context.md\` under a \`## Project Governance\` section \u2014 append if the section already exists, create it if not. If no MUST or SHOULD rules are found in the file, skip writing. If no governance file is found: skip silently. Existing DISCOVER steps are unchanged.
41018
+ - Check for project governance files using the \`glob\` tool with patterns \`project-instructions.md\`, \`docs/project-instructions.md\`, \`CONTRIBUTING.md\`, and \`INSTRUCTIONS.md\` (checked in that priority order \u2014 first match wins). If a file is found: read it and extract all MUST (mandatory constraints) and SHOULD (recommended practices) rules. Write the extracted rules as a summary to \`.swarm/context.md\` under a \`## Project Governance\` section \u2014 append if the section already exists, create it if not. If no MUST or SHOULD rules are found in the file, skip writing. If no governance file is found: skip silently. Existing DISCOVER steps are unchanged.
40828
41019
 
40829
41020
  ### MODE: CONSULT
40830
41021
  Check .swarm/context.md for cached guidance first.
@@ -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);
@@ -54148,7 +54344,9 @@ async function readMergedKnowledge(directory, config3, context) {
54148
54344
  finalScore: 0
54149
54345
  });
54150
54346
  }
54151
- const ranked = merged.map((entry) => {
54347
+ const scopeFilter = config3.scope_filter ?? ["global"];
54348
+ const filtered = merged.filter((entry) => scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern));
54349
+ const ranked = filtered.map((entry) => {
54152
54350
  let categoryScore = 0;
54153
54351
  if (context?.currentPhase) {
54154
54352
  const phaseCategories = inferCategoriesFromPhase(context.currentPhase);
@@ -54393,13 +54591,28 @@ async function processRetractions(retractions, directory) {
54393
54591
  async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, config3) {
54394
54592
  const knowledgePath = resolveSwarmKnowledgePath(directory);
54395
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
+ ]);
54396
54608
  for (const lesson of lessons) {
54397
54609
  const tags = inferTags(lesson);
54398
54610
  let category = "process";
54399
- if (tags.includes("security")) {
54400
- category = "security";
54401
- } else if (tags.includes("testing")) {
54402
- category = "testing";
54611
+ for (const tag of tags) {
54612
+ if (categoryByTag.has(tag)) {
54613
+ category = categoryByTag.get(tag);
54614
+ break;
54615
+ }
54403
54616
  }
54404
54617
  const meta3 = {
54405
54618
  category,
@@ -54408,18 +54621,20 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54408
54621
  };
54409
54622
  const result = validateLesson(lesson, existingEntries.map((e) => e.lesson), meta3);
54410
54623
  if (result.valid === false || result.severity === "error") {
54411
- const rejected = {
54624
+ const rejectedLesson = {
54412
54625
  id: crypto.randomUUID(),
54413
54626
  lesson,
54414
54627
  rejection_reason: result.reason ?? "unknown",
54415
54628
  rejected_at: new Date().toISOString(),
54416
54629
  rejection_layer: result.layer ?? 1
54417
54630
  };
54418
- await appendRejectedLesson(directory, rejected);
54631
+ await appendRejectedLesson(directory, rejectedLesson);
54632
+ rejected++;
54419
54633
  continue;
54420
54634
  }
54421
54635
  const duplicate = findNearDuplicate(lesson, existingEntries, config3.dedup_threshold);
54422
54636
  if (duplicate) {
54637
+ skipped++;
54423
54638
  continue;
54424
54639
  }
54425
54640
  const entry = {
@@ -54450,9 +54665,11 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
54450
54665
  auto_generated: true
54451
54666
  };
54452
54667
  await appendKnowledge(knowledgePath, entry);
54668
+ stored++;
54453
54669
  existingEntries.push(entry);
54454
54670
  }
54455
54671
  await runAutoPromotion(directory, config3);
54672
+ return { stored, skipped, rejected };
54456
54673
  }
54457
54674
  async function runAutoPromotion(directory, config3) {
54458
54675
  const knowledgePath = resolveSwarmKnowledgePath(directory);
@@ -54668,200 +54885,8 @@ Use this data to avoid repeating known failure patterns.`;
54668
54885
  return prefix + summaryText + suffix;
54669
54886
  }
54670
54887
 
54671
- // src/hooks/curator-drift.ts
54672
- init_event_bus();
54673
- init_utils2();
54674
- import * as fs26 from "fs";
54675
- import * as fsSync from "fs";
54676
- import * as path37 from "path";
54677
- var DRIFT_REPORT_PREFIX = "drift-report-phase-";
54678
- async function readPriorDriftReports(directory) {
54679
- const swarmDir = path37.join(directory, ".swarm");
54680
- const entries = await fs26.promises.readdir(swarmDir).catch(() => null);
54681
- if (entries === null)
54682
- return [];
54683
- const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
54684
- const reports = [];
54685
- for (const filename of reportFiles) {
54686
- const content = await readSwarmFileAsync(directory, filename);
54687
- if (content === null)
54688
- continue;
54689
- try {
54690
- const report = JSON.parse(content);
54691
- if (typeof report.phase !== "number" || typeof report.alignment !== "string") {
54692
- console.warn(`[curator-drift] Skipping corrupt drift report: ${filename}`);
54693
- continue;
54694
- }
54695
- reports.push(report);
54696
- } catch {
54697
- console.warn(`[curator-drift] Skipping unreadable drift report: ${filename}`);
54698
- }
54699
- }
54700
- reports.sort((a, b) => a.phase - b.phase);
54701
- return reports;
54702
- }
54703
- async function writeDriftReport(directory, report) {
54704
- const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
54705
- const filePath = validateSwarmPath(directory, filename);
54706
- const swarmDir = path37.dirname(filePath);
54707
- await fs26.promises.mkdir(swarmDir, { recursive: true });
54708
- try {
54709
- await fs26.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
54710
- } catch (err2) {
54711
- throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
54712
- }
54713
- return filePath;
54714
- }
54715
- async function runCriticDriftCheck(directory, phase, curatorResult, config3, injectAdvisory) {
54716
- try {
54717
- const planMd = await readSwarmFileAsync(directory, "plan.md");
54718
- const specMd = await readSwarmFileAsync(directory, "spec.md");
54719
- const priorReports = await readPriorDriftReports(directory);
54720
- const complianceCount = curatorResult.compliance.length;
54721
- const warningCompliance = curatorResult.compliance.filter((obs) => obs.severity === "warning");
54722
- let alignment = "ALIGNED";
54723
- let driftScore = 0;
54724
- if (!planMd) {
54725
- alignment = "MINOR_DRIFT";
54726
- driftScore = 0.3;
54727
- } else if (warningCompliance.length >= 3) {
54728
- alignment = "MAJOR_DRIFT";
54729
- driftScore = Math.min(0.9, 0.5 + warningCompliance.length * 0.1);
54730
- } else if (warningCompliance.length >= 1 || complianceCount >= 3) {
54731
- alignment = "MINOR_DRIFT";
54732
- driftScore = Math.min(0.49, 0.2 + complianceCount * 0.05);
54733
- }
54734
- const priorSummaries = priorReports.map((r) => r.injection_summary).filter(Boolean);
54735
- const keyCorrections = warningCompliance.map((obs) => obs.description);
54736
- const firstDeviation = warningCompliance.length > 0 ? {
54737
- phase,
54738
- task: "unknown",
54739
- description: warningCompliance[0]?.description ?? ""
54740
- } : null;
54741
- const payloadLines = [
54742
- `CURATOR_DIGEST: ${JSON.stringify(curatorResult.digest)}`,
54743
- `CURATOR_COMPLIANCE: ${JSON.stringify(curatorResult.compliance)}`,
54744
- `PLAN: ${planMd ?? "none"}`,
54745
- `SPEC: ${specMd ?? "none"}`,
54746
- `PRIOR_DRIFT_REPORTS: ${JSON.stringify(priorSummaries)}`
54747
- ];
54748
- const payload = payloadLines.join(`
54749
- `);
54750
- const requirementsChecked = curatorResult.digest.tasks_total;
54751
- const requirementsSatisfied = curatorResult.digest.tasks_completed;
54752
- const injectionSummaryRaw = `Phase ${phase}: ${alignment} (${driftScore.toFixed(2)}) \u2014 ${firstDeviation ? firstDeviation.description : "all requirements on track"}.${keyCorrections.length > 0 ? `Correction: ${keyCorrections[0] ?? ""}.` : ""}`;
54753
- const injectionSummary = injectionSummaryRaw.slice(0, config3.drift_inject_max_chars);
54754
- const report = {
54755
- schema_version: 1,
54756
- phase,
54757
- timestamp: new Date().toISOString(),
54758
- alignment,
54759
- drift_score: driftScore,
54760
- first_deviation: firstDeviation,
54761
- compounding_effects: priorReports.filter((r) => r.alignment !== "ALIGNED").map((r) => `Phase ${r.phase}: ${r.alignment}`).slice(0, 5),
54762
- corrections: keyCorrections.slice(0, 5),
54763
- requirements_checked: requirementsChecked,
54764
- requirements_satisfied: requirementsSatisfied,
54765
- scope_additions: [],
54766
- injection_summary: injectionSummary
54767
- };
54768
- const reportPath = await writeDriftReport(directory, report);
54769
- try {
54770
- const evidenceDir = path37.join(directory, ".swarm", "evidence", String(phase));
54771
- fsSync.mkdirSync(evidenceDir, { recursive: true });
54772
- const evidencePath = path37.join(evidenceDir, "drift-verifier.json");
54773
- let verdict;
54774
- let summary;
54775
- if (alignment === "MAJOR_DRIFT") {
54776
- verdict = "rejected";
54777
- summary = `Major drift detected (score: ${driftScore.toFixed(2)}): ${firstDeviation ? firstDeviation.description : "alignment issues detected"}`;
54778
- } else if (alignment === "MINOR_DRIFT") {
54779
- verdict = "approved";
54780
- summary = `Minor drift detected (score: ${driftScore.toFixed(2)}): ${firstDeviation ? firstDeviation.description : "minor alignment issues"}`;
54781
- } else {
54782
- verdict = "approved";
54783
- summary = "Drift check passed: all requirements aligned";
54784
- }
54785
- const evidence = {
54786
- entries: [
54787
- {
54788
- type: "drift-verification",
54789
- verdict,
54790
- summary,
54791
- timestamp: new Date().toISOString(),
54792
- agent: "curator-drift",
54793
- session_id: "curator"
54794
- }
54795
- ]
54796
- };
54797
- fsSync.writeFileSync(evidencePath, JSON.stringify(evidence, null, 2), "utf-8");
54798
- } catch (driftWriteErr) {
54799
- console.warn(`[curator-drift] drift-verifier evidence write failed: ${driftWriteErr instanceof Error ? driftWriteErr.message : String(driftWriteErr)}`);
54800
- }
54801
- getGlobalEventBus().publish("curator.drift.completed", {
54802
- phase,
54803
- alignment,
54804
- drift_score: driftScore,
54805
- report_path: reportPath
54806
- });
54807
- if (injectAdvisory && alignment !== "ALIGNED" && driftScore > 0) {
54808
- try {
54809
- 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.`;
54810
- injectAdvisory(advisoryText);
54811
- } catch {}
54812
- }
54813
- const injectionText = injectionSummary;
54814
- return {
54815
- phase,
54816
- report,
54817
- report_path: reportPath,
54818
- injection_text: injectionText
54819
- };
54820
- } catch (err2) {
54821
- getGlobalEventBus().publish("curator.error", {
54822
- operation: "drift",
54823
- phase,
54824
- error: String(err2)
54825
- });
54826
- const defaultReport = {
54827
- schema_version: 1,
54828
- phase,
54829
- timestamp: new Date().toISOString(),
54830
- alignment: "ALIGNED",
54831
- drift_score: 0,
54832
- first_deviation: null,
54833
- compounding_effects: [],
54834
- corrections: [],
54835
- requirements_checked: 0,
54836
- requirements_satisfied: 0,
54837
- scope_additions: [],
54838
- injection_summary: `Phase ${phase}: drift analysis unavailable (${String(err2)})`
54839
- };
54840
- return {
54841
- phase,
54842
- report: defaultReport,
54843
- report_path: "",
54844
- injection_text: ""
54845
- };
54846
- }
54847
- }
54848
- function buildDriftInjectionText(report, maxChars) {
54849
- if (maxChars <= 0) {
54850
- return "";
54851
- }
54852
- let text;
54853
- if (report.alignment === "ALIGNED" && report.drift_score < 0.1) {
54854
- text = `<drift_report>Phase ${report.phase}: ALIGNED, all requirements on track.</drift_report>`;
54855
- } else {
54856
- const keyFinding = report.first_deviation?.description ?? "no deviation recorded";
54857
- const score = report.drift_score ?? 0;
54858
- const correctionClause = report.corrections?.[0] ? `Correction: ${report.corrections[0]}.` : "";
54859
- text = `<drift_report>Phase ${report.phase}: ${report.alignment} (${score.toFixed(2)}) \u2014 ${keyFinding}. ${correctionClause}</drift_report>`;
54860
- }
54861
- return text.slice(0, maxChars);
54862
- }
54863
-
54864
54888
  // src/hooks/knowledge-injector.ts
54889
+ init_curator_drift();
54865
54890
  init_utils2();
54866
54891
  function formatStars(confidence) {
54867
54892
  if (confidence >= 0.9)
@@ -54913,9 +54938,7 @@ function createKnowledgeInjectorHook(directory, config3) {
54913
54938
  if (!output.messages || output.messages.length === 0)
54914
54939
  return;
54915
54940
  const plan = await loadPlan(directory);
54916
- if (!plan)
54917
- return;
54918
- const currentPhase = plan.current_phase ?? 1;
54941
+ const currentPhase = plan?.current_phase ?? 1;
54919
54942
  const totalChars = output.messages.reduce((sum, msg) => {
54920
54943
  return sum + (msg.parts?.reduce((s, p) => s + (p.text?.length ?? 0), 0) ?? 0);
54921
54944
  }, 0);
@@ -54925,19 +54948,16 @@ function createKnowledgeInjectorHook(directory, config3) {
54925
54948
  const agentName = systemMsg?.info?.agent;
54926
54949
  if (!agentName || !isOrchestratorAgent(agentName))
54927
54950
  return;
54928
- if (lastSeenPhase === null) {
54929
- lastSeenPhase = currentPhase;
54930
- return;
54931
- } else if (currentPhase === lastSeenPhase && cachedInjectionText !== null) {
54951
+ if (currentPhase === lastSeenPhase && cachedInjectionText !== null) {
54932
54952
  injectKnowledgeMessage(output, cachedInjectionText);
54933
54953
  return;
54934
54954
  } else if (currentPhase !== lastSeenPhase) {
54935
54955
  lastSeenPhase = currentPhase;
54936
54956
  cachedInjectionText = null;
54937
54957
  }
54938
- const phaseDescription = extractCurrentPhaseFromPlan2(plan) ?? `Phase ${currentPhase}`;
54958
+ const phaseDescription = plan ? extractCurrentPhaseFromPlan2(plan) ?? `Phase ${currentPhase}` : "Phase 0";
54939
54959
  const context = {
54940
- projectName: plan.title,
54960
+ projectName: plan?.title ?? "unknown",
54941
54961
  currentPhase: phaseDescription
54942
54962
  };
54943
54963
  const entries = await readMergedKnowledge(directory, config3, context);
@@ -55748,7 +55768,7 @@ var build_check = createSwarmTool({
55748
55768
  try {
55749
55769
  await saveEvidence(workingDir, "build", evidence);
55750
55770
  } catch (error93) {
55751
- console.error("Failed to save build evidence:", error93);
55771
+ console.error("Failed to save build evidence:", error93 instanceof Error ? error93.message : String(error93));
55752
55772
  }
55753
55773
  return JSON.stringify(result, null, 2);
55754
55774
  }
@@ -55935,14 +55955,6 @@ init_utils2();
55935
55955
  import * as fs30 from "fs";
55936
55956
  import * as path41 from "path";
55937
55957
  init_create_tool();
55938
- function hasActiveTurboMode2() {
55939
- for (const [_sessionId, session] of swarmState.agentSessions) {
55940
- if (session.turboMode === true) {
55941
- return true;
55942
- }
55943
- }
55944
- return false;
55945
- }
55946
55958
  function extractMatches(regex, text) {
55947
55959
  return Array.from(text.matchAll(regex));
55948
55960
  }
@@ -56007,7 +56019,7 @@ async function executeCompletionVerify(args2, directory) {
56007
56019
  };
56008
56020
  return JSON.stringify(result2, null, 2);
56009
56021
  }
56010
- if (hasActiveTurboMode2()) {
56022
+ if (hasActiveTurboMode(args2.sessionID)) {
56011
56023
  const result2 = {
56012
56024
  success: true,
56013
56025
  phase,
@@ -56538,7 +56550,7 @@ var curator_analyze = createSwarmTool({
56538
56550
  }, null, 2);
56539
56551
  } catch (error93) {
56540
56552
  return JSON.stringify({
56541
- error: String(error93),
56553
+ error: error93 instanceof Error ? error93.message : String(error93),
56542
56554
  phase: typedArgs.phase
56543
56555
  }, null, 2);
56544
56556
  }
@@ -58265,6 +58277,7 @@ var imports = createSwarmTool({
58265
58277
  });
58266
58278
  // src/tools/knowledge-add.ts
58267
58279
  init_dist();
58280
+ init_manager2();
58268
58281
  init_create_tool();
58269
58282
  var VALID_CATEGORIES2 = [
58270
58283
  "process",
@@ -58332,6 +58345,11 @@ var knowledgeAdd = createSwarmTool({
58332
58345
  }
58333
58346
  }
58334
58347
  const scope = typeof scopeInput === "string" && scopeInput.length > 0 ? scopeInput : "global";
58348
+ let project_name = "";
58349
+ try {
58350
+ const plan = await loadPlan(directory);
58351
+ project_name = plan?.title ?? "";
58352
+ } catch {}
58335
58353
  const entry = {
58336
58354
  id: crypto.randomUUID(),
58337
58355
  tier: "swarm",
@@ -58342,7 +58360,7 @@ var knowledgeAdd = createSwarmTool({
58342
58360
  confidence: 0.5,
58343
58361
  status: "candidate",
58344
58362
  confirmed_by: [],
58345
- project_name: "",
58363
+ project_name,
58346
58364
  retrieval_outcomes: {
58347
58365
  applied_count: 0,
58348
58366
  succeeded_after_count: 0,
@@ -58756,7 +58774,7 @@ init_telemetry();
58756
58774
  init_create_tool();
58757
58775
  function safeWarn(message, error93) {
58758
58776
  try {
58759
- console.warn(message, error93);
58777
+ console.warn(message, error93 instanceof Error ? error93.message : String(error93));
58760
58778
  } catch {}
58761
58779
  }
58762
58780
  function collectCrossSessionDispatchedAgents(phaseReferenceTimestamp, callerSessionId) {
@@ -58946,7 +58964,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
58946
58964
  ]
58947
58965
  }, null, 2);
58948
58966
  }
58949
- if (hasActiveTurboMode()) {
58967
+ if (hasActiveTurboMode(sessionID)) {
58950
58968
  console.warn(`[phase_complete] Turbo mode active \u2014 skipping completion-verify and drift-verifier gates for phase ${phase}`);
58951
58969
  } else {
58952
58970
  try {
@@ -59007,7 +59025,23 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59007
59025
  const specPath = path48.join(dir, ".swarm", "spec.md");
59008
59026
  const specExists = fs37.existsSync(specPath);
59009
59027
  if (!specExists) {
59010
- 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
+ }
59011
59045
  } else {
59012
59046
  return JSON.stringify({
59013
59047
  success: false,
@@ -59073,17 +59107,21 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59073
59107
  const curatorConfig = CuratorConfigSchema.parse(config3.curator ?? {});
59074
59108
  if (curatorConfig.enabled && curatorConfig.phase_enabled) {
59075
59109
  const curatorResult = await runCuratorPhase(dir, phase, agentsDispatched, curatorConfig, {});
59076
- await applyCuratorKnowledgeUpdates(dir, curatorResult.knowledge_recommendations, knowledgeConfig);
59077
- const driftResult = await runCriticDriftCheck(dir, phase, curatorResult, curatorConfig);
59110
+ const knowledgeResult = await applyCuratorKnowledgeUpdates(dir, curatorResult.knowledge_recommendations, knowledgeConfig);
59078
59111
  const callerSessionState = swarmState.agentSessions.get(sessionID);
59079
59112
  if (callerSessionState) {
59080
59113
  callerSessionState.pendingAdvisoryMessages ??= [];
59081
59114
  const digestSummary = curatorResult.digest?.summary ? curatorResult.digest.summary.slice(0, 200) : "Phase analysis complete";
59082
59115
  const complianceNote = curatorResult.compliance.length > 0 ? ` (${curatorResult.compliance.length} compliance observation(s))` : "";
59083
- callerSessionState.pendingAdvisoryMessages.push(`[CURATOR] Phase ${phase} digest: ${digestSummary}${complianceNote}. Call curator_analyze with recommendations to apply knowledge updates from this phase.`);
59084
- if (driftResult?.report?.drift_score && driftResult.report.drift_score > 0) {
59085
- 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.`);
59086
- }
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 {}
59087
59125
  }
59088
59126
  if (curatorResult.compliance.length > 0 && !curatorConfig.suppress_warnings) {
59089
59127
  const complianceLines = curatorResult.compliance.map((obs) => `[${obs.severity.toUpperCase()}] ${obs.description}`).slice(0, 5);
@@ -59219,7 +59257,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
59219
59257
  return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
59220
59258
  }
59221
59259
  var phase_complete = createSwarmTool({
59222
- 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.",
59223
59261
  args: {
59224
59262
  phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
59225
59263
  summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
@@ -63217,7 +63255,7 @@ async function executeSavePlan(args2, fallbackDir) {
63217
63255
  return {
63218
63256
  success: false,
63219
63257
  message: "Failed to save plan: retry with save_plan after resolving the error above",
63220
- errors: [String(error93)],
63258
+ errors: [error93 instanceof Error ? error93.message : String(error93)],
63221
63259
  recovery_guidance: "Use save_plan with corrected inputs to create or restructure plans. Never write .swarm/plan.json or .swarm/plan.md directly."
63222
63260
  };
63223
63261
  }
@@ -65378,17 +65416,9 @@ function matchesTier3Pattern(files) {
65378
65416
  }
65379
65417
  return false;
65380
65418
  }
65381
- function hasActiveTurboMode3() {
65382
- for (const [_sessionId, session] of swarmState.agentSessions) {
65383
- if (session.turboMode === true) {
65384
- return true;
65385
- }
65386
- }
65387
- return false;
65388
- }
65389
65419
  function checkReviewerGate(taskId, workingDirectory) {
65390
65420
  try {
65391
- if (hasActiveTurboMode3()) {
65421
+ if (hasActiveTurboMode()) {
65392
65422
  const resolvedDir2 = workingDirectory;
65393
65423
  try {
65394
65424
  const planPath = path59.join(resolvedDir2, ".swarm", "plan.json");
@@ -65425,7 +65455,7 @@ function checkReviewerGate(taskId, workingDirectory) {
65425
65455
  };
65426
65456
  }
65427
65457
  } catch (error93) {
65428
- 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));
65429
65459
  telemetry.gateFailed("", "qa_gate", taskId, `Evidence file corrupt or unreadable`);
65430
65460
  return {
65431
65461
  blocked: true,
@@ -65718,7 +65748,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
65718
65748
  return {
65719
65749
  success: false,
65720
65750
  message: "Failed to update task status",
65721
- errors: [String(error93)]
65751
+ errors: [error93 instanceof Error ? error93.message : String(error93)]
65722
65752
  };
65723
65753
  }
65724
65754
  }
@@ -65983,6 +66013,8 @@ var OpenCodeSwarm = async (ctx) => {
65983
66013
  knowledgeRecall,
65984
66014
  knowledgeRemove,
65985
66015
  detect_domains,
66016
+ doc_extract,
66017
+ doc_scan,
65986
66018
  evidence_check,
65987
66019
  extract_code_blocks,
65988
66020
  gitingest,
@@ -66284,6 +66316,11 @@ var OpenCodeSwarm = async (ctx) => {
66284
66316
  taskSession.delegationActive = false;
66285
66317
  taskSession.lastAgentEventTime = Date.now();
66286
66318
  telemetry.delegationEnd(sessionId, agentName, taskSession.currentTaskId || "", "completed");
66319
+ const baseAgentName = stripKnownSwarmPrefix(agentName);
66320
+ if (baseAgentName === "reviewer" || baseAgentName === "test_engineer" || baseAgentName === "critic" || baseAgentName === "critic_sounding_board") {
66321
+ taskSession.pendingAdvisoryMessages ??= [];
66322
+ taskSession.pendingAdvisoryMessages.push(`[PIPELINE] ${baseAgentName} delegation complete for task ${taskSession.currentTaskId ?? "unknown"}. ` + `Resume the QA gate pipeline \u2014 check your task pipeline steps for the next required action. ` + `Do not stop here.`);
66323
+ }
66287
66324
  }
66288
66325
  if (_dbg)
66289
66326
  console.error(`[DIAG] Task handoff DONE session=${sessionId} activeAgent=${swarmState.activeAgent.get(sessionId)}`);
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.3",
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",