opencode-swarm 6.13.1 → 6.13.3

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/index.js CHANGED
@@ -14001,7 +14001,23 @@ var init_evidence_schema = __esm(() => {
14001
14001
  task_count: exports_external.number().int().min(1),
14002
14002
  task_complexity: exports_external.enum(["trivial", "simple", "moderate", "complex"]),
14003
14003
  top_rejection_reasons: exports_external.array(exports_external.string()).default([]),
14004
- lessons_learned: exports_external.array(exports_external.string()).max(5).default([])
14004
+ lessons_learned: exports_external.array(exports_external.string()).max(5).default([]),
14005
+ user_directives: exports_external.array(exports_external.object({
14006
+ directive: exports_external.string().min(1),
14007
+ category: exports_external.enum([
14008
+ "tooling",
14009
+ "code_style",
14010
+ "architecture",
14011
+ "process",
14012
+ "other"
14013
+ ]),
14014
+ scope: exports_external.enum(["session", "project", "global"])
14015
+ })).default([]),
14016
+ approaches_tried: exports_external.array(exports_external.object({
14017
+ approach: exports_external.string().min(1),
14018
+ result: exports_external.enum(["success", "failure", "partial"]),
14019
+ abandoned_reason: exports_external.string().optional()
14020
+ })).max(10).default([])
14005
14021
  });
