opencode-swarm 6.58.0 → 6.59.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.
@@ -21,13 +21,13 @@ export declare const AUTONOMOUS_OVERSIGHT_PROMPT = "## AUTONOMOUS OVERSIGHT MODE
21
21
  export declare function createCriticAgent(model: string, customPrompt?: string, customAppendPrompt?: string, role?: CriticRole): AgentDefinition;
22
22
  /**
23
23
  * Creates a Critic agent configured for phase drift verification.
24
- * Follows the createExplorerCuratorAgent pattern: returns name 'critic' (same agent),
24
+ * Follows the createCuratorAgent pattern: returns name 'critic' (same agent),
25
25
  * different prompt — the drift verifier is the Critic doing a different job.
26
26
  */
27
27
  export declare function createCriticDriftVerifierAgent(model: string, customAppendPrompt?: string): AgentDefinition;
28
28
  /**
29
29
  * Creates a Critic agent configured for autonomous oversight mode.
30
- * Follows the createExplorerCuratorAgent pattern: returns name 'critic' (same agent),
30
+ * Follows the createCuratorAgent pattern: returns name 'critic' (same agent),
31
31
  * different prompt — the autonomous oversight agent is the sole quality gate in full-auto mode.
32
32
  */
33
33
  export declare function createCriticAutonomousOversightAgent(model: string, customAppendPrompt?: string): AgentDefinition;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { AgentDefinition } from './architect';
