opencode-swarm 7.74.2 → 7.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "opencode-swarm",
55
- version: "7.74.2",
55
+ version: "7.75.0",
56
56
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
57
57
  main: "dist/index.js",
58
58
  types: "dist/index.d.ts",
@@ -18015,6 +18015,7 @@ __export(exports_schema, {
18015
18015
  AutomationModeSchema: () => AutomationModeSchema,
18016
18016
  AutomationConfigSchema: () => AutomationConfigSchema,
18017
18017
  AutomationCapabilitiesSchema: () => AutomationCapabilitiesSchema,
18018
+ AutoReviewConfigSchema: () => AutoReviewConfigSchema,
18018
18019
  AuthorityConfigSchema: () => AuthorityConfigSchema,
18019
18020
  ArchitecturalSupervisionConfigSchema: () => ArchitecturalSupervisionConfigSchema,
18020
18021
  AgentThinkingConfigSchema: () => AgentThinkingConfigSchema,
@@ -18100,7 +18101,7 @@ function resolveExternalSkillsConfig(input) {
18100
18101
  };
18101
18102
  return merged;
18102
18103
  }
18103
- var _internals5, SEPARATORS, CANONICAL_ROLES_LONGEST_FIRST, CANONICAL_ROLES_SET, AgentReasoningConfigSchema, AgentThinkingConfigSchema, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, DesignDocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, ContextMapConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, MemoryConfigSchema, CuratorConfigSchema, ArchitecturalSupervisionConfigSchema, KnowledgeApplicationConfigSchema, SkillPropagationConfigSchema, SkillImproverConfigSchema, SpecWriterConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, PrMonitorConfigSchema, ParallelizationConfigSchema, WorktreeIsolationConfigSchema, LeanTurboConfigSchema, StandardTurboConfigSchema, LeanTurboStrategyConfigSchema, TurboConfigSchema, ExternalSkillCandidateSourceTypeSchema, ExternalSkillCandidateEvaluationVerdictSchema, DiscoverySourceSchema, ExternalSkillCandidateSchema, ExternalSkillsConfigSchema, DEFAULT_EXTERNAL_SKILLS_CONFIG, PluginConfigSchema;
18104
+ var _internals5, SEPARATORS, CANONICAL_ROLES_LONGEST_FIRST, CANONICAL_ROLES_SET, AgentReasoningConfigSchema, AgentThinkingConfigSchema, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AutoReviewConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, DesignDocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, ContextMapConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, MemoryConfigSchema, CuratorConfigSchema, ArchitecturalSupervisionConfigSchema, KnowledgeApplicationConfigSchema, SkillPropagationConfigSchema, SkillImproverConfigSchema, SpecWriterConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PrmConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, GeneralCouncilMemberConfigSchema, GeneralCouncilConfigSchema, CouncilConfigSchema, PrMonitorConfigSchema, ParallelizationConfigSchema, WorktreeIsolationConfigSchema, LeanTurboConfigSchema, StandardTurboConfigSchema, LeanTurboStrategyConfigSchema, TurboConfigSchema, ExternalSkillCandidateSourceTypeSchema, ExternalSkillCandidateEvaluationVerdictSchema, DiscoverySourceSchema, ExternalSkillCandidateSchema, ExternalSkillsConfigSchema, DEFAULT_EXTERNAL_SKILLS_CONFIG, PluginConfigSchema;
18104
18105
  var init_schema = __esm(() => {
18105
18106
  init_zod();
18106
18107
  init_constants();
@@ -18288,6 +18289,12 @@ var init_schema = __esm(() => {
18288
18289
  "**/token/**"
18289
18290
  ])
18290
18291
  });
18292
+ AutoReviewConfigSchema = exports_external.object({
18293
+ enabled: exports_external.boolean().default(false),
18294
+ trigger: exports_external.enum(["task_completion", "phase_boundary", "both"]).default("phase_boundary"),
18295
+ timeout_ms: exports_external.number().int().min(1e4).max(1800000).default(300000),
18296
+ max_diff_kb: exports_external.number().int().min(16).max(2048).default(256)
18297
+ });
18291
18298
  AdversarialDetectionConfigSchema = exports_external.object({
18292
18299
  enabled: exports_external.boolean().default(true),
18293
18300
  policy: exports_external.enum(["warn", "gate", "ignore"]).default("warn"),
@@ -18971,6 +18978,7 @@ var init_schema = __esm(() => {
18971
18978
  guardrails: GuardrailsConfigSchema.optional(),
18972
18979
  watchdog: WatchdogConfigSchema.optional(),
18973
18980
  self_review: SelfReviewConfigSchema.optional(),
18981
+ auto_review: AutoReviewConfigSchema.optional(),
18974
18982
  tool_filter: ToolFilterConfigSchema.optional(),
18975
18983
  authority: AuthorityConfigSchema.optional(),
18976
18984
  plan_cursor: PlanCursorConfigSchema.optional(),
@@ -19021,6 +19029,7 @@ var init_schema = __esm(() => {
19021
19029
  version_check: exports_external.boolean().default(true).optional(),
19022
19030
  full_auto: exports_external.object({
19023
19031
  enabled: exports_external.boolean().default(false),
19032
+ locked: exports_external.boolean().default(false),
19024
19033
  critic_model: exports_external.string().optional(),
19025
19034
  max_interactions_per_phase: exports_external.number().int().min(5).max(200).default(50),
19026
19035
  deadlock_threshold: exports_external.number().int().min(2).max(10).default(3),
@@ -19034,6 +19043,7 @@ var init_schema = __esm(() => {
19034
19043
  protected_paths: exports_external.array(exports_external.string()).default([
19035
19044
  ".git",
19036
19045
  ".github/workflows",
19046
+ ".opencode",
19037
19047
  ".swarm",
19038
19048
  "package.json",
19039
19049
  "package-lock.json",
@@ -19061,6 +19071,7 @@ var init_schema = __esm(() => {
19061
19071
  protected_paths: [
19062
19072
  ".git",
19063
19073
  ".github/workflows",
19074
+ ".opencode",
19064
19075
  ".swarm",
19065
19076
  "package.json",
19066
19077
  "package-lock.json",
@@ -19112,6 +19123,7 @@ var init_schema = __esm(() => {
19112
19123
  }))
19113
19124
  }).optional().default(() => ({
19114
19125
  enabled: false,
19126
+ locked: false,
19115
19127
  max_interactions_per_phase: 50,
19116
19128
  deadlock_threshold: 3,
19117
19129
  escalation_mode: "pause",
@@ -19124,6 +19136,7 @@ var init_schema = __esm(() => {
19124
19136
  protected_paths: [
19125
19137
  ".git",
19126
19138
  ".github/workflows",
19139
+ ".opencode",
19127
19140
  ".swarm",
19128
19141
  "package.json",
19129
19142
  "package-lock.json",
@@ -19319,6 +19332,15 @@ function sanitizeExternalSkillsConfig(raw) {
19319
19332
  delete cleaned.external_skills;
19320
19333
  return cleaned;
19321
19334
  }
19335
+ function rawFullAutoLocked(raw) {
19336
+ if (!raw || typeof raw !== "object")
19337
+ return false;
19338
+ const fullAuto = raw.full_auto;
19339
+ if (!fullAuto || typeof fullAuto !== "object" || Array.isArray(fullAuto)) {
19340
+ return false;
19341
+ }
19342
+ return fullAuto.locked === true;
19343
+ }
19322
19344
  function loadPluginConfig(directory) {
19323
19345
  const userConfigPath = path7.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
19324
19346
  const projectConfigPath = path7.join(directory, ".opencode", CONFIG_FILENAME);
@@ -19332,6 +19354,13 @@ function loadPluginConfig(directory) {
19332
19354
  if (rawProjectConfig) {
19333
19355
  mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
19334
19356
  }
19357
+ if (rawFullAutoLocked(rawUserConfig) || rawFullAutoLocked(rawProjectConfig)) {
19358
+ const fullAutoRaw = mergedRaw.full_auto && typeof mergedRaw.full_auto === "object" && !Array.isArray(mergedRaw.full_auto) ? mergedRaw.full_auto : {};
19359
+ mergedRaw = {
19360
+ ...mergedRaw,
19361
+ full_auto: { ...fullAutoRaw, locked: true }
19362
+ };
19363
+ }
19335
19364
  mergedRaw = migratePresetsConfig(mergedRaw);
19336
19365
  mergedRaw = sanitizeExternalSkillsConfig(mergedRaw);
19337
19366
  const result = PluginConfigSchema.safeParse(mergedRaw);
@@ -19364,8 +19393,9 @@ function loadPluginConfigWithMeta(directory) {
19364
19393
  const userResult = loadRawConfigFromPath(userConfigPath);
19365
19394
  const projectResult = loadRawConfigFromPath(projectConfigPath);
19366
19395
  const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
19396
+ const configHadErrors = userResult.hadError || projectResult.hadError;
19367
19397
  const config2 = loadPluginConfig(directory);
19368
- return { config: config2, loadedFromFile };
19398
+ return { config: config2, loadedFromFile, configHadErrors };
19369
19399
  }
19370
19400
  var CONFIG_FILENAME = "opencode-swarm.json", MAX_CONFIG_FILE_BYTES = 102400;
19371
19401
  var init_loader = __esm(() => {
@@ -47713,6 +47743,7 @@ __export(exports_config_doctor, {
47713
47743
  runConfigDoctor: () => runConfigDoctor,
47714
47744
  restoreFromBackup: () => restoreFromBackup,
47715
47745
  removeStraySwarmDir: () => removeStraySwarmDir,
47746
+ readDoctorArtifact: () => readDoctorArtifact,
47716
47747
  getConfigPaths: () => getConfigPaths,
47717
47748
  detectStraySwarmDirs: () => detectStraySwarmDirs,
47718
47749
  createConfigBackup: () => createConfigBackup,
@@ -47722,6 +47753,37 @@ import * as crypto4 from "crypto";
47722
47753
  import * as fs13 from "fs";
47723
47754
  import * as os7 from "os";
47724
47755
  import * as path35 from "path";
47756
+ function levenshteinDistance(a, b) {
47757
+ const al = a.length;
47758
+ const bl = b.length;
47759
+ const matrix = [];
47760
+ for (let i = 0;i <= al; i++) {
47761
+ matrix[i] = [i];
47762
+ }
47763
+ for (let j = 0;j <= bl; j++) {
47764
+ matrix[0][j] = j;
47765
+ }
47766
+ for (let i = 1;i <= al; i++) {
47767
+ for (let j = 1;j <= bl; j++) {
47768
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
47769
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
47770
+ }
47771
+ }
47772
+ return matrix[al][bl];
47773
+ }
47774
+ function emitObjectTypeMismatch(key, value, findings) {
47775
+ if (value !== undefined && (typeof value !== "object" || Array.isArray(value) || value === null)) {
47776
+ findings.push({
47777
+ id: `invalid-${key}-type`,
47778
+ title: `Invalid ${key} type`,
47779
+ description: `"${key}" must be an object, got ${typeof value}`,
47780
+ severity: "error",
47781
+ path: key,
47782
+ currentValue: value,
47783
+ autoFixable: false
47784
+ });
47785
+ }
47786
+ }
47725
47787
  function getUserConfigDir3() {
47726
47788
  return process.env.XDG_CONFIG_HOME || path35.join(os7.homedir(), ".config");
47727
47789
  }
@@ -47743,23 +47805,39 @@ function isValidConfigPath(configPath, directory) {
47743
47805
  }
47744
47806
  }
47745
47807
  }
47746
- for (const pattern of VALID_CONFIG_PATTERNS) {
47747
- if (pattern.test(normalizedPath)) {
47748
- return true;
47749
- }
47750
- }
47751
47808
  const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
47752
- const normalizedUser = userConfigPath.replace(/\\/g, "/");
47753
- const normalizedProject = projectConfigPath.replace(/\\/g, "/");
47754
47809
  try {
47755
47810
  const resolvedConfig = path35.resolve(configPath);
47756
- const resolvedUser = path35.resolve(normalizedUser);
47757
- const resolvedProject = path35.resolve(normalizedProject);
47758
- return resolvedConfig === resolvedUser || resolvedConfig === resolvedProject;
47811
+ const resolvedUser = path35.resolve(userConfigPath);
47812
+ const resolvedProject = path35.resolve(projectConfigPath);
47813
+ if (resolvedConfig !== resolvedUser && resolvedConfig !== resolvedProject) {
47814
+ return false;
47815
+ }
47816
+ try {
47817
+ if (fs13.existsSync(resolvedConfig)) {
47818
+ const realConfig = fs13.realpathSync(resolvedConfig);
47819
+ if (realConfig !== resolvedConfig) {
47820
+ return false;
47821
+ }
47822
+ }
47823
+ } catch {}
47824
+ return true;
47759
47825
  } catch {
47760
47826
  return false;
47761
47827
  }
47762
47828
  }
47829
+ function atomicWriteFileSync(filePath, content) {
47830
+ const tmpPath = `${filePath}.tmp.${process.pid}`;
47831
+ fs13.writeFileSync(tmpPath, content, "utf-8");
47832
+ try {
47833
+ fs13.renameSync(tmpPath, filePath);
47834
+ } catch {
47835
+ try {
47836
+ fs13.unlinkSync(filePath);
47837
+ } catch {}
47838
+ fs13.renameSync(tmpPath, filePath);
47839
+ }
47840
+ }
47763
47841
  function createConfigBackup(directory) {
47764
47842
  const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
47765
47843
  let configPath = projectConfigPath;
@@ -47807,13 +47885,18 @@ function writeBackupArtifact(directory, backup) {
47807
47885
  content: backup.content,
47808
47886
  preview: backup.content.substring(0, 500) + (backup.content.length > 500 ? "..." : "")
47809
47887
  };
47810
- fs13.writeFileSync(backupPath, JSON.stringify(artifact, null, 2), "utf-8");
47888
+ atomicWriteFileSync(backupPath, JSON.stringify(artifact, null, 2));
47811
47889
  return backupPath;
47812
47890
  }
47813
47891
  function restoreFromBackup(backupPath, directory) {
47814
47892
  if (!fs13.existsSync(backupPath)) {
47815
47893
  return null;
47816
47894
  }
47895
+ const swarmDir = path35.resolve(path35.join(directory, ".swarm"));
47896
+ const resolvedBackup = path35.resolve(backupPath);
47897
+ if (!resolvedBackup.startsWith(swarmDir + path35.sep) && resolvedBackup !== swarmDir) {
47898
+ return null;
47899
+ }
47817
47900
  try {
47818
47901
  const artifact = JSON.parse(fs13.readFileSync(backupPath, "utf-8"));
47819
47902
  if (!artifact.content || !artifact.configPath || !artifact.contentHash) {
@@ -47828,12 +47911,13 @@ function restoreFromBackup(backupPath, directory) {
47828
47911
  if (!isLegacyHash && computedHash !== storedHash) {
47829
47912
  return null;
47830
47913
  }
47914
+ log("[ConfigDoctor] Warning: restoring from backup with legacy numeric hash (pre-SHA-256). Consider re-backing up.", {});
47831
47915
  const targetPath = artifact.configPath;
47832
47916
  const targetDir = path35.dirname(targetPath);
47833
47917
  if (!fs13.existsSync(targetDir)) {
47834
47918
  fs13.mkdirSync(targetDir, { recursive: true });
47835
47919
  }
47836
- fs13.writeFileSync(targetPath, artifact.content, "utf-8");
47920
+ atomicWriteFileSync(targetPath, artifact.content);
47837
47921
  return targetPath;
47838
47922
  } catch {
47839
47923
  return null;
@@ -47856,12 +47940,28 @@ function readConfigFromFile(directory) {
47856
47940
  try {
47857
47941
  const config3 = JSON.parse(configContent);
47858
47942
  return { config: config3, configPath };
47859
- } catch {
47943
+ } catch (error93) {
47944
+ log(`[ConfigDoctor] Failed to parse config file: ${configPath}`, {
47945
+ error: error93 instanceof Error ? error93.message : String(error93)
47946
+ });
47860
47947
  return null;
47861
47948
  }
47862
47949
  }
47863
- function validateConfigKey(path36, value, _config) {
47950
+ function validateConfigKey(path36, value) {
47864
47951
  const findings = [];
47952
+ for (const [depPath, depInfo] of DEPRECATED_FIELDS) {
47953
+ if (path36 === depPath && !depInfo.isDefaultValue(value)) {
47954
+ findings.push({
47955
+ id: "deprecated-field",
47956
+ title: `Deprecated config field: ${depPath}`,
47957
+ description: `Config field "${depPath}" is deprecated. Replacement: ${depInfo.replacement}.`,
47958
+ severity: "info",
47959
+ path: depPath,
47960
+ currentValue: value,
47961
+ autoFixable: false
47962
+ });
47963
+ }
47964
+ }
47865
47965
  switch (path36) {
47866
47966
  case "agents": {
47867
47967
  if (value !== undefined) {
@@ -47979,8 +48079,9 @@ function validateConfigKey(path36, value, _config) {
47979
48079
  break;
47980
48080
  }
47981
48081
  case "hooks": {
47982
- const hooks = value;
47983
- if (hooks) {
48082
+ emitObjectTypeMismatch("hooks", value, findings);
48083
+ if (value !== undefined && typeof value === "object" && !Array.isArray(value) && value !== null) {
48084
+ const hooks = value;
47984
48085
  const validHooks = [
47985
48086
  "system_enhancer",
47986
48087
  "compaction",
@@ -48061,8 +48162,42 @@ function validateConfigKey(path36, value, _config) {
48061
48162
  break;
48062
48163
  }
48063
48164
  case "swarms": {
48064
- const swarms = value;
48065
- if (swarms && typeof swarms === "object") {
48165
+ if (value !== undefined) {
48166
+ if (typeof value !== "object" || Array.isArray(value) || value === null) {
48167
+ findings.push({
48168
+ id: "invalid-swarms-type",
48169
+ title: "Invalid swarms type",
48170
+ description: `"swarms" must be an object, got ${typeof value}`,
48171
+ severity: "error",
48172
+ path: "swarms",
48173
+ currentValue: value,
48174
+ autoFixable: false
48175
+ });
48176
+ break;
48177
+ }
48178
+ const swarms = value;
48179
+ if (Object.keys(swarms).length === 0) {
48180
+ findings.push({
48181
+ id: "empty-swarms",
48182
+ title: "Empty swarms configuration",
48183
+ description: 'The "swarms" field is an empty object. No swarm configurations are defined.',
48184
+ severity: "info",
48185
+ path: "swarms",
48186
+ autoFixable: false
48187
+ });
48188
+ }
48189
+ for (const swarmId of Object.keys(swarms)) {
48190
+ if (swarmId.includes("..") || swarmId.includes("/") || swarmId.includes("\\") || swarmId.includes("\x00")) {
48191
+ findings.push({
48192
+ id: "swarm-id-path-traversal",
48193
+ title: "Path traversal in swarm ID",
48194
+ description: `Swarm ID "${swarmId}" contains path traversal characters.`,
48195
+ severity: "error",
48196
+ path: `swarms.${swarmId}`,
48197
+ autoFixable: false
48198
+ });
48199
+ }
48200
+ }
48066
48201
  const validAgents = new Set(ALL_AGENT_NAMES);
48067
48202
  for (const [swarmId, swarmConfig] of Object.entries(swarms)) {
48068
48203
  const swarm = swarmConfig;
@@ -48096,36 +48231,376 @@ function validateConfigKey(path36, value, _config) {
48096
48231
  }
48097
48232
  break;
48098
48233
  }
48234
+ case "default_agent": {
48235
+ if (value !== undefined && typeof value !== "string") {
48236
+ findings.push({
48237
+ id: "invalid-default_agent-type",
48238
+ title: "Invalid default_agent type",
48239
+ description: `"default_agent" must be a string, got ${typeof value}`,
48240
+ severity: "error",
48241
+ path: "default_agent",
48242
+ currentValue: value,
48243
+ autoFixable: false
48244
+ });
48245
+ }
48246
+ break;
48247
+ }
48248
+ case "auto_select_architect": {
48249
+ if (value !== undefined && typeof value !== "boolean" && typeof value !== "string") {
48250
+ findings.push({
48251
+ id: "invalid-auto_select_architect-type",
48252
+ title: "Invalid auto_select_architect type",
48253
+ description: `"auto_select_architect" must be a boolean or string, got ${typeof value}`,
48254
+ severity: "error",
48255
+ path: "auto_select_architect",
48256
+ currentValue: value,
48257
+ autoFixable: false
48258
+ });
48259
+ }
48260
+ break;
48261
+ }
48262
+ case "pipeline": {
48263
+ emitObjectTypeMismatch("pipeline", value, findings);
48264
+ break;
48265
+ }
48266
+ case "phase_complete": {
48267
+ emitObjectTypeMismatch("phase_complete", value, findings);
48268
+ break;
48269
+ }
48270
+ case "execution_mode": {
48271
+ const validModes = ["strict", "balanced", "fast"];
48272
+ if (value !== undefined && !validModes.includes(value)) {
48273
+ findings.push({
48274
+ id: "invalid-execution_mode-type",
48275
+ title: "Invalid execution_mode",
48276
+ description: `"execution_mode" must be one of: ${validModes.join(", ")}, got "${value}"`,
48277
+ severity: "error",
48278
+ path: "execution_mode",
48279
+ currentValue: value,
48280
+ autoFixable: false
48281
+ });
48282
+ }
48283
+ break;
48284
+ }
48285
+ case "inject_phase_reminders": {
48286
+ if (value !== undefined && typeof value !== "boolean") {
48287
+ findings.push({
48288
+ id: "invalid-inject_phase_reminders-type",
48289
+ title: "Invalid inject_phase_reminders type",
48290
+ description: `"inject_phase_reminders" must be a boolean, got ${typeof value}`,
48291
+ severity: "error",
48292
+ path: "inject_phase_reminders",
48293
+ currentValue: value,
48294
+ autoFixable: false
48295
+ });
48296
+ }
48297
+ break;
48298
+ }
48299
+ case "gates": {
48300
+ emitObjectTypeMismatch("gates", value, findings);
48301
+ break;
48302
+ }
48303
+ case "context_budget": {
48304
+ emitObjectTypeMismatch("context_budget", value, findings);
48305
+ break;
48306
+ }
48307
+ case "guardrails": {
48308
+ emitObjectTypeMismatch("guardrails", value, findings);
48309
+ break;
48310
+ }
48311
+ case "watchdog": {
48312
+ emitObjectTypeMismatch("watchdog", value, findings);
48313
+ break;
48314
+ }
48315
+ case "self_review": {
48316
+ emitObjectTypeMismatch("self_review", value, findings);
48317
+ break;
48318
+ }
48319
+ case "tool_filter": {
48320
+ emitObjectTypeMismatch("tool_filter", value, findings);
48321
+ break;
48322
+ }
48323
+ case "authority": {
48324
+ emitObjectTypeMismatch("authority", value, findings);
48325
+ break;
48326
+ }
48327
+ case "plan_cursor": {
48328
+ emitObjectTypeMismatch("plan_cursor", value, findings);
48329
+ break;
48330
+ }
48331
+ case "context_map": {
48332
+ emitObjectTypeMismatch("context_map", value, findings);
48333
+ break;
48334
+ }
48335
+ case "evidence": {
48336
+ emitObjectTypeMismatch("evidence", value, findings);
48337
+ break;
48338
+ }
48339
+ case "summaries": {
48340
+ emitObjectTypeMismatch("summaries", value, findings);
48341
+ break;
48342
+ }
48343
+ case "review_passes": {
48344
+ emitObjectTypeMismatch("review_passes", value, findings);
48345
+ break;
48346
+ }
48347
+ case "adversarial_detection": {
48348
+ emitObjectTypeMismatch("adversarial_detection", value, findings);
48349
+ break;
48350
+ }
48351
+ case "adversarial_testing": {
48352
+ emitObjectTypeMismatch("adversarial_testing", value, findings);
48353
+ break;
48354
+ }
48355
+ case "integration_analysis": {
48356
+ emitObjectTypeMismatch("integration_analysis", value, findings);
48357
+ break;
48358
+ }
48359
+ case "docs": {
48360
+ emitObjectTypeMismatch("docs", value, findings);
48361
+ break;
48362
+ }
48363
+ case "design_docs": {
48364
+ emitObjectTypeMismatch("design_docs", value, findings);
48365
+ break;
48366
+ }
48367
+ case "ui_review": {
48368
+ emitObjectTypeMismatch("ui_review", value, findings);
48369
+ break;
48370
+ }
48371
+ case "compaction_advisory": {
48372
+ emitObjectTypeMismatch("compaction_advisory", value, findings);
48373
+ break;
48374
+ }
48375
+ case "lint": {
48376
+ emitObjectTypeMismatch("lint", value, findings);
48377
+ break;
48378
+ }
48379
+ case "secretscan": {
48380
+ emitObjectTypeMismatch("secretscan", value, findings);
48381
+ break;
48382
+ }
48383
+ case "checkpoint": {
48384
+ emitObjectTypeMismatch("checkpoint", value, findings);
48385
+ break;
48386
+ }
48387
+ case "automation": {
48388
+ emitObjectTypeMismatch("automation", value, findings);
48389
+ break;
48390
+ }
48391
+ case "knowledge": {
48392
+ emitObjectTypeMismatch("knowledge", value, findings);
48393
+ break;
48394
+ }
48395
+ case "memory": {
48396
+ emitObjectTypeMismatch("memory", value, findings);
48397
+ break;
48398
+ }
48399
+ case "curator": {
48400
+ emitObjectTypeMismatch("curator", value, findings);
48401
+ break;
48402
+ }
48403
+ case "architectural_supervision": {
48404
+ emitObjectTypeMismatch("architectural_supervision", value, findings);
48405
+ break;
48406
+ }
48407
+ case "knowledge_application": {
48408
+ emitObjectTypeMismatch("knowledge_application", value, findings);
48409
+ break;
48410
+ }
48411
+ case "skillPropagation": {
48412
+ emitObjectTypeMismatch("skillPropagation", value, findings);
48413
+ break;
48414
+ }
48415
+ case "skill_improver": {
48416
+ emitObjectTypeMismatch("skill_improver", value, findings);
48417
+ break;
48418
+ }
48419
+ case "spec_writer": {
48420
+ emitObjectTypeMismatch("spec_writer", value, findings);
48421
+ break;
48422
+ }
48423
+ case "tool_output": {
48424
+ emitObjectTypeMismatch("tool_output", value, findings);
48425
+ break;
48426
+ }
48427
+ case "slop_detector": {
48428
+ emitObjectTypeMismatch("slop_detector", value, findings);
48429
+ break;
48430
+ }
48431
+ case "todo_gate": {
48432
+ emitObjectTypeMismatch("todo_gate", value, findings);
48433
+ break;
48434
+ }
48435
+ case "incremental_verify": {
48436
+ emitObjectTypeMismatch("incremental_verify", value, findings);
48437
+ break;
48438
+ }
48439
+ case "compaction_service": {
48440
+ emitObjectTypeMismatch("compaction_service", value, findings);
48441
+ break;
48442
+ }
48443
+ case "prm": {
48444
+ emitObjectTypeMismatch("prm", value, findings);
48445
+ break;
48446
+ }
48447
+ case "council": {
48448
+ emitObjectTypeMismatch("council", value, findings);
48449
+ break;
48450
+ }
48451
+ case "parallelization": {
48452
+ emitObjectTypeMismatch("parallelization", value, findings);
48453
+ break;
48454
+ }
48455
+ case "worktree": {
48456
+ emitObjectTypeMismatch("worktree", value, findings);
48457
+ break;
48458
+ }
48459
+ case "turbo": {
48460
+ emitObjectTypeMismatch("turbo", value, findings);
48461
+ break;
48462
+ }
48463
+ case "turbo_mode": {
48464
+ if (value !== undefined && typeof value !== "boolean") {
48465
+ findings.push({
48466
+ id: "invalid-turbo_mode-type",
48467
+ title: "Invalid turbo_mode type",
48468
+ description: `"turbo_mode" must be a boolean, got ${typeof value}`,
48469
+ severity: "error",
48470
+ path: "turbo_mode",
48471
+ currentValue: value,
48472
+ autoFixable: false
48473
+ });
48474
+ }
48475
+ break;
48476
+ }
48477
+ case "quiet": {
48478
+ if (value !== undefined && typeof value !== "boolean") {
48479
+ findings.push({
48480
+ id: "invalid-quiet-type",
48481
+ title: "Invalid quiet type",
48482
+ description: `"quiet" must be a boolean, got ${typeof value}`,
48483
+ severity: "error",
48484
+ path: "quiet",
48485
+ currentValue: value,
48486
+ autoFixable: false
48487
+ });
48488
+ }
48489
+ break;
48490
+ }
48491
+ case "version_check": {
48492
+ if (value !== undefined && typeof value !== "boolean") {
48493
+ findings.push({
48494
+ id: "invalid-version_check-type",
48495
+ title: "Invalid version_check type",
48496
+ description: `"version_check" must be a boolean, got ${typeof value}`,
48497
+ severity: "error",
48498
+ path: "version_check",
48499
+ currentValue: value,
48500
+ autoFixable: false
48501
+ });
48502
+ }
48503
+ break;
48504
+ }
48505
+ case "full_auto": {
48506
+ emitObjectTypeMismatch("full_auto", value, findings);
48507
+ break;
48508
+ }
48509
+ case "pr_monitor": {
48510
+ emitObjectTypeMismatch("pr_monitor", value, findings);
48511
+ break;
48512
+ }
48513
+ case "external_skills": {
48514
+ emitObjectTypeMismatch("external_skills", value, findings);
48515
+ break;
48516
+ }
48517
+ default: {
48518
+ const topLevel = path36.split(".")[0];
48519
+ if (KNOWN_TOP_LEVEL_KEYS.has(topLevel)) {
48520
+ break;
48521
+ }
48522
+ const MAX_SUGGESTION_KEY_LENGTH = 100;
48523
+ const lowerTopLevel = topLevel.toLowerCase();
48524
+ let suggestion;
48525
+ let matchCount = 0;
48526
+ if (lowerTopLevel.length <= MAX_SUGGESTION_KEY_LENGTH) {
48527
+ for (const knownKey of KNOWN_TOP_LEVEL_KEYS) {
48528
+ if (levenshteinDistance(lowerTopLevel, knownKey.toLowerCase()) <= 2) {
48529
+ matchCount++;
48530
+ if (matchCount === 1) {
48531
+ suggestion = knownKey;
48532
+ }
48533
+ }
48534
+ }
48535
+ }
48536
+ if (matchCount === 1 && suggestion) {
48537
+ findings.push({
48538
+ id: "unknown-config-key",
48539
+ title: `Unknown config key: ${topLevel}`,
48540
+ description: `Unknown config key "${path36}" is not in the schema. Did you mean "${suggestion}"?`,
48541
+ severity: "warn",
48542
+ path: path36,
48543
+ currentValue: value,
48544
+ autoFixable: false
48545
+ });
48546
+ } else {
48547
+ findings.push({
48548
+ id: "unknown-config-key",
48549
+ title: `Unknown config key: ${topLevel}`,
48550
+ description: `Unknown config key "${path36}" is not in the schema.`,
48551
+ severity: "warn",
48552
+ path: path36,
48553
+ currentValue: value,
48554
+ autoFixable: false
48555
+ });
48556
+ }
48557
+ break;
48558
+ }
48099
48559
  }
48100
48560
  return findings;
48101
48561
  }
48102
- function walkConfigAndValidate(obj, path36, config3, findings) {
48562
+ function walkConfigAndValidate(obj, path36, findings, visited = new WeakSet) {
48103
48563
  if (obj === null || obj === undefined) {
48104
48564
  return;
48105
48565
  }
48106
48566
  if (path36 && typeof obj === "object" && !Array.isArray(obj)) {
48107
- const keyFindings = validateConfigKey(path36, obj, config3);
48567
+ const keyFindings = validateConfigKey(path36, obj);
48108
48568
  findings.push(...keyFindings);
48109
48569
  }
48110
48570
  if (typeof obj !== "object") {
48111
- const keyFindings = validateConfigKey(path36, obj, config3);
48571
+ const keyFindings = validateConfigKey(path36, obj);
48112
48572
  findings.push(...keyFindings);
48113
48573
  return;
48114
48574
  }
48575
+ if (visited.has(obj)) {
48576
+ findings.push({
48577
+ id: "circular-reference",
48578
+ title: `Circular reference detected at ${path36}`,
48579
+ description: `Config value at "${path36}" contains a circular reference. Validation stopped at this path to prevent stack overflow.`,
48580
+ severity: "error",
48581
+ path: path36,
48582
+ currentValue: "[circular]",
48583
+ autoFixable: false
48584
+ });
48585
+ return;
48586
+ }
48587
+ visited.add(obj);
48115
48588
  if (Array.isArray(obj)) {
48589
+ const arrayFindings = validateConfigKey(path36, obj);
48590
+ findings.push(...arrayFindings);
48116
48591
  obj.forEach((item, index) => {
48117
- walkConfigAndValidate(item, `${path36}[${index}]`, config3, findings);
48592
+ walkConfigAndValidate(item, `${path36}[${index}]`, findings, visited);
48118
48593
  });
48119
48594
  return;
48120
48595
  }
48121
48596
  for (const [key, value] of Object.entries(obj)) {
48122
48597
  const newPath = path36 ? `${path36}.${key}` : key;
48123
- walkConfigAndValidate(value, newPath, config3, findings);
48598
+ walkConfigAndValidate(value, newPath, findings, visited);
48124
48599
  }
48125
48600
  }
48126
48601
  function runConfigDoctor(config3, directory) {
48127
48602
  const findings = [];
48128
- walkConfigAndValidate(config3, "", config3, findings);
48603
+ walkConfigAndValidate(config3, "", findings);
48129
48604
  const summary = {
48130
48605
  info: findings.filter((f) => f.severity === "info").length,
48131
48606
  warn: findings.filter((f) => f.severity === "warn").length,
@@ -48243,11 +48718,45 @@ function applySafeAutoFixes(directory, result) {
48243
48718
  if (!fs13.existsSync(configDir)) {
48244
48719
  fs13.mkdirSync(configDir, { recursive: true });
48245
48720
  }
48246
- fs13.writeFileSync(configPath, JSON.stringify(config3, null, 2), "utf-8");
48721
+ atomicWriteFileSync(configPath, JSON.stringify(config3, null, 2));
48247
48722
  updatedConfigPath = configPath;
48248
48723
  }
48249
48724
  return { appliedFixes, updatedConfigPath };
48250
48725
  }
48726
+ function readDoctorArtifact(directory) {
48727
+ try {
48728
+ const artifactPath = path35.join(directory, ".swarm", "config-doctor.json");
48729
+ if (!fs13.existsSync(artifactPath)) {
48730
+ return null;
48731
+ }
48732
+ const content = fs13.readFileSync(artifactPath, "utf-8");
48733
+ const artifact = JSON.parse(content);
48734
+ const summary = artifact.summary;
48735
+ if (!summary || typeof summary !== "object") {
48736
+ return null;
48737
+ }
48738
+ const infoVal = summary.info;
48739
+ const warnVal = summary.warn;
48740
+ const errorVal = summary.error;
48741
+ if (typeof infoVal !== "number" || !Number.isFinite(infoVal) || typeof warnVal !== "number" || !Number.isFinite(warnVal) || typeof errorVal !== "number" || !Number.isFinite(errorVal)) {
48742
+ return null;
48743
+ }
48744
+ const ts = artifact.timestamp;
48745
+ if (typeof ts !== "number" || !Number.isFinite(ts)) {
48746
+ return null;
48747
+ }
48748
+ const findingsCount = infoVal + warnVal + errorVal;
48749
+ const findings = artifact.findings;
48750
+ const autoFixableCount = Array.isArray(findings) ? findings.filter((f) => f.autoFixable === true).length : 0;
48751
+ return {
48752
+ timestamp: new Date(ts).toISOString(),
48753
+ findingsCount,
48754
+ autoFixableCount
48755
+ };
48756
+ } catch {
48757
+ return null;
48758
+ }
48759
+ }
48251
48760
  function writeDoctorArtifact(directory, result) {
48252
48761
  const swarmDir = path35.join(directory, ".swarm");
48253
48762
  if (!fs13.existsSync(swarmDir)) {
@@ -48275,7 +48784,7 @@ function writeDoctorArtifact(directory, result) {
48275
48784
  } : null
48276
48785
  }))
48277
48786
  };
48278
- fs13.writeFileSync(artifactPath, JSON.stringify(guiOutput, null, 2), "utf-8");
48787
+ atomicWriteFileSync(artifactPath, JSON.stringify(guiOutput, null, 2));
48279
48788
  return artifactPath;
48280
48789
  }
48281
48790
  function shouldRunOnStartup(automationConfig) {
@@ -48428,15 +48937,46 @@ function removeStraySwarmDir(projectRoot, strayPath) {
48428
48937
  };
48429
48938
  }
48430
48939
  }
48431
- var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
48940
+ var KNOWN_TOP_LEVEL_KEYS, DEPRECATED_FIELDS, DANGEROUS_PATH_SEGMENTS;
48432
48941
  var init_config_doctor = __esm(() => {
48433
48942
  init_constants();
48434
48943
  init_schema();
48435
48944
  init_utils();
48436
- VALID_CONFIG_PATTERNS = [
48437
- /^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
48438
- /\.opencode[\\/]opencode-swarm\.json$/
48439
- ];
48945
+ KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(PluginConfigSchema.shape));
48946
+ DEPRECATED_FIELDS = new Map([
48947
+ [
48948
+ "skill_improver.model",
48949
+ {
48950
+ message: "deprecated",
48951
+ replacement: "agents.skill_improver.model",
48952
+ isDefaultValue: (v) => v === null
48953
+ }
48954
+ ],
48955
+ [
48956
+ "skill_improver.fallback_models",
48957
+ {
48958
+ message: "deprecated",
48959
+ replacement: "agents.skill_improver.fallback_models",
48960
+ isDefaultValue: (v) => Array.isArray(v) && v.length === 0
48961
+ }
48962
+ ],
48963
+ [
48964
+ "spec_writer.model",
48965
+ {
48966
+ message: "deprecated",
48967
+ replacement: "agents.spec_writer.model",
48968
+ isDefaultValue: (v) => v === null
48969
+ }
48970
+ ],
48971
+ [
48972
+ "spec_writer.fallback_models",
48973
+ {
48974
+ message: "deprecated",
48975
+ replacement: "agents.spec_writer.fallback_models",
48976
+ isDefaultValue: (v) => Array.isArray(v) && v.length === 0
48977
+ }
48978
+ ]
48979
+ ]);
48440
48980
  DANGEROUS_PATH_SEGMENTS = new Set([
48441
48981
  "__proto__",
48442
48982
  "constructor",
@@ -50099,7 +50639,14 @@ async function handleDoctorCommand(directory, args) {
50099
50639
  const fixResult = await runConfigDoctorWithFixes2(directory, config3, true);
50100
50640
  output = formatDoctorMarkdown(fixResult.result);
50101
50641
  } else {
50102
- output = formatDoctorMarkdown(result);
50642
+ const lastRun = readDoctorArtifact(directory);
50643
+ let markdown = formatDoctorMarkdown(result);
50644
+ if (lastRun) {
50645
+ markdown = `Last run: ${lastRun.timestamp}, ${lastRun.findingsCount} findings (${lastRun.autoFixableCount} auto-fixable)
50646
+
50647
+ ` + markdown;
50648
+ }
50649
+ output = markdown;
50103
50650
  }
50104
50651
  const strayDirs = detectStraySwarmDirs(directory);
50105
50652
  if (strayDirs.length > 0) {
@@ -50725,6 +51272,25 @@ function emptyCounters() {
50725
51272
  consecutiveNoProgressTurns: 0
50726
51273
  };
50727
51274
  }
51275
+ function sanitizeRunState(raw) {
51276
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
51277
+ return null;
51278
+ const r = raw;
51279
+ if (typeof r.sessionID !== "string" || !r.sessionID)
51280
+ return null;
51281
+ const mode = VALID_RUN_MODES.has(r.mode) ? r.mode : "supervised";
51282
+ const status = VALID_RUN_STATUSES.has(r.status) ? r.status : "idle";
51283
+ return { ...raw, mode, status };
51284
+ }
51285
+ function sanitizeSessions(raw) {
51286
+ const result = {};
51287
+ for (const [id, session] of Object.entries(raw)) {
51288
+ const sanitized = sanitizeRunState(session);
51289
+ if (sanitized)
51290
+ result[id] = sanitized;
51291
+ }
51292
+ return result;
51293
+ }
50728
51294
  function emptyState(sessionID, mode = "supervised") {
50729
51295
  const now = nowISO();
50730
51296
  return {
@@ -50789,26 +51355,45 @@ function clearStateUnreadable() {
50789
51355
  stateUnreadable = false;
50790
51356
  stateUnreadableReason = "";
50791
51357
  }
51358
+ function isFullAutoStateUnreadable() {
51359
+ return { unreadable: stateUnreadable, reason: stateUnreadableReason };
51360
+ }
50792
51361
  function readPersisted(directory) {
50793
51362
  try {
50794
51363
  const filePath = validateSwarmPath(directory, STATE_FILE);
50795
- if (!fs15.existsSync(filePath)) {
51364
+ let stats;
51365
+ try {
51366
+ stats = fs15.statSync(filePath);
51367
+ } catch {
50796
51368
  clearStateUnreadable();
51369
+ readCache.delete(filePath);
50797
51370
  return emptyPersisted();
50798
51371
  }
51372
+ const cached3 = readCache.get(filePath);
51373
+ if (cached3 && cached3.mtimeMs === stats.mtimeMs && cached3.size === stats.size) {
51374
+ clearStateUnreadable();
51375
+ return structuredClone(cached3.state);
51376
+ }
50799
51377
  const raw = fs15.readFileSync(filePath, "utf-8");
50800
51378
  const parsed = JSON.parse(raw);
50801
51379
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 2 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
50802
51380
  markStateUnreadable(`malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
51381
+ readCache.delete(filePath);
50803
51382
  return emptyPersisted();
50804
51383
  }
50805
51384
  clearStateUnreadable();
50806
- return {
51385
+ const state = {
50807
51386
  version: 2,
50808
51387
  updatedAt: parsed.updatedAt ?? nowISO(),
50809
51388
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
50810
- sessions: parsed.sessions
51389
+ sessions: sanitizeSessions(parsed.sessions)
50811
51390
  };
51391
+ readCache.set(filePath, {
51392
+ mtimeMs: stats.mtimeMs,
51393
+ size: stats.size,
51394
+ state: structuredClone(state)
51395
+ });
51396
+ return state;
50812
51397
  } catch (error93) {
50813
51398
  const reason = error93 instanceof Error ? error93.message : String(error93);
50814
51399
  error(`[full-auto/state] Failed to read ${STATE_FILE}: ${reason} \u2014 attempting .bak recovery`);
@@ -50824,7 +51409,7 @@ function readPersisted(directory) {
50824
51409
  version: 2,
50825
51410
  updatedAt: parsed.updatedAt ?? nowISO(),
50826
51411
  oversightSequence: typeof parsed.oversightSequence === "number" ? parsed.oversightSequence : 0,
50827
- sessions: parsed.sessions
51412
+ sessions: sanitizeSessions(parsed.sessions)
50828
51413
  };
50829
51414
  }
50830
51415
  }
@@ -50832,6 +51417,7 @@ function readPersisted(directory) {
50832
51417
  error(`[full-auto/state] .bak recovery also failed: ${bakError instanceof Error ? bakError.message : String(bakError)}`);
50833
51418
  }
50834
51419
  markStateUnreadable(`canonical=${reason}; .bak=missing-or-corrupt`);
51420
+ readCache.clear();
50835
51421
  return emptyPersisted();
50836
51422
  }
50837
51423
  }
@@ -50869,6 +51455,7 @@ function writePersisted(directory, persisted) {
50869
51455
  }
50870
51456
  } catch {}
50871
51457
  fs15.renameSync(tmpPath, filePath);
51458
+ readCache.delete(filePath);
50872
51459
  const readback = fs15.readFileSync(filePath, "utf-8");
50873
51460
  const parsed = JSON.parse(readback);
50874
51461
  if (parsed?.version !== 2) {
@@ -50880,6 +51467,10 @@ function writePersisted(directory, persisted) {
50880
51467
  throw new Error(`Full-Auto state persistence failed: ${msg}`);
50881
51468
  }
50882
51469
  }
51470
+ function loadFullAutoRunState(directory, sessionID) {
51471
+ const persisted = readPersisted(directory);
51472
+ return persisted.sessions[sessionID];
51473
+ }
50883
51474
  function startFullAutoRun(directory, sessionID, config3, options = {}) {
50884
51475
  return withStateLock(directory, () => {
50885
51476
  const persisted = readPersisted(directory);
@@ -50911,14 +51502,15 @@ function startFullAutoRun(directory, sessionID, config3, options = {}) {
50911
51502
  return state;
50912
51503
  });
50913
51504
  }
50914
- function pauseFullAutoRun(directory, sessionID, reason) {
51505
+ function disarmFullAutoRun(directory, sessionID, reason) {
50915
51506
  return withStateLock(directory, () => {
50916
51507
  const persisted = readPersisted(directory);
50917
51508
  const state = persisted.sessions[sessionID];
50918
51509
  if (!state)
50919
51510
  return;
50920
- state.status = "paused";
51511
+ state.status = "idle";
50921
51512
  state.pauseReason = reason;
51513
+ state.terminateReason = undefined;
50922
51514
  state.updatedAt = nowISO();
50923
51515
  persisted.sessions[sessionID] = state;
50924
51516
  writePersisted(directory, persisted);
@@ -50939,15 +51531,26 @@ function terminateFullAutoRun(directory, sessionID, reason) {
50939
51531
  return state;
50940
51532
  });
50941
51533
  }
50942
- var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", stateUnreadable = false, stateUnreadableReason = "";
51534
+ var import_proper_lockfile8, lockfile8, STATE_FILE = "full-auto-state.json", VALID_RUN_MODES, VALID_RUN_STATUSES, stateUnreadable = false, stateUnreadableReason = "", readCache;
50943
51535
  var init_state2 = __esm(() => {
50944
51536
  init_utils2();
50945
51537
  init_logger();
50946
51538
  import_proper_lockfile8 = __toESM(require_proper_lockfile(), 1);
50947
51539
  lockfile8 = import_proper_lockfile8.default;
51540
+ VALID_RUN_MODES = new Set(["assisted", "supervised", "strict"]);
51541
+ VALID_RUN_STATUSES = new Set([
51542
+ "idle",
51543
+ "running",
51544
+ "paused",
51545
+ "terminated"
51546
+ ]);
51547
+ readCache = new Map;
50948
51548
  });
50949
51549
 
50950
51550
  // src/commands/full-auto.ts
51551
+ function isValidMode(value) {
51552
+ return VALID_MODES.includes(value);
51553
+ }
50951
51554
  async function handleFullAutoCommand(directory, args, sessionID) {
50952
51555
  if (!sessionID || sessionID.trim() === "") {
50953
51556
  return "Error: No active session context. Full-Auto Mode requires an active session. Use /swarm-full-auto from within an OpenCode session, or start a session first.";
@@ -50957,39 +51560,63 @@ async function handleFullAutoCommand(directory, args, sessionID) {
50957
51560
  return "Error: No active session. Full-Auto Mode requires an active session to operate.";
50958
51561
  }
50959
51562
  const arg = args[0]?.toLowerCase();
51563
+ if (arg === "status") {
51564
+ return buildStatusReport(directory, sessionID, session.fullAutoMode);
51565
+ }
50960
51566
  let newFullAutoMode;
51567
+ let modeOverride;
50961
51568
  if (arg === "on") {
50962
51569
  newFullAutoMode = true;
51570
+ const modeArg = args[1]?.toLowerCase();
51571
+ if (modeArg) {
51572
+ if (!isValidMode(modeArg)) {
51573
+ return `Error: invalid Full-Auto mode '${args[1]}'. Valid modes: ${VALID_MODES.join(", ")}.`;
51574
+ }
51575
+ modeOverride = modeArg;
51576
+ }
50963
51577
  } else if (arg === "off") {
50964
51578
  newFullAutoMode = false;
51579
+ } else if (arg && isValidMode(arg)) {
51580
+ newFullAutoMode = true;
51581
+ modeOverride = arg;
50965
51582
  } else {
50966
51583
  newFullAutoMode = !session.fullAutoMode;
50967
51584
  }
50968
- if (newFullAutoMode && !swarmState.fullAutoEnabledInConfig) {
50969
- return "Error: Full-Auto Mode cannot be enabled because full_auto.enabled is not set to true in the swarm plugin config. The autonomous oversight hook is inactive without config-level enablement. Set full_auto.enabled = true in your opencode-swarm config and restart.";
50970
- }
50971
51585
  let v2Status = "unavailable";
50972
51586
  let modeLabel = "supervised";
50973
51587
  let denialMaxConsecutive = 3;
50974
51588
  let denialMaxTotal = 20;
50975
51589
  let failClosed = true;
50976
51590
  let durableError;
51591
+ let criticModelAdvisory = "";
50977
51592
  try {
50978
- const { config: config3 } = loadPluginConfigWithMeta(directory);
51593
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
50979
51594
  const fullAutoConfig = config3.full_auto;
50980
- modeLabel = fullAutoConfig?.mode ?? "supervised";
51595
+ if (newFullAutoMode && configHadErrors) {
51596
+ return "Error: Full-Auto Mode cannot be enabled \u2014 a swarm plugin config file exists but could not be loaded, so full_auto.locked cannot be verified. Fix the config file (see warnings above) and retry.";
51597
+ }
51598
+ if (newFullAutoMode && fullAutoConfig?.locked === true) {
51599
+ return "Error: Full-Auto Mode is locked for this project (full_auto.locked is true in the swarm plugin config). Runtime activation is disabled by configuration; remove the lock to use /swarm full-auto on.";
51600
+ }
51601
+ const effectiveMode = modeOverride ?? fullAutoConfig?.mode ?? "supervised";
51602
+ modeLabel = effectiveMode;
50981
51603
  denialMaxConsecutive = fullAutoConfig?.denials?.max_consecutive ?? 3;
50982
51604
  denialMaxTotal = fullAutoConfig?.denials?.max_total ?? 20;
50983
51605
  failClosed = fullAutoConfig?.fail_closed !== false;
50984
51606
  if (newFullAutoMode) {
50985
- startFullAutoRun(directory, sessionID, fullAutoConfig);
51607
+ startFullAutoRun(directory, sessionID, fullAutoConfig ? { ...fullAutoConfig, mode: effectiveMode } : undefined);
50986
51608
  v2Status = "running";
51609
+ const criticModel = fullAutoConfig?.critic_model ?? config3.agents?.critic?.model;
51610
+ const architectModel = config3.agents?.architect?.model;
51611
+ if (criticModel && architectModel && criticModel === architectModel) {
51612
+ criticModelAdvisory = " WARNING: critic model matches architect model \u2014 set full_auto.critic_model (or agents.critic.model) to a different model for independent oversight.";
51613
+ }
50987
51614
  } else {
50988
- const paused = pauseFullAutoRun(directory, sessionID, "/swarm full-auto off");
50989
- if (!paused) {
51615
+ const disarmed = disarmFullAutoRun(directory, sessionID, "/swarm full-auto off");
51616
+ if (!disarmed) {
50990
51617
  terminateFullAutoRun(directory, sessionID, "never started");
50991
51618
  }
50992
- v2Status = "paused";
51619
+ v2Status = "idle";
50993
51620
  }
50994
51621
  } catch (error93) {
50995
51622
  durableError = error93 instanceof Error ? error93.message : String(error93);
@@ -51018,13 +51645,54 @@ async function handleFullAutoCommand(directory, args, sessionID) {
51018
51645
  "Full-Auto Mode enabled",
51019
51646
  `(v2 mode=${modeLabel}, fail_closed=${failClosed},`,
51020
51647
  `denials max ${denialMaxConsecutive} consecutive / ${denialMaxTotal} total)`
51021
- ].join(" ");
51648
+ ].join(" ") + criticModelAdvisory;
51649
+ }
51650
+ function buildStatusReport(directory, sessionID, sessionFlag) {
51651
+ const lines = [];
51652
+ lines.push(`Full-Auto session flag: ${sessionFlag ? "on" : "off"}`);
51653
+ try {
51654
+ const { config: config3, configHadErrors } = loadPluginConfigWithMeta(directory);
51655
+ if (configHadErrors) {
51656
+ lines.push("Config: UNREADABLE (a config file exists but could not be loaded; `full_auto.locked` cannot be verified, so runtime activation refuses by fail-closed default). Fix the config file to restore normal status.");
51657
+ }
51658
+ if (config3.full_auto?.locked === true) {
51659
+ lines.push("Config: locked (runtime activation disabled via full_auto.locked)");
51660
+ }
51661
+ } catch {}
51662
+ try {
51663
+ const runState = loadFullAutoRunState(directory, sessionID);
51664
+ const stateHealth = isFullAutoStateUnreadable();
51665
+ if (stateHealth.unreadable) {
51666
+ lines.push(`Durable run-state: UNREADABLE (${stateHealth.reason}). Non-read-only tools are blocked fail-closed until .swarm/full-auto-state.json (or .bak) is restored or deleted.`);
51667
+ } else if (!runState) {
51668
+ lines.push("Durable run-state: none (no Full-Auto run for this session)");
51669
+ } else {
51670
+ lines.push(`Durable run-state: ${runState.status} (mode=${runState.mode})`);
51671
+ if (runState.pauseReason) {
51672
+ lines.push(`Pause reason: ${runState.pauseReason}`);
51673
+ }
51674
+ if (runState.terminateReason) {
51675
+ lines.push(`Terminate reason: ${runState.terminateReason}`);
51676
+ }
51677
+ lines.push(`Counters: ${runState.counters.toolCalls} tool calls, ${runState.counters.architectTurns} architect turns, ${runState.counters.oversightChecks} oversight checks`);
51678
+ lines.push(`Denials: ${runState.denialCounters.consecutive} consecutive / ${runState.denialCounters.total} total`);
51679
+ if (runState.lastOversightVerdict) {
51680
+ lines.push(`Last oversight verdict: ${runState.lastOversightVerdict}${runState.lastOversightAt ? ` at ${runState.lastOversightAt}` : ""}`);
51681
+ }
51682
+ }
51683
+ } catch (error93) {
51684
+ lines.push(`Durable run-state: unreadable (${error93 instanceof Error ? error93.message : String(error93)})`);
51685
+ }
51686
+ return lines.join(`
51687
+ `);
51022
51688
  }
51689
+ var VALID_MODES;
51023
51690
  var init_full_auto = __esm(() => {
51024
51691
  init_config();
51025
51692
  init_state2();
51026
51693
  init_state();
51027
51694
  init_logger();
51695
+ VALID_MODES = ["assisted", "supervised", "strict"];
51028
51696
  });
51029
51697
 
51030
51698
  // src/services/handoff-service.ts
@@ -51408,7 +52076,7 @@ var init_handoff_service = __esm(() => {
51408
52076
  });
51409
52077
 
51410
52078
  // src/session/snapshot-writer.ts
51411
- import { closeSync as closeSync5, fsyncSync as fsyncSync2, mkdirSync as mkdirSync15, openSync as openSync5, renameSync as renameSync9 } from "fs";
52079
+ import { closeSync as closeSync5, fsyncSync as fsyncSync2, mkdirSync as mkdirSync15, openSync as openSync5, renameSync as renameSync10 } from "fs";
51412
52080
  import * as path38 from "path";
51413
52081
  function serializeAgentSession(s) {
51414
52082
  const gateLog = {};
@@ -51527,7 +52195,7 @@ async function writeSnapshot(directory, state) {
51527
52195
  closeSync5(fd);
51528
52196
  }
51529
52197
  } catch {}
51530
- renameSync9(tempPath, resolvedPath);
52198
+ renameSync10(tempPath, resolvedPath);
51531
52199
  } catch (error93) {
51532
52200
  log("[snapshot-writer] write failed", {
51533
52201
  error: error93 instanceof Error ? error93.message : String(error93)
@@ -51560,7 +52228,7 @@ var init_snapshot_writer = __esm(() => {
51560
52228
 
51561
52229
  // src/commands/handoff.ts
51562
52230
  import crypto5 from "crypto";
51563
- import { renameSync as renameSync10, unlinkSync as unlinkSync6 } from "fs";
52231
+ import { renameSync as renameSync11, unlinkSync as unlinkSync7 } from "fs";
51564
52232
  async function handleHandoffCommand(directory, _args) {
51565
52233
  const handoffData = await getHandoffData(directory);
51566
52234
  const markdown = formatHandoffMarkdown(handoffData);
@@ -51569,10 +52237,10 @@ async function handleHandoffCommand(directory, _args) {
51569
52237
  const tempPath = `${resolvedPath}.tmp.${crypto5.randomUUID()}`;
51570
52238
  await bunWrite(tempPath, markdown);
51571
52239
  try {
51572
- renameSync10(tempPath, resolvedPath);
52240
+ renameSync11(tempPath, resolvedPath);
51573
52241
  } catch (renameErr) {
51574
52242
  try {
51575
- unlinkSync6(tempPath);
52243
+ unlinkSync7(tempPath);
51576
52244
  } catch {}
51577
52245
  throw renameErr;
51578
52246
  }
@@ -51581,10 +52249,10 @@ async function handleHandoffCommand(directory, _args) {
51581
52249
  const promptTempPath = `${promptPath}.tmp.${crypto5.randomUUID()}`;
51582
52250
  await bunWrite(promptTempPath, continuationPrompt);
51583
52251
  try {
51584
- renameSync10(promptTempPath, promptPath);
52252
+ renameSync11(promptTempPath, promptPath);
51585
52253
  } catch (renameErr) {
51586
52254
  try {
51587
- unlinkSync6(promptTempPath);
52255
+ unlinkSync7(promptTempPath);
51588
52256
  } catch {}
51589
52257
  throw renameErr;
51590
52258
  }
@@ -64742,8 +65410,6 @@ var init_write_retro2 = __esm(() => {
64742
65410
  });
64743
65411
 
64744
65412
  // src/commands/command-dispatch.ts
64745
- import fs35 from "fs";
64746
- import path65 from "path";
64747
65413
  function normalizeSwarmCommandInput(command, argumentText) {
64748
65414
  if (command !== "swarm" && !command.startsWith("swarm-")) {
64749
65415
  return { isSwarmCommand: false, tokens: [] };
@@ -64778,25 +65444,6 @@ ${similar.map((cmd) => ` - /swarm ${cmd}`).join(`
64778
65444
 
64779
65445
  `);
64780
65446
  }
64781
- function maybeMarkFirstRun(directory) {
64782
- const sentinelPath = path65.join(directory, ".swarm", ".first-run-complete");
64783
- try {
64784
- const swarmDir = path65.join(directory, ".swarm");
64785
- fs35.mkdirSync(swarmDir, { recursive: true });
64786
- fs35.writeFileSync(sentinelPath, `first-run-complete: ${new Date().toISOString()}
64787
- `, { flag: "wx" });
64788
- return true;
64789
- } catch {
64790
- return false;
64791
- }
64792
- }
64793
- function prependWelcome(text) {
64794
- const welcomeMessage = `Welcome to OpenCode Swarm!
64795
- ` + `
64796
- ` + `Run \`/swarm help\` to see all available commands, or \`/swarm config\` to review your configuration.
64797
- `;
64798
- return welcomeMessage + text;
64799
- }
64800
65447
  async function executeSwarmCommand(args) {
64801
65448
  const {
64802
65449
  directory,
@@ -64804,7 +65451,6 @@ async function executeSwarmCommand(args) {
64804
65451
  sessionID,
64805
65452
  tokens,
64806
65453
  packageRoot,
64807
- includeWelcome = false,
64808
65454
  buildHelpText,
64809
65455
  policy
64810
65456
  } = args;
@@ -64838,9 +65484,6 @@ ${text}`;
64838
65484
  }
64839
65485
  }
64840
65486
  }
64841
- if (includeWelcome && maybeMarkFirstRun(directory)) {
64842
- text = prependWelcome(text);
64843
- }
64844
65487
  return {
64845
65488
  text,
64846
65489
  resolved: resolved ?? undefined,
@@ -65463,7 +66106,7 @@ var init_commands = __esm(() => {
65463
66106
  });
65464
66107
 
65465
66108
  // src/commands/registry.ts
65466
- function levenshteinDistance(a, b) {
66109
+ function levenshteinDistance2(a, b) {
65467
66110
  const matrix = [];
65468
66111
  for (let i = 0;i <= b.length; i++) {
65469
66112
  matrix[i] = [i];
@@ -65590,24 +66233,24 @@ function validateAliases() {
65590
66233
  }
65591
66234
  aliasTargets.get(target).push(name);
65592
66235
  const visited = new Set;
65593
- const path66 = [];
66236
+ const path65 = [];
65594
66237
  let current = target;
65595
66238
  while (current) {
65596
66239
  const currentEntry = COMMAND_REGISTRY[current];
65597
66240
  if (!currentEntry)
65598
66241
  break;
65599
66242
  if (visited.has(current)) {
65600
- const cycleStart = path66.indexOf(current);
66243
+ const cycleStart = path65.indexOf(current);
65601
66244
  const fullChain = [
65602
66245
  name,
65603
- ...path66.slice(0, cycleStart > 0 ? cycleStart : path66.length),
66246
+ ...path65.slice(0, cycleStart > 0 ? cycleStart : path65.length),
65604
66247
  current
65605
66248
  ].join(" \u2192 ");
65606
66249
  errors5.push(`Circular alias detected: ${fullChain}`);
65607
66250
  break;
65608
66251
  }
65609
66252
  visited.add(current);
65610
- path66.push(current);
66253
+ path65.push(current);
65611
66254
  current = currentEntry.aliasOf || "";
65612
66255
  }
65613
66256
  }
@@ -66184,9 +66827,9 @@ Subcommands:
66184
66827
  },
66185
66828
  "full-auto": {
66186
66829
  handler: (ctx) => handleFullAutoCommand(ctx.directory, ctx.args, ctx.sessionID),
66187
- description: "Toggle Full-Auto Mode for the active session [on|off]",
66188
- args: "on, off",
66189
- details: 'Toggles Full-Auto Mode which enables autonomous execution without confirmation prompts. When enabled, the architect proceeds through implementation steps automatically. Session-scoped \u2014 resets on new session. Use "on" or "off" to set explicitly, or toggle with no argument.',
66830
+ description: "Toggle Full-Auto Mode for the active session [on [mode]|off|status]",
66831
+ args: "on [assisted|supervised|strict], off, status",
66832
+ details: 'First-class toggle for Full-Auto Mode \u2014 autonomous execution with the critic reviewing escalations on your behalf. No config-level enablement is required: "on" activates immediately (unless full_auto.locked is true in config), "off" disarms the run and returns the session to normal interactive operation, "status" reports the durable run state. ' + 'An optional mode after "on" overrides full_auto.mode for this run: assisted (critic consulted only on policy escalations), supervised (default \u2014 risky/high-impact actions reviewed by the critic), strict (ALL plan mutations reviewed by the critic). ' + "While active, the critic answers architect questions and reviews phase boundaries, delegations, and risky actions on your behalf; only ESCALATE_TO_HUMAN verdicts halt the run for your input. The run state is durable (.swarm/full-auto-state.json) and survives restarts; toggle with no argument flips the current state.",
66190
66833
  category: "utility"
66191
66834
  },
66192
66835
  "auto-proceed": {
@@ -66329,7 +66972,7 @@ Subcommands:
66329
66972
  handleHelpCommand,
66330
66973
  validateAliases,
66331
66974
  resolveCommand,
66332
- levenshteinDistance,
66975
+ levenshteinDistance: levenshteinDistance2,
66333
66976
  findSimilarCommands,
66334
66977
  buildDetailedHelp
66335
66978
  };
@@ -66351,72 +66994,72 @@ init_package();
66351
66994
  init_registry();
66352
66995
  init_cache_paths();
66353
66996
  init_constants();
66354
- import * as fs36 from "fs";
66997
+ import * as fs35 from "fs";
66355
66998
  import * as os10 from "os";
66356
- import * as path66 from "path";
66999
+ import * as path65 from "path";
66357
67000
  var { version: version5 } = package_default;
66358
67001
  var CONFIG_DIR = getPluginConfigDir();
66359
- var OPENCODE_CONFIG_PATH = path66.join(CONFIG_DIR, "opencode.json");
66360
- var PLUGIN_CONFIG_PATH = path66.join(CONFIG_DIR, "opencode-swarm.json");
66361
- var PROMPTS_DIR = path66.join(CONFIG_DIR, "opencode-swarm");
67002
+ var OPENCODE_CONFIG_PATH = path65.join(CONFIG_DIR, "opencode.json");
67003
+ var PLUGIN_CONFIG_PATH = path65.join(CONFIG_DIR, "opencode-swarm.json");
67004
+ var PROMPTS_DIR = path65.join(CONFIG_DIR, "opencode-swarm");
66362
67005
  var OPENCODE_PLUGIN_CACHE_PATHS = getPluginCachePaths();
66363
67006
  var OPENCODE_PLUGIN_LOCK_FILE_PATHS = getPluginLockFilePaths();
66364
67007
  function isSafeCachePath(p) {
66365
- const resolved = path66.resolve(p);
66366
- const home = path66.resolve(os10.homedir());
67008
+ const resolved = path65.resolve(p);
67009
+ const home = path65.resolve(os10.homedir());
66367
67010
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
66368
67011
  return false;
66369
67012
  }
66370
- const segments = resolved.split(path66.sep).filter((s) => s.length > 0);
67013
+ const segments = resolved.split(path65.sep).filter((s) => s.length > 0);
66371
67014
  if (segments.length < 4) {
66372
67015
  return false;
66373
67016
  }
66374
- const leaf = path66.basename(resolved);
67017
+ const leaf = path65.basename(resolved);
66375
67018
  if (leaf !== "opencode-swarm@latest" && leaf !== "opencode-swarm") {
66376
67019
  return false;
66377
67020
  }
66378
- const parent = path66.basename(path66.dirname(resolved));
67021
+ const parent = path65.basename(path65.dirname(resolved));
66379
67022
  if (parent !== "packages" && parent !== "node_modules") {
66380
67023
  return false;
66381
67024
  }
66382
- const grandparent = path66.basename(path66.dirname(path66.dirname(resolved)));
67025
+ const grandparent = path65.basename(path65.dirname(path65.dirname(resolved)));
66383
67026
  if (grandparent !== "opencode") {
66384
67027
  return false;
66385
67028
  }
66386
67029
  return true;
66387
67030
  }
66388
67031
  function isSafeLockFilePath(p) {
66389
- const resolved = path66.resolve(p);
66390
- const home = path66.resolve(os10.homedir());
67032
+ const resolved = path65.resolve(p);
67033
+ const home = path65.resolve(os10.homedir());
66391
67034
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
66392
67035
  return false;
66393
67036
  }
66394
- const segments = resolved.split(path66.sep).filter((s) => s.length > 0);
67037
+ const segments = resolved.split(path65.sep).filter((s) => s.length > 0);
66395
67038
  if (segments.length < 4) {
66396
67039
  return false;
66397
67040
  }
66398
- const leaf = path66.basename(resolved);
67041
+ const leaf = path65.basename(resolved);
66399
67042
  if (leaf !== "bun.lock" && leaf !== "bun.lockb" && leaf !== "package-lock.json") {
66400
67043
  return false;
66401
67044
  }
66402
- const parent = path66.basename(path66.dirname(resolved));
67045
+ const parent = path65.basename(path65.dirname(resolved));
66403
67046
  if (parent !== "opencode") {
66404
67047
  return false;
66405
67048
  }
66406
- const grandparent = path66.basename(path66.dirname(path66.dirname(resolved)));
67049
+ const grandparent = path65.basename(path65.dirname(path65.dirname(resolved)));
66407
67050
  if (grandparent === "opencode") {
66408
67051
  return false;
66409
67052
  }
66410
67053
  return true;
66411
67054
  }
66412
67055
  function ensureDir(dir) {
66413
- if (!fs36.existsSync(dir)) {
66414
- fs36.mkdirSync(dir, { recursive: true });
67056
+ if (!fs35.existsSync(dir)) {
67057
+ fs35.mkdirSync(dir, { recursive: true });
66415
67058
  }
66416
67059
  }
66417
67060
  function loadJson(filepath) {
66418
67061
  try {
66419
- const content = fs36.readFileSync(filepath, "utf-8");
67062
+ const content = fs35.readFileSync(filepath, "utf-8");
66420
67063
  const stripped = content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (match, comment) => comment ? "" : match).replace(/,(\s*[}\]])/g, "$1");
66421
67064
  return JSON.parse(stripped);
66422
67065
  } catch {
@@ -66424,14 +67067,14 @@ function loadJson(filepath) {
66424
67067
  }
66425
67068
  }
66426
67069
  function saveJson(filepath, data) {
66427
- fs36.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
67070
+ fs35.writeFileSync(filepath, `${JSON.stringify(data, null, 2)}
66428
67071
  `, "utf-8");
66429
67072
  }
66430
67073
  function writeProjectConfigIfMissing(cwd) {
66431
67074
  try {
66432
- const opencodeDir = path66.join(cwd, ".opencode");
66433
- const projectConfigPath = path66.join(opencodeDir, "opencode-swarm.json");
66434
- if (fs36.existsSync(projectConfigPath)) {
67075
+ const opencodeDir = path65.join(cwd, ".opencode");
67076
+ const projectConfigPath = path65.join(opencodeDir, "opencode-swarm.json");
67077
+ if (fs35.existsSync(projectConfigPath)) {
66435
67078
  return;
66436
67079
  }
66437
67080
  ensureDir(opencodeDir);
@@ -66447,7 +67090,7 @@ async function install() {
66447
67090
  `);
66448
67091
  ensureDir(CONFIG_DIR);
66449
67092
  ensureDir(PROMPTS_DIR);
66450
- const LEGACY_CONFIG_PATH = path66.join(CONFIG_DIR, "config.json");
67093
+ const LEGACY_CONFIG_PATH = path65.join(CONFIG_DIR, "config.json");
66451
67094
  let opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
66452
67095
  if (!opencodeConfig) {
66453
67096
  const legacyConfig = loadJson(LEGACY_CONFIG_PATH);
@@ -66494,7 +67137,7 @@ async function install() {
66494
67137
  console.warn(`\u26A0 Could not clear opencode lock file \u2014 you may need to delete it manually:
66495
67138
  ${failed}`);
66496
67139
  }
66497
- if (!fs36.existsSync(PLUGIN_CONFIG_PATH)) {
67140
+ if (!fs35.existsSync(PLUGIN_CONFIG_PATH)) {
66498
67141
  const defaultConfig = {
66499
67142
  agents: { ...DEFAULT_AGENT_CONFIGS },
66500
67143
  max_iterations: 5
@@ -66573,14 +67216,14 @@ function evictPluginCaches() {
66573
67216
  const cleared = [];
66574
67217
  const failed = [];
66575
67218
  for (const cachePath of OPENCODE_PLUGIN_CACHE_PATHS) {
66576
- if (!fs36.existsSync(cachePath))
67219
+ if (!fs35.existsSync(cachePath))
66577
67220
  continue;
66578
67221
  if (!isSafeCachePath(cachePath)) {
66579
67222
  failed.push(`${cachePath} (refused: failed safety check)`);
66580
67223
  continue;
66581
67224
  }
66582
67225
  try {
66583
- fs36.rmSync(cachePath, { recursive: true, force: true });
67226
+ fs35.rmSync(cachePath, { recursive: true, force: true });
66584
67227
  cleared.push(cachePath);
66585
67228
  } catch (err) {
66586
67229
  failed.push(`${cachePath} (${err instanceof Error ? err.message : String(err)})`);
@@ -66592,14 +67235,14 @@ function evictLockFiles() {
66592
67235
  const cleared = [];
66593
67236
  const failed = [];
66594
67237
  for (const lockPath of OPENCODE_PLUGIN_LOCK_FILE_PATHS) {
66595
- if (!fs36.existsSync(lockPath))
67238
+ if (!fs35.existsSync(lockPath))
66596
67239
  continue;
66597
67240
  if (!isSafeLockFilePath(lockPath)) {
66598
67241
  failed.push(`${lockPath} (refused: failed safety check)`);
66599
67242
  continue;
66600
67243
  }
66601
67244
  try {
66602
- fs36.unlinkSync(lockPath);
67245
+ fs35.unlinkSync(lockPath);
66603
67246
  cleared.push(lockPath);
66604
67247
  } catch (err) {
66605
67248
  const code = err?.code;
@@ -66618,7 +67261,7 @@ async function uninstall() {
66618
67261
  `);
66619
67262
  const opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
66620
67263
  if (!opencodeConfig) {
66621
- if (fs36.existsSync(OPENCODE_CONFIG_PATH)) {
67264
+ if (fs35.existsSync(OPENCODE_CONFIG_PATH)) {
66622
67265
  console.log(`\u2717 Could not parse opencode config at: ${OPENCODE_CONFIG_PATH}`);
66623
67266
  return 1;
66624
67267
  } else {
@@ -66650,13 +67293,13 @@ async function uninstall() {
66650
67293
  console.log("\u2713 Re-enabled default OpenCode agents (explore, general)");
66651
67294
  if (process.argv.includes("--clean")) {
66652
67295
  let cleaned = false;
66653
- if (fs36.existsSync(PLUGIN_CONFIG_PATH)) {
66654
- fs36.unlinkSync(PLUGIN_CONFIG_PATH);
67296
+ if (fs35.existsSync(PLUGIN_CONFIG_PATH)) {
67297
+ fs35.unlinkSync(PLUGIN_CONFIG_PATH);
66655
67298
  console.log(`\u2713 Removed plugin config: ${PLUGIN_CONFIG_PATH}`);
66656
67299
  cleaned = true;
66657
67300
  }
66658
- if (fs36.existsSync(PROMPTS_DIR)) {
66659
- fs36.rmSync(PROMPTS_DIR, { recursive: true });
67301
+ if (fs35.existsSync(PROMPTS_DIR)) {
67302
+ fs35.rmSync(PROMPTS_DIR, { recursive: true });
66660
67303
  console.log(`\u2713 Removed custom prompts: ${PROMPTS_DIR}`);
66661
67304
  cleaned = true;
66662
67305
  }