14006
14022
  SyntaxEvidenceSchema = BaseEvidenceSchema.extend({
14007
14023
  type: exports_external.literal("syntax"),
@@ -31423,7 +31439,7 @@ var init_preflight_integration = __esm(() => {
31423
31439
  });
31424
31440
 
31425
31441
  // src/index.ts
31426
- import * as path32 from "path";
31442
+ import * as path31 from "path";
31427
31443
 
31428
31444
  // src/tools/tool-names.ts
31429
31445
  var TOOL_NAMES = [
@@ -31449,7 +31465,8 @@ var TOOL_NAMES = [
31449
31465
  "detect_domains",
31450
31466
  "gitingest",
31451
31467
  "retrieve_summary",
31452
- "extract_code_blocks"
31468
+ "extract_code_blocks",
31469
+ "phase_complete"
31453
31470
  ];
31454
31471
  var TOOL_NAME_SET = new Set(TOOL_NAMES);
31455
31472
 
@@ -31785,12 +31802,19 @@ var GateConfigSchema = exports_external.object({
31785
31802
  var PipelineConfigSchema = exports_external.object({
31786
31803
  parallel_precheck: exports_external.boolean().default(true)
31787
31804
  });
31805
+ var PhaseCompleteConfigSchema = exports_external.object({
31806
+ enabled: exports_external.boolean().default(true),
31807
+ required_agents: exports_external.array(exports_external.enum(["coder", "reviewer", "test_engineer"])).default(["coder", "reviewer", "test_engineer"]),
31808
+ require_docs: exports_external.boolean().default(true),
31809
+ policy: exports_external.enum(["enforce", "warn"]).default("enforce")
31810
+ });
31788
31811
  var SummaryConfigSchema = exports_external.object({
31789
31812
  enabled: exports_external.boolean().default(true),
31790
31813
  threshold_bytes: exports_external.number().min(1024).max(1048576).default(20480),
31791
31814
  max_summary_chars: exports_external.number().min(100).max(5000).default(1000),
31792
31815
  max_stored_bytes: exports_external.number().min(10240).max(104857600).default(10485760),
31793
- retention_days: exports_external.number().min(1).max(365).default(7)
31816
+ retention_days: exports_external.number().min(1).max(365).default(7),
31817
+ exempt_tools: exports_external.array(exports_external.string()).default(["retrieve_summary", "task"])
31794
31818
  });
31795
31819
  var ReviewPassesConfigSchema = exports_external.object({
31796
31820
  always_security_review: exports_external.boolean().default(false),
@@ -31804,6 +31828,11 @@ var ReviewPassesConfigSchema = exports_external.object({
31804
31828
  "**/token/**"
31805
31829
  ])
31806
31830
  });
31831
+ var AdversarialDetectionConfigSchema = exports_external.object({
31832
+ enabled: exports_external.boolean().default(true),
31833
+ policy: exports_external.enum(["warn", "gate", "ignore"]).default("warn"),
31834
+ pairs: exports_external.array(exports_external.tuple([exports_external.string(), exports_external.string()])).default([["coder", "reviewer"]])
31835
+ });
31807
31836
  var IntegrationAnalysisConfigSchema = exports_external.object({
31808
31837
  enabled: exports_external.boolean().default(true)
31809
31838
  });
@@ -32035,6 +32064,7 @@ var PluginConfigSchema = exports_external.object({
32035
32064
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
32036
32065
  max_iterations: exports_external.number().min(1).max(10).default(5),
32037
32066
  pipeline: PipelineConfigSchema.optional(),
32067
+ phase_complete: PhaseCompleteConfigSchema.optional(),
32038
32068
  qa_retry_limit: exports_external.number().min(1).max(10).default(3),
32039
32069
  inject_phase_reminders: exports_external.boolean().default(true),
32040
32070
  hooks: HooksConfigSchema.optional(),
@@ -32046,6 +32076,7 @@ var PluginConfigSchema = exports_external.object({
32046
32076
  evidence: EvidenceConfigSchema.optional(),
32047
32077
  summaries: SummaryConfigSchema.optional(),
32048
32078
  review_passes: ReviewPassesConfigSchema.optional(),
32079
+ adversarial_detection: AdversarialDetectionConfigSchema.optional(),
32049
32080
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
32050
32081
  docs: DocsConfigSchema.optional(),
32051
32082
  ui_review: UIReviewConfigSchema.optional(),
@@ -32466,6 +32497,37 @@ Identify 1-3 relevant domains from the task requirements.
32466
32497
  Call {{AGENT_PREFIX}}sme once per domain, serially. Max 3 SME calls per project phase.
32467
32498
  Re-consult if a new domain emerges or if significant changes require fresh evaluation.
32468
32499
  Cache guidance in context.md.
32500
+ ### MODE: PRE-PHASE BRIEFING (Required Before Starting Any Phase)
32501
+
32502
+ Before creating or resuming any plan, you MUST read the previous phase's retrospective.
32503
+
32504
+ **Phase 2+ (continuing a multi-phase project):**
32505
+ 1. Check \`.swarm/evidence/retro-{N-1}/evidence.json\` for the previous phase's retrospective
32506
+ 2. If it exists: read and internalize \`lessons_learned\` and \`top_rejection_reasons\`
32507
+ 3. If it does NOT exist: note this as a process gap, but proceed
32508
+ 4. Print a briefing acknowledgment:
32509
+ \`\`\`
32510
+ \u2192 BRIEFING: Read Phase {N-1} retrospective.
32511
+ Key lessons: {list 1-3 most relevant lessons}
32512
+ Applying to Phase {N}: {one sentence on how you'll apply them}
32513
+ \`\`\`
32514
+
32515
+ **Phase 1 (starting any new project):**
32516
+ 1. Scan \`.swarm/evidence/\` for any \`retro-*\` bundles from prior projects
32517
+ 2. If found: review the 1-3 most recent retrospectives for relevant lessons
32518
+ 3. Pay special attention to \`user_directives\` \u2014 these carry across projects
32519
+ 4. Print a briefing acknowledgment:
32520
+ \`\`\`
32521
+ \u2192 BRIEFING: Reviewed {N} historical retrospectives from this workspace.
32522
+ Relevant lessons: {list applicable lessons}
32523
+ User directives carried forward: {list any persistent directives}
32524
+ \`\`\`
32525
+ OR if no historical retros exist:
32526
+ \`\`\`
32527
+ \u2192 BRIEFING: No historical retrospectives found. Starting fresh.
32528
+ \`\`\`
32529
+
32530
+ This briefing is a HARD REQUIREMENT for ALL phases. Skipping it is a process violation.
32469
32531
 
32470
32532
  ### MODE: PLAN
32471
32533
 
@@ -32482,6 +32544,15 @@ TASK GRANULARITY RULES:
32482
32544
  - NEVER write a task with compound verbs: "implement X and add Y and update Z" = 3 tasks, not 1. Split before writing to plan.
32483
32545
  - Coder receives ONE task. You make ALL scope decisions in the plan. Coder makes zero scope decisions.
32484
32546
 
32547
+ PHASE COUNT GUIDANCE:
32548
+ - Plans with 5+ tasks SHOULD be split into at least 2 phases.
32549
+ - Plans with 10+ tasks MUST be split into at least 3 phases.
32550
+ - Each phase should be a coherent unit of work that can be reviewed and learned from
32551
+ before proceeding to the next.
32552
+ - Single-phase plans are acceptable ONLY for small projects (1-4 tasks).
32553
+ - Rationale: Retrospectives at phase boundaries capture lessons that improve subsequent
32554
+ phases. A single-phase plan gets zero iterative learning benefit.
32555
+
32485
32556
  Create .swarm/context.md
32486
32557
  - Decisions, patterns, SME cache, file map
32487
32558
 
@@ -32619,6 +32690,54 @@ PRE-COMMIT RULE \u2014 Before ANY commit or push:
32619
32690
 
32620
32691
  5o. Update plan.md [x], proceed to next task.
32621
32692
 
32693
+ ## \u26D4 RETROSPECTIVE GATE
32694
+
32695
+ **MANDATORY before calling phase_complete.** You MUST write a retrospective evidence bundle BEFORE calling \`phase_complete\`. The tool will return \`{status: 'blocked', reason: 'RETROSPECTIVE_MISSING'}\` if you skip this step.
32696
+
32697
+ **How to write the retrospective:**
32698
+
32699
+ Use the evidence manager tool to write a bundle at \`retro-{N}\` (where N is the phase number being completed):
32700
+
32701
+ \`\`\`json
32702
+ {
32703
+ "type": "retrospective",
32704
+ "phase_number": <N>,
32705
+ "verdict": "pass",
32706
+ "reviewer_rejections": <count>,
32707
+ "coder_revisions": <count>,
32708
+ "test_failures": <count>,
32709
+ "security_findings": <count>,
32710
+ "lessons_learned": ["lesson 1 (max 5)", "lesson 2"],
32711
+ "top_rejection_reasons": ["reason 1"],
32712
+ "user_directives": [],
32713
+ "approaches_tried": [],
32714
+ "task_complexity": "low|medium|high",
32715
+ "timestamp": "<ISO 8601>",
32716
+ "agent": "architect",
32717
+ "metadata": { "plan_id": "<current plan title from .swarm/plan.json>" }
32718
+ }
32719
+ \`\`\`
32720
+
32721
+ **Required field rules:**
32722
+ - \`verdict\` MUST be \`"pass"\` \u2014 a verdict of \`"fail"\` or missing verdict blocks phase_complete
32723
+ - \`phase_number\` MUST match the phase number you are completing
32724
+ - \`lessons_learned\` should be 3-5 concrete, actionable items from this phase
32725
+ - Write the bundle as task_id \`retro-{N}\` (e.g., \`retro-1\` for Phase 1, \`retro-2\` for Phase 2)
32726
+ - \`metadata.plan_id\` should be set to the current project's plan title (from \`.swarm/plan.json\` header). This enables cross-project filtering in the retrospective injection system.
32727
+
32728
+ ### Additional retrospective fields (capture when applicable):
32729
+ - \`user_directives\`: Any corrections or preferences the user expressed during this phase
32730
+ - \`directive\`: what the user said (non-empty string)
32731
+ - \`category\`: \`tooling\` | \`code_style\` | \`architecture\` | \`process\` | \`other\`
32732
+ - \`scope\`: \`session\` (one-time, do not carry forward) | \`project\` (persist to context.md) | \`global\` (user preference)
32733
+ - \`approaches_tried\`: Approaches attempted during this phase (max 10)
32734
+ - \`approach\`: what was tried (non-empty string)
32735
+ - \`result\`: \`success\` | \`failure\` | \`partial\`
32736
+ - \`abandoned_reason\`: why it was abandoned (required when result is \`failure\` or \`partial\`)
32737
+
32738
+ **\u26A0\uFE0F WARNING:** Calling \`phase_complete(N)\` without a valid \`retro-N\` bundle will be BLOCKED. The error response will be:
32739
+ \`{ "status": "blocked", "reason": "RETROSPECTIVE_MISSING" }\`
32740
+
32622
32741
  ### MODE: PHASE-WRAP
32623
32742
  1. {{AGENT_PREFIX}}explorer - Rescan
32624
32743
  2. {{AGENT_PREFIX}}docs - Update documentation for all changes in this phase. Provide:
@@ -34451,7 +34570,10 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
34451
34570
  lastGateFailure: null,
34452
34571
  partialGateWarningIssued: false,
34453
34572
  selfFixAttempted: false,
34454
- catastrophicPhaseWarnings: new Set
34573
+ catastrophicPhaseWarnings: new Set,
34574
+ lastPhaseCompleteTimestamp: 0,
34575
+ lastPhaseCompletePhase: 0,
34576
+ phaseAgentsDispatched: new Set
34455
34577
  };
34456
34578
  swarmState.agentSessions.set(sessionId, sessionState);
34457
34579
  swarmState.activeAgent.set(sessionId, agentName);
@@ -34502,6 +34624,15 @@ function ensureAgentSession(sessionId, agentName) {
34502
34624
  if (!session.catastrophicPhaseWarnings) {
34503
34625
  session.catastrophicPhaseWarnings = new Set;
34504
34626
  }
34627
+ if (session.lastPhaseCompleteTimestamp === undefined) {
34628
+ session.lastPhaseCompleteTimestamp = 0;
34629
+ }
34630
+ if (session.lastPhaseCompletePhase === undefined) {
34631
+ session.lastPhaseCompletePhase = 0;
34632
+ }
34633
+ if (!session.phaseAgentsDispatched) {
34634
+ session.phaseAgentsDispatched = new Set;
34635
+ }
34505
34636
  session.lastToolCallTime = now;
34506
34637
  return session;
34507
34638
  }
@@ -34570,6 +34701,17 @@ function pruneOldWindows(sessionId, maxAgeMs = 24 * 60 * 60 * 1000, maxWindows =
34570
34701
  const toKeep = sorted.slice(0, maxWindows);
34571
34702
  session.windows = Object.fromEntries(toKeep);
34572
34703
  }
34704
+ function recordPhaseAgentDispatch(sessionId, agentName) {
34705
+ const session = swarmState.agentSessions.get(sessionId);
34706
+ if (!session) {
34707
+ return;
34708
+ }
34709
+ if (!session.phaseAgentsDispatched) {
34710
+ session.phaseAgentsDispatched = new Set;
34711
+ }
34712
+ const normalizedName = stripKnownSwarmPrefix(agentName);
34713
+ session.phaseAgentsDispatched.add(normalizedName);
34714
+ }
34573
34715
 
34574
34716
  // src/commands/benchmark.ts
34575
34717
  init_utils();
@@ -36680,6 +36822,7 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
36680
36822
  const isArchitect = strippedAgent === ORCHESTRATOR_NAME;
36681
36823
  const session = ensureAgentSession(input.sessionID, agentName);
36682
36824
  session.delegationActive = !isArchitect;
36825
+ recordPhaseAgentDispatch(input.sessionID, agentName);
36683
36826
  if (!isArchitect && guardrailsEnabled) {
36684
36827
  beginInvocation(input.sessionID, agentName);
36685
36828
  }
@@ -37131,8 +37274,8 @@ function hashArgs(args2) {
37131
37274
  // src/hooks/messages-transform.ts
37132
37275
  function consolidateSystemMessages(messages) {
37133
37276
  if (messages.length > 0 && messages[0].role === "system" && messages[0].content !== undefined && typeof messages[0].content === "string" && messages[0].content.trim().length > 0) {
37134
- const systemMessageCount = messages.filter((m) => m.role === "system" && typeof m.content === "string" && m.content.trim().length > 0 && m.tool_call_id === undefined && m.name === undefined).length;
37135
- if (systemMessageCount === 1) {
37277
+ const totalSystemCount = messages.filter((m) => m.role === "system").length;
37278
+ if (totalSystemCount === 1) {
37136
37279
  return [...messages];
37137
37280
  }
37138
37281
  }
@@ -37140,34 +37283,31 @@ function consolidateSystemMessages(messages) {
37140
37283
  const systemContents = [];
37141
37284
  for (let i2 = 0;i2 < messages.length; i2++) {
37142
37285
  const message = messages[i2];
37143
- if (message.role !== "system") {
37286
+ if (message.role !== "system")
37144
37287
  continue;
37145
- }
37146
- if (message.tool_call_id !== undefined || message.name !== undefined) {
37147
- continue;
37148
- }
37149
- if (typeof message.content !== "string") {
37150
- continue;
37151
- }
37152
- const trimmedContent = message.content.trim();
37153
- if (trimmedContent.length === 0) {
37288
+ if (message.tool_call_id !== undefined || message.name !== undefined)
37154
37289
  continue;
37290
+ let textContent = null;
37291
+ if (typeof message.content === "string") {
37292
+ const trimmed = message.content.trim();
37293
+ if (trimmed.length > 0)
37294
+ textContent = trimmed;
37295
+ } else if (Array.isArray(message.content)) {
37296
+ const texts = message.content.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter((t) => t.length > 0);
37297
+ if (texts.length > 0)
37298
+ textContent = texts.join(`
37299
+ `);
37155
37300
  }
37156
37301
  systemMessageIndices.push(i2);
37157
- systemContents.push(trimmedContent);
37302
+ if (textContent) {
37303
+ systemContents.push(textContent);
37304
+ }
37158
37305
  }
37159
37306
  if (systemContents.length === 0) {
37160
- return messages.filter((m) => {
37161
- if (m.role !== "system") {
37162
- return true;
37163
- }
37164
- if (typeof m.content !== "string" || m.name !== undefined || m.tool_call_id !== undefined) {
37307
+ return messages.filter((m, idx) => {
37308
+ if (m.role !== "system")
37165
37309
  return true;
37166
- }
37167
- if (m.content.trim().length > 0) {
37168
- return true;
37169
- }
37170
- return false;
37310
+ return idx === 0;
37171
37311
  });
37172
37312
  }
37173
37313
  const mergedSystemContent = systemContents.join(`
@@ -37178,7 +37318,7 @@ function consolidateSystemMessages(messages) {
37178
37318
  result.push({
37179
37319
  role: "system",
37180
37320
  content: mergedSystemContent,
37181
- ...Object.fromEntries(Object.entries(firstSystemMessage).filter(([key]) => key !== "role" && key !== "content"))
37321
+ ...Object.fromEntries(Object.entries(firstSystemMessage).filter(([key]) => key !== "role" && key !== "content" && key !== "name" && key !== "tool_call_id"))
37182
37322
  });
37183
37323
  for (let i2 = 0;i2 < messages.length; i2++) {
37184
37324
  const message = messages[i2];
@@ -37190,7 +37330,11 @@ function consolidateSystemMessages(messages) {
37190
37330
  }
37191
37331
  result.push({ ...message });
37192
37332
  }
37193
- return result;
37333
+ return result.filter((msg, idx) => {
37334
+ if (idx === 0)
37335
+ return true;
37336
+ return msg.role !== "system";
37337
+ });
37194
37338
  }
37195
37339
  // src/hooks/phase-monitor.ts
37196
37340
  init_manager2();
@@ -37304,8 +37448,7 @@ ${originalText}`;
37304
37448
  };
37305
37449
  }
37306
37450
  // src/hooks/system-enhancer.ts
37307
- import * as fs11 from "fs";
37308
- import * as path17 from "path";
37451
+ init_manager();
37309
37452
  init_manager2();
37310
37453
 
37311
37454
  // src/services/decision-drift-analyzer.ts
@@ -37585,6 +37728,39 @@ init_preflight_service();
37585
37728
  // src/hooks/system-enhancer.ts
37586
37729
  init_utils();
37587
37730
 
37731
+ // src/hooks/adversarial-detector.ts
37732
+ function safeGet(obj, key) {
37733
+ if (!obj || !Object.hasOwn(obj, key))
37734
+ return;
37735
+ return obj[key];
37736
+ }
37737
+ function resolveAgentModel(agentName, config3) {
37738
+ const baseName = stripKnownSwarmPrefix(agentName).toLowerCase();
37739
+ const agentOverride = safeGet(config3.agents, baseName)?.model;
37740
+ if (agentOverride)
37741
+ return agentOverride;
37742
+ if (config3.swarms) {
37743
+ for (const swarm of Object.values(config3.swarms)) {
37744
+ const swarmModel = safeGet(swarm.agents, baseName)?.model;
37745
+ if (swarmModel)
37746
+ return swarmModel;
37747
+ }
37748
+ }
37749
+ const defaultModel = safeGet(DEFAULT_MODELS, baseName);
37750
+ return defaultModel ?? DEFAULT_MODELS.default;
37751
+ }
37752
+ function detectAdversarialPair(agentA, agentB, config3) {
37753
+ const modelA = resolveAgentModel(agentA, config3).toLowerCase();
37754
+ const modelB = resolveAgentModel(agentB, config3).toLowerCase();
37755
+ return modelA === modelB ? modelA : null;
37756
+ }
37757
+ function formatAdversarialWarning(agentA, agentB, sharedModel, policy) {
37758
+ if (policy === "gate") {
37759
+ return `\u26A0\uFE0F GATE POLICY: Same-model adversarial pair detected. Agent ${agentA} and checker ${agentB} both use model ${sharedModel}. This requires extra scrutiny \u2014 escalate if issues are found.`;
37760
+ }
37761
+ return `\u26A0\uFE0F Same-model adversarial pair detected. Agent ${agentA} and checker ${agentB} both use model ${sharedModel}. Review may lack independence.`;
37762
+ }
37763
+
37588
37764
  // src/hooks/context-scoring.ts
37589
37765
  function calculateAgeFactor(ageHours, config3) {
37590
37766
  if (ageHours <= 0) {
@@ -37649,6 +37825,170 @@ function estimateContentType(text) {
37649
37825
  }
37650
37826
  return "prose";
37651
37827
  }
37828
+ async function buildRetroInjection(directory, currentPhaseNumber, currentPlanTitle) {
37829
+ try {
37830
+ const prevPhase = currentPhaseNumber - 1;
37831
+ if (prevPhase >= 1) {
37832
+ const bundle = await loadEvidence(directory, `retro-${prevPhase}`);
37833
+ if (bundle && bundle.entries.length > 0) {
37834
+ const retroEntry = bundle.entries.find((entry) => entry.type === "retrospective");
37835
+ if (retroEntry && retroEntry.verdict !== "fail") {
37836
+ const lessons = retroEntry.lessons_learned ?? [];
37837
+ const rejections = retroEntry.top_rejection_reasons ?? [];
37838
+ const nonSessionDirectives = (retroEntry.user_directives ?? []).filter((d) => d.scope !== "session");
37839
+ let block = `## Previous Phase Retrospective (Phase ${prevPhase})
37840
+ **Outcome:** ${retroEntry.summary ?? "Phase completed."}
37841
+ **Rejection reasons:** ${rejections.join(", ") || "None"}
37842
+ **Lessons learned:**
37843
+ ${lessons.map((l) => `- ${l}`).join(`
37844
+ `)}
37845
+
37846
+ \u26A0\uFE0F Apply these lessons to the current phase. Do not repeat the same mistakes.`;
37847
+ if (nonSessionDirectives.length > 0) {
37848
+ const top5 = nonSessionDirectives.slice(0, 5);
37849
+ block += `
37850
+
37851
+ ## User Directives (from Phase ${prevPhase})
37852
+ ${top5.map((d) => `- [${d.category}] ${d.directive}`).join(`
37853
+ `)}`;
37854
+ }
37855
+ return block;
37856
+ }
37857
+ }
37858
+ const taskIds = await listEvidenceTaskIds(directory);
37859
+ const retroIds = taskIds.filter((id) => id.startsWith("retro-"));
37860
+ let latestRetro = null;
37861
+ for (const taskId of retroIds) {
37862
+ const b = await loadEvidence(directory, taskId);
37863
+ if (b && b.entries.length > 0) {
37864
+ for (const entry of b.entries) {
37865
+ if (entry.type === "retrospective") {
37866
+ const retro = entry;
37867
+ if (retro.verdict !== "fail") {
37868
+ if (latestRetro === null || retro.phase_number > latestRetro.phase) {
37869
+ latestRetro = { entry: retro, phase: retro.phase_number };
37870
+ }
37871
+ }
37872
+ }
37873
+ }
37874
+ }
37875
+ }
37876
+ if (latestRetro) {
37877
+ const { entry, phase } = latestRetro;
37878
+ const lessons = entry.lessons_learned ?? [];
37879
+ const rejections = entry.top_rejection_reasons ?? [];
37880
+ const nonSessionDirectives = (entry.user_directives ?? []).filter((d) => d.scope !== "session");
37881
+ let block = `## Previous Phase Retrospective (Phase ${phase})
37882
+ **Outcome:** ${entry.summary ?? "Phase completed."}
37883
+ **Rejection reasons:** ${rejections.join(", ") || "None"}
37884
+ **Lessons learned:**
37885
+ ${lessons.map((l) => `- ${l}`).join(`
37886
+ `)}
37887
+
37888
+ \u26A0\uFE0F Apply these lessons to the current phase. Do not repeat the same mistakes.`;
37889
+ if (nonSessionDirectives.length > 0) {
37890
+ const top5 = nonSessionDirectives.slice(0, 5);
37891
+ block += `
37892
+
37893
+ ## User Directives (from Phase ${phase})
37894
+ ${top5.map((d) => `- [${d.category}] ${d.directive}`).join(`
37895
+ `)}`;
37896
+ }
37897
+ return block;
37898
+ }
37899
+ return null;
37900
+ }
37901
+ const allTaskIds = await listEvidenceTaskIds(directory);
37902
+ const allRetroIds = allTaskIds.filter((id) => id.startsWith("retro-"));
37903
+ if (allRetroIds.length === 0) {
37904
+ return null;
37905
+ }
37906
+ const allRetros = [];
37907
+ const cutoffMs = 30 * 24 * 60 * 60 * 1000;
37908
+ const now = Date.now();
37909
+ for (const taskId of allRetroIds) {
37910
+ const b = await loadEvidence(directory, taskId);
37911
+ if (!b)
37912
+ continue;
37913
+ for (const e of b.entries) {
37914
+ if (e.type === "retrospective") {
37915
+ const retro = e;
37916
+ if (retro.verdict === "fail")
37917
+ continue;
37918
+ if (currentPlanTitle && typeof retro.metadata === "object" && retro.metadata !== null && "plan_id" in retro.metadata && retro.metadata.plan_id === currentPlanTitle)
37919
+ continue;
37920
+ const ts = retro.timestamp ?? b.created_at;
37921
+ const ageMs = now - new Date(ts).getTime();
37922
+ if (isNaN(ageMs) || ageMs > cutoffMs)
37923
+ continue;
37924
+ allRetros.push({ entry: retro, timestamp: ts });
37925
+ }
37926
+ }
37927
+ }
37928
+ if (allRetros.length === 0) {
37929
+ return null;
37930
+ }
37931
+ allRetros.sort((a, b) => {
37932
+ const ta = new Date(a.timestamp).getTime();
37933
+ const tb = new Date(b.timestamp).getTime();
37934
+ if (isNaN(ta) && isNaN(tb))
37935
+ return 0;
37936
+ if (isNaN(ta))
37937
+ return 1;
37938
+ if (isNaN(tb))
37939
+ return -1;
37940
+ return tb - ta;
37941
+ });
37942
+ const top3 = allRetros.slice(0, 3);
37943
+ const lines = [
37944
+ "## Historical Lessons (from recent prior projects)"
37945
+ ];
37946
+ lines.push("Most recent retrospectives in this workspace:");
37947
+ const allCarriedDirectives = [];
37948
+ for (const { entry, timestamp } of top3) {
37949
+ const date9 = timestamp.split("T")[0] ?? "unknown";
37950
+ const summary = entry.summary ?? `Phase ${entry.phase_number} completed`;
37951
+ const topLesson = entry.lessons_learned?.[0] ?? "No lessons recorded";
37952
+ lines.push(`- Phase ${entry.phase_number} (${date9}): ${summary}`);
37953
+ lines.push(` Key lesson: ${topLesson}`);
37954
+ const nonSession = (entry.user_directives ?? []).filter((d) => d.scope !== "session");
37955
+ allCarriedDirectives.push(...nonSession);
37956
+ }
37957
+ if (allCarriedDirectives.length > 0) {
37958
+ const top5 = allCarriedDirectives.slice(0, 5);
37959
+ lines.push("User directives carried forward:");
37960
+ for (const d of top5) {
37961
+ lines.push(`- [${d.category}] ${d.directive}`);
37962
+ }
37963
+ }
37964
+ const tier2Block = lines.join(`
37965
+ `);
37966
+ return tier2Block.length <= 800 ? tier2Block : `${tier2Block.substring(0, 797)}...`;
37967
+ } catch {
37968
+ return null;
37969
+ }
37970
+ }
37971
+ async function buildCoderRetroInjection(directory, currentPhaseNumber) {
37972
+ try {
37973
+ const prevPhase = currentPhaseNumber - 1;
37974
+ if (prevPhase < 1)
37975
+ return null;
37976
+ const bundle = await loadEvidence(directory, `retro-${prevPhase}`);
37977
+ if (!bundle || bundle.entries.length === 0)
37978
+ return null;
37979
+ const retroEntry = bundle.entries.find((entry) => entry.type === "retrospective");
37980
+ if (!retroEntry || retroEntry.verdict === "fail")
37981
+ return null;
37982
+ const lessons = retroEntry.lessons_learned ?? [];
37983
+ const summaryLine = `[SWARM RETROSPECTIVE] From Phase ${prevPhase}:${retroEntry.summary ? " " + retroEntry.summary : ""}`;
37984
+ const allLines = [summaryLine, ...lessons];
37985
+ const text = allLines.join(`
37986
+ `);
37987
+ return text.length <= 400 ? text : `${text.substring(0, 397)}...`;
37988
+ } catch {
37989
+ return null;
37990
+ }
37991
+ }
37652
37992
  function createSystemEnhancerHook(config3, directory) {
37653
37993
  const enabled = config3.hooks?.system_enhancer !== false;
37654
37994
  if (!enabled) {
@@ -37727,6 +38067,35 @@ function createSystemEnhancerHook(config3, directory) {
37727
38067
  if (config3.secretscan?.enabled === false) {
37728
38068
  tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
37729
38069
  }
38070
+ const activeAgent_hf1 = swarmState.activeAgent.get(_input.sessionID ?? "");
38071
+ const baseRole = activeAgent_hf1 ? stripKnownSwarmPrefix(activeAgent_hf1) : null;
38072
+ if (baseRole === "coder" || baseRole === "test_engineer") {
38073
+ tryInject("[SWARM CONFIG] You must NOT run build, test, lint, or type-check commands (npm run build, bun test, npx tsc, eslint, etc.). Make ONLY the code changes specified in your task. Verification is handled by the reviewer agent \u2014 do not self-verify. If your task explicitly asks you to run a specific command, that is the only exception.");
38074
+ }
38075
+ if (baseRole === "architect" || baseRole === null) {
38076
+ tryInject("[SWARM CONFIG] You must NEVER run the full test suite or batch test files. If you need to verify changes, run ONLY the specific test files for code YOU modified in this session \u2014 one file at a time, strictly serial. Do not run tests from directories or files unrelated to your changes. Do not run bun test without an explicit file path. When possible, delegate test execution to the test_engineer agent instead of running tests yourself.");
38077
+ }
38078
+ if (config3.adversarial_detection?.enabled !== false) {
38079
+ const activeAgent_adv = swarmState.activeAgent.get(_input.sessionID ?? "");
38080
+ if (activeAgent_adv) {
38081
+ const baseRole_adv = stripKnownSwarmPrefix(activeAgent_adv);
38082
+ const pairs_adv = config3.adversarial_detection?.pairs ?? [
38083
+ ["coder", "reviewer"]
38084
+ ];
38085
+ const policy_adv = config3.adversarial_detection?.policy ?? "warn";
38086
+ for (const [agentA, agentB] of pairs_adv) {
38087
+ if (baseRole_adv === agentB) {
38088
+ const sharedModel = detectAdversarialPair(agentA, agentB, config3);
38089
+ if (sharedModel) {
38090
+ const warningText = formatAdversarialWarning(agentA, agentB, sharedModel, policy_adv);
38091
+ if (policy_adv !== "ignore") {
38092
+ tryInject(`[SWARM CONFIG] ${warningText}`);
38093
+ }
38094
+ }
38095
+ }
38096
+ }
38097
+ }
38098
+ }
37730
38099
  if (mode !== "DISCOVER") {
37731
38100
  const sessionId_preflight = _input.sessionID;
37732
38101
  const activeAgent_preflight = swarmState.activeAgent.get(sessionId_preflight ?? "");
@@ -37739,43 +38108,27 @@ function createSystemEnhancerHook(config3, directory) {
37739
38108
  }
37740
38109
  }
37741
38110
  }
38111
+ if (baseRole === "coder") {
38112
+ try {
38113
+ const currentPhaseNum_coder = plan2?.current_phase ?? 1;
38114
+ const coderRetro = await buildCoderRetroInjection(directory, currentPhaseNum_coder);
38115
+ if (coderRetro) {
38116
+ tryInject(coderRetro);
38117
+ }
38118
+ } catch {}
38119
+ }
37742
38120
  const sessionId_retro = _input.sessionID;
37743
38121
  const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
37744
38122
  const isArchitect2 = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
37745
38123
  if (isArchitect2) {
37746
38124
  try {
37747
- const evidenceDir = path17.join(directory, ".swarm", "evidence");
37748
- if (fs11.existsSync(evidenceDir)) {
37749
- const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
37750
- for (const file3 of files.slice(0, 5)) {
37751
- let content;
37752
- try {
37753
- content = JSON.parse(fs11.readFileSync(path17.join(evidenceDir, file3), "utf-8"));
37754
- } catch {
37755
- continue;
37756
- }
37757
- if (content !== null && typeof content === "object" && content.type === "retrospective") {
37758
- const retro = content;
37759
- const hints = [];
37760
- if (retro.reviewer_rejections > 2) {
37761
- hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
37762
- }
37763
- if (retro.top_rejection_reasons.length > 0) {
37764
- hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
37765
- }
37766
- if (retro.lessons_learned.length > 0) {
37767
- hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
37768
- }
37769
- if (hints.length > 0) {
37770
- const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
37771
- if (retroHint.length <= 800) {
37772
- tryInject(retroHint);
37773
- } else {
37774
- tryInject(`${retroHint.substring(0, 800)}...`);
37775
- }
37776
- }
37777
- break;
37778
- }
38125
+ const currentPhaseNum = plan2?.current_phase ?? 1;
38126
+ const retroText = await buildRetroInjection(directory, currentPhaseNum, plan2?.title ?? undefined);
38127
+ if (retroText) {
38128
+ if (retroText.length <= 1600) {
38129
+ tryInject(retroText);
38130
+ } else {
38131
+ tryInject(`${retroText.substring(0, 1600)}...`);
37779
38132
  }
37780
38133
  }
37781
38134
  } catch {}
@@ -37988,6 +38341,34 @@ function createSystemEnhancerHook(config3, directory) {
37988
38341
  metadata: { contentType: "prose" }
37989
38342
  });
37990
38343
  }
38344
+ if (config3.adversarial_detection?.enabled !== false) {
38345
+ const activeAgent_adv_b = swarmState.activeAgent.get(_input.sessionID ?? "");
38346
+ if (activeAgent_adv_b) {
38347
+ const baseRole_adv_b = stripKnownSwarmPrefix(activeAgent_adv_b);
38348
+ const pairs_adv_b = config3.adversarial_detection?.pairs ?? [
38349
+ ["coder", "reviewer"]
38350
+ ];
38351
+ const policy_adv_b = config3.adversarial_detection?.policy ?? "warn";
38352
+ for (const [agentA_b, agentB_b] of pairs_adv_b) {
38353
+ if (baseRole_adv_b === agentB_b) {
38354
+ const sharedModel_b = detectAdversarialPair(agentA_b, agentB_b, config3);
38355
+ if (sharedModel_b) {
38356
+ const warningText_b = formatAdversarialWarning(agentA_b, agentB_b, sharedModel_b, policy_adv_b);
38357
+ if (policy_adv_b !== "ignore") {
38358
+ candidates.push({
38359
+ id: `candidate-${idCounter++}`,
38360
+ kind: "agent_context",
38361
+ text: `[SWARM CONFIG] ${warningText_b}`,
38362
+ tokens: estimateTokens(warningText_b),
38363
+ priority: 2,
38364
+ metadata: { contentType: "prose" }
38365
+ });
38366
+ }
38367
+ }
38368
+ }
38369
+ }
38370
+ }
38371
+ }
37991
38372
  const sessionId_preflight_b = _input.sessionID;
37992
38373
  const activeAgent_preflight_b = swarmState.activeAgent.get(sessionId_preflight_b ?? "");
37993
38374
  const isArchitectForPreflight_b = !activeAgent_preflight_b || stripKnownSwarmPrefix(activeAgent_preflight_b) === "architect";
@@ -38007,43 +38388,18 @@ function createSystemEnhancerHook(config3, directory) {
38007
38388
  const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
38008
38389
  if (isArchitect_b) {
38009
38390
  try {
38010
- const evidenceDir_b = path17.join(directory, ".swarm", "evidence");
38011
- if (fs11.existsSync(evidenceDir_b)) {
38012
- const files_b = fs11.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
38013
- for (const file3 of files_b.slice(0, 5)) {
38014
- let content_b;
38015
- try {
38016
- content_b = JSON.parse(fs11.readFileSync(path17.join(evidenceDir_b, file3), "utf-8"));
38017
- } catch {
38018
- continue;
38019
- }
38020
- if (content_b !== null && typeof content_b === "object" && content_b.type === "retrospective") {
38021
- const retro_b = content_b;
38022
- const hints_b = [];
38023
- if (retro_b.reviewer_rejections > 2) {
38024
- hints_b.push(`Phase ${retro_b.phase_number} had ${retro_b.reviewer_rejections} reviewer rejections.`);
38025
- }
38026
- if (retro_b.top_rejection_reasons.length > 0) {
38027
- hints_b.push(`Common rejection reasons: ${retro_b.top_rejection_reasons.join(", ")}.`);
38028
- }
38029
- if (retro_b.lessons_learned.length > 0) {
38030
- hints_b.push(`Lessons: ${retro_b.lessons_learned.join("; ")}.`);
38031
- }
38032
- if (hints_b.length > 0) {
38033
- const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
38034
- const retroText = retroHint_b.length <= 800 ? retroHint_b : `${retroHint_b.substring(0, 800)}...`;
38035
- candidates.push({
38036
- id: `candidate-${idCounter++}`,
38037
- kind: "phase",
38038
- text: retroText,
38039
- tokens: estimateTokens(retroText),
38040
- priority: 2,
38041
- metadata: { contentType: "prose" }
38042
- });
38043
- }
38044
- break;
38045
- }
38046
- }
38391
+ const currentPhaseNum_b = plan?.current_phase ?? 1;
38392
+ const retroText_b = await buildRetroInjection(directory, currentPhaseNum_b, plan?.title ?? undefined);
38393
+ if (retroText_b) {
38394
+ const text = retroText_b.length <= 1600 ? retroText_b : `${retroText_b.substring(0, 1597)}...`;
38395
+ candidates.push({
38396
+ id: `candidate-${idCounter++}`,
38397
+ kind: "phase",
38398
+ text,
38399
+ tokens: estimateTokens(text),
38400
+ priority: 2,
38401
+ metadata: { contentType: "prose" }
38402
+ });
38047
38403
  }
38048
38404
  } catch {}
38049
38405
  if (mode_b !== "DISCOVER") {
@@ -38081,6 +38437,24 @@ function createSystemEnhancerHook(config3, directory) {
38081
38437
  }
38082
38438
  }
38083
38439
  }
38440
+ const activeAgent_coder_b = swarmState.activeAgent.get(_input.sessionID ?? "");
38441
+ const isCoder_b = activeAgent_coder_b && stripKnownSwarmPrefix(activeAgent_coder_b) === "coder";
38442
+ if (isCoder_b) {
38443
+ try {
38444
+ const currentPhaseNum_coder_b = plan?.current_phase ?? 1;
38445
+ const coderRetro_b = await buildCoderRetroInjection(directory, currentPhaseNum_coder_b);
38446
+ if (coderRetro_b) {
38447
+ candidates.push({
38448
+ id: `candidate-${idCounter++}`,
38449
+ kind: "agent_context",
38450
+ text: coderRetro_b,
38451
+ tokens: estimateTokens(coderRetro_b),
38452
+ priority: 2,
38453
+ metadata: { contentType: "prose" }
38454
+ });
38455
+ }
38456
+ } catch {}
38457
+ }
38084
38458
  const automationCapabilities_b = config3.automation?.capabilities;
38085
38459
  if (automationCapabilities_b?.decision_drift_detection === true && sessionId_retro_b) {
38086
38460
  const activeAgentForDrift_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
@@ -38306,6 +38680,10 @@ function createToolSummarizerHook(config3, directory) {
38306
38680
  if (typeof output.output !== "string" || output.output.length === 0) {
38307
38681
  return;
38308
38682
  }
38683
+ const exemptTools = config3.exempt_tools ?? ["retrieve_summary", "task"];
38684
+ if (exemptTools.includes(input.tool)) {
38685
+ return;
38686
+ }
38309
38687
  if (!shouldSummarize(output.output, config3.threshold_bytes)) {
38310
38688
  return;
38311
38689
  }
@@ -38331,8 +38709,8 @@ init_dist();
38331
38709
 
38332
38710
  // src/build/discovery.ts
38333
38711
  init_dist();
38334
- import * as fs12 from "fs";
38335
- import * as path18 from "path";
38712
+ import * as fs11 from "fs";
38713
+ import * as path17 from "path";
38336
38714
  var ECOSYSTEMS = [
38337
38715
  {
38338
38716
  ecosystem: "node",
@@ -38444,18 +38822,18 @@ function findBuildFiles(workingDir, patterns) {
38444
38822
  if (pattern.includes("*")) {
38445
38823
  const dir = workingDir;
38446
38824
  try {
38447
- const files = fs12.readdirSync(dir);
38825
+ const files = fs11.readdirSync(dir);
38448
38826
  const matches = files.filter((f) => {
38449
38827
  const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
38450
38828
  return regex.test(f);
38451
38829
  });
38452
38830
  if (matches.length > 0) {
38453
- return path18.join(dir, matches[0]);
38831
+ return path17.join(dir, matches[0]);
38454
38832
  }
38455
38833
  } catch {}
38456
38834
  } else {
38457
- const filePath = path18.join(workingDir, pattern);
38458
- if (fs12.existsSync(filePath)) {
38835
+ const filePath = path17.join(workingDir, pattern);
38836
+ if (fs11.existsSync(filePath)) {
38459
38837
  return filePath;
38460
38838
  }
38461
38839
  }
@@ -38463,12 +38841,12 @@ function findBuildFiles(workingDir, patterns) {
38463
38841
  return null;
38464
38842
  }
38465
38843
  function getRepoDefinedScripts(workingDir, scripts) {
38466
- const packageJsonPath = path18.join(workingDir, "package.json");
38467
- if (!fs12.existsSync(packageJsonPath)) {
38844
+ const packageJsonPath = path17.join(workingDir, "package.json");
38845
+ if (!fs11.existsSync(packageJsonPath)) {
38468
38846
  return [];
38469
38847
  }
38470
38848
  try {
38471
- const content = fs12.readFileSync(packageJsonPath, "utf-8");
38849
+ const content = fs11.readFileSync(packageJsonPath, "utf-8");
38472
38850
  const pkg = JSON.parse(content);
38473
38851
  if (!pkg.scripts || typeof pkg.scripts !== "object") {
38474
38852
  return [];
@@ -38504,8 +38882,8 @@ function findAllBuildFiles(workingDir) {
38504
38882
  const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
38505
38883
  findFilesRecursive(workingDir, regex, allBuildFiles);
38506
38884
  } else {
38507
- const filePath = path18.join(workingDir, pattern);
38508
- if (fs12.existsSync(filePath)) {
38885
+ const filePath = path17.join(workingDir, pattern);
38886
+ if (fs11.existsSync(filePath)) {
38509
38887
  allBuildFiles.add(filePath);
38510
38888
  }
38511
38889
  }
@@ -38515,9 +38893,9 @@ function findAllBuildFiles(workingDir) {
38515
38893
  }
38516
38894
  function findFilesRecursive(dir, regex, results) {
38517
38895
  try {
38518
- const entries = fs12.readdirSync(dir, { withFileTypes: true });
38896
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
38519
38897
  for (const entry of entries) {
38520
- const fullPath = path18.join(dir, entry.name);
38898
+ const fullPath = path17.join(dir, entry.name);
38521
38899
  if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
38522
38900
  findFilesRecursive(fullPath, regex, results);
38523
38901
  } else if (entry.isFile() && regex.test(entry.name)) {
@@ -38758,8 +39136,8 @@ var build_check = tool({
38758
39136
  // src/tools/checkpoint.ts
38759
39137
  init_tool();
38760
39138
  import { spawnSync } from "child_process";
38761
- import * as fs13 from "fs";
38762
- import * as path19 from "path";
39139
+ import * as fs12 from "fs";
39140
+ import * as path18 from "path";
38763
39141
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
38764
39142
  var MAX_LABEL_LENGTH = 100;
38765
39143
  var GIT_TIMEOUT_MS = 30000;
@@ -38810,13 +39188,13 @@ function validateLabel(label) {
38810
39188
  return null;
38811
39189
  }
38812
39190
  function getCheckpointLogPath() {
38813
- return path19.join(process.cwd(), CHECKPOINT_LOG_PATH);
39191
+ return path18.join(process.cwd(), CHECKPOINT_LOG_PATH);
38814
39192
  }
38815
39193
  function readCheckpointLog() {
38816
39194
  const logPath = getCheckpointLogPath();
38817
39195
  try {
38818
- if (fs13.existsSync(logPath)) {
38819
- const content = fs13.readFileSync(logPath, "utf-8");
39196
+ if (fs12.existsSync(logPath)) {
39197
+ const content = fs12.readFileSync(logPath, "utf-8");
38820
39198
  const parsed = JSON.parse(content);
38821
39199
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
38822
39200
  return { version: 1, checkpoints: [] };
@@ -38828,13 +39206,13 @@ function readCheckpointLog() {
38828
39206
  }
38829
39207
  function writeCheckpointLog(log2) {
38830
39208
  const logPath = getCheckpointLogPath();
38831
- const dir = path19.dirname(logPath);
38832
- if (!fs13.existsSync(dir)) {
38833
- fs13.mkdirSync(dir, { recursive: true });
39209
+ const dir = path18.dirname(logPath);
39210
+ if (!fs12.existsSync(dir)) {
39211
+ fs12.mkdirSync(dir, { recursive: true });
38834
39212
  }
38835
39213
  const tempPath = `${logPath}.tmp`;
38836
- fs13.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
38837
- fs13.renameSync(tempPath, logPath);
39214
+ fs12.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
39215
+ fs12.renameSync(tempPath, logPath);
38838
39216
  }
38839
39217
  function gitExec(args2) {
38840
39218
  const result = spawnSync("git", args2, {
@@ -39034,8 +39412,8 @@ var checkpoint = tool({
39034
39412
  });
39035
39413
  // src/tools/complexity-hotspots.ts
39036
39414
  init_dist();
39037
- import * as fs14 from "fs";
39038
- import * as path20 from "path";
39415
+ import * as fs13 from "fs";
39416
+ import * as path19 from "path";
39039
39417
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
39040
39418
  var DEFAULT_DAYS = 90;
39041
39419
  var DEFAULT_TOP_N = 20;
@@ -39163,11 +39541,11 @@ function estimateComplexity(content) {
39163
39541
  }
39164
39542
  function getComplexityForFile(filePath) {
39165
39543
  try {
39166
- const stat = fs14.statSync(filePath);
39544
+ const stat = fs13.statSync(filePath);
39167
39545
  if (stat.size > MAX_FILE_SIZE_BYTES2) {
39168
39546
  return null;
39169
39547
  }
39170
- const content = fs14.readFileSync(filePath, "utf-8");
39548
+ const content = fs13.readFileSync(filePath, "utf-8");
39171
39549
  return estimateComplexity(content);
39172
39550
  } catch {
39173
39551
  return null;
@@ -39178,7 +39556,7 @@ async function analyzeHotspots(days, topN, extensions) {
39178
39556
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
39179
39557
  const filteredChurn = new Map;
39180
39558
  for (const [file3, count] of churnMap) {
39181
- const ext = path20.extname(file3).toLowerCase();
39559
+ const ext = path19.extname(file3).toLowerCase();
39182
39560
  if (extSet.has(ext)) {
39183
39561
  filteredChurn.set(file3, count);
39184
39562
  }
@@ -39188,8 +39566,8 @@ async function analyzeHotspots(days, topN, extensions) {
39188
39566
  let analyzedFiles = 0;
39189
39567
  for (const [file3, churnCount] of filteredChurn) {
39190
39568
  let fullPath = file3;
39191
- if (!fs14.existsSync(fullPath)) {
39192
- fullPath = path20.join(cwd, file3);
39569
+ if (!fs13.existsSync(fullPath)) {
39570
+ fullPath = path19.join(cwd, file3);
39193
39571
  }
39194
39572
  const complexity = getComplexityForFile(fullPath);
39195
39573
  if (complexity !== null) {
@@ -39347,14 +39725,14 @@ function validateBase(base) {
39347
39725
  function validatePaths(paths) {
39348
39726
  if (!paths)
39349
39727
  return null;
39350
- for (const path21 of paths) {
39351
- if (!path21 || path21.length === 0) {
39728
+ for (const path20 of paths) {
39729
+ if (!path20 || path20.length === 0) {
39352
39730
  return "empty path not allowed";
39353
39731
  }
39354
- if (path21.length > MAX_PATH_LENGTH) {
39732
+ if (path20.length > MAX_PATH_LENGTH) {
39355
39733
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
39356
39734
  }
39357
- if (SHELL_METACHARACTERS2.test(path21)) {
39735
+ if (SHELL_METACHARACTERS2.test(path20)) {
39358
39736
  return "path contains shell metacharacters";
39359
39737
  }
39360
39738
  }
@@ -39417,8 +39795,8 @@ var diff = tool({
39417
39795
  if (parts2.length >= 3) {
39418
39796
  const additions = parseInt(parts2[0], 10) || 0;
39419
39797
  const deletions = parseInt(parts2[1], 10) || 0;
39420
- const path21 = parts2[2];
39421
- files.push({ path: path21, additions, deletions });
39798
+ const path20 = parts2[2];
39799
+ files.push({ path: path20, additions, deletions });
39422
39800
  }
39423
39801
  }
39424
39802
  const contractChanges = [];
@@ -39646,8 +40024,8 @@ Use these as DOMAIN values when delegating to @sme.`;
39646
40024
  });
39647
40025
  // src/tools/evidence-check.ts
39648
40026
  init_dist();
39649
- import * as fs15 from "fs";
39650
- import * as path21 from "path";
40027
+ import * as fs14 from "fs";
40028
+ import * as path20 from "path";
39651
40029
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
39652
40030
  var MAX_EVIDENCE_FILES = 1000;
39653
40031
  var EVIDENCE_DIR = ".swarm/evidence";
@@ -39670,9 +40048,9 @@ function validateRequiredTypes(input) {
39670
40048
  return null;
39671
40049
  }
39672
40050
  function isPathWithinSwarm(filePath, cwd) {
39673
- const normalizedCwd = path21.resolve(cwd);
39674
- const swarmPath = path21.join(normalizedCwd, ".swarm");
39675
- const normalizedPath = path21.resolve(filePath);
40051
+ const normalizedCwd = path20.resolve(cwd);
40052
+ const swarmPath = path20.join(normalizedCwd, ".swarm");
40053
+ const normalizedPath = path20.resolve(filePath);
39676
40054
  return normalizedPath.startsWith(swarmPath);
39677
40055
  }
39678
40056
  function parseCompletedTasks(planContent) {
@@ -39688,12 +40066,12 @@ function parseCompletedTasks(planContent) {
39688
40066
  }
39689
40067
  function readEvidenceFiles(evidenceDir, _cwd) {
39690
40068
  const evidence = [];
39691
- if (!fs15.existsSync(evidenceDir) || !fs15.statSync(evidenceDir).isDirectory()) {
40069
+ if (!fs14.existsSync(evidenceDir) || !fs14.statSync(evidenceDir).isDirectory()) {
39692
40070
  return evidence;
39693
40071
  }
39694
40072
  let files;
39695
40073
  try {
39696
- files = fs15.readdirSync(evidenceDir);
40074
+ files = fs14.readdirSync(evidenceDir);
39697
40075
  } catch {
39698
40076
  return evidence;
39699
40077
  }
@@ -39702,14 +40080,14 @@ function readEvidenceFiles(evidenceDir, _cwd) {
39702
40080
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
39703
40081
  continue;
39704
40082
  }
39705
- const filePath = path21.join(evidenceDir, filename);
40083
+ const filePath = path20.join(evidenceDir, filename);
39706
40084
  try {
39707
- const resolvedPath = path21.resolve(filePath);
39708
- const evidenceDirResolved = path21.resolve(evidenceDir);
40085
+ const resolvedPath = path20.resolve(filePath);
40086
+ const evidenceDirResolved = path20.resolve(evidenceDir);
39709
40087
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
39710
40088
  continue;
39711
40089
  }
39712
- const stat = fs15.lstatSync(filePath);
40090
+ const stat = fs14.lstatSync(filePath);
39713
40091
  if (!stat.isFile()) {
39714
40092
  continue;
39715
40093
  }
@@ -39718,7 +40096,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
39718
40096
  }
39719
40097
  let fileStat;
39720
40098
  try {
39721
- fileStat = fs15.statSync(filePath);
40099
+ fileStat = fs14.statSync(filePath);
39722
40100
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
39723
40101
  continue;
39724
40102
  }
@@ -39727,7 +40105,7 @@ function readEvidenceFiles(evidenceDir, _cwd) {
39727
40105
  }
39728
40106
  let content;
39729
40107
  try {
39730
- content = fs15.readFileSync(filePath, "utf-8");
40108
+ content = fs14.readFileSync(filePath, "utf-8");
39731
40109
  } catch {
39732
40110
  continue;
39733
40111
  }
@@ -39812,7 +40190,7 @@ var evidence_check = tool({
39812
40190
  return JSON.stringify(errorResult, null, 2);
39813
40191
  }
39814
40192
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
39815
- const planPath = path21.join(cwd, PLAN_FILE);
40193
+ const planPath = path20.join(cwd, PLAN_FILE);
39816
40194
  if (!isPathWithinSwarm(planPath, cwd)) {
39817
40195
  const errorResult = {
39818
40196
  error: "plan file path validation failed",
@@ -39826,7 +40204,7 @@ var evidence_check = tool({
39826
40204
  }
39827
40205
  let planContent;
39828
40206
  try {
39829
- planContent = fs15.readFileSync(planPath, "utf-8");
40207
+ planContent = fs14.readFileSync(planPath, "utf-8");
39830
40208
  } catch {
39831
40209
  const result2 = {
39832
40210
  message: "No completed tasks found in plan.",
@@ -39844,7 +40222,7 @@ var evidence_check = tool({
39844
40222
  };
39845
40223
  return JSON.stringify(result2, null, 2);
39846
40224
  }
39847
- const evidenceDir = path21.join(cwd, EVIDENCE_DIR);
40225
+ const evidenceDir = path20.join(cwd, EVIDENCE_DIR);
39848
40226
  const evidence = readEvidenceFiles(evidenceDir, cwd);
39849
40227
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
39850
40228
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -39860,8 +40238,8 @@ var evidence_check = tool({
39860
40238
  });
39861
40239
  // src/tools/file-extractor.ts
39862
40240
  init_tool();
39863
- import * as fs16 from "fs";
39864
- import * as path22 from "path";
40241
+ import * as fs15 from "fs";
40242
+ import * as path21 from "path";
39865
40243
  var EXT_MAP = {
39866
40244
  python: ".py",
39867
40245
  py: ".py",
@@ -39923,8 +40301,8 @@ var extract_code_blocks = tool({
39923
40301
  execute: async (args2) => {
39924
40302
  const { content, output_dir, prefix } = args2;
39925
40303
  const targetDir = output_dir || process.cwd();
39926
- if (!fs16.existsSync(targetDir)) {
39927
- fs16.mkdirSync(targetDir, { recursive: true });
40304
+ if (!fs15.existsSync(targetDir)) {
40305
+ fs15.mkdirSync(targetDir, { recursive: true });
39928
40306
  }
39929
40307
  const pattern = /```(\w*)\n([\s\S]*?)```/g;
39930
40308
  const matches = [...content.matchAll(pattern)];
@@ -39939,16 +40317,16 @@ var extract_code_blocks = tool({
39939
40317
  if (prefix) {
39940
40318
  filename = `${prefix}_${filename}`;
39941
40319
  }
39942
- let filepath = path22.join(targetDir, filename);
39943
- const base = path22.basename(filepath, path22.extname(filepath));
39944
- const ext = path22.extname(filepath);
40320
+ let filepath = path21.join(targetDir, filename);
40321
+ const base = path21.basename(filepath, path21.extname(filepath));
40322
+ const ext = path21.extname(filepath);
39945
40323
  let counter = 1;
39946
- while (fs16.existsSync(filepath)) {
39947
- filepath = path22.join(targetDir, `${base}_${counter}${ext}`);
40324
+ while (fs15.existsSync(filepath)) {
40325
+ filepath = path21.join(targetDir, `${base}_${counter}${ext}`);
39948
40326
  counter++;
39949
40327
  }
39950
40328
  try {
39951
- fs16.writeFileSync(filepath, code.trim(), "utf-8");
40329
+ fs15.writeFileSync(filepath, code.trim(), "utf-8");
39952
40330
  savedFiles.push(filepath);
39953
40331
  } catch (error93) {
39954
40332
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -40056,8 +40434,8 @@ var gitingest = tool({
40056
40434
  });
40057
40435
  // src/tools/imports.ts
40058
40436
  init_dist();
40059
- import * as fs17 from "fs";
40060
- import * as path23 from "path";
40437
+ import * as fs16 from "fs";
40438
+ import * as path22 from "path";
40061
40439
  var MAX_FILE_PATH_LENGTH2 = 500;
40062
40440
  var MAX_SYMBOL_LENGTH = 256;
40063
40441
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -40111,7 +40489,7 @@ function validateSymbolInput(symbol3) {
40111
40489
  return null;
40112
40490
  }
40113
40491
  function isBinaryFile2(filePath, buffer) {
40114
- const ext = path23.extname(filePath).toLowerCase();
40492
+ const ext = path22.extname(filePath).toLowerCase();
40115
40493
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
40116
40494
  return false;
40117
40495
  }
@@ -40135,15 +40513,15 @@ function parseImports(content, targetFile, targetSymbol) {
40135
40513
  const imports = [];
40136
40514
  let _resolvedTarget;
40137
40515
  try {
40138
- _resolvedTarget = path23.resolve(targetFile);
40516
+ _resolvedTarget = path22.resolve(targetFile);
40139
40517
  } catch {
40140
40518
  _resolvedTarget = targetFile;
40141
40519
  }
40142
- const targetBasename = path23.basename(targetFile, path23.extname(targetFile));
40520
+ const targetBasename = path22.basename(targetFile, path22.extname(targetFile));
40143
40521
  const targetWithExt = targetFile;
40144
40522
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
40145
- const normalizedTargetWithExt = path23.normalize(targetWithExt).replace(/\\/g, "/");
40146
- const normalizedTargetWithoutExt = path23.normalize(targetWithoutExt).replace(/\\/g, "/");
40523
+ const normalizedTargetWithExt = path22.normalize(targetWithExt).replace(/\\/g, "/");
40524
+ const normalizedTargetWithoutExt = path22.normalize(targetWithoutExt).replace(/\\/g, "/");
40147
40525
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
40148
40526
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
40149
40527
  const modulePath = match[1] || match[2] || match[3];
@@ -40166,9 +40544,9 @@ function parseImports(content, targetFile, targetSymbol) {
40166
40544
  }
40167
40545
  const _normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
40168
40546
  let isMatch = false;
40169
- const _targetDir = path23.dirname(targetFile);
40170
- const targetExt = path23.extname(targetFile);
40171
- const targetBasenameNoExt = path23.basename(targetFile, targetExt);
40547
+ const _targetDir = path22.dirname(targetFile);
40548
+ const targetExt = path22.extname(targetFile);
40549
+ const targetBasenameNoExt = path22.basename(targetFile, targetExt);
40172
40550
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
40173
40551
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
40174
40552
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -40225,7 +40603,7 @@ var SKIP_DIRECTORIES2 = new Set([
40225
40603
  function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
40226
40604
  let entries;
40227
40605
  try {
40228
- entries = fs17.readdirSync(dir);
40606
+ entries = fs16.readdirSync(dir);
40229
40607
  } catch (e) {
40230
40608
  stats.fileErrors.push({
40231
40609
  path: dir,
@@ -40236,13 +40614,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
40236
40614
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
40237
40615
  for (const entry of entries) {
40238
40616
  if (SKIP_DIRECTORIES2.has(entry)) {
40239
- stats.skippedDirs.push(path23.join(dir, entry));
40617
+ stats.skippedDirs.push(path22.join(dir, entry));
40240
40618
  continue;
40241
40619
  }
40242
- const fullPath = path23.join(dir, entry);
40620
+ const fullPath = path22.join(dir, entry);
40243
40621
  let stat;
40244
40622
  try {
40245
- stat = fs17.statSync(fullPath);
40623
+ stat = fs16.statSync(fullPath);
40246
40624
  } catch (e) {
40247
40625
  stats.fileErrors.push({
40248
40626
  path: fullPath,
@@ -40253,7 +40631,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
40253
40631
  if (stat.isDirectory()) {
40254
40632
  findSourceFiles2(fullPath, files, stats);
40255
40633
  } else if (stat.isFile()) {
40256
- const ext = path23.extname(fullPath).toLowerCase();
40634
+ const ext = path22.extname(fullPath).toLowerCase();
40257
40635
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
40258
40636
  files.push(fullPath);
40259
40637
  }
@@ -40309,8 +40687,8 @@ var imports = tool({
40309
40687
  return JSON.stringify(errorResult, null, 2);
40310
40688
  }
40311
40689
  try {
40312
- const targetFile = path23.resolve(file3);
40313
- if (!fs17.existsSync(targetFile)) {
40690
+ const targetFile = path22.resolve(file3);
40691
+ if (!fs16.existsSync(targetFile)) {
40314
40692
  const errorResult = {
40315
40693
  error: `target file not found: ${file3}`,
40316
40694
  target: file3,
@@ -40320,7 +40698,7 @@ var imports = tool({
40320
40698
  };
40321
40699
  return JSON.stringify(errorResult, null, 2);
40322
40700
  }
40323
- const targetStat = fs17.statSync(targetFile);
40701
+ const targetStat = fs16.statSync(targetFile);
40324
40702
  if (!targetStat.isFile()) {
40325
40703
  const errorResult = {
40326
40704
  error: "target must be a file, not a directory",
@@ -40331,7 +40709,7 @@ var imports = tool({
40331
40709
  };
40332
40710
  return JSON.stringify(errorResult, null, 2);
40333
40711
  }
40334
- const baseDir = path23.dirname(targetFile);
40712
+ const baseDir = path22.dirname(targetFile);
40335
40713
  const scanStats = {
40336
40714
  skippedDirs: [],
40337
40715
  skippedFiles: 0,
@@ -40346,12 +40724,12 @@ var imports = tool({
40346
40724
  if (consumers.length >= MAX_CONSUMERS)
40347
40725
  break;
40348
40726
  try {
40349
- const stat = fs17.statSync(filePath);
40727
+ const stat = fs16.statSync(filePath);
40350
40728
  if (stat.size > MAX_FILE_SIZE_BYTES4) {
40351
40729
  skippedFileCount++;
40352
40730
  continue;
40353
40731
  }
40354
- const buffer = fs17.readFileSync(filePath);
40732
+ const buffer = fs16.readFileSync(filePath);
40355
40733
  if (isBinaryFile2(filePath, buffer)) {
40356
40734
  skippedFileCount++;
40357
40735
  continue;
@@ -40418,10 +40796,208 @@ var imports = tool({
40418
40796
  // src/tools/index.ts
40419
40797
  init_lint();
40420
40798
 
40799
+ // src/tools/phase-complete.ts
40800
+ init_tool();
40801
+ import * as fs17 from "fs";
40802
+ init_manager();
40803
+ init_utils2();
40804
+ function getDelegationsSince(sessionID, sinceTimestamp) {
40805
+ const chain = swarmState.delegationChains.get(sessionID);
40806
+ if (!chain) {
40807
+ return [];
40808
+ }
40809
+ if (sinceTimestamp === 0) {
40810
+ return chain;
40811
+ }
40812
+ return chain.filter((entry) => entry.timestamp > sinceTimestamp);
40813
+ }
40814
+ function normalizeAgentsFromDelegations(delegations) {
40815
+ const agents = new Set;
40816
+ for (const delegation of delegations) {
40817
+ const normalizedFrom = stripKnownSwarmPrefix(delegation.from);
40818
+ const normalizedTo = stripKnownSwarmPrefix(delegation.to);
40819
+ agents.add(normalizedFrom);
40820
+ agents.add(normalizedTo);
40821
+ }
40822
+ return agents;
40823
+ }
40824
+ function isValidRetroEntry(entry, phase) {
40825
+ return entry.type === "retrospective" && "phase_number" in entry && entry.phase_number === phase && "verdict" in entry && entry.verdict === "pass";
40826
+ }
40827
+ async function executePhaseComplete(args2) {
40828
+ const phase = Number(args2.phase);
40829
+ const summary = args2.summary;
40830
+ const sessionID = args2.sessionID;
40831
+ if (Number.isNaN(phase) || phase < 1) {
40832
+ return JSON.stringify({
40833
+ success: false,
40834
+ phase,
40835
+ message: "Invalid phase number",
40836
+ agentsDispatched: [],
40837
+ warnings: ["Phase must be a positive number"]
40838
+ }, null, 2);
40839
+ }
40840
+ if (!sessionID) {
40841
+ return JSON.stringify({
40842
+ success: false,
40843
+ phase,
40844
+ message: "Session ID is required",
40845
+ agentsDispatched: [],
40846
+ warnings: [
40847
+ "sessionID parameter is required for phase completion tracking"
40848
+ ]
40849
+ }, null, 2);
40850
+ }
40851
+ const session = ensureAgentSession(sessionID);
40852
+ const lastCompletionTimestamp = session.lastPhaseCompleteTimestamp ?? 0;
40853
+ const recentDelegations = getDelegationsSince(sessionID, lastCompletionTimestamp);
40854
+ const delegationAgents = normalizeAgentsFromDelegations(recentDelegations);
40855
+ const trackedAgents = session.phaseAgentsDispatched ?? new Set;
40856
+ const allAgents = new Set([...delegationAgents, ...trackedAgents]);
40857
+ const agentsDispatched = Array.from(allAgents).sort();
40858
+ const directory = process.cwd();
40859
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
40860
+ let phaseCompleteConfig;
40861
+ try {
40862
+ phaseCompleteConfig = PhaseCompleteConfigSchema.parse(config3.phase_complete ?? {});
40863
+ } catch (parseError) {
40864
+ return JSON.stringify({
40865
+ success: false,
40866
+ phase,
40867
+ status: "incomplete",
40868
+ message: `Invalid phase_complete configuration: ${parseError instanceof Error ? parseError.message : "Unknown error"}`,
40869
+ agentsDispatched,
40870
+ agentsMissing: [],
40871
+ warnings: ["Configuration validation failed"]
40872
+ }, null, 2);
40873
+ }
40874
+ if (phaseCompleteConfig.enabled === false) {
40875
+ return JSON.stringify({
40876
+ success: true,
40877
+ phase,
40878
+ status: "disabled",
40879
+ message: `Phase ${phase} complete (enforcement disabled)`,
40880
+ agentsDispatched,
40881
+ agentsMissing: [],
40882
+ warnings: []
40883
+ }, null, 2);
40884
+ }
40885
+ const retroBundle = await loadEvidence(directory, `retro-${phase}`);
40886
+ let retroFound = false;
40887
+ if (retroBundle !== null) {
40888
+ retroFound = retroBundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
40889
+ }
40890
+ if (!retroFound) {
40891
+ const allTaskIds = await listEvidenceTaskIds(directory);
40892
+ const retroTaskIds = allTaskIds.filter((id) => id.startsWith("retro-"));
40893
+ for (const taskId of retroTaskIds) {
40894
+ const bundle = await loadEvidence(directory, taskId);
40895
+ if (bundle === null)
40896
+ continue;
40897
+ retroFound = bundle.entries?.some((entry) => isValidRetroEntry(entry, phase)) ?? false;
40898
+ if (retroFound)
40899
+ break;
40900
+ }
40901
+ }
40902
+ if (!retroFound) {
40903
+ return JSON.stringify({
40904
+ success: false,
40905
+ phase,
40906
+ status: "blocked",
40907
+ reason: "RETROSPECTIVE_MISSING",
40908
+ message: `Phase ${phase} cannot be completed: no valid retrospective evidence found. Write a retrospective bundle at .swarm/evidence/retro-${phase}/evidence.json with type='retrospective', phase_number=${phase}, verdict='pass' before calling phase_complete.`,
40909
+ agentsDispatched: [],
40910
+ agentsMissing: [],
40911
+ warnings: [
40912
+ `Retrospective missing for phase ${phase}. Write a retro bundle with verdict='pass' at .swarm/evidence/retro-${phase}/evidence.json`
40913
+ ]
40914
+ }, null, 2);
40915
+ }
40916
+ const effectiveRequired = [...phaseCompleteConfig.required_agents];
40917
+ if (phaseCompleteConfig.require_docs && !effectiveRequired.includes("docs")) {
40918
+ effectiveRequired.push("docs");
40919
+ }
40920
+ const agentsMissing = effectiveRequired.filter((req) => !allAgents.has(req));
40921
+ const warnings = [];
40922
+ let success3 = true;
40923
+ let status = "success";
40924
+ const safeSummary = summary?.trim().slice(0, 500);
40925
+ let message = safeSummary ? `Phase ${phase} completed: ${safeSummary}` : `Phase ${phase} completed`;
40926
+ if (agentsMissing.length > 0) {
40927
+ if (phaseCompleteConfig.policy === "enforce") {
40928
+ success3 = false;
40929
+ status = "incomplete";
40930
+ message = `Phase ${phase} incomplete: missing required agents: ${agentsMissing.join(", ")}`;
40931
+ } else {
40932
+ status = "warned";
40933
+ warnings.push(`Warning: phase ${phase} missing required agents: ${agentsMissing.join(", ")}`);
40934
+ }
40935
+ }
40936
+ const now = Date.now();
40937
+ const durationMs = now - lastCompletionTimestamp;
40938
+ const event = {
40939
+ event: "phase_complete",
40940
+ phase,
40941
+ timestamp: new Date(now).toISOString(),
40942
+ agents_dispatched: agentsDispatched,
40943
+ agents_missing: agentsMissing,
40944
+ status,
40945
+ summary: safeSummary ?? null
40946
+ };
40947
+ try {
40948
+ const eventsPath = validateSwarmPath(directory, "events.jsonl");
40949
+ fs17.appendFileSync(eventsPath, `${JSON.stringify(event)}
40950
+ `, "utf-8");
40951
+ } catch (writeError) {
40952
+ warnings.push(`Warning: failed to write phase complete event: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
40953
+ }
40954
+ if (success3) {
40955
+ session.phaseAgentsDispatched = new Set;
40956
+ session.lastPhaseCompleteTimestamp = now;
40957
+ session.lastPhaseCompletePhase = phase;
40958
+ }
40959
+ const result = {
40960
+ success: success3,
40961
+ phase,
40962
+ status,
40963
+ message,
40964
+ agentsDispatched,
40965
+ agentsMissing,
40966
+ warnings
40967
+ };
40968
+ return JSON.stringify({ ...result, timestamp: event.timestamp, duration_ms: durationMs }, null, 2);
40969
+ }
40970
+ var phase_complete = tool({
40971
+ 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.",
40972
+ args: {
40973
+ phase: tool.schema.number().describe("The phase number being completed (e.g., 1, 2, 3)"),
40974
+ summary: tool.schema.string().optional().describe("Optional summary of what was accomplished in this phase"),
40975
+ sessionID: tool.schema.string().optional().describe("Session ID for tracking state (auto-provided by plugin context)")
40976
+ },
40977
+ execute: async (args2) => {
40978
+ let phaseCompleteArgs;
40979
+ try {
40980
+ phaseCompleteArgs = {
40981
+ phase: Number(args2.phase),
40982
+ summary: args2.summary !== undefined ? String(args2.summary) : undefined,
40983
+ sessionID: args2.sessionID !== undefined ? String(args2.sessionID) : undefined
40984
+ };
40985
+ } catch {
40986
+ return JSON.stringify({
40987
+ success: false,
40988
+ phase: 0,
40989
+ message: "Invalid arguments",
40990
+ agentsDispatched: [],
40991
+ warnings: ["Failed to parse arguments"]
40992
+ }, null, 2);
40993
+ }
40994
+ return executePhaseComplete(phaseCompleteArgs);
40995
+ }
40996
+ });
40421
40997
  // src/tools/pkg-audit.ts
40422
40998
  init_dist();
40423
40999
  import * as fs18 from "fs";
40424
- import * as path24 from "path";
41000
+ import * as path23 from "path";
40425
41001
  var MAX_OUTPUT_BYTES5 = 52428800;
40426
41002
  var AUDIT_TIMEOUT_MS = 120000;
40427
41003
  function isValidEcosystem(value) {
@@ -40439,13 +41015,13 @@ function validateArgs3(args2) {
40439
41015
  function detectEcosystems() {
40440
41016
  const ecosystems = [];
40441
41017
  const cwd = process.cwd();
40442
- if (fs18.existsSync(path24.join(cwd, "package.json"))) {
41018
+ if (fs18.existsSync(path23.join(cwd, "package.json"))) {
40443
41019
  ecosystems.push("npm");
40444
41020
  }
40445
- if (fs18.existsSync(path24.join(cwd, "pyproject.toml")) || fs18.existsSync(path24.join(cwd, "requirements.txt"))) {
41021
+ if (fs18.existsSync(path23.join(cwd, "pyproject.toml")) || fs18.existsSync(path23.join(cwd, "requirements.txt"))) {
40446
41022
  ecosystems.push("pip");
40447
41023
  }
40448
- if (fs18.existsSync(path24.join(cwd, "Cargo.toml"))) {
41024
+ if (fs18.existsSync(path23.join(cwd, "Cargo.toml"))) {
40449
41025
  ecosystems.push("cargo");
40450
41026
  }
40451
41027
  return ecosystems;
@@ -42353,11 +42929,11 @@ var Module2 = (() => {
42353
42929
  throw toThrow;
42354
42930
  }, "quit_");
42355
42931
  var scriptDirectory = "";
42356
- function locateFile(path25) {
42932
+ function locateFile(path24) {
42357
42933
  if (Module["locateFile"]) {
42358
- return Module["locateFile"](path25, scriptDirectory);
42934
+ return Module["locateFile"](path24, scriptDirectory);
42359
42935
  }
42360
- return scriptDirectory + path25;
42936
+ return scriptDirectory + path24;
42361
42937
  }
42362
42938
  __name(locateFile, "locateFile");
42363
42939
  var readAsync, readBinary;
@@ -44171,7 +44747,7 @@ var SUPPORTED_PARSER_EXTENSIONS = new Set([
44171
44747
  ]);
44172
44748
  // src/tools/pre-check-batch.ts
44173
44749
  init_dist();
44174
- import * as path27 from "path";
44750
+ import * as path26 from "path";
44175
44751
 
44176
44752
  // node_modules/yocto-queue/index.js
44177
44753
  class Node2 {
@@ -44338,7 +44914,7 @@ init_manager();
44338
44914
 
44339
44915
  // src/quality/metrics.ts
44340
44916
  import * as fs19 from "fs";
44341
- import * as path25 from "path";
44917
+ import * as path24 from "path";
44342
44918
  var MAX_FILE_SIZE_BYTES5 = 256 * 1024;
44343
44919
  var MIN_DUPLICATION_LINES = 10;
44344
44920
  function estimateCyclomaticComplexity(content) {
@@ -44390,7 +44966,7 @@ async function computeComplexityDelta(files, workingDir) {
44390
44966
  let totalComplexity = 0;
44391
44967
  const analyzedFiles = [];
44392
44968
  for (const file3 of files) {
44393
- const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
44969
+ const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44394
44970
  if (!fs19.existsSync(fullPath)) {
44395
44971
  continue;
44396
44972
  }
@@ -44513,7 +45089,7 @@ function countGoExports(content) {
44513
45089
  function getExportCountForFile(filePath) {
44514
45090
  try {
44515
45091
  const content = fs19.readFileSync(filePath, "utf-8");
44516
- const ext = path25.extname(filePath).toLowerCase();
45092
+ const ext = path24.extname(filePath).toLowerCase();
44517
45093
  switch (ext) {
44518
45094
  case ".ts":
44519
45095
  case ".tsx":
@@ -44539,7 +45115,7 @@ async function computePublicApiDelta(files, workingDir) {
44539
45115
  let totalExports = 0;
44540
45116
  const analyzedFiles = [];
44541
45117
  for (const file3 of files) {
44542
- const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45118
+ const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44543
45119
  if (!fs19.existsSync(fullPath)) {
44544
45120
  continue;
44545
45121
  }
@@ -44573,7 +45149,7 @@ async function computeDuplicationRatio(files, workingDir) {
44573
45149
  let duplicateLines = 0;
44574
45150
  const analyzedFiles = [];
44575
45151
  for (const file3 of files) {
44576
- const fullPath = path25.isAbsolute(file3) ? file3 : path25.join(workingDir, file3);
45152
+ const fullPath = path24.isAbsolute(file3) ? file3 : path24.join(workingDir, file3);
44577
45153
  if (!fs19.existsSync(fullPath)) {
44578
45154
  continue;
44579
45155
  }
@@ -44606,8 +45182,8 @@ function countCodeLines(content) {
44606
45182
  return lines.length;
44607
45183
  }
44608
45184
  function isTestFile(filePath) {
44609
- const basename5 = path25.basename(filePath);
44610
- const _ext = path25.extname(filePath).toLowerCase();
45185
+ const basename5 = path24.basename(filePath);
45186
+ const _ext = path24.extname(filePath).toLowerCase();
44611
45187
  const testPatterns = [
44612
45188
  ".test.",
44613
45189
  ".spec.",
@@ -44649,7 +45225,7 @@ function shouldExcludeFile(filePath, excludeGlobs) {
44649
45225
  async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
44650
45226
  let testLines = 0;
44651
45227
  let codeLines = 0;
44652
- const srcDir = path25.join(workingDir, "src");
45228
+ const srcDir = path24.join(workingDir, "src");
44653
45229
  if (fs19.existsSync(srcDir)) {
44654
45230
  await scanDirectoryForLines(srcDir, enforceGlobs, excludeGlobs, false, (lines) => {
44655
45231
  codeLines += lines;
@@ -44657,14 +45233,14 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
44657
45233
  }
44658
45234
  const possibleSrcDirs = ["lib", "app", "source", "core"];
44659
45235
  for (const dir of possibleSrcDirs) {
44660
- const dirPath = path25.join(workingDir, dir);
45236
+ const dirPath = path24.join(workingDir, dir);
44661
45237
  if (fs19.existsSync(dirPath)) {
44662
45238
  await scanDirectoryForLines(dirPath, enforceGlobs, excludeGlobs, false, (lines) => {
44663
45239
  codeLines += lines;
44664
45240
  });
44665
45241
  }
44666
45242
  }
44667
- const testsDir = path25.join(workingDir, "tests");
45243
+ const testsDir = path24.join(workingDir, "tests");
44668
45244
  if (fs19.existsSync(testsDir)) {
44669
45245
  await scanDirectoryForLines(testsDir, ["**"], ["node_modules", "dist"], true, (lines) => {
44670
45246
  testLines += lines;
@@ -44672,7 +45248,7 @@ async function computeTestToCodeRatio(workingDir, enforceGlobs, excludeGlobs) {
44672
45248
  }
44673
45249
  const possibleTestDirs = ["test", "__tests__", "specs"];
44674
45250
  for (const dir of possibleTestDirs) {
44675
- const dirPath = path25.join(workingDir, dir);
45251
+ const dirPath = path24.join(workingDir, dir);
44676
45252
  if (fs19.existsSync(dirPath) && dirPath !== testsDir) {
44677
45253
  await scanDirectoryForLines(dirPath, ["**"], ["node_modules", "dist"], true, (lines) => {
44678
45254
  testLines += lines;
@@ -44687,7 +45263,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
44687
45263
  try {
44688
45264
  const entries = fs19.readdirSync(dirPath, { withFileTypes: true });
44689
45265
  for (const entry of entries) {
44690
- const fullPath = path25.join(dirPath, entry.name);
45266
+ const fullPath = path24.join(dirPath, entry.name);
44691
45267
  if (entry.isDirectory()) {
44692
45268
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === ".git") {
44693
45269
  continue;
@@ -44695,7 +45271,7 @@ async function scanDirectoryForLines(dirPath, includeGlobs, excludeGlobs, isTest
44695
45271
  await scanDirectoryForLines(fullPath, includeGlobs, excludeGlobs, isTestScan, callback);
44696
45272
  } else if (entry.isFile()) {
44697
45273
  const relativePath = fullPath.replace(`${process.cwd()}/`, "");
44698
- const ext = path25.extname(entry.name).toLowerCase();
45274
+ const ext = path24.extname(entry.name).toLowerCase();
44699
45275
  const validExts = [
44700
45276
  ".ts",
44701
45277
  ".tsx",
@@ -44950,7 +45526,7 @@ async function qualityBudget(input, directory) {
44950
45526
  // src/tools/sast-scan.ts
44951
45527
  init_manager();
44952
45528
  import * as fs20 from "fs";
44953
- import * as path26 from "path";
45529
+ import * as path25 from "path";
44954
45530
  import { extname as extname7 } from "path";
44955
45531
 
44956
45532
  // src/sast/rules/c.ts
@@ -45905,7 +46481,7 @@ async function sastScan(input, directory, config3) {
45905
46481
  const engine = semgrepAvailable ? "tier_a+tier_b" : "tier_a";
45906
46482
  const filesByLanguage = new Map;
45907
46483
  for (const filePath of changed_files) {
45908
- const resolvedPath = path26.isAbsolute(filePath) ? filePath : path26.resolve(directory, filePath);
46484
+ const resolvedPath = path25.isAbsolute(filePath) ? filePath : path25.resolve(directory, filePath);
45909
46485
  if (!fs20.existsSync(resolvedPath)) {
45910
46486
  _filesSkipped++;
45911
46487
  continue;
@@ -46014,10 +46590,10 @@ function validatePath(inputPath, baseDir) {
46014
46590
  if (!inputPath || inputPath.length === 0) {
46015
46591
  return "path is required";
46016
46592
  }
46017
- const resolved = path27.resolve(baseDir, inputPath);
46018
- const baseResolved = path27.resolve(baseDir);
46019
- const relative3 = path27.relative(baseResolved, resolved);
46020
- if (relative3.startsWith("..") || path27.isAbsolute(relative3)) {
46593
+ const resolved = path26.resolve(baseDir, inputPath);
46594
+ const baseResolved = path26.resolve(baseDir);
46595
+ const relative3 = path26.relative(baseResolved, resolved);
46596
+ if (relative3.startsWith("..") || path26.isAbsolute(relative3)) {
46021
46597
  return "path traversal detected";
46022
46598
  }
46023
46599
  return null;
@@ -46139,7 +46715,7 @@ async function runPreCheckBatch(input) {
46139
46715
  warn(`pre_check_batch: Invalid file path: ${file3}`);
46140
46716
  continue;
46141
46717
  }
46142
- changedFiles.push(path27.resolve(directory, file3));
46718
+ changedFiles.push(path26.resolve(directory, file3));
46143
46719
  }
46144
46720
  } else {
46145
46721
  changedFiles = [];
@@ -46330,7 +46906,7 @@ var retrieve_summary = tool({
46330
46906
  init_dist();
46331
46907
  init_manager();
46332
46908
  import * as fs21 from "fs";
46333
- import * as path28 from "path";
46909
+ import * as path27 from "path";
46334
46910
 
46335
46911
  // src/sbom/detectors/dart.ts
46336
46912
  function parsePubspecLock(content) {
@@ -47177,7 +47753,7 @@ function findManifestFiles(rootDir) {
47177
47753
  try {
47178
47754
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47179
47755
  for (const entry of entries) {
47180
- const fullPath = path28.join(dir, entry.name);
47756
+ const fullPath = path27.join(dir, entry.name);
47181
47757
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "target") {
47182
47758
  continue;
47183
47759
  }
@@ -47187,7 +47763,7 @@ function findManifestFiles(rootDir) {
47187
47763
  for (const pattern of patterns) {
47188
47764
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47189
47765
  if (new RegExp(regex, "i").test(entry.name)) {
47190
- manifestFiles.push(path28.relative(cwd, fullPath));
47766
+ manifestFiles.push(path27.relative(cwd, fullPath));
47191
47767
  break;
47192
47768
  }
47193
47769
  }
@@ -47206,12 +47782,12 @@ function findManifestFilesInDirs(directories, workingDir) {
47206
47782
  try {
47207
47783
  const entries = fs21.readdirSync(dir, { withFileTypes: true });
47208
47784
  for (const entry of entries) {
47209
- const fullPath = path28.join(dir, entry.name);
47785
+ const fullPath = path27.join(dir, entry.name);
47210
47786
  if (entry.isFile()) {
47211
47787
  for (const pattern of patterns) {
47212
47788
  const regex = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".");
47213
47789
  if (new RegExp(regex, "i").test(entry.name)) {
47214
- found.push(path28.relative(workingDir, fullPath));
47790
+ found.push(path27.relative(workingDir, fullPath));
47215
47791
  break;
47216
47792
  }
47217
47793
  }
@@ -47224,11 +47800,11 @@ function findManifestFilesInDirs(directories, workingDir) {
47224
47800
  function getDirectoriesFromChangedFiles(changedFiles, workingDir) {
47225
47801
  const dirs = new Set;
47226
47802
  for (const file3 of changedFiles) {
47227
- let currentDir = path28.dirname(file3);
47803
+ let currentDir = path27.dirname(file3);
47228
47804
  while (true) {
47229
- if (currentDir && currentDir !== "." && currentDir !== path28.sep) {
47230
- dirs.add(path28.join(workingDir, currentDir));
47231
- const parent = path28.dirname(currentDir);
47805
+ if (currentDir && currentDir !== "." && currentDir !== path27.sep) {
47806
+ dirs.add(path27.join(workingDir, currentDir));
47807
+ const parent = path27.dirname(currentDir);
47232
47808
  if (parent === currentDir)
47233
47809
  break;
47234
47810
  currentDir = parent;
@@ -47335,7 +47911,7 @@ var sbom_generate = tool({
47335
47911
  const processedFiles = [];
47336
47912
  for (const manifestFile of manifestFiles) {
47337
47913
  try {
47338
- const fullPath = path28.isAbsolute(manifestFile) ? manifestFile : path28.join(workingDir, manifestFile);
47914
+ const fullPath = path27.isAbsolute(manifestFile) ? manifestFile : path27.join(workingDir, manifestFile);
47339
47915
  if (!fs21.existsSync(fullPath)) {
47340
47916
  continue;
47341
47917
  }
@@ -47352,7 +47928,7 @@ var sbom_generate = tool({
47352
47928
  const bom = generateCycloneDX(allComponents);
47353
47929
  const bomJson = serializeCycloneDX(bom);
47354
47930
  const filename = generateSbomFilename();
47355
- const outputPath = path28.join(outputDir, filename);
47931
+ const outputPath = path27.join(outputDir, filename);
47356
47932
  fs21.writeFileSync(outputPath, bomJson, "utf-8");
47357
47933
  const verdict = processedFiles.length > 0 ? "pass" : "pass";
47358
47934
  try {
@@ -47395,7 +47971,7 @@ var sbom_generate = tool({
47395
47971
  // src/tools/schema-drift.ts
47396
47972
  init_dist();
47397
47973
  import * as fs22 from "fs";
47398
- import * as path29 from "path";
47974
+ import * as path28 from "path";
47399
47975
  var SPEC_CANDIDATES = [
47400
47976
  "openapi.json",
47401
47977
  "openapi.yaml",
@@ -47427,12 +48003,12 @@ function normalizePath(p) {
47427
48003
  }
47428
48004
  function discoverSpecFile(cwd, specFileArg) {
47429
48005
  if (specFileArg) {
47430
- const resolvedPath = path29.resolve(cwd, specFileArg);
47431
- const normalizedCwd = cwd.endsWith(path29.sep) ? cwd : cwd + path29.sep;
48006
+ const resolvedPath = path28.resolve(cwd, specFileArg);
48007
+ const normalizedCwd = cwd.endsWith(path28.sep) ? cwd : cwd + path28.sep;
47432
48008
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
47433
48009
  throw new Error("Invalid spec_file: path traversal detected");
47434
48010
  }
47435
- const ext = path29.extname(resolvedPath).toLowerCase();
48011
+ const ext = path28.extname(resolvedPath).toLowerCase();
47436
48012
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
47437
48013
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
47438
48014
  }
@@ -47446,7 +48022,7 @@ function discoverSpecFile(cwd, specFileArg) {
47446
48022
  return resolvedPath;
47447
48023
  }
47448
48024
  for (const candidate of SPEC_CANDIDATES) {
47449
- const candidatePath = path29.resolve(cwd, candidate);
48025
+ const candidatePath = path28.resolve(cwd, candidate);
47450
48026
  if (fs22.existsSync(candidatePath)) {
47451
48027
  const stats = fs22.statSync(candidatePath);
47452
48028
  if (stats.size <= MAX_SPEC_SIZE) {
@@ -47458,7 +48034,7 @@ function discoverSpecFile(cwd, specFileArg) {
47458
48034
  }
47459
48035
  function parseSpec(specFile) {
47460
48036
  const content = fs22.readFileSync(specFile, "utf-8");
47461
- const ext = path29.extname(specFile).toLowerCase();
48037
+ const ext = path28.extname(specFile).toLowerCase();
47462
48038
  if (ext === ".json") {
47463
48039
  return parseJsonSpec(content);
47464
48040
  }
@@ -47529,7 +48105,7 @@ function extractRoutes(cwd) {
47529
48105
  return;
47530
48106
  }
47531
48107
  for (const entry of entries) {
47532
- const fullPath = path29.join(dir, entry.name);
48108
+ const fullPath = path28.join(dir, entry.name);
47533
48109
  if (entry.isSymbolicLink()) {
47534
48110
  continue;
47535
48111
  }
@@ -47539,7 +48115,7 @@ function extractRoutes(cwd) {
47539
48115
  }
47540
48116
  walkDir(fullPath);
47541
48117
  } else if (entry.isFile()) {
47542
- const ext = path29.extname(entry.name).toLowerCase();
48118
+ const ext = path28.extname(entry.name).toLowerCase();
47543
48119
  const baseName = entry.name.toLowerCase();
47544
48120
  if (![".ts", ".js", ".mjs"].includes(ext)) {
47545
48121
  continue;
@@ -47708,7 +48284,7 @@ init_secretscan();
47708
48284
  // src/tools/symbols.ts
47709
48285
  init_tool();
47710
48286
  import * as fs23 from "fs";
47711
- import * as path30 from "path";
48287
+ import * as path29 from "path";
47712
48288
  var MAX_FILE_SIZE_BYTES7 = 1024 * 1024;
47713
48289
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
47714
48290
  function containsControlCharacters(str) {
@@ -47737,11 +48313,11 @@ function containsWindowsAttacks(str) {
47737
48313
  }
47738
48314
  function isPathInWorkspace(filePath, workspace) {
47739
48315
  try {
47740
- const resolvedPath = path30.resolve(workspace, filePath);
48316
+ const resolvedPath = path29.resolve(workspace, filePath);
47741
48317
  const realWorkspace = fs23.realpathSync(workspace);
47742
48318
  const realResolvedPath = fs23.realpathSync(resolvedPath);
47743
- const relativePath = path30.relative(realWorkspace, realResolvedPath);
47744
- if (relativePath.startsWith("..") || path30.isAbsolute(relativePath)) {
48319
+ const relativePath = path29.relative(realWorkspace, realResolvedPath);
48320
+ if (relativePath.startsWith("..") || path29.isAbsolute(relativePath)) {
47745
48321
  return false;
47746
48322
  }
47747
48323
  return true;
@@ -47753,7 +48329,7 @@ function validatePathForRead(filePath, workspace) {
47753
48329
  return isPathInWorkspace(filePath, workspace);
47754
48330
  }
47755
48331
  function extractTSSymbols(filePath, cwd) {
47756
- const fullPath = path30.join(cwd, filePath);
48332
+ const fullPath = path29.join(cwd, filePath);
47757
48333
  if (!validatePathForRead(fullPath, cwd)) {
47758
48334
  return [];
47759
48335
  }
@@ -47905,7 +48481,7 @@ function extractTSSymbols(filePath, cwd) {
47905
48481
  });
47906
48482
  }
47907
48483
  function extractPythonSymbols(filePath, cwd) {
47908
- const fullPath = path30.join(cwd, filePath);
48484
+ const fullPath = path29.join(cwd, filePath);
47909
48485
  if (!validatePathForRead(fullPath, cwd)) {
47910
48486
  return [];
47911
48487
  }
@@ -47987,7 +48563,7 @@ var symbols = tool({
47987
48563
  }, null, 2);
47988
48564
  }
47989
48565
  const cwd = process.cwd();
47990
- const ext = path30.extname(file3);
48566
+ const ext = path29.extname(file3);
47991
48567
  if (containsControlCharacters(file3)) {
47992
48568
  return JSON.stringify({
47993
48569
  file: file3,
@@ -48056,7 +48632,7 @@ init_test_runner();
48056
48632
  // src/tools/todo-extract.ts
48057
48633
  init_dist();
48058
48634
  import * as fs24 from "fs";
48059
- import * as path31 from "path";
48635
+ import * as path30 from "path";
48060
48636
  var MAX_TEXT_LENGTH = 200;
48061
48637
  var MAX_FILE_SIZE_BYTES8 = 1024 * 1024;
48062
48638
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -48127,9 +48703,9 @@ function validatePathsInput(paths, cwd) {
48127
48703
  return { error: "paths contains path traversal", resolvedPath: null };
48128
48704
  }
48129
48705
  try {
48130
- const resolvedPath = path31.resolve(paths);
48131
- const normalizedCwd = path31.resolve(cwd);
48132
- const normalizedResolved = path31.resolve(resolvedPath);
48706
+ const resolvedPath = path30.resolve(paths);
48707
+ const normalizedCwd = path30.resolve(cwd);
48708
+ const normalizedResolved = path30.resolve(resolvedPath);
48133
48709
  if (!normalizedResolved.startsWith(normalizedCwd)) {
48134
48710
  return {
48135
48711
  error: "paths must be within the current working directory",
@@ -48145,7 +48721,7 @@ function validatePathsInput(paths, cwd) {
48145
48721
  }
48146
48722
  }
48147
48723
  function isSupportedExtension(filePath) {
48148
- const ext = path31.extname(filePath).toLowerCase();
48724
+ const ext = path30.extname(filePath).toLowerCase();
48149
48725
  return SUPPORTED_EXTENSIONS2.has(ext);
48150
48726
  }
48151
48727
  function findSourceFiles3(dir, files = []) {
@@ -48160,7 +48736,7 @@ function findSourceFiles3(dir, files = []) {
48160
48736
  if (SKIP_DIRECTORIES3.has(entry)) {
48161
48737
  continue;
48162
48738
  }
48163
- const fullPath = path31.join(dir, entry);
48739
+ const fullPath = path30.join(dir, entry);
48164
48740
  let stat;
48165
48741
  try {
48166
48742
  stat = fs24.statSync(fullPath);
@@ -48272,7 +48848,7 @@ var todo_extract = tool({
48272
48848
  filesToScan.push(scanPath);
48273
48849
  } else {
48274
48850
  const errorResult = {
48275
- error: `unsupported file extension: ${path31.extname(scanPath)}`,
48851
+ error: `unsupported file extension: ${path30.extname(scanPath)}`,
48276
48852
  total: 0,
48277
48853
  byPriority: { high: 0, medium: 0, low: 0 },
48278
48854
  entries: []
@@ -48387,7 +48963,7 @@ var OpenCodeSwarm = async (ctx) => {
48387
48963
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
48388
48964
  preflightTriggerManager = new PTM(automationConfig);
48389
48965
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
48390
- const swarmDir = path32.resolve(ctx.directory, ".swarm");
48966
+ const swarmDir = path31.resolve(ctx.directory, ".swarm");
48391
48967
  statusArtifact = new ASA(swarmDir);
48392
48968
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
48393
48969
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {
@@ -48490,6 +49066,7 @@ var OpenCodeSwarm = async (ctx) => {
48490
49066
  lint,
48491
49067
  diff,
48492
49068
  pkg_audit,
49069
+ phase_complete,
48493
49070
  pre_check_batch,
48494
49071
  retrieve_summary,
48495
49072
  schema_drift,
@@ -48507,7 +49084,7 @@ var OpenCodeSwarm = async (ctx) => {
48507
49084
  opencodeConfig.command = {
48508
49085
  ...opencodeConfig.command || {},
48509
49086
  swarm: {
48510
- template: 'The /swarm command has been processed by the plugin handler. Acknowledge with: "Done." Do not take any further action. User input was: $ARGUMENTS',
49087
+ template: "/swarm $ARGUMENTS",
48511
49088
  description: "Swarm management commands"
48512
49089
  }
48513
49090
  };