opencode-swarm 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
3
18
  var __export = (target, all) => {
4
19
  for (var name in all)
5
20
  __defProp(target, name, {
@@ -9,6 +24,7 @@ var __export = (target, all) => {
9
24
  set: (newValue) => all[name] = () => newValue
10
25
  });
11
26
  };
27
+ var __require = import.meta.require;
12
28
 
13
29
  // src/config/constants.ts
14
30
  var QA_AGENTS = ["reviewer", "critic"];
@@ -33,6 +49,11 @@ var DEFAULT_MODELS = {
33
49
  critic: "google/gemini-2.0-flash",
34
50
  default: "google/gemini-2.0-flash"
35
51
  };
52
+ // src/config/loader.ts
53
+ import * as fs from "fs";
54
+ import * as os from "os";
55
+ import * as path from "path";
56
+
36
57
  // node_modules/zod/v4/classic/external.js
37
58
  var exports_external = {};
38
59
  __export(exports_external, {
@@ -13586,8 +13607,38 @@ var ContextBudgetConfigSchema = exports_external.object({
13586
13607
  enabled: exports_external.boolean().default(true),
13587
13608
  warn_threshold: exports_external.number().min(0).max(1).default(0.7),
13588
13609
  critical_threshold: exports_external.number().min(0).max(1).default(0.9),
13589
- model_limits: exports_external.record(exports_external.string(), exports_external.number().min(1000)).default({ default: 128000 })
13610
+ model_limits: exports_external.record(exports_external.string(), exports_external.number().min(1000)).default({ default: 128000 }),
13611
+ max_injection_tokens: exports_external.number().min(100).max(50000).default(4000)
13590
13612
  });
13613
+ var EvidenceConfigSchema = exports_external.object({
13614
+ enabled: exports_external.boolean().default(true),
13615
+ max_age_days: exports_external.number().min(1).max(365).default(90),
13616
+ max_bundles: exports_external.number().min(10).max(1e4).default(1000),
13617
+ auto_archive: exports_external.boolean().default(false)
13618
+ });
13619
+ var GuardrailsProfileSchema = exports_external.object({
13620
+ max_tool_calls: exports_external.number().min(10).max(1000).optional(),
13621
+ max_duration_minutes: exports_external.number().min(1).max(120).optional(),
13622
+ max_repetitions: exports_external.number().min(3).max(50).optional(),
13623
+ max_consecutive_errors: exports_external.number().min(2).max(20).optional(),
13624
+ warning_threshold: exports_external.number().min(0.1).max(0.9).optional()
13625
+ });
13626
+ var GuardrailsConfigSchema = exports_external.object({
13627
+ enabled: exports_external.boolean().default(true),
13628
+ max_tool_calls: exports_external.number().min(10).max(1000).default(200),
13629
+ max_duration_minutes: exports_external.number().min(1).max(120).default(30),
13630
+ max_repetitions: exports_external.number().min(3).max(50).default(10),
13631
+ max_consecutive_errors: exports_external.number().min(2).max(20).default(5),
13632
+ warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.5),
13633
+ profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
13634
+ });
13635
+ function resolveGuardrailsConfig(base, agentName) {
13636
+ if (!agentName || !base.profiles?.[agentName]) {
13637
+ return base;
13638
+ }
13639
+ const profile = base.profiles[agentName];
13640
+ return { ...base, ...profile };
13641
+ }
13591
13642
  var PluginConfigSchema = exports_external.object({
13592
13643
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
13593
13644
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
@@ -13595,12 +13646,12 @@ var PluginConfigSchema = exports_external.object({
13595
13646
  qa_retry_limit: exports_external.number().min(1).max(10).default(3),
13596
13647
  inject_phase_reminders: exports_external.boolean().default(true),
13597
13648
  hooks: HooksConfigSchema.optional(),
13598
- context_budget: ContextBudgetConfigSchema.optional()
13649
+ context_budget: ContextBudgetConfigSchema.optional(),
13650
+ guardrails: GuardrailsConfigSchema.optional(),
13651
+ evidence: EvidenceConfigSchema.optional()
13599
13652
  });
13653
+
13600
13654
  // src/config/loader.ts
13601
- import * as fs from "fs";
13602
- import * as os from "os";
13603
- import * as path from "path";
13604
13655
  var CONFIG_FILENAME = "opencode-swarm.json";
13605
13656
  var PROMPTS_DIR_NAME = "opencode-swarm";
13606
13657
  var MAX_CONFIG_FILE_BYTES = 102400;
@@ -13693,6 +13744,125 @@ function loadAgentPrompt(agentName) {
13693
13744
  }
13694
13745
  return result;
13695
13746
  }
13747
+ // src/config/plan-schema.ts
13748
+ var TaskStatusSchema = exports_external.enum([
13749
+ "pending",
13750
+ "in_progress",
13751
+ "completed",
13752
+ "blocked"
13753
+ ]);
13754
+ var TaskSizeSchema = exports_external.enum(["small", "medium", "large"]);
13755
+ var PhaseStatusSchema = exports_external.enum([
13756
+ "pending",
13757
+ "in_progress",
13758
+ "complete",
13759
+ "blocked"
13760
+ ]);
13761
+ var MigrationStatusSchema = exports_external.enum([
13762
+ "native",
13763
+ "migrated",
13764
+ "migration_failed"
13765
+ ]);
13766
+ var TaskSchema = exports_external.object({
13767
+ id: exports_external.string(),
13768
+ phase: exports_external.number().int().min(1),
13769
+ status: TaskStatusSchema.default("pending"),
13770
+ size: TaskSizeSchema.default("small"),
13771
+ description: exports_external.string().min(1),
13772
+ depends: exports_external.array(exports_external.string()).default([]),
13773
+ acceptance: exports_external.string().optional(),
13774
+ files_touched: exports_external.array(exports_external.string()).default([]),
13775
+ evidence_path: exports_external.string().optional(),
13776
+ blocked_reason: exports_external.string().optional()
13777
+ });
13778
+ var PhaseSchema = exports_external.object({
13779
+ id: exports_external.number().int().min(1),
13780
+ name: exports_external.string().min(1),
13781
+ status: PhaseStatusSchema.default("pending"),
13782
+ tasks: exports_external.array(TaskSchema).default([])
13783
+ });
13784
+ var PlanSchema = exports_external.object({
13785
+ schema_version: exports_external.literal("1.0.0"),
13786
+ title: exports_external.string().min(1),
13787
+ swarm: exports_external.string().min(1),
13788
+ current_phase: exports_external.number().int().min(1),
13789
+ phases: exports_external.array(PhaseSchema).min(1),
13790
+ migration_status: MigrationStatusSchema.optional()
13791
+ });
13792
+ // src/config/evidence-schema.ts
13793
+ var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
13794
+ var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
13795
+ var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
13796
+ var EvidenceTypeSchema = exports_external.enum([
13797
+ "review",
13798
+ "test",
13799
+ "diff",
13800
+ "approval",
13801
+ "note"
13802
+ ]);
13803
+ var EvidenceVerdictSchema = exports_external.enum([
13804
+ "pass",
13805
+ "fail",
13806
+ "approved",
13807
+ "rejected",
13808
+ "info"
13809
+ ]);
13810
+ var BaseEvidenceSchema = exports_external.object({
13811
+ task_id: exports_external.string().min(1),
13812
+ type: EvidenceTypeSchema,
13813
+ timestamp: exports_external.string().datetime(),
13814
+ agent: exports_external.string().min(1),
13815
+ verdict: EvidenceVerdictSchema,
13816
+ summary: exports_external.string().min(1),
13817
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
13818
+ });
13819
+ var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
13820
+ type: exports_external.literal("review"),
13821
+ risk: exports_external.enum(["low", "medium", "high", "critical"]),
13822
+ issues: exports_external.array(exports_external.object({
13823
+ severity: exports_external.enum(["error", "warning", "info"]),
13824
+ message: exports_external.string().min(1),
13825
+ file: exports_external.string().optional(),
13826
+ line: exports_external.number().int().optional()
13827
+ })).default([])
13828
+ });
13829
+ var TestEvidenceSchema = BaseEvidenceSchema.extend({
13830
+ type: exports_external.literal("test"),
13831
+ tests_passed: exports_external.number().int().min(0),
13832
+ tests_failed: exports_external.number().int().min(0),
13833
+ test_file: exports_external.string().optional(),
13834
+ failures: exports_external.array(exports_external.object({
13835
+ name: exports_external.string().min(1),
13836
+ message: exports_external.string().min(1)
13837
+ })).default([])
13838
+ });
13839
+ var DiffEvidenceSchema = BaseEvidenceSchema.extend({
13840
+ type: exports_external.literal("diff"),
13841
+ files_changed: exports_external.array(exports_external.string()).default([]),
13842
+ additions: exports_external.number().int().min(0).default(0),
13843
+ deletions: exports_external.number().int().min(0).default(0),
13844
+ patch_path: exports_external.string().optional()
13845
+ });
13846
+ var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
13847
+ type: exports_external.literal("approval")
13848
+ });
13849
+ var NoteEvidenceSchema = BaseEvidenceSchema.extend({
13850
+ type: exports_external.literal("note")
13851
+ });
13852
+ var EvidenceSchema = exports_external.discriminatedUnion("type", [
13853
+ ReviewEvidenceSchema,
13854
+ TestEvidenceSchema,
13855
+ DiffEvidenceSchema,
13856
+ ApprovalEvidenceSchema,
13857
+ NoteEvidenceSchema
13858
+ ]);
13859
+ var EvidenceBundleSchema = exports_external.object({
13860
+ schema_version: exports_external.literal("1.0.0"),
13861
+ task_id: exports_external.string().min(1),
13862
+ entries: exports_external.array(EvidenceSchema).default([]),
13863
+ created_at: exports_external.string().datetime(),
13864
+ updated_at: exports_external.string().datetime()
13865
+ });
13696
13866
  // src/agents/architect.ts