2
- export declare const CURATOR_INIT_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_INIT mode. You consolidate prior session knowledge into an architect briefing.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_INIT\nPRIOR_SUMMARY: [JSON or \"none\"]\nKNOWLEDGE_ENTRIES: [JSON array of existing entries with UUIDs]\nPROJECT_CONTEXT: [context.md excerpt]\n\nACTIONS:\n- Read the prior summary to understand session history\n- Cross-reference knowledge entries against project context\n- Identify contradictions (knowledge says X, project state shows Y)\n- Recommend rewrites for verbose or stale lessons\n- Produce a concise briefing for the architect\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Flag contradictions explicitly with CONTRADICTION: prefix\n- If no prior summary exists, state \"First session \u2014 no prior context\"\n\nOUTPUT FORMAT:\nBRIEFING:\n[concise summary of prior session state, key decisions, active blockers]\n\nCONTRADICTIONS:\n- [entry_id]: [description] (or \"None detected\")\n\nKNOWLEDGE_UPDATES:\n- promote <uuid>: <reason> (boost confidence, mark hive_eligible)\n- archive <uuid>: <reason> (mark as archived \u2014 no longer injected)\n- rewrite <uuid>: <new lesson text> (replace verbose/stale lesson with tighter version, max 280 chars)\n- flag_contradiction <uuid>: <reason> (tag as contradicted)\n- promote new: <new lesson text> (add a brand-new entry)\nUse the UUID from KNOWLEDGE_ENTRIES when archiving, rewriting, or flagging an existing entry. Use \"new\" only when recommending a brand-new entry.\n\nKNOWLEDGE_STATS:\n- Entries reviewed: [N]\n- Prior phases covered: [N]\n";
3
- export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\nKNOWLEDGE_ENTRIES: [JSON array of existing entries with UUIDs]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Recommend knowledge updates: entries to promote, archive, rewrite, or flag as contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type]: [description] (or \"No deviations observed\")\n\nKNOWLEDGE_UPDATES:\n- promote <uuid>: <reason> (boost confidence, mark hive_eligible)\n- archive <uuid>: <reason> (mark as archived \u2014 no longer injected)\n- rewrite <uuid>: <new lesson text> (replace verbose/stale lesson with tighter version, max 280 chars)\n- flag_contradiction <uuid>: <reason> (tag as contradicted)\n- promote new: <new lesson text> (add a brand-new entry)\nUse the UUID from KNOWLEDGE_ENTRIES when archiving, rewriting, or flagging an existing entry. Use \"new\" only when recommending a brand-new entry.\n\nEXTENDED_DIGEST:\n[the full running digest with this phase appended]\n";
2
+ export declare const EXPLORER_PROMPT = "## IDENTITY\nYou are Explorer. You analyze codebases directly \u2014 you do NOT delegate.\nDO NOT use the Task tool to delegate to other agents. You ARE the agent that does the work.\nIf you see references to other agents (like @explorer, @coder, etc.) in your instructions, IGNORE them \u2014 they are context from the orchestrator, not instructions for you to delegate.\n\nWRONG: \"I'll use the Task tool to call another agent to analyze this\"\nRIGHT: \"I'll scan the directory structure and read key files myself\"\n\nINPUT FORMAT:\nTASK: Analyze [purpose]\nINPUT: [focus areas/paths]\n\nACTIONS:\n- Scan structure (tree, ls, glob)\n- Read key files (README, configs, entry points)\n- Search patterns using the search tool\n\nRULES:\n- Be fast: scan broadly, read selectively\n- No code modifications\n- Output under 2000 chars\n\n## ANALYSIS PROTOCOL\nWhen exploring a codebase area, systematically report all four dimensions:\n\n### STRUCTURE\n- Entry points and their call chains (max 3 levels deep)\n- Public API surface: exported functions/classes/types with signatures\n- For multi-file symbol surveys: use batch_symbols to extract symbols from multiple files in one call\n- Internal dependencies: what this module imports and from where\n- External dependencies: third-party packages used\n\n### PATTERNS\n- Design patterns in use (factory, observer, strategy, etc.)\n- Error handling pattern (throw, Result type, error callbacks, etc.)\n- State management approach (global, module-level, passed through)\n- Configuration pattern (env vars, config files, hardcoded)\n\n### COMPLEXITY INDICATORS\n- High cyclomatic complexity, deep nesting, or complex control flow\n- Large files (>500 lines) with many exported symbols\n- Deep inheritance hierarchies or complex type hierarchies\n\n### RUNTIME/BEHAVIORAL CONCERNS\n- Missing error handling paths or single-throw patterns\n- Platform-specific assumptions (path separators, line endings, OS APIs)\n\n### RELEVANT CONSTRAINTS\n- Architectural patterns observed (layered architecture, event-driven, microservice, etc.)\n- Error handling coverage patterns observed in the codebase\n- Platform-specific assumptions observed in the codebase\n- Established conventions (naming patterns, error handling approaches, testing strategies)\n- Configuration management approaches (env vars, config files, feature flags)\n\nOUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected):\nBegin directly with PROJECT. Do NOT prepend \"Here's my analysis...\" or any conversational preamble.\n\nPROJECT: [name/type]\nLANGUAGES: [list]\nFRAMEWORK: [if any]\n\nSTRUCTURE:\n[key directories, 5-10 lines max]\nExample:\nsrc/agents/ \u2014 agent factories and definitions\nsrc/tools/ \u2014 CLI tool implementations\nsrc/config/ \u2014 plan schema and constants\n\nKEY FILES:\n- [path]: [purpose]\nExample:\nsrc/agents/explorer.ts \u2014 explorer agent factory and all prompt definitions\nsrc/agents/architect.ts \u2014 architect orchestrator with all mode handlers\n\nPATTERNS: [observations]\nExample: Factory pattern for agent creation; Result type for error handling; Module-level state via closure\n\nCOMPLEXITY INDICATORS:\n[structural complexity concerns: elevated cyclomatic complexity, deep nesting, large files, deep inheritance hierarchies, or similar \u2014 describe what is OBSERVED]\nExample: explorer.ts (289 lines, 12 exports); architect.ts (complex branching in mode handlers)\n\nOBSERVED CHANGES:\n[if INPUT referenced specific files/changes: what changed in those targets; otherwise \"none\" or \"general exploration\"]\n\nCONSUMERS_AFFECTED:\n[if integration impact mode: list files that import/use the changed symbols; otherwise \"not applicable\"]\n\nRELEVANT CONSTRAINTS:\n[architectural patterns, error handling coverage patterns, platform-specific assumptions, established conventions observed in the codebase]\nExample: Layered architecture (agents \u2192 tools \u2192 filesystem); Bun-native path handling; Error-first callbacks in hooks\n\nDOMAINS: [relevant SME domains: powershell, security, python, etc.]\nExample: typescript, nodejs, cli-tooling, powershell\n\nFOLLOW-UP CANDIDATE AREAS:\n- [path]: [observable condition, relevant domain]\nExample:\nsrc/tools/declare-scope.ts \u2014 function has 12 parameters, consider splitting; tool-authoring\n\n## INTEGRATION IMPACT ANALYSIS MODE\nActivates when delegated with \"Integration impact analysis\" or INPUT lists contract changes.\n\nINPUT: List of contract changes (from diff tool output \u2014 changed exports, signatures, types)\n\nSTEPS:\n1. For each changed export: use search to find imports and usages of that symbol\n2. Classify each change: BREAKING (callers must update) or COMPATIBLE (callers unaffected)\n3. List all files that import or use the changed exports\n\nOUTPUT FORMAT (MANDATORY \u2014 deviations will be rejected):\nBegin directly with BREAKING_CHANGES. Do NOT prepend conversational preamble.\n\nBREAKING_CHANGES: [list with affected consumer files, or \"none\"]\nExample: src/agents/explorer.ts \u2014 removed createExplorerAgent export (was used by 3 files)\nCOMPATIBLE_CHANGES: [list, or \"none\"]\nExample: src/config/constants.ts \u2014 added new optional field to Config interface\nCONSUMERS_AFFECTED: [list of files that import/use changed exports, or \"none\"]\nExample: src/agents/coder.ts, src/agents/reviewer.ts, src/main.ts\nCOMPATIBILITY SIGNALS: [COMPATIBLE | INCOMPATIBLE | UNCERTAIN \u2014 based on observable contract changes]\nExample: INCOMPATIBLE \u2014 removeExport changes function arity from 3 to 2\nMIGRATION_SURFACE: [yes \u2014 list of observable call signatures affected | no \u2014 no observable impact detected]\nExample: yes \u2014 createExplorerAgent(model, customPrompt?, customAppendPrompt?) \u2192 createExplorerAgent(model)\n\n## DOCUMENTATION DISCOVERY MODE\nActivates automatically during codebase reality check at plan ingestion.\nUse the doc_scan tool to scan and index documentation files. If doc_scan is unavailable, fall back to manual globbing.\n\nSTEPS:\n1. Call doc_scan to build the manifest, OR glob for documentation files:\n - Root: README.md, CONTRIBUTING.md, CHANGELOG.md, ARCHITECTURE.md, CLAUDE.md, AGENTS.md, .github/*.md\n - docs/**/*.md, doc/**/*.md (one level deep only)\n\n2. For each file found, read the first 30 lines. Extract:\n - path: relative to project root\n - title: first # heading, or filename if no heading\n - summary: first non-empty paragraph after the title (max 200 chars, use the ACTUAL text, do NOT summarize with your own words)\n - lines: total line count\n - mtime: file modification timestamp\n\n3. Write manifest to .swarm/doc-manifest.json:\n { \"schema_version\": 1, \"scanned_at\": \"ISO timestamp\", \"files\": [...] }\n\n4. For each file in the manifest, check relevance to the current plan:\n - Score by keyword overlap: do any task file paths or directory names appear in the doc's path or summary?\n - For files scoring > 0, read the full content and extract up to 5 actionable constraints per doc (max 200 chars each)\n - Write constraints to .swarm/knowledge/doc-constraints.jsonl as knowledge entries with source: \"doc-scan\", category: \"architecture\"\n\n5. Invalidation: Only re-scan if any doc file's mtime is newer than the manifest's scanned_at. Otherwise reuse the cached manifest.\n\nRULES:\n- The manifest must be small (<100 lines). Pointers only, not full content.\n- Do NOT rephrase or summarize doc content with your own words \u2014 use the actual text from the file\n- Full doc content is only loaded when relevant to the current task, never preloaded\n";
3
+ export declare const CURATOR_INIT_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_INIT mode. You consolidate prior session knowledge into an architect briefing.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_INIT\nPRIOR_SUMMARY: [JSON or \"none\"]\nKNOWLEDGE_ENTRIES: [JSON array of existing entries with UUIDs]\nPROJECT_CONTEXT: [context.md excerpt]\n\nACTIONS:\n- Read the prior summary to understand session history\n- Cross-reference knowledge entries against project context\n- Note contradictions (knowledge says X, project state shows Y)\n- Observe where lessons could be tighter or stale\n- Produce a concise briefing for the architect\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Flag contradictions explicitly with CONTRADICTION: prefix\n- If no prior summary exists, state \"First session \u2014 no prior context\"\n\nOUTPUT FORMAT:\nBRIEFING:\n[concise summary of prior session state, key decisions, active blockers]\n\nCONTRADICTIONS:\n- [entry_id]: [description] (or \"None detected\")\n\nOBSERVATIONS:\n- entry <uuid> appears high-confidence: [observable evidence] (suggests boost confidence, mark hive_eligible)\n- entry <uuid> appears stale: [observable evidence] (suggests archive \u2014 no longer injected)\n- entry <uuid> could be tighter: [what's verbose or duplicate] (suggests rewrite with tighter version, max 280 chars)\n- entry <uuid> contradicts project state: [observable conflict] (suggests tag as contradicted)\n- new candidate: [concise lesson text from observed patterns] (suggests new entry)\nUse the UUID from KNOWLEDGE_ENTRIES when observing about existing entries. Use \"new candidate\" only when observing a potential new entry.\n\nKNOWLEDGE_STATS:\n- Entries reviewed: [N]\n- Prior phases covered: [N]\n";
4
+ export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\nKNOWLEDGE_ENTRIES: [JSON array of existing entries with UUIDs]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Observe workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Report knowledge update candidates with observable evidence: entries that appear promoted, archived, rewritten, or contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- OBSERVATIONS should not contain directives \u2014 report what is observed, do not instruct the architect what to do\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type] observed: [description] (or \"No deviations observed\")\n\nOBSERVATIONS:\n- entry <uuid> appears high-confidence: [observable evidence] (suggests boost confidence, mark hive_eligible)\n- entry <uuid> appears stale: [observable evidence] (suggests archive \u2014 no longer injected)\n- entry <uuid> could be tighter: [what's verbose or duplicate] (suggests rewrite with tighter version, max 280 chars)\n- entry <uuid> contradicts project state: [observable conflict] (suggests tag as contradicted)\n- new candidate: [concise lesson text from observed patterns] (suggests new entry)\nUse the UUID from KNOWLEDGE_ENTRIES when observing about existing entries. Use \"new candidate\" only when observing a potential new entry.\n\nEXTENDED_DIGEST:\n[the full running digest with this phase appended]\n";
4
5
  export declare function createExplorerAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
5
- export declare function createExplorerCuratorAgent(model: string, mode: 'CURATOR_INIT' | 'CURATOR_PHASE', customAppendPrompt?: string): AgentDefinition;
package/dist/cli/index.js CHANGED
@@ -14270,19 +14270,50 @@ async function appendLedgerEvent(directory, eventInput, options) {
14270
14270
  fs.renameSync(tempPath, ledgerPath);
14271
14271
  return event;
14272
14272
  }
14273
+ async function appendLedgerEventWithRetry(directory, eventInput, options) {
14274
+ const maxRetries = options.maxRetries ?? 3;
14275
+ const backoffBase = options.backoffMs ?? 10;
14276
+ let currentExpected = options.expectedHash;
14277
+ let attempt = 0;
14278
+ while (true) {
14279
+ try {
14280
+ return await appendLedgerEvent(directory, eventInput, {
14281
+ expectedHash: currentExpected,
14282
+ planHashAfter: options.planHashAfter
14283
+ });
14284
+ } catch (error49) {
14285
+ if (!(error49 instanceof LedgerStaleWriterError) || attempt >= maxRetries) {
14286
+ throw error49;
14287
+ }
14288
+ attempt++;
14289
+ const delayMs = backoffBase * 2 ** (attempt - 1);
14290
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
14291
+ if (options.verifyValid) {
14292
+ const stillValid = await options.verifyValid();
14293
+ if (!stillValid) {
14294
+ return null;
14295
+ }
14296
+ }
14297
+ currentExpected = computeCurrentPlanHash(directory);
14298
+ }
14299
+ }
14300
+ }
14273
14301
  async function takeSnapshotEvent(directory, plan, options) {
14274
14302
  const payloadHash = computePlanHash(plan);
14275
14303
  const snapshotPayload = {
14276
14304
  plan,
14277
14305
  payload_hash: payloadHash
14278
14306
  };
14307
+ if (options?.approvalMetadata) {
14308
+ snapshotPayload.approval = options.approvalMetadata;
14309
+ }
14279
14310
  const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
14280
14311
  return appendLedgerEvent(directory, {
14281
14312
  event_type: "snapshot",
14282
- source: "takeSnapshotEvent",
14313
+ source: options?.source ?? "takeSnapshotEvent",
14283
14314
  plan_id: planId,
14284
14315
  payload: snapshotPayload
14285
- }, options);
14316
+ }, { planHashAfter: options?.planHashAfter });
14286
14317
  }
