opencode-swarm 7.83.0 → 7.84.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.
Files changed (54) hide show
  1. package/README.md +3 -1
  2. package/dist/cli/capability-probe-jevmgwmf.js +18 -0
  3. package/dist/cli/config-doctor-4tcdd9vt.js +35 -0
  4. package/dist/cli/dispatch-k86d928w.js +477 -0
  5. package/dist/cli/evidence-summary-service-g2znnd33.js +320 -0
  6. package/dist/cli/explorer-gz70sm9b.js +16 -0
  7. package/dist/cli/gate-evidence-y8zn7fe2.js +29 -0
  8. package/dist/cli/guardrail-explain-tcamcdfy.js +30 -0
  9. package/dist/cli/guardrail-log-fd14n96q.js +15 -0
  10. package/dist/cli/index-293f68mj.js +13538 -0
  11. package/dist/cli/index-8ra2qpk8.js +29027 -0
  12. package/dist/cli/index-a76rekgs.js +67 -0
  13. package/dist/cli/index-a82d6d87.js +1241 -0
  14. package/dist/cli/index-b9v501fr.js +371 -0
  15. package/dist/cli/index-bcp79s17.js +1673 -0
  16. package/dist/cli/index-ckntc5gf.js +91 -0
  17. package/dist/cli/index-d9fbxaqd.js +2314 -0
  18. package/dist/cli/index-e7h9bb6v.js +233 -0
  19. package/dist/cli/index-e8pk68cc.js +540 -0
  20. package/dist/cli/index-eb85wtx9.js +242 -0
  21. package/dist/cli/index-f8r50m3h.js +14505 -0
  22. package/dist/cli/index-fjwwrwr5.js +37 -0
  23. package/dist/cli/index-hz59hg4h.js +452 -0
  24. package/dist/cli/index-j710h2ge.js +412 -0
  25. package/dist/cli/index-jfgr5gye.js +110 -0
  26. package/dist/cli/index-jtqkh8jf.js +119 -0
  27. package/dist/cli/index-p0arc26j.js +28 -0
  28. package/dist/cli/index-p0ye10nd.js +222 -0
  29. package/dist/cli/index-pv2xmc9k.js +2391 -0
  30. package/dist/cli/index-red8fm8p.js +2914 -0
  31. package/dist/cli/index-wg3r6acj.js +2042 -0
  32. package/dist/cli/index-xw0bcy0v.js +583 -0
  33. package/dist/cli/index-yhsmmv2z.js +339 -0
  34. package/dist/cli/index-yx44zd0p.js +40 -0
  35. package/dist/cli/index-zfsbaaqh.js +29 -0
  36. package/dist/cli/index.js +73 -69708
  37. package/dist/cli/knowledge-store-n4x6zyk7.js +73 -0
  38. package/dist/cli/pending-delegations-pz61mrsz.js +255 -0
  39. package/dist/cli/pr-subscriptions-y1nn36e5.js +33 -0
  40. package/dist/cli/schema-c2dbzhm8.js +168 -0
  41. package/dist/cli/skill-generator-a5ehggyg.js +55 -0
  42. package/dist/cli/task-envelope-qn0qtnh0.js +90 -0
  43. package/dist/cli/telemetry-9bbyxrvn.js +20 -0
  44. package/dist/cli/workspace-snapshot-w58jr2ga.js +90 -0
  45. package/dist/commands/guardrail-explain.d.ts +1 -0
  46. package/dist/commands/guardrail-log.d.ts +1 -0
  47. package/dist/commands/index.d.ts +2 -0
  48. package/dist/commands/registry.d.ts +14 -0
  49. package/dist/hooks/guardrails/audit-log.d.ts +114 -0
  50. package/dist/index.js +3569 -2366
  51. package/dist/services/diagnose-service.d.ts +5 -0
  52. package/dist/services/guardrail-explain-service.d.ts +42 -0
  53. package/dist/services/guardrail-log-service.d.ts +10 -0
  54. package/package.json +2 -2