13697
13867
  var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
13698
13868
 
@@ -14388,12 +14558,12 @@ function getAgentConfigs(config2) {
14388
14558
  }
14389
14559
 
14390
14560
  // src/commands/agents.ts
14391
- function handleAgentsCommand(agents) {
14561
+ function handleAgentsCommand(agents, guardrails) {
14392
14562
  const entries = Object.entries(agents);
14393
14563
  if (entries.length === 0) {
14394
14564
  return "No agents registered.";
14395
14565
  }
14396
- const lines = ["## Registered Agents", ""];
14566
+ const lines = [`## Registered Agents (${entries.length} total)`, ""];
14397
14567
  for (const [key, agent] of entries) {
14398
14568
  const model = agent.config.model || "default";
14399
14569
  const temp = agent.config.temperature !== undefined ? agent.config.temperature.toString() : "default";
@@ -14401,43 +14571,46 @@ function handleAgentsCommand(agents) {
14401
14571
  const isReadOnly = tools.write === false || tools.edit === false;
14402
14572
  const access = isReadOnly ? "\uD83D\uDD12 read-only" : "\u270F\uFE0F read-write";
14403
14573
  const desc = agent.description || agent.config.description || "";
14404
- lines.push(`- **${key}** | model: \`${model}\` | temp: ${temp} | ${access}`);
14574
+ const hasCustomProfile = guardrails?.profiles?.[key] !== undefined;
14575
+ const profileIndicator = hasCustomProfile ? " | \u26A1 custom limits" : "";
14576
+ lines.push(`- **${key}** | model: \`${model}\` | temp: ${temp} | ${access}${profileIndicator}`);
14405
14577
  if (desc) {
14406
14578
  lines.push(` ${desc}`);
14407
14579
  }
14408
14580
  }
14581
+ if (guardrails?.profiles && Object.keys(guardrails.profiles).length > 0) {
14582
+ lines.push("", "### Guardrail Profiles", "");
14583
+ for (const [profileName, profile] of Object.entries(guardrails.profiles)) {
14584
+ const overrides = [];
14585
+ if (profile.max_tool_calls !== undefined) {
14586
+ overrides.push(`max_tool_calls=${profile.max_tool_calls}`);
14587
+ }
14588
+ if (profile.max_duration_minutes !== undefined) {
14589
+ overrides.push(`max_duration_minutes=${profile.max_duration_minutes}`);
14590
+ }
14591
+ if (profile.max_repetitions !== undefined) {
14592
+ overrides.push(`max_repetitions=${profile.max_repetitions}`);
14593
+ }
14594
+ if (profile.max_consecutive_errors !== undefined) {
14595
+ overrides.push(`max_consecutive_errors=${profile.max_consecutive_errors}`);
14596
+ }
14597
+ if (profile.warning_threshold !== undefined) {
14598
+ overrides.push(`warning_threshold=${profile.warning_threshold}`);
14599
+ }
14600
+ const overrideStr = overrides.length > 0 ? overrides.join(", ") : "no overrides";
14601
+ lines.push(`- **${profileName}**: ${overrideStr}`);
14602
+ }
14603
+ }
14409
14604
  return lines.join(`
14410
14605
  `);
14411
14606
  }
14412
14607
 
14413
- // src/commands/config.ts
14414
- import * as os2 from "os";
14415
- import * as path2 from "path";
14416
- function getUserConfigDir2() {
14417
- return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
14418
- }
14419
- async function handleConfigCommand(directory, _args) {
14420
- const config2 = loadPluginConfig(directory);
14421
- const userConfigPath = path2.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
14422
- const projectConfigPath = path2.join(directory, ".opencode", "opencode-swarm.json");
14423
- const lines = [
14424
- "## Swarm Configuration",
14425
- "",
14426
- "### Config Files",
14427
- `- User: \`${userConfigPath}\``,
14428
- `- Project: \`${projectConfigPath}\``,
14429
- "",
14430
- "### Resolved Config",
14431
- "```json",
14432
- JSON.stringify(config2, null, 2),
14433
- "```"
14434
- ];
14435
- return lines.join(`
14436
- `);
14437
- }
14608
+ // src/evidence/manager.ts
14609
+ import { mkdirSync, readdirSync, renameSync, rmSync, statSync as statSync2 } from "fs";
14610
+ import * as path3 from "path";
14438
14611
 
14439
14612
  // src/hooks/utils.ts
14440
- import * as path3 from "path";
14613
+ import * as path2 from "path";
14441
14614
 
14442
14615
  // src/utils/errors.ts
14443
14616
  class SwarmError extends Error {
@@ -14504,14 +14677,14 @@ function validateSwarmPath(directory, filename) {
14504
14677
  if (/\.\.[/\\]/.test(filename)) {
14505
14678
  throw new Error("Invalid filename: path traversal detected");
14506
14679
  }
14507
- const baseDir = path3.normalize(path3.resolve(directory, ".swarm"));
14508
- const resolved = path3.normalize(path3.resolve(baseDir, filename));
14680
+ const baseDir = path2.normalize(path2.resolve(directory, ".swarm"));
14681
+ const resolved = path2.normalize(path2.resolve(baseDir, filename));
14509
14682
  if (process.platform === "win32") {
14510
- if (!resolved.toLowerCase().startsWith((baseDir + path3.sep).toLowerCase())) {
14683
+ if (!resolved.toLowerCase().startsWith((baseDir + path2.sep).toLowerCase())) {
14511
14684
  throw new Error("Invalid filename: path escapes .swarm directory");
14512
14685
  }
14513
14686
  } else {
14514
- if (!resolved.startsWith(baseDir + path3.sep)) {
14687
+ if (!resolved.startsWith(baseDir + path2.sep)) {
14515
14688
  throw new Error("Invalid filename: path escapes .swarm directory");
14516
14689
  }
14517
14690
  }
@@ -14534,30 +14707,535 @@ function estimateTokens(text) {
14534
14707
  return Math.ceil(text.length * 0.33);
14535
14708
  }
14536
14709
 
14710
+ // src/evidence/manager.ts
14711
+ var TASK_ID_REGEX = /^[\w-]+(\.[\w-]+)*$/;
14712
+ function sanitizeTaskId(taskId) {
14713
+ if (!taskId || taskId.length === 0) {
14714
+ throw new Error("Invalid task ID: empty string");
14715
+ }
14716
+ if (/\0/.test(taskId)) {
14717
+ throw new Error("Invalid task ID: contains null bytes");
14718
+ }
14719
+ for (let i = 0;i < taskId.length; i++) {
14720
+ if (taskId.charCodeAt(i) < 32) {
14721
+ throw new Error("Invalid task ID: contains control characters");
14722
+ }
14723
+ }
14724
+ if (taskId.includes("..") || taskId.includes("../") || taskId.includes("..\\")) {
14725
+ throw new Error("Invalid task ID: path traversal detected");
14726
+ }
14727
+ if (!TASK_ID_REGEX.test(taskId)) {
14728
+ throw new Error(`Invalid task ID: must match pattern ^[\\w-]+(\\.[\\w-]+)*$, got "${taskId}"`);
14729
+ }
14730
+ return taskId;
14731
+ }
14732
+ async function loadEvidence(directory, taskId) {
14733
+ const sanitizedTaskId = sanitizeTaskId(taskId);
14734
+ const relativePath = path3.join("evidence", sanitizedTaskId, "evidence.json");
14735
+ validateSwarmPath(directory, relativePath);
14736
+ const content = await readSwarmFileAsync(directory, relativePath);
14737
+ if (content === null) {
14738
+ return null;
14739
+ }
14740
+ try {
14741
+ const parsed = JSON.parse(content);
14742
+ const validated = EvidenceBundleSchema.parse(parsed);
14743
+ return validated;
14744
+ } catch (error49) {
14745
+ warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14746
+ return null;
14747
+ }
14748
+ }
14749
+ async function listEvidenceTaskIds(directory) {
14750
+ const evidenceBasePath = validateSwarmPath(directory, "evidence");
14751
+ try {
14752
+ statSync2(evidenceBasePath);
14753
+ } catch {
14754
+ return [];
14755
+ }
14756
+ let entries;
14757
+ try {
14758
+ entries = readdirSync(evidenceBasePath);
14759
+ } catch {
14760
+ return [];
14761
+ }
14762
+ const taskIds = [];
14763
+ for (const entry of entries) {
14764
+ const entryPath = path3.join(evidenceBasePath, entry);
14765
+ try {
14766
+ const stats = statSync2(entryPath);
14767
+ if (!stats.isDirectory()) {
14768
+ continue;
14769
+ }
14770
+ sanitizeTaskId(entry);
14771
+ taskIds.push(entry);
14772
+ } catch (error49) {
14773
+ if (error49 instanceof Error && !error49.message.startsWith("Invalid task ID")) {
14774
+ warn(`Error reading evidence entry '${entry}': ${error49.message}`);
14775
+ }
14776
+ }
14777
+ }
14778
+ return taskIds.sort();
14779
+ }
14780
+ async function deleteEvidence(directory, taskId) {
14781
+ const sanitizedTaskId = sanitizeTaskId(taskId);
14782
+ const relativePath = path3.join("evidence", sanitizedTaskId);
14783
+ const evidenceDir = validateSwarmPath(directory, relativePath);
14784
+ try {
14785
+ statSync2(evidenceDir);
14786
+ } catch {
14787
+ return false;
14788
+ }
14789
+ try {
14790
+ rmSync(evidenceDir, { recursive: true, force: true });
14791
+ return true;
14792
+ } catch (error49) {
14793
+ warn(`Failed to delete evidence for task ${sanitizedTaskId}: ${error49 instanceof Error ? error49.message : String(error49)}`);
14794
+ return false;
14795
+ }
14796
+ }
14797
+ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
14798
+ const taskIds = await listEvidenceTaskIds(directory);
14799
+ const cutoffDate = new Date;
14800
+ cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
14801
+ const cutoffIso = cutoffDate.toISOString();
14802
+ const archived = [];
14803
+ const remainingBundles = [];
14804
+ for (const taskId of taskIds) {
14805
+ const bundle = await loadEvidence(directory, taskId);
14806
+ if (!bundle) {
14807
+ continue;
14808
+ }
14809
+ if (bundle.updated_at < cutoffIso) {
14810
+ const deleted = await deleteEvidence(directory, taskId);
14811
+ if (deleted) {
14812
+ archived.push(taskId);
14813
+ }
14814
+ } else {
14815
+ remainingBundles.push({
14816
+ taskId,
14817
+ updatedAt: bundle.updated_at
14818
+ });
14819
+ }
14820
+ }
14821
+ if (maxBundles !== undefined && remainingBundles.length > maxBundles) {
14822
+ remainingBundles.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
14823
+ const toDelete = remainingBundles.length - maxBundles;
14824
+ for (let i = 0;i < toDelete; i++) {
14825
+ const deleted = await deleteEvidence(directory, remainingBundles[i].taskId);
14826
+ if (deleted) {
14827
+ archived.push(remainingBundles[i].taskId);
14828
+ }
14829
+ }
14830
+ }
14831
+ return archived;
14832
+ }
14833
+
14834
+ // src/commands/archive.ts
14835
+ async function handleArchiveCommand(directory, args) {
14836
+ const config2 = loadPluginConfig(directory);
14837
+ const maxAgeDays = config2?.evidence?.max_age_days ?? 90;
14838
+ const maxBundles = config2?.evidence?.max_bundles ?? 1000;
14839
+ const dryRun = args.includes("--dry-run");
14840
+ const beforeTaskIds = await listEvidenceTaskIds(directory);
14841
+ if (beforeTaskIds.length === 0) {
14842
+ return "No evidence bundles to archive.";
14843
+ }
14844
+ if (dryRun) {
14845
+ const cutoffDate = new Date;
14846
+ cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
14847
+ const cutoffIso = cutoffDate.toISOString();
14848
+ const wouldArchiveAge = [];
14849
+ const remainingBundles = [];
14850
+ for (const taskId of beforeTaskIds) {
14851
+ const bundle = await loadEvidence(directory, taskId);
14852
+ if (bundle && bundle.updated_at < cutoffIso) {
14853
+ wouldArchiveAge.push(taskId);
14854
+ } else if (bundle) {
14855
+ remainingBundles.push({ taskId, updatedAt: bundle.updated_at });
14856
+ }
14857
+ }
14858
+ const wouldArchiveMaxBundles = [];
14859
+ const remainingAfterAge = beforeTaskIds.length - wouldArchiveAge.length;
14860
+ if (remainingAfterAge > maxBundles) {
14861
+ remainingBundles.sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
14862
+ const excessCount = remainingAfterAge - maxBundles;
14863
+ wouldArchiveMaxBundles.push(...remainingBundles.slice(0, excessCount).map((b) => b.taskId));
14864
+ }
14865
+ const totalWouldArchive = wouldArchiveAge.length + wouldArchiveMaxBundles.length;
14866
+ if (totalWouldArchive === 0) {
14867
+ return `No evidence bundles older than ${maxAgeDays} days found, and bundle count (${beforeTaskIds.length}) is within max_bundles limit (${maxBundles}).`;
14868
+ }
14869
+ const lines2 = [
14870
+ "## Archive Preview (dry run)",
14871
+ "",
14872
+ `**Retention**: ${maxAgeDays} days`,
14873
+ `**Max bundles**: ${maxBundles}`,
14874
+ `**Would archive**: ${totalWouldArchive} bundle(s)`
14875
+ ];
14876
+ if (wouldArchiveAge.length > 0) {
14877
+ lines2.push("", `**Age-based (${wouldArchiveAge.length})**:`, ...wouldArchiveAge.map((id) => `- ${id}`));
14878
+ }
14879
+ if (wouldArchiveMaxBundles.length > 0) {
14880
+ lines2.push("", `**Max bundles limit (${wouldArchiveMaxBundles.length})**:`, ...wouldArchiveMaxBundles.map((id) => `- ${id}`));
14881
+ }
14882
+ return lines2.join(`
14883
+ `);
14884
+ }
14885
+ const archived = await archiveEvidence(directory, maxAgeDays, maxBundles);
14886
+ if (archived.length === 0) {
14887
+ return `No evidence bundles older than ${maxAgeDays} days found.`;
14888
+ }
14889
+ const lines = [
14890
+ "## Evidence Archived",
14891
+ "",
14892
+ `**Retention**: ${maxAgeDays} days`,
14893
+ `**Archived**: ${archived.length} bundle(s)`,
14894
+ "",
14895
+ ...archived.map((id) => `- ${id}`)
14896
+ ];
14897
+ return lines.join(`
14898
+ `);
14899
+ }
14900
+
14901
+ // src/commands/config.ts
14902
+ import * as os2 from "os";
14903
+ import * as path4 from "path";
14904
+ function getUserConfigDir2() {
14905
+ return process.env.XDG_CONFIG_HOME || path4.join(os2.homedir(), ".config");
14906
+ }
14907
+ async function handleConfigCommand(directory, _args) {
14908
+ const config2 = loadPluginConfig(directory);
14909
+ const userConfigPath = path4.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
14910
+ const projectConfigPath = path4.join(directory, ".opencode", "opencode-swarm.json");
14911
+ const lines = [
14912
+ "## Swarm Configuration",
14913
+ "",
14914
+ "### Config Files",
14915
+ `- User: \`${userConfigPath}\``,
14916
+ `- Project: \`${projectConfigPath}\``,
14917
+ "",
14918
+ "### Resolved Config",
14919
+ "```json",
14920
+ JSON.stringify(config2, null, 2),
14921
+ "```"
14922
+ ];
14923
+ return lines.join(`
14924
+ `);
14925
+ }
14926
+
14927
+ // src/plan/manager.ts
14928
+ import * as path5 from "path";
14929
+ async function loadPlanJsonOnly(directory) {
14930
+ const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
14931
+ if (planJsonContent !== null) {
14932
+ try {
14933
+ const parsed = JSON.parse(planJsonContent);
14934
+ const validated = PlanSchema.parse(parsed);
14935
+ return validated;
14936
+ } catch (error49) {
14937
+ warn(`Plan validation failed for .swarm/plan.json: ${error49 instanceof Error ? error49.message : String(error49)}`);
14938
+ }
14939
+ }
14940
+ return null;
14941
+ }
14942
+ async function loadPlan(directory) {
14943
+ const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
14944
+ if (planJsonContent !== null) {
14945
+ try {
14946
+ const parsed = JSON.parse(planJsonContent);
14947
+ const validated = PlanSchema.parse(parsed);
14948
+ return validated;
14949
+ } catch (error49) {
14950
+ warn(`Plan validation failed for .swarm/plan.json: ${error49 instanceof Error ? error49.message : String(error49)}`);
14951
+ }
14952
+ }
14953
+ const planMdContent = await readSwarmFileAsync(directory, "plan.md");
14954
+ if (planMdContent !== null) {
14955
+ const migrated = migrateLegacyPlan(planMdContent);
14956
+ await savePlan(directory, migrated);
14957
+ return migrated;
14958
+ }
14959
+ return null;
14960
+ }
14961
+ async function savePlan(directory, plan) {
14962
+ const validated = PlanSchema.parse(plan);
14963
+ const swarmDir = path5.resolve(directory, ".swarm");
14964
+ const planPath = path5.join(swarmDir, "plan.json");
14965
+ const tempPath = path5.join(swarmDir, `plan.json.tmp.${Date.now()}`);
14966
+ await Bun.write(tempPath, JSON.stringify(validated, null, 2));
14967
+ const { renameSync: renameSync2 } = await import("fs");
14968
+ renameSync2(tempPath, planPath);
14969
+ const markdown = derivePlanMarkdown(validated);
14970
+ await Bun.write(path5.join(swarmDir, "plan.md"), markdown);
14971
+ }
14972
+ function derivePlanMarkdown(plan) {
14973
+ const statusMap = {
14974
+ pending: "PENDING",
14975
+ in_progress: "IN PROGRESS",
14976
+ complete: "COMPLETE",
14977
+ blocked: "BLOCKED"
14978
+ };
14979
+ const now = new Date().toISOString();
14980
+ const phaseStatus = statusMap[plan.phases[plan.current_phase - 1]?.status] || "PENDING";
14981
+ let markdown = `# ${plan.title}
14982
+ Swarm: ${plan.swarm}
14983
+ Phase: ${plan.current_phase} [${phaseStatus}] | Updated: ${now}
14984
+ `;
14985
+ for (const phase of plan.phases) {
14986
+ const phaseStatusText = statusMap[phase.status] || "PENDING";
14987
+ markdown += `
14988
+ ## Phase ${phase.id}: ${phase.name} [${phaseStatusText}]
14989
+ `;
14990
+ let currentTaskMarked = false;
14991
+ for (const task of phase.tasks) {
14992
+ let taskLine = "";
14993
+ let suffix = "";
14994
+ if (task.status === "completed") {
14995
+ taskLine = `- [x] ${task.id}: ${task.description}`;
14996
+ } else if (task.status === "blocked") {
14997
+ taskLine = `- [BLOCKED] ${task.id}: ${task.description}`;
14998
+ if (task.blocked_reason) {
14999
+ taskLine += ` - ${task.blocked_reason}`;
15000
+ }
15001
+ } else {
15002
+ taskLine = `- [ ] ${task.id}: ${task.description}`;
15003
+ }
15004
+ taskLine += ` [${task.size.toUpperCase()}]`;
15005
+ if (task.depends.length > 0) {
15006
+ suffix += ` (depends: ${task.depends.join(", ")})`;
15007
+ }
15008
+ if (phase.id === plan.current_phase && task.status === "in_progress" && !currentTaskMarked) {
15009
+ suffix += " \u2190 CURRENT";
15010
+ currentTaskMarked = true;
15011
+ }
15012
+ markdown += `${taskLine}${suffix}
15013
+ `;
15014
+ }
15015
+ }
15016
+ const phaseSections = markdown.split(`
15017
+ ## `);
15018
+ if (phaseSections.length > 1) {
15019
+ const header = phaseSections[0];
15020
+ const phases = phaseSections.slice(1).map((p) => `## ${p}`);
15021
+ markdown = `${header}
15022
+ ---
15023
+ ${phases.join(`
15024
+ ---
15025
+ `)}`;
15026
+ }
15027
+ return `${markdown.trim()}
15028
+ `;
15029
+ }
15030
+ function migrateLegacyPlan(planContent, swarmId) {
15031
+ const lines = planContent.split(`
15032
+ `);
15033
+ let title = "Untitled Plan";
15034
+ let swarm = swarmId || "default-swarm";
15035
+ let currentPhaseNum = 1;
15036
+ const phases = [];
15037
+ let currentPhase = null;
15038
+ for (const line of lines) {
15039
+ const trimmed = line.trim();
15040
+ if (trimmed.startsWith("# ") && title === "Untitled Plan") {
15041
+ title = trimmed.substring(2).trim();
15042
+ continue;
15043
+ }
15044
+ if (trimmed.startsWith("Swarm:")) {
15045
+ swarm = trimmed.substring(6).trim();
15046
+ continue;
15047
+ }
15048
+ if (trimmed.startsWith("Phase:")) {
15049
+ const match = trimmed.match(/Phase:\s*(\d+)/i);
15050
+ if (match) {
15051
+ currentPhaseNum = parseInt(match[1], 10);
15052
+ }
15053
+ continue;
15054
+ }
15055
+ const phaseMatch = trimmed.match(/^##\s*Phase\s+(\d+)(?::\s*([^[]+))?\s*(?:\[([^\]]+)\])?/i);
15056
+ if (phaseMatch) {
15057
+ if (currentPhase !== null) {
15058
+ phases.push(currentPhase);
15059
+ }
15060
+ const phaseId = parseInt(phaseMatch[1], 10);
15061
+ const phaseName = phaseMatch[2]?.trim() || `Phase ${phaseId}`;
15062
+ const statusText = phaseMatch[3]?.toLowerCase() || "pending";
15063
+ const statusMap = {
15064
+ complete: "complete",
15065
+ completed: "complete",
15066
+ "in progress": "in_progress",
15067
+ in_progress: "in_progress",
15068
+ inprogress: "in_progress",
15069
+ pending: "pending",
15070
+ blocked: "blocked"
15071
+ };
15072
+ currentPhase = {
15073
+ id: phaseId,
15074
+ name: phaseName,
15075
+ status: statusMap[statusText] || "pending",
15076
+ tasks: []
15077
+ };
15078
+ continue;
15079
+ }
15080
+ const taskMatch = trimmed.match(/^-\s*\[([^\]]+)\]\s+(\d+\.\d+):\s*(.+?)(?:\s*\[(\w+)\])?(?:\s*-\s*(.+))?$/i);
15081
+ if (taskMatch && currentPhase !== null) {
15082
+ const checkbox = taskMatch[1].toLowerCase();
15083
+ const taskId = taskMatch[2];
15084
+ let description = taskMatch[3].trim();
15085
+ const sizeText = taskMatch[4]?.toLowerCase() || "small";
15086
+ let blockedReason;
15087
+ const dependsMatch = description.match(/\s*\(depends:\s*([^)]+)\)$/i);
15088
+ const depends = [];
15089
+ if (dependsMatch) {
15090
+ const depsText = dependsMatch[1];
15091
+ depends.push(...depsText.split(",").map((d) => d.trim()));
15092
+ description = description.substring(0, dependsMatch.index).trim();
15093
+ }
15094
+ let status = "pending";
15095
+ if (checkbox === "x") {
15096
+ status = "completed";
15097
+ } else if (checkbox === "blocked") {
15098
+ status = "blocked";
15099
+ const blockedReasonMatch = taskMatch[5];
15100
+ if (blockedReasonMatch) {
15101
+ blockedReason = blockedReasonMatch.trim();
15102
+ }
15103
+ }
15104
+ const sizeMap = {
15105
+ small: "small",
15106
+ medium: "medium",
15107
+ large: "large"
15108
+ };
15109
+ const task = {
15110
+ id: taskId,
15111
+ phase: currentPhase.id,
15112
+ status,
15113
+ size: sizeMap[sizeText] || "small",
15114
+ description,
15115
+ depends,
15116
+ acceptance: undefined,
15117
+ files_touched: [],
15118
+ evidence_path: undefined,
15119
+ blocked_reason: blockedReason
15120
+ };
15121
+ currentPhase.tasks.push(task);
15122
+ }
15123
+ }
15124
+ if (currentPhase !== null) {
15125
+ phases.push(currentPhase);
15126
+ }
15127
+ let migrationStatus = "migrated";
15128
+ if (phases.length === 0) {
15129
+ migrationStatus = "migration_failed";
15130
+ phases.push({
15131
+ id: 1,
15132
+ name: "Migration Failed",
15133
+ status: "blocked",
15134
+ tasks: [
15135
+ {
15136
+ id: "1.1",
15137
+ phase: 1,
15138
+ status: "blocked",
15139
+ size: "large",
15140
+ description: "Review and restructure plan manually",
15141
+ depends: [],
15142
+ files_touched: [],
15143
+ blocked_reason: "Legacy plan could not be parsed automatically"
15144
+ }
15145
+ ]
15146
+ });
15147
+ }
15148
+ phases.sort((a, b) => a.id - b.id);
15149
+ const plan = {
15150
+ schema_version: "1.0.0",
15151
+ title,
15152
+ swarm,
15153
+ current_phase: currentPhaseNum,
15154
+ phases,
15155
+ migration_status: migrationStatus
15156
+ };
15157
+ return plan;
15158
+ }
15159
+
14537
15160
  // src/commands/diagnose.ts
14538
15161
  async function handleDiagnoseCommand(directory, _args) {
14539
15162
  const checks3 = [];
14540
- const planContent = await readSwarmFileAsync(directory, "plan.md");
14541
- const contextContent = await readSwarmFileAsync(directory, "context.md");
14542
- if (planContent) {
14543
- const hasPhases = /^## Phase \d+/m.test(planContent);
14544
- const hasTasks = /^- \[[ x]\]/m.test(planContent);
14545
- if (hasPhases && hasTasks) {
15163
+ const plan = await loadPlanJsonOnly(directory);
15164
+ if (plan) {
15165
+ checks3.push({
15166
+ name: "plan.json",
15167
+ status: "\u2705",
15168
+ detail: "Valid schema (v1.0.0)"
15169
+ });
15170
+ if (plan.migration_status === "migrated") {
14546
15171
  checks3.push({
14547
- name: "plan.md",
15172
+ name: "Migration",
14548
15173
  status: "\u2705",
14549
- detail: "Found with valid phase structure"
15174
+ detail: "Plan was migrated from legacy plan.md"
15175
+ });
15176
+ } else if (plan.migration_status === "migration_failed") {
15177
+ checks3.push({
15178
+ name: "Migration",
15179
+ status: "\u274C",
15180
+ detail: "Migration from plan.md failed \u2014 review manually"
15181
+ });
15182
+ }
15183
+ const allTaskIds = new Set;
15184
+ for (const phase of plan.phases) {
15185
+ for (const task of phase.tasks) {
15186
+ allTaskIds.add(task.id);
15187
+ }
15188
+ }
15189
+ const missingDeps = [];
15190
+ for (const phase of plan.phases) {
15191
+ for (const task of phase.tasks) {
15192
+ for (const dep of task.depends) {
15193
+ if (!allTaskIds.has(dep)) {
15194
+ missingDeps.push(`${task.id} depends on missing ${dep}`);
15195
+ }
15196
+ }
15197
+ }
15198
+ }
15199
+ if (missingDeps.length > 0) {
15200
+ checks3.push({
15201
+ name: "Task DAG",
15202
+ status: "\u274C",
15203
+ detail: `Missing dependencies: ${missingDeps.join(", ")}`
14550
15204
  });
15205
+ } else {
15206
+ checks3.push({
15207
+ name: "Task DAG",
15208
+ status: "\u2705",
15209
+ detail: "All dependencies resolved"
15210
+ });
15211
+ }
15212
+ } else {
15213
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
15214
+ if (planContent) {
15215
+ const hasPhases = /^## Phase \d+/m.test(planContent);
15216
+ const hasTasks = /^- \[[ x]\]/m.test(planContent);
15217
+ if (hasPhases && hasTasks) {
15218
+ checks3.push({
15219
+ name: "plan.md",
15220
+ status: "\u2705",
15221
+ detail: "Found with valid phase structure"
15222
+ });
15223
+ } else {
15224
+ checks3.push({
15225
+ name: "plan.md",
15226
+ status: "\u274C",
15227
+ detail: "Found but missing phase/task structure"
15228
+ });
15229
+ }
14551
15230
  } else {
14552
15231
  checks3.push({
14553
15232
  name: "plan.md",
14554
15233
  status: "\u274C",
14555
- detail: "Found but missing phase/task structure"
15234
+ detail: "Not found"
14556
15235
  });
14557
15236
  }
14558
- } else {
14559
- checks3.push({ name: "plan.md", status: "\u274C", detail: "Not found" });
14560
15237
  }
15238
+ const contextContent = await readSwarmFileAsync(directory, "context.md");
14561
15239
  if (contextContent) {
14562
15240
  checks3.push({ name: "context.md", status: "\u2705", detail: "Found" });
14563
15241
  } else {
@@ -14585,6 +15263,39 @@ async function handleDiagnoseCommand(directory, _args) {
14585
15263
  detail: "Invalid configuration"
14586
15264
  });
14587
15265
  }
15266
+ if (plan) {
15267
+ const completedTaskIds = [];
15268
+ for (const phase of plan.phases) {
15269
+ for (const task of phase.tasks) {
15270
+ if (task.status === "completed") {
15271
+ completedTaskIds.push(task.id);
15272
+ }
15273
+ }
15274
+ }
15275
+ if (completedTaskIds.length > 0) {
15276
+ const evidenceTaskIds = new Set(await listEvidenceTaskIds(directory));
15277
+ const missingEvidence = completedTaskIds.filter((id) => !evidenceTaskIds.has(id));
15278
+ if (missingEvidence.length === 0) {
15279
+ checks3.push({
15280
+ name: "Evidence",
15281
+ status: "\u2705",
15282
+ detail: `All ${completedTaskIds.length} completed tasks have evidence`
15283
+ });
15284
+ } else {
15285
+ checks3.push({
15286
+ name: "Evidence",
15287
+ status: "\u274C",
15288
+ detail: `${missingEvidence.length} completed task(s) missing evidence: ${missingEvidence.join(", ")}`
15289
+ });
15290
+ }
15291
+ } else {
15292
+ checks3.push({
15293
+ name: "Evidence",
15294
+ status: "\u2705",
15295
+ detail: "No completed tasks yet"
15296
+ });
15297
+ }
15298
+ }
14588
15299
  const passCount = checks3.filter((c) => c.status === "\u2705").length;
14589
15300
  const totalCount = checks3.length;
14590
15301
  const allPassed = passCount === totalCount;
@@ -14599,14 +15310,98 @@ async function handleDiagnoseCommand(directory, _args) {
14599
15310
  `);
14600
15311
  }
14601
15312
 
15313
+ // src/commands/evidence.ts
15314
+ async function handleEvidenceCommand(directory, args) {
15315
+ if (args.length === 0) {
15316
+ const taskIds = await listEvidenceTaskIds(directory);
15317
+ if (taskIds.length === 0) {
15318
+ return "No evidence bundles found.";
15319
+ }
15320
+ const tableLines = [
15321
+ "## Evidence Bundles",
15322
+ "",
15323
+ "| Task | Entries | Last Updated |",
15324
+ "|------|---------|-------------|"
15325
+ ];
15326
+ for (const taskId2 of taskIds) {
15327
+ const bundle2 = await loadEvidence(directory, taskId2);
15328
+ if (bundle2) {
15329
+ const entryCount = bundle2.entries.length;
15330
+ const lastUpdated = bundle2.updated_at;
15331
+ tableLines.push(`| ${taskId2} | ${entryCount} | ${lastUpdated} |`);
15332
+ } else {
15333
+ tableLines.push(`| ${taskId2} | ? | unknown |`);
15334
+ }
15335
+ }
15336
+ return tableLines.join(`
15337
+ `);
15338
+ }
15339
+ const taskId = args[0];
15340
+ const bundle = await loadEvidence(directory, taskId);
15341
+ if (!bundle) {
15342
+ return `No evidence found for task ${taskId}.`;
15343
+ }
15344
+ const lines = [
15345
+ `## Evidence for Task ${taskId}`,
15346
+ "",
15347
+ `**Created**: ${bundle.created_at}`,
15348
+ `**Updated**: ${bundle.updated_at}`,
15349
+ `**Entries**: ${bundle.entries.length}`
15350
+ ];
15351
+ if (bundle.entries.length > 0) {
15352
+ lines.push("");
15353
+ }
15354
+ for (let i = 0;i < bundle.entries.length; i++) {
15355
+ const entry = bundle.entries[i];
15356
+ lines.push(...formatEntry(i + 1, entry));
15357
+ }
15358
+ return lines.join(`
15359
+ `);
15360
+ }
15361
+ function formatEntry(index, entry) {
15362
+ const lines = [];
15363
+ const verdictEmoji = getVerdictEmoji(entry.verdict);
15364
+ lines.push(`### Entry ${index}: ${entry.type} (${entry.verdict}) ${verdictEmoji}`);
15365
+ lines.push(`- **Agent**: ${entry.agent}`);
15366
+ lines.push(`- **Summary**: ${entry.summary}`);
15367
+ lines.push(`- **Time**: ${entry.timestamp}`);
15368
+ if (entry.type === "review") {
15369
+ const reviewEntry = entry;
15370
+ lines.push(`- **Risk Level**: ${reviewEntry.risk}`);
15371
+ if (reviewEntry.issues && reviewEntry.issues.length > 0) {
15372
+ lines.push(`- **Issues**: ${reviewEntry.issues.length}`);
15373
+ }
15374
+ } else if (entry.type === "test") {
15375
+ const testEntry = entry;
15376
+ lines.push(`- **Tests**: ${testEntry.tests_passed} passed, ${testEntry.tests_failed} failed`);
15377
+ }
15378
+ lines.push("");
15379
+ return lines;
15380
+ }
15381
+ function getVerdictEmoji(verdict) {
15382
+ switch (verdict) {
15383
+ case "pass":
15384
+ case "approved":
15385
+ return "\u2705";
15386
+ case "fail":
15387
+ case "rejected":
15388
+ return "\u274C";
15389
+ case "info":
15390
+ return "\u2139\uFE0F";
15391
+ default:
15392
+ return "";
15393
+ }
15394
+ }
15395
+
14602
15396
  // src/commands/export.ts
14603
15397
  async function handleExportCommand(directory, _args) {
15398
+ const planStructured = await loadPlanJsonOnly(directory);
14604
15399
  const planContent = await readSwarmFileAsync(directory, "plan.md");
14605
15400
  const contextContent = await readSwarmFileAsync(directory, "context.md");
14606
15401
  const exportData = {
14607
15402
  version: "4.5.0",
14608
15403
  exported: new Date().toISOString(),
14609
- plan: planContent,
15404
+ plan: planStructured || planContent,
14610
15405
  context: contextContent
14611
15406
  };
14612
15407
  const lines = [
@@ -14622,6 +15417,34 @@ async function handleExportCommand(directory, _args) {
14622
15417
 
14623
15418
  // src/commands/history.ts
14624
15419
  async function handleHistoryCommand(directory, _args) {
15420
+ const plan = await loadPlanJsonOnly(directory);
15421
+ if (plan) {
15422
+ if (plan.phases.length === 0) {
15423
+ return "No history available.";
15424
+ }
15425
+ const tableLines2 = [
15426
+ "## Swarm History",
15427
+ "",
15428
+ "| Phase | Name | Status | Tasks |",
15429
+ "|-------|------|--------|-------|"
15430
+ ];
15431
+ for (const phase of plan.phases) {
15432
+ const statusMap = {
15433
+ complete: "COMPLETE",
15434
+ in_progress: "IN PROGRESS",
15435
+ pending: "PENDING",
15436
+ blocked: "BLOCKED"
15437
+ };
15438
+ const statusText = statusMap[phase.status] || "PENDING";
15439
+ const statusIcon = phase.status === "complete" ? "\u2705" : phase.status === "in_progress" ? "\uD83D\uDD04" : phase.status === "blocked" ? "\uD83D\uDEAB" : "\u23F3";
15440
+ const completed = phase.tasks.filter((t) => t.status === "completed").length;
15441
+ const total = phase.tasks.length;
15442
+ const tasks = total > 0 ? `${completed}/${total}` : "-";
15443
+ tableLines2.push(`| ${phase.id} | ${phase.name} | ${statusIcon} ${statusText} | ${tasks} |`);
15444
+ }
15445
+ return tableLines2.join(`
15446
+ `);
15447
+ }
14625
15448
  const planContent = await readSwarmFileAsync(directory, "plan.md");
14626
15449
  if (!planContent) {
14627
15450
  return "No history available.";
@@ -14673,6 +15496,46 @@ async function handleHistoryCommand(directory, _args) {
14673
15496
 
14674
15497
  // src/commands/plan.ts
14675
15498
  async function handlePlanCommand(directory, args) {
15499
+ const plan = await loadPlanJsonOnly(directory);
15500
+ if (plan) {
15501
+ if (args.length === 0) {
15502
+ return derivePlanMarkdown(plan);
15503
+ }
15504
+ const phaseNum2 = parseInt(args[0], 10);
15505
+ if (Number.isNaN(phaseNum2)) {
15506
+ return derivePlanMarkdown(plan);
15507
+ }
15508
+ const phase = plan.phases.find((p) => p.id === phaseNum2);
15509
+ if (!phase) {
15510
+ return `Phase ${phaseNum2} not found in plan.`;
15511
+ }
15512
+ const fullMarkdown = derivePlanMarkdown(plan);
15513
+ const lines2 = fullMarkdown.split(`
15514
+ `);
15515
+ const phaseLines2 = [];
15516
+ let inTargetPhase2 = false;
15517
+ for (const line of lines2) {
15518
+ const phaseMatch = line.match(/^## Phase (\d+)/);
15519
+ if (phaseMatch) {
15520
+ const num = parseInt(phaseMatch[1], 10);
15521
+ if (num === phaseNum2) {
15522
+ inTargetPhase2 = true;
15523
+ phaseLines2.push(line);
15524
+ continue;
15525
+ } else if (inTargetPhase2) {
15526
+ break;
15527
+ }
15528
+ }
15529
+ if (inTargetPhase2 && line.trim() === "---" && phaseLines2.length > 1) {
15530
+ break;
15531
+ }
15532
+ if (inTargetPhase2) {
15533
+ phaseLines2.push(line);
15534
+ }
15535
+ }
15536
+ return phaseLines2.length > 0 ? phaseLines2.join(`
15537
+ `).trim() : `Phase ${phaseNum2} not found in plan.`;
15538
+ }
14676
15539
  const planContent = await readSwarmFileAsync(directory, "plan.md");
14677
15540
  if (!planContent) {
14678
15541
  return "No active swarm plan found.";
@@ -14900,13 +15763,81 @@ function extractPatterns(contextContent, maxChars = 500) {
14900
15763
  }
14901
15764
  return `${trimmed.slice(0, maxChars)}...`;
14902
15765
  }
15766
+ function extractCurrentPhaseFromPlan(plan) {
15767
+ const phase = plan.phases.find((p) => p.id === plan.current_phase);
15768
+ if (!phase)
15769
+ return null;
15770
+ const statusMap = {
15771
+ pending: "PENDING",
15772
+ in_progress: "IN PROGRESS",
15773
+ complete: "COMPLETE",
15774
+ blocked: "BLOCKED"
15775
+ };
15776
+ const statusText = statusMap[phase.status] || "PENDING";
15777
+ return `Phase ${phase.id}: ${phase.name} [${statusText}]`;
15778
+ }
15779
+ function extractCurrentTaskFromPlan(plan) {
15780
+ const phase = plan.phases.find((p) => p.id === plan.current_phase);
15781
+ if (!phase)
15782
+ return null;
15783
+ const inProgress = phase.tasks.find((t) => t.status === "in_progress");
15784
+ if (inProgress) {
15785
+ const deps = inProgress.depends.length > 0 ? ` (depends: ${inProgress.depends.join(", ")})` : "";
15786
+ return `- [ ] ${inProgress.id}: ${inProgress.description} [${inProgress.size.toUpperCase()}]${deps} \u2190 CURRENT`;
15787
+ }
15788
+ const pending = phase.tasks.find((t) => t.status === "pending");
15789
+ if (pending) {
15790
+ const deps = pending.depends.length > 0 ? ` (depends: ${pending.depends.join(", ")})` : "";
15791
+ return `- [ ] ${pending.id}: ${pending.description} [${pending.size.toUpperCase()}]${deps}`;
15792
+ }
15793
+ return null;
15794
+ }
15795
+ function extractIncompleteTasksFromPlan(plan, maxChars = 500) {
15796
+ const phase = plan.phases.find((p) => p.id === plan.current_phase);
15797
+ if (!phase)
15798
+ return null;
15799
+ const incomplete = phase.tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
15800
+ if (incomplete.length === 0)
15801
+ return null;
15802
+ const lines = incomplete.map((t) => {
15803
+ const deps = t.depends.length > 0 ? ` (depends: ${t.depends.join(", ")})` : "";
15804
+ return `- [ ] ${t.id}: ${t.description} [${t.size.toUpperCase()}]${deps}`;
15805
+ });
15806
+ const text = lines.join(`
15807
+ `);
15808
+ if (text.length <= maxChars)
15809
+ return text;
15810
+ return `${text.slice(0, maxChars)}...`;
15811
+ }
14903
15812
 
14904
15813
  // src/commands/status.ts
14905
15814
  async function handleStatusCommand(directory, agents) {
15815
+ const plan = await loadPlan(directory);
15816
+ if (plan && plan.migration_status !== "migration_failed") {
15817
+ const currentPhase2 = extractCurrentPhaseFromPlan(plan) || "Unknown";
15818
+ let completedTasks2 = 0;
15819
+ let totalTasks2 = 0;
15820
+ for (const phase of plan.phases) {
15821
+ for (const task of phase.tasks) {
15822
+ totalTasks2++;
15823
+ if (task.status === "completed")
15824
+ completedTasks2++;
15825
+ }
15826
+ }
15827
+ const agentCount2 = Object.keys(agents).length;
15828
+ const lines2 = [
15829
+ "## Swarm Status",
15830
+ "",
15831
+ `**Current Phase**: ${currentPhase2}`,
15832
+ `**Tasks**: ${completedTasks2}/${totalTasks2} complete`,
15833
+ `**Agents**: ${agentCount2} registered`
15834
+ ];
15835
+ return lines2.join(`
15836
+ `);
15837
+ }
14906
15838
  const planContent = await readSwarmFileAsync(directory, "plan.md");
14907
- if (!planContent) {
15839
+ if (!planContent)
14908
15840
  return "No active swarm plan found.";
14909
- }
14910
15841
  const currentPhase = extractCurrentPhase(planContent) || "Unknown";
14911
15842
  const completedTasks = (planContent.match(/^- \[x\]/gm) || []).length;
14912
15843
  const incompleteTasks = (planContent.match(/^- \[ \]/gm) || []).length;
@@ -14932,6 +15863,8 @@ var HELP_TEXT = [
14932
15863
  "- `/swarm agents` \u2014 List registered agents",
14933
15864
  "- `/swarm history` \u2014 Show completed phases summary",
14934
15865
  "- `/swarm config` \u2014 Show current resolved configuration",
15866
+ "- `/swarm evidence [taskId]` \u2014 Show evidence bundles",
15867
+ "- `/swarm archive [--dry-run]` \u2014 Archive old evidence bundles",
14935
15868
  "- `/swarm diagnose` \u2014 Run health check on swarm state",
14936
15869
  "- `/swarm export` \u2014 Export plan and context as JSON",
14937
15870
  "- `/swarm reset --confirm` \u2014 Clear swarm state files"
@@ -14952,8 +15885,14 @@ function createSwarmCommandHandler(directory, agents) {
14952
15885
  case "plan":
14953
15886
  text = await handlePlanCommand(directory, args);
14954
15887
  break;
14955
- case "agents":
14956
- text = handleAgentsCommand(agents);
15888
+ case "agents": {
15889
+ const pluginConfig = loadPluginConfig(directory);
15890
+ const guardrailsConfig = pluginConfig?.guardrails ? GuardrailsConfigSchema.parse(pluginConfig.guardrails) : undefined;
15891
+ text = handleAgentsCommand(agents, guardrailsConfig);
15892
+ break;
15893
+ }
15894
+ case "archive":
15895
+ text = await handleArchiveCommand(directory, args);
14957
15896
  break;
14958
15897
  case "history":
14959
15898
  text = await handleHistoryCommand(directory, args);
@@ -14961,6 +15900,9 @@ function createSwarmCommandHandler(directory, agents) {
14961
15900
  case "config":
14962
15901
  text = await handleConfigCommand(directory, args);
14963
15902
  break;
15903
+ case "evidence":
15904
+ text = await handleEvidenceCommand(directory, args);
15905
+ break;
14964
15906
  case "diagnose":
14965
15907
  text = await handleDiagnoseCommand(directory, args);
14966
15908
  break;
@@ -14986,8 +15928,34 @@ var swarmState = {
14986
15928
  toolAggregates: new Map,
14987
15929
  activeAgent: new Map,
14988
15930
  delegationChains: new Map,
14989
- pendingEvents: 0
15931
+ pendingEvents: 0,
15932
+ agentSessions: new Map
14990
15933
  };
15934
+ function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
15935
+ const now = Date.now();
15936
+ const staleIds = [];
15937
+ for (const [id, session] of swarmState.agentSessions) {
15938
+ if (now - session.startTime > staleDurationMs) {
15939
+ staleIds.push(id);
15940
+ }
15941
+ }
15942
+ for (const id of staleIds) {
15943
+ swarmState.agentSessions.delete(id);
15944
+ }
15945
+ const sessionState = {
15946
+ agentName,
15947
+ startTime: now,
15948
+ toolCallCount: 0,
15949
+ consecutiveErrors: 0,
15950
+ recentToolCalls: [],
15951
+ warningIssued: false,
15952
+ hardLimitHit: false
15953
+ };
15954
+ swarmState.agentSessions.set(sessionId, sessionState);
15955
+ }
15956
+ function getAgentSession(sessionId) {
15957
+ return swarmState.agentSessions.get(sessionId);
15958
+ }
14991
15959
 
14992
15960
  // src/hooks/agent-activity.ts
14993
15961
  function createAgentActivityHooks(config2, directory) {
@@ -15057,8 +16025,8 @@ async function doFlush(directory) {
15057
16025
  const activitySection = renderActivitySection();
15058
16026
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
15059
16027
  const flushedCount = swarmState.pendingEvents;
15060
- const path4 = `${directory}/.swarm/context.md`;
15061
- await Bun.write(path4, updated);
16028
+ const path6 = `${directory}/.swarm/context.md`;
16029
+ await Bun.write(path6, updated);
15062
16030
  swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
15063
16031
  } catch (error49) {
15064
16032
  warn("Agent activity flush failed:", error49);
@@ -15107,13 +16075,29 @@ function createCompactionCustomizerHook(config2, directory) {
15107
16075
  }
15108
16076
  return {
15109
16077
  "experimental.session.compacting": safeHook(async (_input, output) => {
15110
- const planContent = await readSwarmFileAsync(directory, "plan.md");
15111
16078
  const contextContent = await readSwarmFileAsync(directory, "context.md");
15112
- if (planContent) {
15113
- const currentPhase = extractCurrentPhase(planContent);
16079
+ const plan = await loadPlan(directory);
16080
+ if (plan && plan.migration_status !== "migration_failed") {
16081
+ const currentPhase = extractCurrentPhaseFromPlan(plan);
15114
16082
  if (currentPhase) {
15115
16083
  output.context.push(`[SWARM PLAN] ${currentPhase}`);
15116
16084
  }
16085
+ const incompleteTasks = extractIncompleteTasksFromPlan(plan);
16086
+ if (incompleteTasks) {
16087
+ output.context.push(`[SWARM TASKS] ${incompleteTasks}`);
16088
+ }
16089
+ } else {
16090
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
16091
+ if (planContent) {
16092
+ const currentPhase = extractCurrentPhase(planContent);
16093
+ if (currentPhase) {
16094
+ output.context.push(`[SWARM PLAN] ${currentPhase}`);
16095
+ }
16096
+ const incompleteTasks = extractIncompleteTasks(planContent);
16097
+ if (incompleteTasks) {
16098
+ output.context.push(`[SWARM TASKS] ${incompleteTasks}`);
16099
+ }
16100
+ }
15117
16101
  }
15118
16102
  if (contextContent) {
15119
16103
  const decisionsSummary = extractDecisions(contextContent);
@@ -15121,12 +16105,6 @@ function createCompactionCustomizerHook(config2, directory) {
15121
16105
  output.context.push(`[SWARM DECISIONS] ${decisionsSummary}`);
15122
16106
  }
15123
16107
  }
15124
- if (planContent) {
15125
- const incompleteTasks = extractIncompleteTasks(planContent);
15126
- if (incompleteTasks) {
15127
- output.context.push(`[SWARM TASKS] ${incompleteTasks}`);
15128
- }
15129
- }
15130
16108
  if (contextContent) {
15131
16109
  const patterns = extractPatterns(contextContent);
15132
16110
  if (patterns) {
@@ -15221,6 +16199,140 @@ function createDelegationTrackerHook(config2) {
15221
16199
  }
15222
16200
  };
15223
16201
  }
16202
+ // src/hooks/guardrails.ts
16203
+ function createGuardrailsHooks(config2) {
16204
+ if (config2.enabled === false) {
16205
+ return {
16206
+ toolBefore: async () => {},
16207
+ toolAfter: async () => {},
16208
+ messagesTransform: async () => {}
16209
+ };
16210
+ }
16211
+ return {
16212
+ toolBefore: async (input, output) => {
16213
+ let session = getAgentSession(input.sessionID);
16214
+ if (!session) {
16215
+ startAgentSession(input.sessionID, "unknown");
16216
+ session = getAgentSession(input.sessionID);
16217
+ if (!session) {
16218
+ warn(`Failed to create session for ${input.sessionID}`);
16219
+ return;
16220
+ }
16221
+ }
16222
+ const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
16223
+ if (session.hardLimitHit) {
16224
+ throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
16225
+ }
16226
+ session.toolCallCount++;
16227
+ const hash2 = hashArgs(output.args);
16228
+ session.recentToolCalls.push({
16229
+ tool: input.tool,
16230
+ argsHash: hash2,
16231
+ timestamp: Date.now()
16232
+ });
16233
+ if (session.recentToolCalls.length > 20) {
16234
+ session.recentToolCalls.shift();
16235
+ }
16236
+ let repetitionCount = 0;
16237
+ if (session.recentToolCalls.length > 0) {
16238
+ const lastEntry = session.recentToolCalls[session.recentToolCalls.length - 1];
16239
+ for (let i = session.recentToolCalls.length - 1;i >= 0; i--) {
16240
+ const entry = session.recentToolCalls[i];
16241
+ if (entry.tool === lastEntry.tool && entry.argsHash === lastEntry.argsHash) {
16242
+ repetitionCount++;
16243
+ } else {
16244
+ break;
16245
+ }
16246
+ }
16247
+ }
16248
+ const elapsedMinutes = (Date.now() - session.startTime) / 60000;
16249
+ if (session.toolCallCount >= agentConfig.max_tool_calls) {
16250
+ session.hardLimitHit = true;
16251
+ throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Tool call limit reached (${session.toolCallCount}/${agentConfig.max_tool_calls}). Stop making tool calls and return your progress summary.`);
16252
+ }
16253
+ if (elapsedMinutes >= agentConfig.max_duration_minutes) {
16254
+ session.hardLimitHit = true;
16255
+ throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Duration limit reached (${Math.floor(elapsedMinutes)} min). Stop making tool calls and return your progress summary.`);
16256
+ }
16257
+ if (repetitionCount >= agentConfig.max_repetitions) {
16258
+ session.hardLimitHit = true;
16259
+ throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Repetition detected (same call ${repetitionCount} times). Stop making tool calls and return your progress summary.`);
16260
+ }
16261
+ if (session.consecutiveErrors >= agentConfig.max_consecutive_errors) {
16262
+ session.hardLimitHit = true;
16263
+ throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Too many consecutive errors (${session.consecutiveErrors}). Stop making tool calls and return your progress summary.`);
16264
+ }
16265
+ if (!session.warningIssued) {
16266
+ const toolWarning = session.toolCallCount >= agentConfig.max_tool_calls * agentConfig.warning_threshold;
16267
+ const durationWarning = elapsedMinutes >= agentConfig.max_duration_minutes * agentConfig.warning_threshold;
16268
+ const repetitionWarning = repetitionCount >= agentConfig.max_repetitions * agentConfig.warning_threshold;
16269
+ const errorWarning = session.consecutiveErrors >= agentConfig.max_consecutive_errors * agentConfig.warning_threshold;
16270
+ if (toolWarning || durationWarning || repetitionWarning || errorWarning) {
16271
+ session.warningIssued = true;
16272
+ }
16273
+ }
16274
+ },
16275
+ toolAfter: async (input, output) => {
16276
+ const session = getAgentSession(input.sessionID);
16277
+ if (!session) {
16278
+ return;
16279
+ }
16280
+ const hasError = output.output === null || output.output === undefined;
16281
+ if (hasError) {
16282
+ session.consecutiveErrors++;
16283
+ } else {
16284
+ session.consecutiveErrors = 0;
16285
+ }
16286
+ },
16287
+ messagesTransform: async (_input, output) => {
16288
+ const messages = output.messages;
16289
+ if (!messages || messages.length === 0) {
16290
+ return;
16291
+ }
16292
+ const lastMessage = messages[messages.length - 1];
16293
+ let sessionId = lastMessage.info?.sessionID;
16294
+ if (!sessionId) {
16295
+ for (const [id, session2] of swarmState.agentSessions) {
16296
+ if (session2.warningIssued || session2.hardLimitHit) {
16297
+ sessionId = id;
16298
+ break;
16299
+ }
16300
+ }
16301
+ }
16302
+ if (!sessionId) {
16303
+ return;
16304
+ }
16305
+ const session = getAgentSession(sessionId);
16306
+ if (!session || !session.warningIssued && !session.hardLimitHit) {
16307
+ return;
16308
+ }
16309
+ const textPart = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
16310
+ if (!textPart) {
16311
+ return;
16312
+ }
16313
+ if (session.hardLimitHit) {
16314
+ textPart.text = `[\uD83D\uDED1 CIRCUIT BREAKER ACTIVE: You have exceeded your resource limits. Do NOT make any more tool calls. Immediately return a summary of your progress so far. Any further tool calls will be blocked.]
16315
+
16316
+ ` + textPart.text;
16317
+ } else if (session.warningIssued) {
16318
+ textPart.text = `[\u26A0\uFE0F GUARDRAIL WARNING: You are approaching resource limits. Please wrap up your current task efficiently. Avoid unnecessary tool calls and prepare to return your results soon.]
16319
+
16320
+ ` + textPart.text;
16321
+ }
16322
+ }
16323
+ };
16324
+ }
16325
+ function hashArgs(args) {
16326
+ try {
16327
+ if (typeof args !== "object" || args === null) {
16328
+ return 0;
16329
+ }
16330
+ const sortedKeys = Object.keys(args).sort();
16331
+ return Number(Bun.hash(JSON.stringify(args, sortedKeys)));
16332
+ } catch {
16333
+ return 0;
16334
+ }
16335
+ }
15224
16336
  // src/hooks/pipeline-tracker.ts
15225
16337
  var PHASE_REMINDER = `<swarm_reminder>
15226
16338
  \u26A0\uFE0F ARCHITECT WORKFLOW REMINDER:
@@ -15283,29 +16395,51 @@ function createSystemEnhancerHook(config2, directory) {
15283
16395
  return {
15284
16396
  "experimental.chat.system.transform": safeHook(async (_input, output) => {
15285
16397
  try {
15286
- const planContent = await readSwarmFileAsync(directory, "plan.md");
16398
+ let tryInject = function(text) {
16399
+ const tokens = estimateTokens(text);
16400
+ if (injectedTokens + tokens > maxInjectionTokens) {
16401
+ return;
16402
+ }
16403
+ output.system.push(text);
16404
+ injectedTokens += tokens;
16405
+ };
16406
+ const maxInjectionTokens = config2.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
16407
+ let injectedTokens = 0;
15287
16408
  const contextContent = await readSwarmFileAsync(directory, "context.md");
15288
- if (planContent) {
15289
- const currentPhase = extractCurrentPhase(planContent);
16409
+ const plan = await loadPlan(directory);
16410
+ if (plan && plan.migration_status !== "migration_failed") {
16411
+ const currentPhase = extractCurrentPhaseFromPlan(plan);
15290
16412
  if (currentPhase) {
15291
- output.system.push(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
16413
+ tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
15292
16414
  }
15293
- const currentTask = extractCurrentTask(planContent);
16415
+ const currentTask = extractCurrentTaskFromPlan(plan);
15294
16416
  if (currentTask) {
15295
- output.system.push(`[SWARM CONTEXT] Current task: ${currentTask}`);
16417
+ tryInject(`[SWARM CONTEXT] Current task: ${currentTask}`);
16418
+ }
16419
+ } else {
16420
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
16421
+ if (planContent) {
16422
+ const currentPhase = extractCurrentPhase(planContent);
16423
+ if (currentPhase) {
16424
+ tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase}`);
16425
+ }
16426
+ const currentTask = extractCurrentTask(planContent);
16427
+ if (currentTask) {
16428
+ tryInject(`[SWARM CONTEXT] Current task: ${currentTask}`);
16429
+ }
15296
16430
  }
15297
16431
  }
15298
16432
  if (contextContent) {
15299
16433
  const decisions = extractDecisions(contextContent, 200);
15300
16434
  if (decisions) {
15301
- output.system.push(`[SWARM CONTEXT] Key decisions: ${decisions}`);
16435
+ tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
15302
16436
  }
15303
16437
  if (config2.hooks?.agent_activity !== false && _input.sessionID) {
15304
16438
  const activeAgent = swarmState.activeAgent.get(_input.sessionID);
15305
16439
  if (activeAgent) {
15306
16440
  const agentContext = extractAgentContext(contextContent, activeAgent, config2.hooks?.agent_awareness_max_chars ?? 300);
15307
16441
  if (agentContext) {
15308
- output.system.push(`[SWARM AGENT CONTEXT] ${agentContext}`);
16442
+ tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
15309
16443
  }
15310
16444
  }
15311
16445
  }
@@ -16077,10 +17211,10 @@ function mergeDefs2(...defs) {
16077
17211
  function cloneDef2(schema) {
16078
17212
  return mergeDefs2(schema._zod.def);
16079
17213
  }
16080
- function getElementAtPath2(obj, path4) {
16081
- if (!path4)
17214
+ function getElementAtPath2(obj, path6) {
17215
+ if (!path6)
16082
17216
  return obj;
16083
- return path4.reduce((acc, key) => acc?.[key], obj);
17217
+ return path6.reduce((acc, key) => acc?.[key], obj);
16084
17218
  }
16085
17219
  function promiseAllObject2(promisesObj) {
16086
17220
  const keys = Object.keys(promisesObj);
@@ -16439,11 +17573,11 @@ function aborted2(x, startIndex = 0) {
16439
17573
  }
16440
17574
  return false;
16441
17575
  }
16442
- function prefixIssues2(path4, issues) {
17576
+ function prefixIssues2(path6, issues) {
16443
17577
  return issues.map((iss) => {
16444
17578
  var _a2;
16445
17579
  (_a2 = iss).path ?? (_a2.path = []);
16446
- iss.path.unshift(path4);
17580
+ iss.path.unshift(path6);
16447
17581
  return iss;
16448
17582
  });
16449
17583
  }
@@ -16611,7 +17745,7 @@ function treeifyError2(error49, _mapper) {
16611
17745
  return issue3.message;
16612
17746
  };
16613
17747
  const result = { errors: [] };
16614
- const processError = (error50, path4 = []) => {
17748
+ const processError = (error50, path6 = []) => {
16615
17749
  var _a2, _b;
16616
17750
  for (const issue3 of error50.issues) {
16617
17751
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -16621,7 +17755,7 @@ function treeifyError2(error49, _mapper) {
16621
17755
  } else if (issue3.code === "invalid_element") {
16622
17756
  processError({ issues: issue3.issues }, issue3.path);
16623
17757
  } else {
16624
- const fullpath = [...path4, ...issue3.path];
17758
+ const fullpath = [...path6, ...issue3.path];
16625
17759
  if (fullpath.length === 0) {
16626
17760
  result.errors.push(mapper(issue3));
16627
17761
  continue;
@@ -16653,8 +17787,8 @@ function treeifyError2(error49, _mapper) {
16653
17787
  }
16654
17788
  function toDotPath2(_path) {
16655
17789
  const segs = [];
16656
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
16657
- for (const seg of path4) {
17790
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
17791
+ for (const seg of path6) {
16658
17792
  if (typeof seg === "number")
16659
17793
  segs.push(`[${seg}]`);
16660
17794
  else if (typeof seg === "symbol")
@@ -27850,7 +28984,7 @@ Use these as DOMAIN values when delegating to @sme.`;
27850
28984
  });
27851
28985
  // src/tools/file-extractor.ts
27852
28986
  import * as fs3 from "fs";
27853
- import * as path4 from "path";
28987
+ import * as path6 from "path";
27854
28988
  var EXT_MAP = {
27855
28989
  python: ".py",
27856
28990
  py: ".py",
@@ -27928,12 +29062,12 @@ var extract_code_blocks = tool({
27928
29062
  if (prefix) {
27929
29063
  filename = `${prefix}_${filename}`;
27930
29064
  }
27931
- let filepath = path4.join(targetDir, filename);
27932
- const base = path4.basename(filepath, path4.extname(filepath));
27933
- const ext = path4.extname(filepath);
29065
+ let filepath = path6.join(targetDir, filename);
29066
+ const base = path6.basename(filepath, path6.extname(filepath));
29067
+ const ext = path6.extname(filepath);
27934
29068
  let counter = 1;
27935
29069
  while (fs3.existsSync(filepath)) {
27936
- filepath = path4.join(targetDir, `${base}_${counter}${ext}`);
29070
+ filepath = path6.join(targetDir, `${base}_${counter}${ext}`);
27937
29071
  counter++;
27938
29072
  }
27939
29073
  try {
@@ -27965,7 +29099,7 @@ Errors:
27965
29099
  var GITINGEST_TIMEOUT_MS = 1e4;
27966
29100
  var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
27967
29101
  var GITINGEST_MAX_RETRIES = 2;
27968
- var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
29102
+ var delay = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
27969
29103
  async function fetchGitingest(args) {
27970
29104
  for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
27971
29105
  try {
@@ -28054,6 +29188,8 @@ var OpenCodeSwarm = async (ctx) => {
28054
29188
  const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
28055
29189
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
28056
29190
  const delegationHandler = createDelegationTrackerHook(config3);
29191
+ const guardrailsConfig = GuardrailsConfigSchema.parse(config3.guardrails ?? {});
29192
+ const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
28057
29193
  log("Plugin initialized", {
28058
29194
  directory: ctx.directory,
28059
29195
  maxIterations: config3.max_iterations,
@@ -28066,7 +29202,8 @@ var OpenCodeSwarm = async (ctx) => {
28066
29202
  contextBudget: !!contextBudgetHandler,
28067
29203
  commands: true,
28068
29204
  agentActivity: config3.hooks?.agent_activity !== false,
28069
- delegationTracker: config3.hooks?.delegation_tracker === true
29205
+ delegationTracker: config3.hooks?.delegation_tracker === true,
29206
+ guardrails: guardrailsConfig.enabled
28070
29207
  }
28071
29208
  });
28072
29209
  return {
@@ -28097,13 +29234,17 @@ var OpenCodeSwarm = async (ctx) => {
28097
29234
  },
28098
29235
  "experimental.chat.messages.transform": composeHandlers(...[
28099
29236
  pipelineHook["experimental.chat.messages.transform"],
28100
- contextBudgetHandler
29237
+ contextBudgetHandler,
29238
+ guardrailsHooks.messagesTransform
28101
29239
  ].filter((fn) => Boolean(fn))),
28102
29240
  "experimental.chat.system.transform": systemEnhancerHook["experimental.chat.system.transform"],
28103
29241
  "experimental.session.compacting": compactionHook["experimental.session.compacting"],
28104
29242
  "command.execute.before": safeHook(commandHandler),
28105
- "tool.execute.before": safeHook(activityHooks.toolBefore),
28106
- "tool.execute.after": safeHook(activityHooks.toolAfter),
29243
+ "tool.execute.before": async (input, output) => {
29244
+ await guardrailsHooks.toolBefore(input, output);
29245
+ await safeHook(activityHooks.toolBefore)(input, output);
29246
+ },
29247
+ "tool.execute.after": composeHandlers(activityHooks.toolAfter, guardrailsHooks.toolAfter),
28107
29248
  "chat.message": safeHook(delegationHandler)
28108
29249
  };
28109
29250
  };