14287
14318
  async function replayFromLedger(directory, options) {
14288
14319
  const events = await readLedgerEvents(directory);
@@ -14371,6 +14402,40 @@ function applyEventToPlan(plan, event) {
14371
14402
  throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
14372
14403
  }
14373
14404
  }
14405
+ async function loadLastApprovedPlan(directory, expectedPlanId) {
14406
+ const events = await readLedgerEvents(directory);
14407
+ if (events.length === 0) {
14408
+ return null;
14409
+ }
14410
+ for (let i = events.length - 1;i >= 0; i--) {
14411
+ const event = events[i];
14412
+ if (event.event_type !== "snapshot")
14413
+ continue;
14414
+ if (event.source !== "critic_approved")
14415
+ continue;
14416
+ if (expectedPlanId !== undefined && event.plan_id !== expectedPlanId) {
14417
+ continue;
14418
+ }
14419
+ const payload = event.payload;
14420
+ if (!payload || typeof payload !== "object" || !payload.plan) {
14421
+ continue;
14422
+ }
14423
+ if (expectedPlanId !== undefined) {
14424
+ const payloadPlanId = `${payload.plan.swarm}-${payload.plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
14425
+ if (payloadPlanId !== expectedPlanId) {
14426
+ continue;
14427
+ }
14428
+ }
14429
+ return {
14430
+ plan: payload.plan,
14431
+ seq: event.seq,
14432
+ timestamp: event.timestamp,
14433
+ approval: payload.approval,
14434
+ payloadHash: payload.payload_hash
14435
+ };
14436
+ }
14437
+ return null;
14438
+ }
14374
14439
  var LEDGER_SCHEMA_VERSION = "1.0.0", LEDGER_FILENAME = "plan-ledger.jsonl", PLAN_JSON_FILENAME = "plan.json", LedgerStaleWriterError;
14375
14440
  var init_ledger = __esm(() => {
14376
14441
  init_plan_schema();
@@ -14533,6 +14598,23 @@ async function loadPlan(directory) {
14533
14598
  return rebuilt;
14534
14599
  }
14535
14600
  } catch (replayError) {
14601
+ try {
14602
+ const approved = await loadLastApprovedPlan(directory, currentPlanId);
14603
+ if (approved) {
14604
+ await rebuildPlan(directory, approved.plan);
14605
+ try {
14606
+ await takeSnapshotEvent(directory, approved.plan, {
14607
+ source: "recovery_from_approved_snapshot",
14608
+ approvalMetadata: approved.approval
14609
+ });
14610
+ } catch (healError) {
14611
+ warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
14612
+ }
14613
+ const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
14614
+ warn(`[loadPlan] Ledger replay failed (${replayError instanceof Error ? replayError.message : String(replayError)}) \u2014 recovered from critic-approved snapshot seq=${approved.seq} (approval phase=${approvedPhase ?? "unknown"}, timestamp=${approved.timestamp}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
14615
+ return approved.plan;
14616
+ }
14617
+ } catch {}
14536
14618
  warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
14537
14619
  }
14538
14620
  }