@@ -0,0 +1,1241 @@
1
+ // @bun
2
+ import {
3
+ ALL_AGENT_NAMES,
4
+ PluginConfigSchema,
5
+ stripKnownSwarmPrefix
6
+ } from "./index-wg3r6acj.js";
7
+ import {
8
+ log
9
+ } from "./index-yx44zd0p.js";
10
+
11
+ // src/services/config-doctor.ts
12
+ import * as crypto from "crypto";
13
+ import * as fs from "fs";
14
+ import * as os from "os";
15
+ import * as path from "path";
16
+ var KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(PluginConfigSchema.shape));
17
+ var DEPRECATED_FIELDS = new Map([
18
+ [
19
+ "skill_improver.model",
20
+ {
21
+ message: "deprecated",
22
+ replacement: "agents.skill_improver.model",
23
+ isDefaultValue: (v) => v === null
24
+ }
25
+ ],
26
+ [
27
+ "skill_improver.fallback_models",
28
+ {
29
+ message: "deprecated",
30
+ replacement: "agents.skill_improver.fallback_models",
31
+ isDefaultValue: (v) => Array.isArray(v) && v.length === 0
32
+ }
33
+ ],
34
+ [
35
+ "spec_writer.model",
36
+ {
37
+ message: "deprecated",
38
+ replacement: "agents.spec_writer.model",
39
+ isDefaultValue: (v) => v === null
40
+ }
41
+ ],
42
+ [
43
+ "spec_writer.fallback_models",
44
+ {
45
+ message: "deprecated",
46
+ replacement: "agents.spec_writer.fallback_models",
47
+ isDefaultValue: (v) => Array.isArray(v) && v.length === 0
48
+ }
49
+ ]
50
+ ]);
51
+ function levenshteinDistance(a, b) {
52
+ const al = a.length;
53
+ const bl = b.length;
54
+ const matrix = [];
55
+ for (let i = 0;i <= al; i++) {
56
+ matrix[i] = [i];
57
+ }
58
+ for (let j = 0;j <= bl; j++) {
59
+ matrix[0][j] = j;
60
+ }
61
+ for (let i = 1;i <= al; i++) {
62
+ for (let j = 1;j <= bl; j++) {
63
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
64
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
65
+ }
66
+ }
67
+ return matrix[al][bl];
68
+ }
69
+ function emitObjectTypeMismatch(key, value, findings) {
70
+ if (value !== undefined && (typeof value !== "object" || Array.isArray(value) || value === null)) {
71
+ findings.push({
72
+ id: `invalid-${key}-type`,
73
+ title: `Invalid ${key} type`,
74
+ description: `"${key}" must be an object, got ${typeof value}`,
75
+ severity: "error",
76
+ path: key,
77
+ currentValue: value,
78
+ autoFixable: false
79
+ });
80
+ }
81
+ }
82
+ function getUserConfigDir() {
83
+ return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
84
+ }
85
+ function getConfigPaths(directory) {
86
+ const userConfigPath = path.join(getUserConfigDir(), "opencode", "opencode-swarm.json");
87
+ const projectConfigPath = path.join(directory, ".opencode", "opencode-swarm.json");
88
+ return { userConfigPath, projectConfigPath };
89
+ }
90
+ function computeHash(content) {
91
+ return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
92
+ }
93
+ function isValidConfigPath(configPath, directory) {
94
+ const normalizedPath = configPath.replace(/\\/g, "/");
95
+ const pathParts = normalizedPath.split("/");
96
+ for (const part of pathParts) {
97
+ if (part === ".." || part === "") {
98
+ if (part === "..") {
99
+ return false;
100
+ }
101
+ }
102
+ }
103
+ const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
104
+ try {
105
+ const resolvedConfig = path.resolve(configPath);
106
+ const resolvedUser = path.resolve(userConfigPath);
107
+ const resolvedProject = path.resolve(projectConfigPath);
108
+ if (resolvedConfig !== resolvedUser && resolvedConfig !== resolvedProject) {
109
+ return false;
110
+ }
111
+ try {
112
+ if (fs.existsSync(resolvedConfig)) {
113
+ const realConfig = fs.realpathSync(resolvedConfig);
114
+ if (realConfig !== resolvedConfig) {
115
+ return false;
116
+ }
117
+ }
118
+ } catch {}
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ function atomicWriteFileSync(filePath, content) {
125
+ const tmpPath = `${filePath}.tmp.${process.pid}`;
126
+ fs.writeFileSync(tmpPath, content, "utf-8");
127
+ try {
128
+ fs.renameSync(tmpPath, filePath);
129
+ } catch {
130
+ try {
131
+ fs.unlinkSync(filePath);
132
+ } catch {}
133
+ fs.renameSync(tmpPath, filePath);
134
+ }
135
+ }
136
+ function createConfigBackup(directory) {
137
+ const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
138
+ let configPath = projectConfigPath;
139
+ let content = null;
140
+ if (fs.existsSync(projectConfigPath)) {
141
+ try {
142
+ content = fs.readFileSync(projectConfigPath, "utf-8");
143
+ } catch (error) {
144
+ log("[ConfigDoctor] project config read failed", {
145
+ error: error instanceof Error ? error.message : String(error)
146
+ });
147
+ }
148
+ }
149
+ if (content === null && fs.existsSync(userConfigPath)) {
150
+ configPath = userConfigPath;
151
+ try {
152
+ content = fs.readFileSync(userConfigPath, "utf-8");
153
+ } catch (error) {
154
+ log("[ConfigDoctor] user config read failed", {
155
+ error: error instanceof Error ? error.message : String(error)
156
+ });
157
+ }
158
+ }
159
+ if (content === null) {
160
+ return null;
161
+ }
162
+ return {
163
+ createdAt: Date.now(),
164
+ configPath,
165
+ content,
166
+ contentHash: computeHash(content)
167
+ };
168
+ }
169
+ function writeBackupArtifact(directory, backup) {
170
+ const swarmDir = path.join(directory, ".swarm");
171
+ if (!fs.existsSync(swarmDir)) {
172
+ fs.mkdirSync(swarmDir, { recursive: true });
173
+ }
174
+ const backupFilename = `config-backup-${backup.createdAt}.json`;
175
+ const backupPath = path.join(swarmDir, backupFilename);
176
+ const artifact = {
177
+ createdAt: backup.createdAt,
178
+ configPath: backup.configPath,
179
+ contentHash: backup.contentHash,
180
+ content: backup.content,
181
+ preview: backup.content.substring(0, 500) + (backup.content.length > 500 ? "..." : "")
182
+ };
183
+ atomicWriteFileSync(backupPath, JSON.stringify(artifact, null, 2));
184
+ return backupPath;
185
+ }
186
+ function restoreFromBackup(backupPath, directory) {
187
+ if (!fs.existsSync(backupPath)) {
188
+ return null;
189
+ }
190
+ const swarmDir = path.resolve(path.join(directory, ".swarm"));
191
+ const resolvedBackup = path.resolve(backupPath);
192
+ if (!resolvedBackup.startsWith(swarmDir + path.sep) && resolvedBackup !== swarmDir) {
193
+ return null;
194
+ }
195
+ try {
196
+ const artifact = JSON.parse(fs.readFileSync(backupPath, "utf-8"));
197
+ if (!artifact.content || !artifact.configPath || !artifact.contentHash) {
198
+ return null;
199
+ }
200
+ if (!isValidConfigPath(artifact.configPath, directory)) {
201
+ return null;
202
+ }
203
+ const computedHash = computeHash(artifact.content);
204
+ const storedHash = artifact.contentHash;
205
+ const isLegacyHash = /^\d+$/.test(storedHash);
206
+ if (!isLegacyHash && computedHash !== storedHash) {
207
+ return null;
208
+ }
209
+ log("[ConfigDoctor] Warning: restoring from backup with legacy numeric hash (pre-SHA-256). Consider re-backing up.", {});
210
+ const targetPath = artifact.configPath;
211
+ const targetDir = path.dirname(targetPath);
212
+ if (!fs.existsSync(targetDir)) {
213
+ fs.mkdirSync(targetDir, { recursive: true });
214
+ }
215
+ atomicWriteFileSync(targetPath, artifact.content);
216
+ return targetPath;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function readConfigFromFile(directory) {
222
+ const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
223
+ let configPath = projectConfigPath;
224
+ let configContent = null;
225
+ if (fs.existsSync(projectConfigPath)) {
226
+ configPath = projectConfigPath;
227
+ configContent = fs.readFileSync(projectConfigPath, "utf-8");
228
+ } else if (fs.existsSync(userConfigPath)) {
229
+ configPath = userConfigPath;
230
+ configContent = fs.readFileSync(userConfigPath, "utf-8");
231
+ }
232
+ if (configContent === null) {
233
+ return null;
234
+ }
235
+ try {
236
+ const config = JSON.parse(configContent);
237
+ return { config, configPath };
238
+ } catch (error) {
239
+ log(`[ConfigDoctor] Failed to parse config file: ${configPath}`, {
240
+ error: error instanceof Error ? error.message : String(error)
241
+ });
242
+ return null;
243
+ }
244
+ }
245
+ function validateConfigKey(path2, value) {
246
+ const findings = [];
247
+ for (const [depPath, depInfo] of DEPRECATED_FIELDS) {
248
+ if (path2 === depPath && !depInfo.isDefaultValue(value)) {
249
+ findings.push({
250
+ id: "deprecated-field",
251
+ title: `Deprecated config field: ${depPath}`,
252
+ description: `Config field "${depPath}" is deprecated. Replacement: ${depInfo.replacement}.`,
253
+ severity: "info",
254
+ path: depPath,
255
+ currentValue: value,
256
+ autoFixable: false
257
+ });
258
+ }
259
+ }
260
+ switch (path2) {
261
+ case "agents": {
262
+ if (value !== undefined) {
263
+ findings.push({
264
+ id: "deprecated-agents-config",
265
+ title: "Deprecated agents configuration",
266
+ description: 'The "agents" field is deprecated. Use "swarms" instead for multi-swarm support.',
267
+ severity: "warn",
268
+ path: "agents",
269
+ currentValue: value,
270
+ autoFixable: false,
271
+ proposedFix: {
272
+ type: "remove",
273
+ path: "agents",
274
+ description: "Remove deprecated agents config - use swarms instead",
275
+ risk: "low"
276
+ }
277
+ });
278
+ }
279
+ break;
280
+ }
281
+ case "guardrails.enabled": {
282
+ if (value === false) {
283
+ findings.push({
284
+ id: "guardrails-disabled",
285
+ title: "Guardrails disabled",
286
+ description: "Guardrails have been explicitly disabled. This removes safety limits.",
287
+ severity: "error",
288
+ path: "guardrails.enabled",
289
+ currentValue: value,
290
+ autoFixable: false
291
+ });
292
+ }
293
+ break;
294
+ }
295
+ case "guardrails.profiles": {
296
+ const profiles = value;
297
+ if (profiles) {
298
+ const validAgents = new Set(ALL_AGENT_NAMES);
299
+ for (const [agentName, profile] of Object.entries(profiles)) {
300
+ if (!validAgents.has(agentName)) {
301
+ findings.push({
302
+ id: "unknown-agent-profile",
303
+ title: "Unknown agent profile",
304
+ description: `Profile for unknown agent "${agentName}" will be ignored.`,
305
+ severity: "info",
306
+ path: `guardrails.profiles.${agentName}`,
307
+ currentValue: profile,
308
+ autoFixable: true,
309
+ proposedFix: {
310
+ type: "remove",
311
+ path: `guardrails.profiles.${agentName}`,
312
+ description: `Remove unknown agent profile "${agentName}"`,
313
+ risk: "low"
314
+ }
315
+ });
316
+ }
317
+ }
318
+ }
319
+ break;
320
+ }
321
+ case "automation.mode": {
322
+ const validModes = ["manual", "hybrid", "auto"];
323
+ if (value !== undefined && !validModes.includes(value)) {
324
+ findings.push({
325
+ id: "invalid-automation-mode",
326
+ title: "Invalid automation mode",
327
+ description: `Invalid automation mode "${value}". Valid: ${validModes.join(", ")}`,
328
+ severity: "error",
329
+ path: "automation.mode",
330
+ currentValue: value,
331
+ autoFixable: true,
332
+ proposedFix: {
333
+ type: "update",
334
+ path: "automation.mode",
335
+ value: "manual",
336
+ description: 'Reset to safe default "manual"',
337
+ risk: "low"
338
+ }
339
+ });
340
+ }
341
+ break;
342
+ }
343
+ case "automation.capabilities": {
344
+ const caps = value;
345
+ if (caps) {
346
+ const capabilityNames = [
347
+ "plan_sync",
348
+ "phase_preflight",
349
+ "config_doctor_on_startup",
350
+ "evidence_auto_summaries",
351
+ "decision_drift_detection"
352
+ ];
353
+ for (const [name, capValue] of Object.entries(caps)) {
354
+ if (capabilityNames.includes(name) && typeof capValue !== "boolean") {
355
+ findings.push({
356
+ id: "invalid-capability-type",
357
+ title: "Invalid capability type",
358
+ description: `Capability "${name}" must be boolean, got ${typeof capValue}`,
359
+ severity: "error",
360
+ path: `automation.capabilities.${name}`,
361
+ currentValue: capValue,
362
+ autoFixable: true,
363
+ proposedFix: {
364
+ type: "update",
365
+ path: `automation.capabilities.${name}`,
366
+ value: false,
367
+ description: `Reset capability "${name}" to false`,
368
+ risk: "low"
369
+ }
370
+ });
371
+ }
372
+ }
373
+ }
374
+ break;
375
+ }
376
+ case "hooks": {
377
+ emitObjectTypeMismatch("hooks", value, findings);
378
+ if (value !== undefined && typeof value === "object" && !Array.isArray(value) && value !== null) {
379
+ const hooks = value;
380
+ const validHooks = [
381
+ "system_enhancer",
382
+ "compaction",
383
+ "agent_activity",
384
+ "delegation_tracker",
385
+ "agent_awareness_max_chars",
386
+ "delegation_gate",
387
+ "delegation_max_chars"
388
+ ];
389
+ for (const hookName of Object.keys(hooks)) {
390
+ if (!validHooks.includes(hookName)) {
391
+ findings.push({
392
+ id: "unknown-hook-field",
393
+ title: "Unknown hook configuration",
394
+ description: `Unknown hook "${hookName}" will be ignored.`,
395
+ severity: "info",
396
+ path: `hooks.${hookName}`,
397
+ currentValue: hooks[hookName],
398
+ autoFixable: true,
399
+ proposedFix: {
400
+ type: "remove",
401
+ path: `hooks.${hookName}`,
402
+ description: `Remove unknown hook "${hookName}"`,
403
+ risk: "low"
404
+ }
405
+ });
406
+ }
407
+ }
408
+ }
409
+ break;
410
+ }
411
+ case "max_iterations": {
412
+ const numValue = value;
413
+ if (typeof numValue === "number") {
414
+ if (numValue < 1 || numValue > 10) {
415
+ findings.push({
416
+ id: "out-of-bounds-iterations",
417
+ title: "max_iterations out of bounds",
418
+ description: `max_iterations must be 1-10, got ${numValue}`,
419
+ severity: "error",
420
+ path: "max_iterations",
421
+ currentValue: numValue,
422
+ autoFixable: true,
423
+ proposedFix: {
424
+ type: "update",
425
+ path: "max_iterations",
426
+ value: Math.max(1, Math.min(10, numValue)),
427
+ description: "Clamp to valid range 1-10",
428
+ risk: "low"
429
+ }
430
+ });
431
+ }
432
+ }
433
+ break;
434
+ }
435
+ case "qa_retry_limit": {
436
+ const numValue = value;
437
+ if (typeof numValue === "number") {
438
+ if (numValue < 1 || numValue > 10) {
439
+ findings.push({
440
+ id: "out-of-bounds-retry-limit",
441
+ title: "qa_retry_limit out of bounds",
442
+ description: `qa_retry_limit must be 1-10, got ${numValue}`,
443
+ severity: "error",
444
+ path: "qa_retry_limit",
445
+ currentValue: numValue,
446
+ autoFixable: true,
447
+ proposedFix: {
448
+ type: "update",
449
+ path: "qa_retry_limit",
450
+ value: Math.max(1, Math.min(10, numValue)),
451
+ description: "Clamp to valid range 1-10",
452
+ risk: "low"
453
+ }
454
+ });
455
+ }
456
+ }
457
+ break;
458
+ }
459
+ case "swarms": {
460
+ if (value !== undefined) {
461
+ if (typeof value !== "object" || Array.isArray(value) || value === null) {
462
+ findings.push({
463
+ id: "invalid-swarms-type",
464
+ title: "Invalid swarms type",
465
+ description: `"swarms" must be an object, got ${typeof value}`,
466
+ severity: "error",
467
+ path: "swarms",
468
+ currentValue: value,
469
+ autoFixable: false
470
+ });
471
+ break;
472
+ }
473
+ const swarms = value;
474
+ if (Object.keys(swarms).length === 0) {
475
+ findings.push({
476
+ id: "empty-swarms",
477
+ title: "Empty swarms configuration",
478
+ description: 'The "swarms" field is an empty object. No swarm configurations are defined.',
479
+ severity: "info",
480
+ path: "swarms",
481
+ autoFixable: false
482
+ });
483
+ }
484
+ for (const swarmId of Object.keys(swarms)) {
485
+ if (swarmId.includes("..") || swarmId.includes("/") || swarmId.includes("\\") || swarmId.includes("\x00")) {
486
+ findings.push({
487
+ id: "swarm-id-path-traversal",
488
+ title: "Path traversal in swarm ID",
489
+ description: `Swarm ID "${swarmId}" contains path traversal characters.`,
490
+ severity: "error",
491
+ path: `swarms.${swarmId}`,
492
+ autoFixable: false
493
+ });
494
+ }
495
+ }
496
+ const validAgents = new Set(ALL_AGENT_NAMES);
497
+ for (const [swarmId, swarmConfig] of Object.entries(swarms)) {
498
+ const swarm = swarmConfig;
499
+ if (swarm.agents && typeof swarm.agents === "object") {
500
+ for (const [agentName] of Object.entries(swarm.agents)) {
501
+ const baseName = stripKnownSwarmPrefix(agentName);
502
+ if (baseName !== agentName && agentName.startsWith(`${swarmId}_`) && validAgents.has(baseName)) {
503
+ findings.push({
504
+ id: "prefixed-swarm-agent-override",
505
+ title: "Prefixed agent override is ignored",
506
+ description: `Agent "${agentName}" in swarm "${swarmId}" uses a generated agent name. ` + `Per-swarm overrides must use the canonical key "${baseName}", e.g. ` + `"swarms.${swarmId}.agents.${baseName}.model". Otherwise the override is ignored and the agent falls back to its default model.`,
507
+ severity: "warn",
508
+ path: `swarms.${swarmId}.agents.${agentName}`,
509
+ currentValue: swarm.agents[agentName],
510
+ autoFixable: false
511
+ });
512
+ } else if (!validAgents.has(baseName)) {
513
+ findings.push({
514
+ id: "unknown-swarm-agent",
515
+ title: "Unknown agent in swarm",
516
+ description: `Agent "${agentName}" in swarm "${swarmId}" may not be recognized.`,
517
+ severity: "info",
518
+ path: `swarms.${swarmId}.agents.${agentName}`,
519
+ currentValue: swarm.agents[agentName],
520
+ autoFixable: false
521
+ });
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+ break;
528
+ }
529
+ case "default_agent": {
530
+ if (value !== undefined && typeof value !== "string") {
531
+ findings.push({
532
+ id: "invalid-default_agent-type",
533
+ title: "Invalid default_agent type",
534
+ description: `"default_agent" must be a string, got ${typeof value}`,
535
+ severity: "error",
536
+ path: "default_agent",
537
+ currentValue: value,
538
+ autoFixable: false
539
+ });
540
+ }
541
+ break;
542
+ }
543
+ case "auto_select_architect": {
544
+ if (value !== undefined && typeof value !== "boolean" && typeof value !== "string") {
545
+ findings.push({
546
+ id: "invalid-auto_select_architect-type",
547
+ title: "Invalid auto_select_architect type",
548
+ description: `"auto_select_architect" must be a boolean or string, got ${typeof value}`,
549
+ severity: "error",
550
+ path: "auto_select_architect",
551
+ currentValue: value,
552
+ autoFixable: false
553
+ });
554
+ }
555
+ break;
556
+ }
557
+ case "pipeline": {
558
+ emitObjectTypeMismatch("pipeline", value, findings);
559
+ break;
560
+ }
561
+ case "phase_complete": {
562
+ emitObjectTypeMismatch("phase_complete", value, findings);
563
+ break;
564
+ }
565
+ case "execution_mode": {
566
+ const validModes = ["strict", "balanced", "fast"];
567
+ if (value !== undefined && !validModes.includes(value)) {
568
+ findings.push({
569
+ id: "invalid-execution_mode-type",
570
+ title: "Invalid execution_mode",
571
+ description: `"execution_mode" must be one of: ${validModes.join(", ")}, got "${value}"`,
572
+ severity: "error",
573
+ path: "execution_mode",
574
+ currentValue: value,
575
+ autoFixable: false
576
+ });
577
+ }
578
+ break;
579
+ }
580
+ case "inject_phase_reminders": {
581
+ if (value !== undefined && typeof value !== "boolean") {
582
+ findings.push({
583
+ id: "invalid-inject_phase_reminders-type",
584
+ title: "Invalid inject_phase_reminders type",
585
+ description: `"inject_phase_reminders" must be a boolean, got ${typeof value}`,
586
+ severity: "error",
587
+ path: "inject_phase_reminders",
588
+ currentValue: value,
589
+ autoFixable: false
590
+ });
591
+ }
592
+ break;
593
+ }
594
+ case "gates": {
595
+ emitObjectTypeMismatch("gates", value, findings);
596
+ break;
597
+ }
598
+ case "context_budget": {
599
+ emitObjectTypeMismatch("context_budget", value, findings);
600
+ break;
601
+ }
602
+ case "guardrails": {
603
+ emitObjectTypeMismatch("guardrails", value, findings);
604
+ break;
605
+ }
606
+ case "watchdog": {
607
+ emitObjectTypeMismatch("watchdog", value, findings);
608
+ break;
609
+ }
610
+ case "self_review": {
611
+ emitObjectTypeMismatch("self_review", value, findings);
612
+ break;
613
+ }
614
+ case "tool_filter": {
615
+ emitObjectTypeMismatch("tool_filter", value, findings);
616
+ break;
617
+ }
618
+ case "authority": {
619
+ emitObjectTypeMismatch("authority", value, findings);
620
+ break;
621
+ }
622
+ case "plan_cursor": {
623
+ emitObjectTypeMismatch("plan_cursor", value, findings);
624
+ break;
625
+ }
626
+ case "context_map": {
627
+ emitObjectTypeMismatch("context_map", value, findings);
628
+ break;
629
+ }
630
+ case "evidence": {
631
+ emitObjectTypeMismatch("evidence", value, findings);
632
+ break;
633
+ }
634
+ case "summaries": {
635
+ emitObjectTypeMismatch("summaries", value, findings);
636
+ break;
637
+ }
638
+ case "review_passes": {
639
+ emitObjectTypeMismatch("review_passes", value, findings);
640
+ break;
641
+ }
642
+ case "adversarial_detection": {
643
+ emitObjectTypeMismatch("adversarial_detection", value, findings);
644
+ break;
645
+ }
646
+ case "adversarial_testing": {
647
+ emitObjectTypeMismatch("adversarial_testing", value, findings);
648
+ break;
649
+ }
650
+ case "integration_analysis": {
651
+ emitObjectTypeMismatch("integration_analysis", value, findings);
652
+ break;
653
+ }
654
+ case "docs": {
655
+ emitObjectTypeMismatch("docs", value, findings);
656
+ break;
657
+ }
658
+ case "design_docs": {
659
+ emitObjectTypeMismatch("design_docs", value, findings);
660
+ break;
661
+ }
662
+ case "ui_review": {
663
+ emitObjectTypeMismatch("ui_review", value, findings);
664
+ break;
665
+ }
666
+ case "compaction_advisory": {
667
+ emitObjectTypeMismatch("compaction_advisory", value, findings);
668
+ break;
669
+ }
670
+ case "lint": {
671
+ emitObjectTypeMismatch("lint", value, findings);
672
+ break;
673
+ }
674
+ case "secretscan": {
675
+ emitObjectTypeMismatch("secretscan", value, findings);
676
+ break;
677
+ }
678
+ case "checkpoint": {
679
+ emitObjectTypeMismatch("checkpoint", value, findings);
680
+ break;
681
+ }
682
+ case "automation": {
683
+ emitObjectTypeMismatch("automation", value, findings);
684
+ break;
685
+ }
686
+ case "knowledge": {
687
+ emitObjectTypeMismatch("knowledge", value, findings);
688
+ break;
689
+ }
690
+ case "memory": {
691
+ emitObjectTypeMismatch("memory", value, findings);
692
+ break;
693
+ }
694
+ case "curator": {
695
+ emitObjectTypeMismatch("curator", value, findings);
696
+ break;
697
+ }
698
+ case "architectural_supervision": {
699
+ emitObjectTypeMismatch("architectural_supervision", value, findings);
700
+ break;
701
+ }
702
+ case "knowledge_application": {
703
+ emitObjectTypeMismatch("knowledge_application", value, findings);
704
+ break;
705
+ }
706
+ case "skillPropagation": {
707
+ emitObjectTypeMismatch("skillPropagation", value, findings);
708
+ break;
709
+ }
710
+ case "skill_improver": {
711
+ emitObjectTypeMismatch("skill_improver", value, findings);
712
+ break;
713
+ }
714
+ case "spec_writer": {
715
+ emitObjectTypeMismatch("spec_writer", value, findings);
716
+ break;
717
+ }
718
+ case "tool_output": {
719
+ emitObjectTypeMismatch("tool_output", value, findings);
720
+ break;
721
+ }
722
+ case "slop_detector": {
723
+ emitObjectTypeMismatch("slop_detector", value, findings);
724
+ break;
725
+ }
726
+ case "todo_gate": {
727
+ emitObjectTypeMismatch("todo_gate", value, findings);
728
+ break;
729
+ }
730
+ case "incremental_verify": {
731
+ emitObjectTypeMismatch("incremental_verify", value, findings);
732
+ break;
733
+ }
734
+ case "compaction_service": {
735
+ emitObjectTypeMismatch("compaction_service", value, findings);
736
+ break;
737
+ }
738
+ case "prm": {
739
+ emitObjectTypeMismatch("prm", value, findings);
740
+ break;
741
+ }
742
+ case "council": {
743
+ emitObjectTypeMismatch("council", value, findings);
744
+ break;
745
+ }
746
+ case "parallelization": {
747
+ emitObjectTypeMismatch("parallelization", value, findings);
748
+ break;
749
+ }
750
+ case "worktree": {
751
+ emitObjectTypeMismatch("worktree", value, findings);
752
+ break;
753
+ }
754
+ case "turbo": {
755
+ emitObjectTypeMismatch("turbo", value, findings);
756
+ break;
757
+ }
758
+ case "turbo_mode": {
759
+ if (value !== undefined && typeof value !== "boolean") {
760
+ findings.push({
761
+ id: "invalid-turbo_mode-type",
762
+ title: "Invalid turbo_mode type",
763
+ description: `"turbo_mode" must be a boolean, got ${typeof value}`,
764
+ severity: "error",
765
+ path: "turbo_mode",
766
+ currentValue: value,
767
+ autoFixable: false
768
+ });
769
+ }
770
+ break;
771
+ }
772
+ case "quiet": {
773
+ if (value !== undefined && typeof value !== "boolean") {
774
+ findings.push({
775
+ id: "invalid-quiet-type",
776
+ title: "Invalid quiet type",
777
+ description: `"quiet" must be a boolean, got ${typeof value}`,
778
+ severity: "error",
779
+ path: "quiet",
780
+ currentValue: value,
781
+ autoFixable: false
782
+ });
783
+ }
784
+ break;
785
+ }
786
+ case "version_check": {
787
+ if (value !== undefined && typeof value !== "boolean") {
788
+ findings.push({
789
+ id: "invalid-version_check-type",
790
+ title: "Invalid version_check type",
791
+ description: `"version_check" must be a boolean, got ${typeof value}`,
792
+ severity: "error",
793
+ path: "version_check",
794
+ currentValue: value,
795
+ autoFixable: false
796
+ });
797
+ }
798
+ break;
799
+ }
800
+ case "full_auto": {
801
+ emitObjectTypeMismatch("full_auto", value, findings);
802
+ break;
803
+ }
804
+ case "pr_monitor": {
805
+ emitObjectTypeMismatch("pr_monitor", value, findings);
806
+ break;
807
+ }
808
+ case "external_skills": {
809
+ emitObjectTypeMismatch("external_skills", value, findings);
810
+ break;
811
+ }
812
+ default: {
813
+ const topLevel = path2.split(".")[0];
814
+ if (KNOWN_TOP_LEVEL_KEYS.has(topLevel)) {
815
+ break;
816
+ }
817
+ const MAX_SUGGESTION_KEY_LENGTH = 100;
818
+ const lowerTopLevel = topLevel.toLowerCase();
819
+ let suggestion;
820
+ let matchCount = 0;
821
+ if (lowerTopLevel.length <= MAX_SUGGESTION_KEY_LENGTH) {
822
+ for (const knownKey of KNOWN_TOP_LEVEL_KEYS) {
823
+ if (levenshteinDistance(lowerTopLevel, knownKey.toLowerCase()) <= 2) {
824
+ matchCount++;
825
+ if (matchCount === 1) {
826
+ suggestion = knownKey;
827
+ }
828
+ }
829
+ }
830
+ }
831
+ if (matchCount === 1 && suggestion) {
832
+ findings.push({
833
+ id: "unknown-config-key",
834
+ title: `Unknown config key: ${topLevel}`,
835
+ description: `Unknown config key "${path2}" is not in the schema. Did you mean "${suggestion}"?`,
836
+ severity: "warn",
837
+ path: path2,
838
+ currentValue: value,
839
+ autoFixable: false
840
+ });
841
+ } else {
842
+ findings.push({
843
+ id: "unknown-config-key",
844
+ title: `Unknown config key: ${topLevel}`,
845
+ description: `Unknown config key "${path2}" is not in the schema.`,
846
+ severity: "warn",
847
+ path: path2,
848
+ currentValue: value,
849
+ autoFixable: false
850
+ });
851
+ }
852
+ break;
853
+ }
854
+ }
855
+ return findings;
856
+ }
857
+ function walkConfigAndValidate(obj, path2, findings, visited = new WeakSet) {
858
+ if (obj === null || obj === undefined) {
859
+ return;
860
+ }
861
+ if (path2 && typeof obj === "object" && !Array.isArray(obj)) {
862
+ const keyFindings = validateConfigKey(path2, obj);
863
+ findings.push(...keyFindings);
864
+ }
865
+ if (typeof obj !== "object") {
866
+ const keyFindings = validateConfigKey(path2, obj);
867
+ findings.push(...keyFindings);
868
+ return;
869
+ }
870
+ if (visited.has(obj)) {
871
+ findings.push({
872
+ id: "circular-reference",
873
+ title: `Circular reference detected at ${path2}`,
874
+ description: `Config value at "${path2}" contains a circular reference. Validation stopped at this path to prevent stack overflow.`,
875
+ severity: "error",
876
+ path: path2,
877
+ currentValue: "[circular]",
878
+ autoFixable: false
879
+ });
880
+ return;
881
+ }
882
+ visited.add(obj);
883
+ if (Array.isArray(obj)) {
884
+ const arrayFindings = validateConfigKey(path2, obj);
885
+ findings.push(...arrayFindings);
886
+ obj.forEach((item, index) => {
887
+ walkConfigAndValidate(item, `${path2}[${index}]`, findings, visited);
888
+ });
889
+ return;
890
+ }
891
+ for (const [key, value] of Object.entries(obj)) {
892
+ const newPath = path2 ? `${path2}.${key}` : key;
893
+ walkConfigAndValidate(value, newPath, findings, visited);
894
+ }
895
+ }
896
+ function runConfigDoctor(config, directory) {
897
+ const findings = [];
898
+ walkConfigAndValidate(config, "", findings);
899
+ const summary = {
900
+ info: findings.filter((f) => f.severity === "info").length,
901
+ warn: findings.filter((f) => f.severity === "warn").length,
902
+ error: findings.filter((f) => f.severity === "error").length
903
+ };
904
+ const hasAutoFixableIssues = findings.some((f) => f.autoFixable && f.proposedFix?.risk === "low");
905
+ const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
906
+ let configSource = "defaults";
907
+ if (fs.existsSync(projectConfigPath)) {
908
+ configSource = projectConfigPath;
909
+ } else if (fs.existsSync(userConfigPath)) {
910
+ configSource = userConfigPath;
911
+ }
912
+ return {
913
+ findings,
914
+ summary,
915
+ hasAutoFixableIssues,
916
+ timestamp: Date.now(),
917
+ configSource
918
+ };
919
+ }
920
+ var DANGEROUS_PATH_SEGMENTS = new Set([
921
+ "__proto__",
922
+ "constructor",
923
+ "prototype"
924
+ ]);
925
+ function isDangerousPathSegment(segment) {
926
+ return DANGEROUS_PATH_SEGMENTS.has(segment);
927
+ }
928
+ function isPathSafe(fixPath) {
929
+ const segments = fixPath.split(".");
930
+ for (const segment of segments) {
931
+ if (isDangerousPathSegment(segment)) {
932
+ return false;
933
+ }
934
+ }
935
+ return true;
936
+ }
937
+ function applySafeAutoFixes(directory, result) {
938
+ const appliedFixes = [];
939
+ let updatedConfigPath = null;
940
+ const { userConfigPath, projectConfigPath } = getConfigPaths(directory);
941
+ let configPath = projectConfigPath;
942
+ let configContent;
943
+ if (fs.existsSync(projectConfigPath)) {
944
+ configPath = projectConfigPath;
945
+ configContent = fs.readFileSync(projectConfigPath, "utf-8");
946
+ } else if (fs.existsSync(userConfigPath)) {
947
+ configPath = userConfigPath;
948
+ configContent = fs.readFileSync(userConfigPath, "utf-8");
949
+ } else {
950
+ return { appliedFixes, updatedConfigPath: null };
951
+ }
952
+ let config;
953
+ try {
954
+ config = JSON.parse(configContent);
955
+ } catch {
956
+ return { appliedFixes, updatedConfigPath: null };
957
+ }
958
+ const safeFixes = result.findings.filter((f) => f.autoFixable && f.proposedFix?.risk === "low");
959
+ for (const finding of safeFixes) {
960
+ const fix = finding.proposedFix;
961
+ if (!fix)
962
+ continue;
963
+ if (!isPathSafe(fix.path)) {
964
+ continue;
965
+ }
966
+ const pathParts = fix.path.split(".");
967
+ let current = config;
968
+ let navigated = true;
969
+ for (let i = 0;i < pathParts.length - 1; i++) {
970
+ const part = pathParts[i];
971
+ if (current === null || current === undefined) {
972
+ navigated = false;
973
+ break;
974
+ }
975
+ if (typeof current !== "object" || Array.isArray(current)) {
976
+ navigated = false;
977
+ break;
978
+ }
979
+ const obj = current;
980
+ if (obj[part] === undefined) {
981
+ obj[part] = {};
982
+ } else if (obj[part] === null) {
983
+ navigated = false;
984
+ break;
985
+ } else if (typeof obj[part] !== "object") {
986
+ navigated = false;
987
+ break;
988
+ }
989
+ current = obj[part];
990
+ }
991
+ if (!navigated) {
992
+ continue;
993
+ }
994
+ const lastPart = pathParts[pathParts.length - 1];
995
+ switch (fix.type) {
996
+ case "remove":
997
+ if (current !== null && current !== undefined && typeof current === "object") {
998
+ delete current[lastPart];
999
+ appliedFixes.push(fix);
1000
+ }
1001
+ break;
1002
+ case "update":
1003
+ if (current !== null && current !== undefined && typeof current === "object") {
1004
+ current[lastPart] = fix.value;
1005
+ appliedFixes.push(fix);
1006
+ }
1007
+ break;
1008
+ case "add":
1009
+ if (current !== null && current !== undefined && typeof current === "object") {
1010
+ current[lastPart] = fix.value;
1011
+ appliedFixes.push(fix);
1012
+ }
1013
+ break;
1014
+ }
1015
+ }
1016
+ if (appliedFixes.length > 0) {
1017
+ const configDir = path.dirname(configPath);
1018
+ if (!fs.existsSync(configDir)) {
1019
+ fs.mkdirSync(configDir, { recursive: true });
1020
+ }
1021
+ atomicWriteFileSync(configPath, JSON.stringify(config, null, 2));
1022
+ updatedConfigPath = configPath;
1023
+ }
1024
+ return { appliedFixes, updatedConfigPath };
1025
+ }
1026
+ function readDoctorArtifact(directory) {
1027
+ try {
1028
+ const artifactPath = path.join(directory, ".swarm", "config-doctor.json");
1029
+ if (!fs.existsSync(artifactPath)) {
1030
+ return null;
1031
+ }
1032
+ const content = fs.readFileSync(artifactPath, "utf-8");
1033
+ const artifact = JSON.parse(content);
1034
+ const summary = artifact.summary;
1035
+ if (!summary || typeof summary !== "object") {
1036
+ return null;
1037
+ }
1038
+ const infoVal = summary.info;
1039
+ const warnVal = summary.warn;
1040
+ const errorVal = summary.error;
1041
+ if (typeof infoVal !== "number" || !Number.isFinite(infoVal) || typeof warnVal !== "number" || !Number.isFinite(warnVal) || typeof errorVal !== "number" || !Number.isFinite(errorVal)) {
1042
+ return null;
1043
+ }
1044
+ const ts = artifact.timestamp;
1045
+ if (typeof ts !== "number" || !Number.isFinite(ts)) {
1046
+ return null;
1047
+ }
1048
+ const findingsCount = infoVal + warnVal + errorVal;
1049
+ const findings = artifact.findings;
1050
+ const autoFixableCount = Array.isArray(findings) ? findings.filter((f) => f.autoFixable === true).length : 0;
1051
+ return {
1052
+ timestamp: new Date(ts).toISOString(),
1053
+ findingsCount,
1054
+ autoFixableCount
1055
+ };
1056
+ } catch {
1057
+ return null;
1058
+ }
1059
+ }
1060
+ function writeDoctorArtifact(directory, result) {
1061
+ const swarmDir = path.join(directory, ".swarm");
1062
+ if (!fs.existsSync(swarmDir)) {
1063
+ fs.mkdirSync(swarmDir, { recursive: true });
1064
+ }
1065
+ const artifactFilename = "config-doctor.json";
1066
+ const artifactPath = path.join(swarmDir, artifactFilename);
1067
+ const guiOutput = {
1068
+ timestamp: result.timestamp,
1069
+ summary: result.summary,
1070
+ hasAutoFixableIssues: result.hasAutoFixableIssues,
1071
+ configSource: result.configSource,
1072
+ findings: result.findings.map((f) => ({
1073
+ id: f.id,
1074
+ title: f.title,
1075
+ description: f.description,
1076
+ severity: f.severity,
1077
+ path: f.path,
1078
+ autoFixable: f.autoFixable,
1079
+ proposedFix: f.proposedFix ? {
1080
+ type: f.proposedFix.type,
1081
+ path: f.proposedFix.path,
1082
+ description: f.proposedFix.description,
1083
+ risk: f.proposedFix.risk
1084
+ } : null
1085
+ }))
1086
+ };
1087
+ atomicWriteFileSync(artifactPath, JSON.stringify(guiOutput, null, 2));
1088
+ return artifactPath;
1089
+ }
1090
+ function shouldRunOnStartup(automationConfig) {
1091
+ if (!automationConfig) {
1092
+ return false;
1093
+ }
1094
+ if (automationConfig.mode === "manual") {
1095
+ return false;
1096
+ }
1097
+ return automationConfig.capabilities?.config_doctor_on_startup === true;
1098
+ }
1099
+ async function runConfigDoctorWithFixes(directory, config, autoFix = false) {
1100
+ const result = runConfigDoctor(config, directory);
1101
+ const artifactPath = writeDoctorArtifact(directory, result);
1102
+ if (!autoFix) {
1103
+ return {
1104
+ result,
1105
+ backupPath: null,
1106
+ appliedFixes: [],
1107
+ updatedConfigPath: null,
1108
+ artifactPath
1109
+ };
1110
+ }
1111
+ const backup = createConfigBackup(directory);
1112
+ let backupPath = null;
1113
+ if (backup) {
1114
+ backupPath = writeBackupArtifact(directory, backup);
1115
+ }
1116
+ const { appliedFixes, updatedConfigPath } = applySafeAutoFixes(directory, result);
1117
+ if (appliedFixes.length > 0) {
1118
+ const freshConfig = readConfigFromFile(directory);
1119
+ if (freshConfig) {
1120
+ const newResult = runConfigDoctor(freshConfig.config, directory);
1121
+ writeDoctorArtifact(directory, newResult);
1122
+ }
1123
+ }
1124
+ return {
1125
+ result,
1126
+ backupPath,
1127
+ appliedFixes,
1128
+ updatedConfigPath,
1129
+ artifactPath
1130
+ };
1131
+ }
1132
+ function detectStraySwarmDirs(projectRoot) {
1133
+ const findings = [];
1134
+ const SKIP_DIRS = new Set([
1135
+ "node_modules",
1136
+ ".git",
1137
+ "dist",
1138
+ ".cache",
1139
+ ".next",
1140
+ "coverage",
1141
+ ".turbo",
1142
+ ".vercel",
1143
+ ".terraform",
1144
+ "__pycache__",
1145
+ ".tox"
1146
+ ]);
1147
+ const MAX_DEPTH = 10;
1148
+ const MAX_CONTENTS_ENTRIES = 20;
1149
+ function walk(dir, depth) {
1150
+ if (depth > MAX_DEPTH)
1151
+ return;
1152
+ let entries;
1153
+ try {
1154
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1155
+ } catch {
1156
+ return;
1157
+ }
1158
+ for (const entry of entries) {
1159
+ if (!entry.isDirectory())
1160
+ continue;
1161
+ const name = entry.name;
1162
+ const fullPath = path.join(dir, name);
1163
+ if (SKIP_DIRS.has(name))
1164
+ continue;
1165
+ const gitPath = path.join(fullPath, ".git");
1166
+ try {
1167
+ const gitStat = fs.statSync(gitPath);
1168
+ if (gitStat.isFile() || gitStat.isDirectory())
1169
+ continue;
1170
+ } catch {}
1171
+ if (name === ".swarm") {
1172
+ const parentDir = path.dirname(fullPath);
1173
+ if (parentDir === projectRoot)
1174
+ continue;
1175
+ let contents = [];
1176
+ try {
1177
+ contents = fs.readdirSync(fullPath);
1178
+ } catch {
1179
+ contents = ["<unreadable>"];
1180
+ }
1181
+ findings.push({
1182
+ path: path.relative(projectRoot, fullPath).replace(/\\/g, "/"),
1183
+ absolutePath: fullPath,
1184
+ contents: contents.slice(0, MAX_CONTENTS_ENTRIES),
1185
+ totalEntries: contents.length
1186
+ });
1187
+ continue;
1188
+ }
1189
+ walk(fullPath, depth + 1);
1190
+ }
1191
+ }
1192
+ walk(projectRoot, 0);
1193
+ return findings;
1194
+ }
1195
+ function removeStraySwarmDir(projectRoot, strayPath) {
1196
+ let canonicalRoot;
1197
+ let canonicalStray;
1198
+ try {
1199
+ canonicalRoot = fs.realpathSync(projectRoot);
1200
+ canonicalStray = fs.realpathSync(path.isAbsolute(strayPath) ? strayPath : path.resolve(projectRoot, strayPath));
1201
+ } catch (err) {
1202
+ return {
1203
+ success: false,
1204
+ message: `Failed to resolve paths: ${err instanceof Error ? err.message : String(err)}`
1205
+ };
1206
+ }
1207
+ const rootSwarm = path.join(canonicalRoot, ".swarm");
1208
+ if (canonicalStray === rootSwarm || canonicalStray === canonicalRoot) {
1209
+ return {
1210
+ success: false,
1211
+ message: "Refusing to remove root .swarm/ directory"
1212
+ };
1213
+ }
1214
+ if (!canonicalStray.startsWith(canonicalRoot + path.sep)) {
1215
+ return {
1216
+ success: false,
1217
+ message: "Path is outside project root \u2014 refusing to remove"
1218
+ };
1219
+ }
1220
+ const normalizedStray = canonicalStray.replace(/\\/g, "/");
1221
+ if (!normalizedStray.endsWith("/.swarm")) {
1222
+ return {
1223
+ success: false,
1224
+ message: "Path is not a .swarm directory \u2014 refusing to remove"
1225
+ };
1226
+ }
1227
+ try {
1228
+ fs.rmSync(canonicalStray, { recursive: true, force: true });
1229
+ return {
1230
+ success: true,
1231
+ message: `Removed stray .swarm directory: ${canonicalStray}`
1232
+ };
1233
+ } catch (err) {
1234
+ return {
1235
+ success: false,
1236
+ message: `Failed to remove: ${err instanceof Error ? err.message : String(err)}`
1237
+ };
1238
+ }
1239
+ }
1240
+
1241
+ export { getConfigPaths, createConfigBackup, writeBackupArtifact, restoreFromBackup, runConfigDoctor, applySafeAutoFixes, readDoctorArtifact, writeDoctorArtifact, shouldRunOnStartup, runConfigDoctorWithFixes, detectStraySwarmDirs, removeStraySwarmDir };