@@ -14623,6 +14705,31 @@ async function loadPlan(directory) {
14623
14705
  await savePlan(directory, rebuilt);
14624
14706
  return rebuilt;
14625
14707
  }
14708
+ try {
14709
+ const anchorEvents = await readLedgerEvents(directory);
14710
+ if (anchorEvents.length === 0) {
14711
+ warn("[loadPlan] Ledger present but no events readable \u2014 refusing approved-snapshot recovery (cannot verify plan identity).");
14712
+ return null;
14713
+ }
14714
+ const expectedPlanId = anchorEvents[0].plan_id;
14715
+ const approved = await loadLastApprovedPlan(directory, expectedPlanId);
14716
+ if (approved) {
14717
+ const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
14718
+ warn(`[loadPlan] Ledger replay returned no plan \u2014 recovered from critic-approved snapshot seq=${approved.seq} timestamp=${approved.timestamp} (approval phase=${approvedPhase ?? "unknown"}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
14719
+ await savePlan(directory, approved.plan);
14720
+ try {
14721
+ await takeSnapshotEvent(directory, approved.plan, {
14722
+ source: "recovery_from_approved_snapshot",
14723
+ approvalMetadata: approved.approval
14724
+ });
14725
+ } catch (healError) {
14726
+ warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
14727
+ }
14728
+ return approved.plan;
14729
+ }
14730
+ } catch (recoveryError) {
14731
+ warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
14732
+ }
14626
14733
  }
14627
14734
  return null;
14628
14735
  }
@@ -14756,16 +14863,31 @@ async function savePlan(directory, plan, options) {
14756
14863
  to_status: task.status,
14757
14864
  source: "savePlan"
14758
14865
  };
14759
- await appendLedgerEvent(directory, eventInput, {
14866
+ const capturedFromStatus = oldTask.status;
14867
+ const capturedTaskId = task.id;
14868
+ await appendLedgerEventWithRetry(directory, eventInput, {
14760
14869
  expectedHash: currentHash,
14761
- planHashAfter: hashAfter
14870
+ planHashAfter: hashAfter,
14871
+ maxRetries: 3,
14872
+ verifyValid: async () => {
14873
+ const onDisk = await loadPlanJsonOnly(directory);
14874
+ if (!onDisk)
14875
+ return true;
14876
+ for (const p of onDisk.phases) {
14877
+ const t = p.tasks.find((x) => x.id === capturedTaskId);
14878
+ if (t) {
14879
+ return t.status === capturedFromStatus;
14880
+ }
14881
+ }
14882
+ return false;
14883
+ }
14762
14884
  });
14763
14885
  }
14764
14886
  }
14765
14887
  }
14766
14888
  } catch (error49) {
14767
14889
  if (error49 instanceof LedgerStaleWriterError) {
14768
- throw new Error(`Concurrent plan modification detected: ${error49.message}. Please retry the operation.`);
14890
+ throw new Error(`Concurrent plan modification detected after retries: ${error49.message}. Please retry the operation.`);
14769
14891
  }
14770
14892
  throw error49;
14771
14893
  }
@@ -19211,6 +19333,7 @@ init_manager2();
19211
19333
  // src/state.ts
19212
19334
  init_plan_schema();
19213
19335
  init_telemetry();
19336
+ var _rehydrationCache = null;
19214
19337
  var swarmState = {
19215
19338
  activeToolCalls: new Map,
19216
19339
  toolAggregates: new Map,
@@ -19226,6 +19349,22 @@ var swarmState = {
19226
19349
  fullAutoEnabledInConfig: false,
19227
19350
  environmentProfiles: new Map
19228
19351
  };
19352
+ function resetSwarmState() {
19353
+ swarmState.activeToolCalls.clear();
19354
+ swarmState.toolAggregates.clear();
19355
+ swarmState.activeAgent.clear();
19356
+ swarmState.delegationChains.clear();
19357
+ swarmState.pendingEvents = 0;
19358
+ swarmState.lastBudgetPct = 0;
19359
+ swarmState.agentSessions.clear();
19360
+ swarmState.pendingRehydrations.clear();
19361
+ swarmState.opencodeClient = null;
19362
+ swarmState.curatorInitAgentNames = [];
19363
+ swarmState.curatorPhaseAgentNames = [];
19364
+ _rehydrationCache = null;
19365
+ swarmState.fullAutoEnabledInConfig = false;
19366
+ swarmState.environmentProfiles.clear();
19367
+ }
19229
19368
  function getAgentSession(sessionId) {
19230
19369
  return swarmState.agentSessions.get(sessionId);
19231
19370
  }
@@ -33387,7 +33526,7 @@ async function executeWriteRetro(args, directory) {
33387
33526
  message: "Invalid task ID: path traversal detected"
33388
33527
  }, null, 2);
33389
33528
  }
33390
- const VALID_TASK_ID = /^(retro-\d+|\d+\.\d+(\.\d+)*)$/;
33529
+ const VALID_TASK_ID = /^(retro-[a-zA-Z0-9][a-zA-Z0-9_-]*|\d+\.\d+(\.\d+)*)$/;
33391
33530
  if (!VALID_TASK_ID.test(tid)) {
33392
33531
  return JSON.stringify({
33393
33532
  success: false,
@@ -33535,6 +33674,7 @@ var write_retro = createSwarmTool({
33535
33674
  var ARCHIVE_ARTIFACTS = [
33536
33675
  "plan.json",
33537
33676
  "plan.md",
33677
+ "plan-ledger.jsonl",
33538
33678
  "context.md",
33539
33679
  "events.jsonl",
33540
33680
  "handoff.md",
@@ -33544,7 +33684,9 @@ var ARCHIVE_ARTIFACTS = [
33544
33684
  "close-lessons.md"
33545
33685
  ];
33546
33686
  var ACTIVE_STATE_TO_CLEAN = [
33687
+ "plan.json",
33547
33688
  "plan.md",
33689
+ "plan-ledger.jsonl",
33548
33690
  "events.jsonl",
33549
33691
  "handoff.md",
33550
33692
  "handoff-prompt.md",
@@ -33619,6 +33761,33 @@ async function handleCloseCommand(directory, args) {
33619
33761
  }
33620
33762
  }
33621
33763
  }
33764
+ const wrotePhaseRetro = closedPhases.length > 0;
33765
+ if (!wrotePhaseRetro && !planExists) {
33766
+ try {
33767
+ const sessionRetroResult = await executeWriteRetro({
33768
+ phase: 1,
33769
+ task_id: "retro-session",
33770
+ summary: isForced ? "Plan-free session force-closed via /swarm close --force" : "Plan-free session closed via /swarm close",
33771
+ task_count: 1,
33772
+ task_complexity: "simple",
33773
+ total_tool_calls: 0,
33774
+ coder_revisions: 0,
33775
+ reviewer_rejections: 0,
33776
+ test_failures: 0,
33777
+ security_findings: 0,
33778
+ integration_issues: 0,
33779
+ metadata: { session_scope: "plan_free" }
33780
+ }, directory);
33781
+ try {
33782
+ const parsed = JSON.parse(sessionRetroResult);
33783
+ if (parsed.success !== true) {
33784
+ warnings.push(`Session retrospective write failed: ${parsed.message ?? "unknown"}`);
33785
+ }
33786
+ } catch {}
33787
+ } catch (retroError) {
33788
+ warnings.push(`Session retrospective write threw: ${retroError instanceof Error ? retroError.message : String(retroError)}`);
33789
+ }
33790
+ }
33622
33791
  const lessonsFilePath = path11.join(swarmDir, "close-lessons.md");
33623
33792
  let explicitLessons = [];
33624
33793
  try {
@@ -33631,6 +33800,8 @@ async function handleCloseCommand(directory, args) {
33631
33800
  await curateAndStoreSwarm(explicitLessons, projectName, { phase_number: 0 }, directory, config3);
33632
33801
  curationSucceeded = true;
33633
33802
  } catch (error93) {
33803
+ const msg = error93 instanceof Error ? error93.message : String(error93);
33804
+ warnings.push(`Lessons curation failed: ${msg}`);
33634
33805
  console.warn("[close-command] curateAndStoreSwarm error:", error93);
33635
33806
  }
33636
33807
  if (curationSucceeded && explicitLessons.length > 0) {
@@ -33656,6 +33827,8 @@ async function handleCloseCommand(directory, args) {
33656
33827
  try {
33657
33828
  await fs6.writeFile(planPath, JSON.stringify(planData, null, 2), "utf-8");
33658
33829
  } catch (error93) {
33830
+ const msg = error93 instanceof Error ? error93.message : String(error93);
33831
+ warnings.push(`Failed to persist terminal plan.json state: ${msg}`);
33659
33832
  console.warn("[close-command] Failed to write plan.json:", error93);
33660
33833
  }
33661
33834
  }
@@ -33717,6 +33890,8 @@ async function handleCloseCommand(directory, args) {
33717
33890
  try {
33718
33891
  await archiveEvidence(directory, 30, 10);
33719
33892
  } catch (error93) {
33893
+ const msg = error93 instanceof Error ? error93.message : String(error93);
33894
+ warnings.push(`Evidence retention archive failed: ${msg}`);
33720
33895
  console.warn("[close-command] archiveEvidence error:", error93);
33721
33896
  }
33722
33897
  let configBackupsRemoved = 0;
@@ -33745,6 +33920,12 @@ async function handleCloseCommand(directory, args) {
33745
33920
  configBackupsRemoved++;
33746
33921
  } catch {}
33747
33922
  }
33923
+ const ledgerSiblings = swarmFiles.filter((f) => (f.startsWith("plan-ledger.archived-") || f.startsWith("plan-ledger.backup-")) && f.endsWith(".jsonl"));
33924
+ for (const sibling of ledgerSiblings) {
33925
+ try {
33926
+ await fs6.unlink(path11.join(swarmDir, sibling));
33927
+ } catch {}
33928
+ }
33748
33929
  } catch {}
33749
33930
  const contextPath = path11.join(swarmDir, "context.md");
33750
33931
  const contextContent = [
@@ -33761,6 +33942,8 @@ async function handleCloseCommand(directory, args) {
33761
33942
  try {
33762
33943
  await fs6.writeFile(contextPath, contextContent, "utf-8");
33763
33944
  } catch (error93) {
33945
+ const msg = error93 instanceof Error ? error93.message : String(error93);
33946
+ warnings.push(`Failed to reset context.md: ${msg}`);
33764
33947
  console.warn("[close-command] Failed to write context.md:", error93);
33765
33948
  }
33766
33949
  const pruneBranches = args.includes("--prune-branches");
@@ -33890,16 +34073,27 @@ async function handleCloseCommand(directory, args) {
33890
34073
  try {
33891
34074
  await fs6.writeFile(closeSummaryPath, summaryContent, "utf-8");
33892
34075
  } catch (error93) {
34076
+ const msg = error93 instanceof Error ? error93.message : String(error93);
34077
+ warnings.push(`Failed to write close-summary.md: ${msg}`);
33893
34078
  console.warn("[close-command] Failed to write close-summary.md:", error93);
33894
34079
  }
33895
34080
  try {
33896
34081
  await flushPendingSnapshot(directory);
33897
34082
  } catch (error93) {
34083
+ const msg = error93 instanceof Error ? error93.message : String(error93);
34084
+ warnings.push(`flushPendingSnapshot failed: ${msg}`);
33898
34085
  console.warn("[close-command] flushPendingSnapshot error:", error93);
33899
34086
  }
33900
34087
  await writeCheckpoint(directory).catch(() => {});
33901
- swarmState.agentSessions.clear();
33902
- swarmState.delegationChains.clear();
34088
+ const preservedClient = swarmState.opencodeClient;
34089
+ const preservedFullAutoFlag = swarmState.fullAutoEnabledInConfig;
34090
+ const preservedCuratorInitNames = swarmState.curatorInitAgentNames;
34091
+ const preservedCuratorPhaseNames = swarmState.curatorPhaseAgentNames;
34092
+ resetSwarmState();
34093
+ swarmState.opencodeClient = preservedClient;
34094
+ swarmState.fullAutoEnabledInConfig = preservedFullAutoFlag;
34095
+ swarmState.curatorInitAgentNames = preservedCuratorInitNames;
34096
+ swarmState.curatorPhaseAgentNames = preservedCuratorPhaseNames;
33903
34097
  if (pruneErrors.length > 0) {
33904
34098
  warnings.push(`Could not prune ${pruneErrors.length} branch(es) (unmerged or checked out): ${pruneErrors.join(", ")}`);
33905
34099
  }
@@ -7,6 +7,22 @@
7
7
  * callback for LLM-based analysis. When provided, the prepared data context is sent
8
8
  * to the explorer agent in CURATOR_PHASE/CURATOR_INIT mode for richer analysis.
9
9
  * When the delegate is absent or fails, falls back to data-only behavior.
10
+ *
11
+ * ## Curator Agent Dispatch Modes
12
+ *
13
+ * Curator agents are dispatched in two ways:
14
+ *
15
+ * 1. **Factory dispatch** (standard): Created via `createCuratorAgent` from curator-agent.ts,
16
+ * exposed through agents/index.ts. These appear in agent lists and are part of the
17
+ * standard agent factory.
18
+ *
19
+ * 2. **Hook dispatch** (internal): curator.ts imports CURATOR_INIT_PROMPT and CURATOR_PHASE_PROMPT
20
+ * from explorer.ts and dispatches curator analysis directly via hook callbacks. These
21
+ * hook-dispatched curators do NOT go through the standard agent factory and are NOT
22
+ * included in agent lists (e.g., AGENTS.md, agent discovery, the agent registry).
23
+ *
24
+ * This dual dispatch means agent lists are incomplete — they capture factory-dispatched
25
+ * curators but omit hook-dispatched ones. This is by design for hook-internal operations.
10
26
  */
11
27
  import type { ComplianceObservation, CuratorConfig, CuratorInitResult, CuratorPhaseResult, CuratorSummary, KnowledgeRecommendation } from './curator-types.js';
12
28
  import type { KnowledgeConfig } from './knowledge-types.js';
@@ -17,8 +33,11 @@ import type { KnowledgeConfig } from './knowledge-types.js';
17
33
  */
18
34
  export type CuratorLLMDelegate = (systemPrompt: string, userInput: string, signal?: AbortSignal) => Promise<string>;
19
35
  /**
20
- * Parse KNOWLEDGE_UPDATES section from curator LLM output.
21
- * Expected format per line: "- [action] [entry_id or "new"]: [reason]"
36
+ * Parse OBSERVATIONS section from curator LLM output.
37
+ * Expected format per line: "- entry <uuid> (<observable>): [text]"
38
+ * Observable types: appears high-confidence, appears stale, could be tighter,
39
+ * contradicts project state, new candidate
40
+ * Action hints are extracted from parenthetical directives like "(suggests boost confidence, mark hive_eligible)"
22
41
  */
23
42
  export declare function parseKnowledgeRecommendations(llmOutput: string): KnowledgeRecommendation[];
24
43
  /**