opencode-swarm 6.8.0 → 6.8.1

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 (126) hide show
  1. package/README.md +76 -5
  2. package/dist/index.js +1252 -1316
  3. package/dist/src/agents/architect.d.ts +8 -0
  4. package/dist/{agents/test-engineer.d.ts → src/agents/coder.d.ts} +2 -1
  5. package/dist/src/agents/critic.d.ts +3 -0
  6. package/dist/src/agents/designer.d.ts +3 -0
  7. package/dist/src/agents/docs.d.ts +3 -0
  8. package/dist/src/agents/explorer.d.ts +3 -0
  9. package/dist/src/agents/model.d.ts +2 -0
  10. package/dist/{agents → src/agents}/reviewer.d.ts +2 -1
  11. package/dist/src/agents/sme.d.ts +3 -0
  12. package/dist/src/agents/test-engineer.d.ts +3 -0
  13. package/dist/{background → src/background}/trigger.d.ts +0 -16
  14. package/dist/src/config/loader.d.ts +16 -0
  15. package/dist/{config → src/config}/schema.d.ts +171 -0
  16. package/dist/{index.d.ts → src/index.d.ts} +0 -10
  17. package/dist/{state.d.ts → src/state.d.ts} +5 -0
  18. package/dist/{tools → src/tools}/gitingest.d.ts +2 -1
  19. package/dist/{tools/test-runner.d.ts → src/tools/test-runner/constants.d.ts} +0 -4
  20. package/dist/src/tools/test-runner/detect.d.ts +2 -0
  21. package/dist/src/tools/test-runner/discover.d.ts +4 -0
  22. package/dist/src/tools/test-runner/index.d.ts +6 -0
  23. package/dist/src/tools/test-runner/run.d.ts +2 -0
  24. package/dist/src/tools/test-runner/validate.d.ts +2 -0
  25. package/dist/src/utils/index.d.ts +8 -0
  26. package/package.json +1 -1
  27. package/dist/agents/architect.d.ts +0 -7
  28. package/dist/agents/coder.d.ts +0 -2
  29. package/dist/agents/critic.d.ts +0 -2
  30. package/dist/agents/designer.d.ts +0 -2
  31. package/dist/agents/docs.d.ts +0 -2
  32. package/dist/agents/explorer.d.ts +0 -2
  33. package/dist/agents/sme.d.ts +0 -2
  34. package/dist/config/loader.d.ts +0 -32
  35. package/dist/utils/index.d.ts +0 -3
  36. /package/dist/{__tests__ → src/__tests__}/security-adversarial.test.d.ts +0 -0
  37. /package/dist/{agents → src/agents}/index.d.ts +0 -0
  38. /package/dist/{agents → src/agents}/test-engineer.adversarial.test.d.ts +0 -0
  39. /package/dist/{agents → src/agents}/test-engineer.security.test.d.ts +0 -0
  40. /package/dist/{background → src/background}/circuit-breaker.d.ts +0 -0
  41. /package/dist/{background → src/background}/event-bus.d.ts +0 -0
  42. /package/dist/{background → src/background}/evidence-summary-integration.d.ts +0 -0
  43. /package/dist/{background → src/background}/index.d.ts +0 -0
  44. /package/dist/{background → src/background}/manager.d.ts +0 -0
  45. /package/dist/{background → src/background}/plan-sync-worker.d.ts +0 -0
  46. /package/dist/{background → src/background}/queue.d.ts +0 -0
  47. /package/dist/{background → src/background}/status-artifact.d.ts +0 -0
  48. /package/dist/{background → src/background}/trigger.vulnerability.test.d.ts +0 -0
  49. /package/dist/{background → src/background}/worker.d.ts +0 -0
  50. /package/dist/{cli → src/cli}/index.d.ts +0 -0
  51. /package/dist/{commands → src/commands}/agents.d.ts +0 -0
  52. /package/dist/{commands → src/commands}/archive.d.ts +0 -0
  53. /package/dist/{commands → src/commands}/benchmark.d.ts +0 -0
  54. /package/dist/{commands → src/commands}/command-adapters.security.test.d.ts +0 -0
  55. /package/dist/{commands → src/commands}/commands.test.d.ts +0 -0
  56. /package/dist/{commands → src/commands}/config.d.ts +0 -0
  57. /package/dist/{commands → src/commands}/diagnose.d.ts +0 -0
  58. /package/dist/{commands → src/commands}/doctor.d.ts +0 -0
  59. /package/dist/{commands → src/commands}/evidence.d.ts +0 -0
  60. /package/dist/{commands → src/commands}/export.d.ts +0 -0
  61. /package/dist/{commands → src/commands}/history.d.ts +0 -0
  62. /package/dist/{commands → src/commands}/index.d.ts +0 -0
  63. /package/dist/{commands → src/commands}/plan.d.ts +0 -0
  64. /package/dist/{commands → src/commands}/preflight.d.ts +0 -0
  65. /package/dist/{commands → src/commands}/reset.d.ts +0 -0
  66. /package/dist/{commands → src/commands}/retrieve.d.ts +0 -0
  67. /package/dist/{commands → src/commands}/status.d.ts +0 -0
  68. /package/dist/{commands → src/commands}/sync-plan.d.ts +0 -0
  69. /package/dist/{config → src/config}/constants.d.ts +0 -0
  70. /package/dist/{config → src/config}/evidence-schema.d.ts +0 -0
  71. /package/dist/{config → src/config}/index.d.ts +0 -0
  72. /package/dist/{config → src/config}/plan-schema.d.ts +0 -0
  73. /package/dist/{evidence → src/evidence}/index.d.ts +0 -0
  74. /package/dist/{evidence → src/evidence}/manager.d.ts +0 -0
  75. /package/dist/{hooks → src/hooks}/agent-activity.d.ts +0 -0
  76. /package/dist/{hooks → src/hooks}/compaction-customizer.d.ts +0 -0
  77. /package/dist/{hooks → src/hooks}/context-budget.d.ts +0 -0
  78. /package/dist/{hooks → src/hooks}/context-scoring.d.ts +0 -0
  79. /package/dist/{hooks → src/hooks}/delegation-gate.d.ts +0 -0
  80. /package/dist/{hooks → src/hooks}/delegation-tracker.d.ts +0 -0
  81. /package/dist/{hooks → src/hooks}/extractors.d.ts +0 -0
  82. /package/dist/{hooks → src/hooks}/guardrails.d.ts +0 -0
  83. /package/dist/{hooks → src/hooks}/index.d.ts +0 -0
  84. /package/dist/{hooks → src/hooks}/phase-monitor.d.ts +0 -0
  85. /package/dist/{hooks → src/hooks}/pipeline-tracker.d.ts +0 -0
  86. /package/dist/{hooks → src/hooks}/system-enhancer.d.ts +0 -0
  87. /package/dist/{hooks → src/hooks}/tool-summarizer.d.ts +0 -0
  88. /package/dist/{hooks → src/hooks}/utils.d.ts +0 -0
  89. /package/dist/{plan → src/plan}/index.d.ts +0 -0
  90. /package/dist/{plan → src/plan}/manager.d.ts +0 -0
  91. /package/dist/{services → src/services}/config-doctor.d.ts +0 -0
  92. /package/dist/{services → src/services}/config-doctor.security.test.d.ts +0 -0
  93. /package/dist/{services → src/services}/config-doctor.test.d.ts +0 -0
  94. /package/dist/{services → src/services}/decision-drift-analyzer.d.ts +0 -0
  95. /package/dist/{services → src/services}/diagnose-service.d.ts +0 -0
  96. /package/dist/{services → src/services}/evidence-service.d.ts +0 -0
  97. /package/dist/{services → src/services}/evidence-summary-service.d.ts +0 -0
  98. /package/dist/{services → src/services}/export-service.d.ts +0 -0
  99. /package/dist/{services → src/services}/history-service.d.ts +0 -0
  100. /package/dist/{services → src/services}/index.d.ts +0 -0
  101. /package/dist/{services → src/services}/plan-service.d.ts +0 -0
  102. /package/dist/{services → src/services}/preflight-integration.d.ts +0 -0
  103. /package/dist/{services → src/services}/preflight-service.d.ts +0 -0
  104. /package/dist/{services → src/services}/status-service.d.ts +0 -0
  105. /package/dist/{summaries → src/summaries}/index.d.ts +0 -0
  106. /package/dist/{summaries → src/summaries}/manager.d.ts +0 -0
  107. /package/dist/{summaries → src/summaries}/summarizer.d.ts +0 -0
  108. /package/dist/{tools → src/tools}/checkpoint.d.ts +0 -0
  109. /package/dist/{tools → src/tools}/complexity-hotspots.d.ts +0 -0
  110. /package/dist/{tools → src/tools}/diff.d.ts +0 -0
  111. /package/dist/{tools → src/tools}/domain-detector.d.ts +0 -0
  112. /package/dist/{tools → src/tools}/evidence-check.d.ts +0 -0
  113. /package/dist/{tools → src/tools}/file-extractor.d.ts +0 -0
  114. /package/dist/{tools → src/tools}/imports.d.ts +0 -0
  115. /package/dist/{tools → src/tools}/index.d.ts +0 -0
  116. /package/dist/{tools → src/tools}/lint.d.ts +0 -0
  117. /package/dist/{tools → src/tools}/pkg-audit.d.ts +0 -0
  118. /package/dist/{tools → src/tools}/retrieve-summary.d.ts +0 -0
  119. /package/dist/{tools → src/tools}/schema-drift.d.ts +0 -0
  120. /package/dist/{tools → src/tools}/secretscan.d.ts +0 -0
  121. /package/dist/{tools → src/tools}/symbols.d.ts +0 -0
  122. /package/dist/{tools → src/tools}/test-runner.security-adversarial.test.d.ts +0 -0
  123. /package/dist/{tools → src/tools}/todo-extract.d.ts +0 -0
  124. /package/dist/{utils → src/utils}/errors.d.ts +0 -0
  125. /package/dist/{utils → src/utils}/logger.d.ts +0 -0
  126. /package/dist/{utils → src/utils}/merge.d.ts +0 -0
package/dist/index.js CHANGED
@@ -1,5 +1,20 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
3
18
  var __export = (target, all) => {
4
19
  for (var name in all)
5
20
  __defProp(target, name, {
@@ -13999,6 +14014,52 @@ var init_evidence_schema = __esm(() => {
13999
14014
  });
14000
14015
  });
14001
14016
 
14017
+ // src/utils/errors.ts
14018
+ var SwarmError;
14019
+ var init_errors3 = __esm(() => {
14020
+ SwarmError = class SwarmError extends Error {
14021
+ code;
14022
+ guidance;
14023
+ constructor(message, code, guidance) {
14024
+ super(message);
14025
+ this.name = "SwarmError";
14026
+ this.code = code;
14027
+ this.guidance = guidance;
14028
+ }
14029
+ };
14030
+ });
14031
+
14032
+ // src/utils/logger.ts
14033
+ function log(message, data) {
14034
+ if (!isDebug())
14035
+ return;
14036
+ const timestamp = new Date().toISOString();
14037
+ if (data !== undefined) {
14038
+ console.log(`[opencode-swarm ${timestamp}] ${message}`, data);
14039
+ } else {
14040
+ console.log(`[opencode-swarm ${timestamp}] ${message}`);
14041
+ }
14042
+ }
14043
+ function warn(message, data) {
14044
+ const timestamp = new Date().toISOString();
14045
+ if (data !== undefined) {
14046
+ console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
14047
+ } else {
14048
+ console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`);
14049
+ }
14050
+ }
14051
+ var isDebug = () => process.env.OPENCODE_SWARM_DEBUG === "1";
14052
+
14053
+ // src/utils/index.ts
14054
+ import * as os from "os";
14055
+ import * as path from "path";
14056
+ function getUserConfigDir() {
14057
+ return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
14058
+ }
14059
+ var init_utils = __esm(() => {
14060
+ init_errors3();
14061
+ });
14062
+
14002
14063
  // src/config/plan-schema.ts
14003
14064
  var TaskStatusSchema, TaskSizeSchema, PhaseStatusSchema, MigrationStatusSchema, TaskSchema, PhaseSchema, PlanSchema;
14004
14065
  var init_plan_schema = __esm(() => {
@@ -14049,51 +14110,6 @@ var init_plan_schema = __esm(() => {
14049
14110
  });
14050
14111
  });
14051
14112
 
14052
- // src/utils/errors.ts
14053
- var SwarmError;
14054
- var init_errors3 = __esm(() => {
14055
- SwarmError = class SwarmError extends Error {
14056
- code;
14057
- guidance;
14058
- constructor(message, code, guidance) {
14059
- super(message);
14060
- this.name = "SwarmError";
14061
- this.code = code;
14062
- this.guidance = guidance;
14063
- }
14064
- };
14065
- });
14066
-
14067
- // src/utils/logger.ts
14068
- function log(message, data) {
14069
- if (!DEBUG)
14070
- return;
14071
- const timestamp = new Date().toISOString();
14072
- if (data !== undefined) {
14073
- console.log(`[opencode-swarm ${timestamp}] ${message}`, data);
14074
- } else {
14075
- console.log(`[opencode-swarm ${timestamp}] ${message}`);
14076
- }
14077
- }
14078
- function warn(message, data) {
14079
- const timestamp = new Date().toISOString();
14080
- if (data !== undefined) {
14081
- console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
14082
- } else {
14083
- console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`);
14084
- }
14085
- }
14086
- var DEBUG;
14087
- var init_logger = __esm(() => {
14088
- DEBUG = process.env.OPENCODE_SWARM_DEBUG === "1";
14089
- });
14090
-
14091
- // src/utils/index.ts
14092
- var init_utils = __esm(() => {
14093
- init_errors3();
14094
- init_logger();
14095
- });
14096
-
14097
14113
  // src/background/event-bus.ts
14098
14114
  class AutomationEventBus {
14099
14115
  listeners = new Map;
@@ -14165,7 +14181,7 @@ var init_event_bus = __esm(() => {
14165
14181
  });
14166
14182
 
14167
14183
  // src/hooks/utils.ts
14168
- import * as path2 from "path";
14184
+ import * as path3 from "path";
14169
14185
  function safeHook(fn) {
14170
14186
  return async (input, output) => {
14171
14187
  try {
@@ -14196,17 +14212,40 @@ function validateSwarmPath(directory, filename) {
14196
14212
  if (/[\0]/.test(filename)) {
14197
14213
  throw new Error("Invalid filename: contains null bytes");
14198
14214
  }
14215
+ if (filename.includes("%")) {
14216
+ let decoded = filename;
14217
+ try {
14218
+ let prev = decoded;
14219
+ do {
14220
+ prev = decoded;
14221
+ decoded = decodeURIComponent(decoded);
14222
+ } while (decoded !== prev && decoded.includes("%"));
14223
+ } catch {
14224
+ throw new Error("Invalid filename: malformed percent-encoding");
14225
+ }
14226
+ if (/[\0]/.test(decoded)) {
14227
+ throw new Error("Invalid filename: contains null bytes (encoded)");
14228
+ }
14229
+ if (/\.\.[/\\]/.test(decoded) || decoded === "..") {
14230
+ throw new Error("Invalid filename: path traversal detected (encoded)");
14231
+ }
14232
+ }
14233
+ const basename2 = path3.basename(filename).split(".")[0].toUpperCase();
14234
+ const WINDOWS_RESERVED = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/;
14235
+ if (WINDOWS_RESERVED.test(basename2)) {
14236
+ throw new Error(`Invalid filename: reserved device name '${basename2}'`);
14237
+ }
14199
14238
  if (/\.\.[/\\]/.test(filename)) {
14200
14239
  throw new Error("Invalid filename: path traversal detected");
14201
14240
  }
14202
- const baseDir = path2.normalize(path2.resolve(directory, ".swarm"));
14203
- const resolved = path2.normalize(path2.resolve(baseDir, filename));
14241
+ const baseDir = path3.normalize(path3.resolve(directory, ".swarm"));
14242
+ const resolved = path3.normalize(path3.resolve(baseDir, filename));
14204
14243
  if (process.platform === "win32") {
14205
- if (!resolved.toLowerCase().startsWith((baseDir + path2.sep).toLowerCase())) {
14244
+ if (!resolved.toLowerCase().startsWith((baseDir + path3.sep).toLowerCase())) {
14206
14245
  throw new Error("Invalid filename: path escapes .swarm directory");
14207
14246
  }
14208
14247
  } else {
14209
- if (!resolved.startsWith(baseDir + path2.sep)) {
14248
+ if (!resolved.startsWith(baseDir + path3.sep)) {
14210
14249
  throw new Error("Invalid filename: path escapes .swarm directory");
14211
14250
  }
14212
14251
  }
@@ -14233,8 +14272,8 @@ var init_utils2 = __esm(() => {
14233
14272
  });
14234
14273
 
14235
14274
  // src/evidence/manager.ts
14236
- import { mkdirSync, readdirSync, renameSync, rmSync, statSync as statSync2 } from "fs";
14237
- import * as path3 from "path";
14275
+ import { mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
14276
+ import * as path4 from "path";
14238
14277
  function sanitizeTaskId(taskId) {
14239
14278
  if (!taskId || taskId.length === 0) {
14240
14279
  throw new Error("Invalid task ID: empty string");
@@ -14257,7 +14296,7 @@ function sanitizeTaskId(taskId) {
14257
14296
  }
14258
14297
  async function loadEvidence(directory, taskId) {
14259
14298
  const sanitizedTaskId = sanitizeTaskId(taskId);
14260
- const relativePath = path3.join("evidence", sanitizedTaskId, "evidence.json");
14299
+ const relativePath = path4.join("evidence", sanitizedTaskId, "evidence.json");
14261
14300
  validateSwarmPath(directory, relativePath);
14262
14301
  const content = await readSwarmFileAsync(directory, relativePath);
14263
14302
  if (content === null) {
@@ -14275,7 +14314,7 @@ async function loadEvidence(directory, taskId) {
14275
14314
  async function listEvidenceTaskIds(directory) {
14276
14315
  const evidenceBasePath = validateSwarmPath(directory, "evidence");
14277
14316
  try {
14278
- statSync2(evidenceBasePath);
14317
+ statSync(evidenceBasePath);
14279
14318
  } catch {
14280
14319
  return [];
14281
14320
  }
@@ -14287,9 +14326,9 @@ async function listEvidenceTaskIds(directory) {
14287
14326
  }
14288
14327
  const taskIds = [];
14289
14328
  for (const entry of entries) {
14290
- const entryPath = path3.join(evidenceBasePath, entry);
14329
+ const entryPath = path4.join(evidenceBasePath, entry);
14291
14330
  try {
14292
- const stats = statSync2(entryPath);
14331
+ const stats = statSync(entryPath);
14293
14332
  if (!stats.isDirectory()) {
14294
14333
  continue;
14295
14334
  }
@@ -14305,10 +14344,10 @@ async function listEvidenceTaskIds(directory) {
14305
14344
  }
14306
14345
  async function deleteEvidence(directory, taskId) {
14307
14346
  const sanitizedTaskId = sanitizeTaskId(taskId);
14308
- const relativePath = path3.join("evidence", sanitizedTaskId);
14347
+ const relativePath = path4.join("evidence", sanitizedTaskId);
14309
14348
  const evidenceDir = validateSwarmPath(directory, relativePath);
14310
14349
  try {
14311
- statSync2(evidenceDir);
14350
+ statSync(evidenceDir);
14312
14351
  } catch {
14313
14352
  return false;
14314
14353
  }
@@ -14365,7 +14404,8 @@ var init_manager = __esm(() => {
14365
14404
  });
14366
14405
 
14367
14406
  // src/plan/manager.ts
14368
- import * as path4 from "path";
14407
+ import { createHash } from "crypto";
14408
+ import * as path5 from "path";
14369
14409
  async function loadPlanJsonOnly(directory) {
14370
14410
  const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
14371
14411
  if (planJsonContent !== null) {
@@ -14418,7 +14458,7 @@ function computePlanContentHash(plan) {
14418
14458
  })).sort((a, b) => a.id - b.id)
14419
14459
  };
14420
14460
  const jsonString = JSON.stringify(content);
14421
- return Bun.hash(jsonString).toString(36);
14461
+ return createHash("sha256").update(jsonString).digest("hex").slice(0, 12);
14422
14462
  }
14423
14463
  function extractPlanHashFromMarkdown(markdown) {
14424
14464
  const match = markdown.match(/<!--\s*PLAN_HASH:\s*(\S+)\s*-->/);
@@ -14443,12 +14483,12 @@ async function isPlanMdInSync(directory, plan) {
14443
14483
  return normalizedActual.includes(normalizedExpected) || normalizedExpected.includes(normalizedActual.replace(/^#.*$/gm, "").trim());
14444
14484
  }
14445
14485
  async function regeneratePlanMarkdown(directory, plan) {
14446
- const swarmDir = path4.resolve(directory, ".swarm");
14486
+ const swarmDir = path5.resolve(directory, ".swarm");
14447
14487
  const contentHash = computePlanContentHash(plan);
14448
14488
  const markdown = derivePlanMarkdown(plan);
14449
14489
  const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
14450
14490
  ${markdown}`;
14451
- await Bun.write(path4.join(swarmDir, "plan.md"), markdownWithHash);
14491
+ await Bun.write(path5.join(swarmDir, "plan.md"), markdownWithHash);
14452
14492
  }
14453
14493
  async function loadPlan(directory) {
14454
14494
  const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
@@ -14485,9 +14525,9 @@ async function loadPlan(directory) {
14485
14525
  }
14486
14526
  async function savePlan(directory, plan) {
14487
14527
  const validated = PlanSchema.parse(plan);
14488
- const swarmDir = path4.resolve(directory, ".swarm");
14489
- const planPath = path4.join(swarmDir, "plan.json");
14490
- const tempPath = path4.join(swarmDir, `plan.json.tmp.${Date.now()}`);
14528
+ const swarmDir = path5.resolve(directory, ".swarm");
14529
+ const planPath = path5.join(swarmDir, "plan.json");
14530
+ const tempPath = path5.join(swarmDir, `plan.json.tmp.${Date.now()}`);
14491
14531
  await Bun.write(tempPath, JSON.stringify(validated, null, 2));
14492
14532
  const { renameSync: renameSync2 } = await import("fs");
14493
14533
  renameSync2(tempPath, planPath);
@@ -14495,7 +14535,7 @@ async function savePlan(directory, plan) {
14495
14535
  const markdown = derivePlanMarkdown(validated);
14496
14536
  const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
14497
14537
  ${markdown}`;
14498
- await Bun.write(path4.join(swarmDir, "plan.md"), markdownWithHash);
14538
+ await Bun.write(path5.join(swarmDir, "plan.md"), markdownWithHash);
14499
14539
  }
14500
14540
  function derivePlanMarkdown(plan) {
14501
14541
  const statusMap = {
@@ -14979,13 +15019,13 @@ __export(exports_evidence_summary_integration, {
14979
15019
  EvidenceSummaryIntegration: () => EvidenceSummaryIntegration
14980
15020
  });
14981
15021
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
14982
- import * as path5 from "path";
15022
+ import * as path6 from "path";
14983
15023
  function persistSummary(swarmDir, artifact, filename) {
14984
- const swarmPath = path5.join(swarmDir, ".swarm");
15024
+ const swarmPath = path6.join(swarmDir, ".swarm");
14985
15025
  if (!existsSync2(swarmPath)) {
14986
15026
  mkdirSync2(swarmPath, { recursive: true });
14987
15027
  }
14988
- const artifactPath = path5.join(swarmPath, filename);
15028
+ const artifactPath = path6.join(swarmPath, filename);
14989
15029
  const content = JSON.stringify(artifact, null, 2);
14990
15030
  writeFileSync(artifactPath, content, "utf-8");
14991
15031
  log("[EvidenceSummaryIntegration] Summary persisted", {
@@ -15250,7 +15290,7 @@ __export(exports_status_artifact, {
15250
15290
  AutomationStatusArtifact: () => AutomationStatusArtifact
15251
15291
  });
15252
15292
  import * as fs3 from "fs";
15253
- import * as path7 from "path";
15293
+ import * as path8 from "path";
15254
15294
  function createEmptySnapshot(mode, capabilities) {
15255
15295
  return {
15256
15296
  timestamp: Date.now(),
@@ -15309,7 +15349,7 @@ class AutomationStatusArtifact {
15309
15349
  });
15310
15350
  }
15311
15351
  getFilePath() {
15312
- return path7.join(this.swarmDir, this.filename);
15352
+ return path8.join(this.swarmDir, this.filename);
15313
15353
  }
15314
15354
  load() {
15315
15355
  const filePath = this.getFilePath();
@@ -15714,14 +15754,10 @@ __export(exports_config_doctor, {
15714
15754
  });
15715
15755
  import * as crypto from "crypto";
15716
15756
  import * as fs4 from "fs";
15717
- import * as os3 from "os";
15718
- import * as path9 from "path";
15719
- function getUserConfigDir3() {
15720
- return process.env.XDG_CONFIG_HOME || path9.join(os3.homedir(), ".config");
15721
- }
15757
+ import * as path10 from "path";
15722
15758
  function getConfigPaths(directory) {
15723
- const userConfigPath = path9.join(getUserConfigDir3(), "opencode", "opencode-swarm.json");
15724
- const projectConfigPath = path9.join(directory, ".opencode", "opencode-swarm.json");
15759
+ const userConfigPath = path10.join(getUserConfigDir(), "opencode", "opencode-swarm.json");
15760
+ const projectConfigPath = path10.join(directory, ".opencode", "opencode-swarm.json");
15725
15761
  return { userConfigPath, projectConfigPath };
15726
15762
  }
15727
15763
  function computeHash(content) {
@@ -15746,9 +15782,9 @@ function isValidConfigPath(configPath, directory) {
15746
15782
  const normalizedUser = userConfigPath.replace(/\\/g, "/");
15747
15783
  const normalizedProject = projectConfigPath.replace(/\\/g, "/");
15748
15784
  try {
15749
- const resolvedConfig = path9.resolve(configPath);
15750
- const resolvedUser = path9.resolve(normalizedUser);
15751
- const resolvedProject = path9.resolve(normalizedProject);
15785
+ const resolvedConfig = path10.resolve(configPath);
15786
+ const resolvedUser = path10.resolve(normalizedUser);
15787
+ const resolvedProject = path10.resolve(normalizedProject);
15752
15788
  return resolvedConfig === resolvedUser || resolvedConfig === resolvedProject;
15753
15789
  } catch {
15754
15790
  return false;
@@ -15780,12 +15816,12 @@ function createConfigBackup(directory) {
15780
15816
  };
15781
15817
  }
15782
15818
  function writeBackupArtifact(directory, backup) {
15783
- const swarmDir = path9.join(directory, ".swarm");
15819
+ const swarmDir = path10.join(directory, ".swarm");
15784
15820
  if (!fs4.existsSync(swarmDir)) {
15785
15821
  fs4.mkdirSync(swarmDir, { recursive: true });
15786
15822
  }
15787
15823
  const backupFilename = `config-backup-${backup.createdAt}.json`;
15788
- const backupPath = path9.join(swarmDir, backupFilename);
15824
+ const backupPath = path10.join(swarmDir, backupFilename);
15789
15825
  const artifact = {
15790
15826
  createdAt: backup.createdAt,
15791
15827
  configPath: backup.configPath,
@@ -15815,7 +15851,7 @@ function restoreFromBackup(backupPath, directory) {
15815
15851
  return null;
15816
15852
  }
15817
15853
  const targetPath = artifact.configPath;
15818
- const targetDir = path9.dirname(targetPath);
15854
+ const targetDir = path10.dirname(targetPath);
15819
15855
  if (!fs4.existsSync(targetDir)) {
15820
15856
  fs4.mkdirSync(targetDir, { recursive: true });
15821
15857
  }
@@ -15846,9 +15882,9 @@ function readConfigFromFile(directory) {
15846
15882
  return null;
15847
15883
  }
15848
15884
  }
15849
- function validateConfigKey(path10, value, config2) {
15885
+ function validateConfigKey(path11, value, config2) {
15850
15886
  const findings = [];
15851
- switch (path10) {
15887
+ switch (path11) {
15852
15888
  case "agents": {
15853
15889
  if (value !== undefined) {
15854
15890
  findings.push({
@@ -16095,27 +16131,27 @@ function validateConfigKey(path10, value, config2) {
16095
16131
  }
16096
16132
  return findings;
16097
16133
  }
16098
- function walkConfigAndValidate(obj, path10, config2, findings) {
16134
+ function walkConfigAndValidate(obj, path11, config2, findings) {
16099
16135
  if (obj === null || obj === undefined) {
16100
16136
  return;
16101
16137
  }
16102
- if (path10 && typeof obj === "object" && !Array.isArray(obj)) {
16103
- const keyFindings = validateConfigKey(path10, obj, config2);
16138
+ if (path11 && typeof obj === "object" && !Array.isArray(obj)) {
16139
+ const keyFindings = validateConfigKey(path11, obj, config2);
16104
16140
  findings.push(...keyFindings);
16105
16141
  }
16106
16142
  if (typeof obj !== "object") {
16107
- const keyFindings = validateConfigKey(path10, obj, config2);
16143
+ const keyFindings = validateConfigKey(path11, obj, config2);
16108
16144
  findings.push(...keyFindings);
16109
16145
  return;
16110
16146
  }
16111
16147
  if (Array.isArray(obj)) {
16112
16148
  obj.forEach((item, index) => {
16113
- walkConfigAndValidate(item, `${path10}[${index}]`, config2, findings);
16149
+ walkConfigAndValidate(item, `${path11}[${index}]`, config2, findings);
16114
16150
  });
16115
16151
  return;
16116
16152
  }
16117
16153
  for (const [key, value] of Object.entries(obj)) {
16118
- const newPath = path10 ? `${path10}.${key}` : key;
16154
+ const newPath = path11 ? `${path11}.${key}` : key;
16119
16155
  walkConfigAndValidate(value, newPath, config2, findings);
16120
16156
  }
16121
16157
  }
@@ -16235,7 +16271,7 @@ function applySafeAutoFixes(directory, result) {
16235
16271
  }
16236
16272
  }
16237
16273
  if (appliedFixes.length > 0) {
16238
- const configDir = path9.dirname(configPath);
16274
+ const configDir = path10.dirname(configPath);
16239
16275
  if (!fs4.existsSync(configDir)) {
16240
16276
  fs4.mkdirSync(configDir, { recursive: true });
16241
16277
  }
@@ -16245,12 +16281,12 @@ function applySafeAutoFixes(directory, result) {
16245
16281
  return { appliedFixes, updatedConfigPath };
16246
16282
  }
16247
16283
  function writeDoctorArtifact(directory, result) {
16248
- const swarmDir = path9.join(directory, ".swarm");
16284
+ const swarmDir = path10.join(directory, ".swarm");
16249
16285
  if (!fs4.existsSync(swarmDir)) {
16250
16286
  fs4.mkdirSync(swarmDir, { recursive: true });
16251
16287
  }
16252
16288
  const artifactFilename = "config-doctor.json";
16253
- const artifactPath = path9.join(swarmDir, artifactFilename);
16289
+ const artifactPath = path10.join(swarmDir, artifactFilename);
16254
16290
  const guiOutput = {
16255
16291
  timestamp: result.timestamp,
16256
16292
  summary: result.summary,
@@ -16318,6 +16354,7 @@ async function runConfigDoctorWithFixes(directory, config2, autoFix = false) {
16318
16354
  }
16319
16355
  var VALID_CONFIG_PATTERNS, DANGEROUS_PATH_SEGMENTS;
16320
16356
  var init_config_doctor = __esm(() => {
16357
+ init_utils();
16321
16358
  VALID_CONFIG_PATTERNS = [
16322
16359
  /^\.config[\\/]opencode[\\/]opencode-swarm\.json$/,
16323
16360
  /\.opencode[\\/]opencode-swarm\.json$/
@@ -16565,10 +16602,10 @@ function mergeDefs2(...defs) {
16565
16602
  function cloneDef2(schema) {
16566
16603
  return mergeDefs2(schema._zod.def);
16567
16604
  }
16568
- function getElementAtPath2(obj, path10) {
16569
- if (!path10)
16605
+ function getElementAtPath2(obj, path11) {
16606
+ if (!path11)
16570
16607
  return obj;
16571
- return path10.reduce((acc, key) => acc?.[key], obj);
16608
+ return path11.reduce((acc, key) => acc?.[key], obj);
16572
16609
  }
16573
16610
  function promiseAllObject2(promisesObj) {
16574
16611
  const keys = Object.keys(promisesObj);
@@ -16857,11 +16894,11 @@ function aborted2(x, startIndex = 0) {
16857
16894
  }
16858
16895
  return false;
16859
16896
  }
16860
- function prefixIssues2(path10, issues) {
16897
+ function prefixIssues2(path11, issues) {
16861
16898
  return issues.map((iss) => {
16862
16899
  var _a2;
16863
16900
  (_a2 = iss).path ?? (_a2.path = []);
16864
- iss.path.unshift(path10);
16901
+ iss.path.unshift(path11);
16865
16902
  return iss;
16866
16903
  });
16867
16904
  }
@@ -17084,7 +17121,7 @@ function treeifyError2(error49, _mapper) {
17084
17121
  return issue3.message;
17085
17122
  };
17086
17123
  const result = { errors: [] };
17087
- const processError = (error50, path10 = []) => {
17124
+ const processError = (error50, path11 = []) => {
17088
17125
  var _a2, _b;
17089
17126
  for (const issue3 of error50.issues) {
17090
17127
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -17094,7 +17131,7 @@ function treeifyError2(error49, _mapper) {
17094
17131
  } else if (issue3.code === "invalid_element") {
17095
17132
  processError({ issues: issue3.issues }, issue3.path);
17096
17133
  } else {
17097
- const fullpath = [...path10, ...issue3.path];
17134
+ const fullpath = [...path11, ...issue3.path];
17098
17135
  if (fullpath.length === 0) {
17099
17136
  result.errors.push(mapper(issue3));
17100
17137
  continue;
@@ -17126,8 +17163,8 @@ function treeifyError2(error49, _mapper) {
17126
17163
  }
17127
17164
  function toDotPath2(_path) {
17128
17165
  const segs = [];
17129
- const path10 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
17130
- for (const seg of path10) {
17166
+ const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
17167
+ for (const seg of path11) {
17131
17168
  if (typeof seg === "number")
17132
17169
  segs.push(`[${seg}]`);
17133
17170
  else if (typeof seg === "symbol")
@@ -29139,7 +29176,7 @@ var init_lint = __esm(() => {
29139
29176
 
29140
29177
  // src/tools/secretscan.ts
29141
29178
  import * as fs5 from "fs";
29142
- import * as path10 from "path";
29179
+ import * as path11 from "path";
29143
29180
  function calculateShannonEntropy(str) {
29144
29181
  if (str.length === 0)
29145
29182
  return 0;
@@ -29166,7 +29203,7 @@ function isHighEntropyString(str) {
29166
29203
  function containsPathTraversal(str) {
29167
29204
  if (/\.\.[/\\]/.test(str))
29168
29205
  return true;
29169
- const normalized = path10.normalize(str);
29206
+ const normalized = path11.normalize(str);
29170
29207
  if (/\.\.[/\\]/.test(normalized))
29171
29208
  return true;
29172
29209
  if (str.includes("%2e%2e") || str.includes("%2E%2E"))
@@ -29194,7 +29231,7 @@ function validateDirectoryInput(dir) {
29194
29231
  return null;
29195
29232
  }
29196
29233
  function isBinaryFile(filePath, buffer) {
29197
- const ext = path10.extname(filePath).toLowerCase();
29234
+ const ext = path11.extname(filePath).toLowerCase();
29198
29235
  if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
29199
29236
  return true;
29200
29237
  }
@@ -29331,9 +29368,9 @@ function isSymlinkLoop(realPath, visited) {
29331
29368
  return false;
29332
29369
  }
29333
29370
  function isPathWithinScope(realPath, scanDir) {
29334
- const resolvedScanDir = path10.resolve(scanDir);
29335
- const resolvedRealPath = path10.resolve(realPath);
29336
- return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path10.sep) || resolvedRealPath.startsWith(resolvedScanDir + "/") || resolvedRealPath.startsWith(resolvedScanDir + "\\");
29371
+ const resolvedScanDir = path11.resolve(scanDir);
29372
+ const resolvedRealPath = path11.resolve(realPath);
29373
+ return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path11.sep) || resolvedRealPath.startsWith(resolvedScanDir + "/") || resolvedRealPath.startsWith(resolvedScanDir + "\\");
29337
29374
  }
29338
29375
  function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
29339
29376
  skippedDirs: 0,
@@ -29363,7 +29400,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
29363
29400
  stats.skippedDirs++;
29364
29401
  continue;
29365
29402
  }
29366
- const fullPath = path10.join(dir, entry);
29403
+ const fullPath = path11.join(dir, entry);
29367
29404
  let lstat;
29368
29405
  try {
29369
29406
  lstat = fs5.lstatSync(fullPath);
@@ -29394,7 +29431,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
29394
29431
  const subFiles = findScannableFiles(fullPath, excludeDirs, scanDir, visited, stats);
29395
29432
  files.push(...subFiles);
29396
29433
  } else if (lstat.isFile()) {
29397
- const ext = path10.extname(fullPath).toLowerCase();
29434
+ const ext = path11.extname(fullPath).toLowerCase();
29398
29435
  if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
29399
29436
  files.push(fullPath);
29400
29437
  } else {
@@ -29660,7 +29697,7 @@ var init_secretscan = __esm(() => {
29660
29697
  }
29661
29698
  }
29662
29699
  try {
29663
- const scanDir = path10.resolve(directory);
29700
+ const scanDir = path11.resolve(directory);
29664
29701
  if (!fs5.existsSync(scanDir)) {
29665
29702
  const errorResult = {
29666
29703
  error: "directory not found",
@@ -29787,88 +29824,13 @@ var init_secretscan = __esm(() => {
29787
29824
  });
29788
29825
  });
29789
29826
 
29790
- // src/tools/test-runner.ts
29827
+ // src/tools/test-runner/constants.ts
29828
+ var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000;
29829
+ var init_constants = () => {};
29830
+
29831
+ // src/tools/test-runner/detect.ts
29791
29832
  import * as fs6 from "fs";
29792
- import * as path11 from "path";
29793
- function containsPathTraversal2(str) {
29794
- if (/\.\.[/\\]/.test(str))
29795
- return true;
29796
- if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
29797
- return true;
29798
- if (/%2e%2e/i.test(str))
29799
- return true;
29800
- if (/%2e\./i.test(str))
29801
- return true;
29802
- if (/%2e/i.test(str) && /\.\./.test(str))
29803
- return true;
29804
- if (/%252e%252e/i.test(str))
29805
- return true;
29806
- if (/\uff0e/.test(str))
29807
- return true;
29808
- if (/\u3002/.test(str))
29809
- return true;
29810
- if (/\uff65/.test(str))
29811
- return true;
29812
- if (/%2f/i.test(str))
29813
- return true;
29814
- if (/%5c/i.test(str))
29815
- return true;
29816
- return false;
29817
- }
29818
- function isAbsolutePath(str) {
29819
- if (str.startsWith("/"))
29820
- return true;
29821
- if (/^[a-zA-Z]:[/\\]/.test(str))
29822
- return true;
29823
- if (/^\\\\/.test(str))
29824
- return true;
29825
- if (/^\\\\\.\\/.test(str))
29826
- return true;
29827
- return false;
29828
- }
29829
- function containsControlChars2(str) {
29830
- return /[\x00-\x08\x0a\x0b\x0c\x0d\x0e-\x1f\x7f\x80-\x9f]/.test(str);
29831
- }
29832
- function containsPowerShellMetacharacters(str) {
29833
- return POWERSHELL_METACHARACTERS.test(str);
29834
- }
29835
- function validateArgs2(args) {
29836
- if (typeof args !== "object" || args === null)
29837
- return false;
29838
- const obj = args;
29839
- if (obj.scope !== undefined) {
29840
- if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph") {
29841
- return false;
29842
- }
29843
- }
29844
- if (obj.files !== undefined) {
29845
- if (!Array.isArray(obj.files))
29846
- return false;
29847
- for (const f of obj.files) {
29848
- if (typeof f !== "string")
29849
- return false;
29850
- if (isAbsolutePath(f))
29851
- return false;
29852
- if (containsPathTraversal2(f))
29853
- return false;
29854
- if (containsControlChars2(f))
29855
- return false;
29856
- if (containsPowerShellMetacharacters(f))
29857
- return false;
29858
- }
29859
- }
29860
- if (obj.coverage !== undefined) {
29861
- if (typeof obj.coverage !== "boolean")
29862
- return false;
29863
- }
29864
- if (obj.timeout_ms !== undefined) {
29865
- if (typeof obj.timeout_ms !== "number")
29866
- return false;
29867
- if (obj.timeout_ms < 0 || obj.timeout_ms > MAX_TIMEOUT_MS)
29868
- return false;
29869
- }
29870
- return true;
29871
- }
29833
+ import * as path12 from "path";
29872
29834
  function hasPackageJsonDependency(deps, ...patterns) {
29873
29835
  for (const pattern of patterns) {
29874
29836
  if (deps[pattern])
@@ -29883,7 +29845,7 @@ function hasDevDependency(devDeps, ...patterns) {
29883
29845
  }
29884
29846
  async function detectTestFramework() {
29885
29847
  try {
29886
- const packageJsonPath = path11.join(process.cwd(), "package.json");
29848
+ const packageJsonPath = path12.join(process.cwd(), "package.json");
29887
29849
  if (fs6.existsSync(packageJsonPath)) {
29888
29850
  const content = fs6.readFileSync(packageJsonPath, "utf-8");
29889
29851
  const pkg = JSON.parse(content);
@@ -29904,16 +29866,16 @@ async function detectTestFramework() {
29904
29866
  return "jest";
29905
29867
  if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
29906
29868
  return "mocha";
29907
- if (fs6.existsSync(path11.join(process.cwd(), "bun.lockb")) || fs6.existsSync(path11.join(process.cwd(), "bun.lock"))) {
29869
+ if (fs6.existsSync(path12.join(process.cwd(), "bun.lockb")) || fs6.existsSync(path12.join(process.cwd(), "bun.lock"))) {
29908
29870
  if (scripts.test?.includes("bun"))
29909
29871
  return "bun";
29910
29872
  }
29911
29873
  }
29912
29874
  } catch {}
29913
29875
  try {
29914
- const pyprojectTomlPath = path11.join(process.cwd(), "pyproject.toml");
29915
- const setupCfgPath = path11.join(process.cwd(), "setup.cfg");
29916
- const requirementsTxtPath = path11.join(process.cwd(), "requirements.txt");
29876
+ const pyprojectTomlPath = path12.join(process.cwd(), "pyproject.toml");
29877
+ const setupCfgPath = path12.join(process.cwd(), "setup.cfg");
29878
+ const requirementsTxtPath = path12.join(process.cwd(), "requirements.txt");
29917
29879
  if (fs6.existsSync(pyprojectTomlPath)) {
29918
29880
  const content = fs6.readFileSync(pyprojectTomlPath, "utf-8");
29919
29881
  if (content.includes("[tool.pytest"))
@@ -29933,7 +29895,7 @@ async function detectTestFramework() {
29933
29895
  }
29934
29896
  } catch {}
29935
29897
  try {
29936
- const cargoTomlPath = path11.join(process.cwd(), "Cargo.toml");
29898
+ const cargoTomlPath = path12.join(process.cwd(), "Cargo.toml");
29937
29899
  if (fs6.existsSync(cargoTomlPath)) {
29938
29900
  const content = fs6.readFileSync(cargoTomlPath, "utf-8");
29939
29901
  if (content.includes("[dev-dependencies]")) {
@@ -29944,15 +29906,20 @@ async function detectTestFramework() {
29944
29906
  }
29945
29907
  } catch {}
29946
29908
  try {
29947
- const pesterConfigPath = path11.join(process.cwd(), "pester.config.ps1");
29948
- const pesterConfigJsonPath = path11.join(process.cwd(), "pester.config.ps1.json");
29949
- const pesterPs1Path = path11.join(process.cwd(), "tests.ps1");
29909
+ const pesterConfigPath = path12.join(process.cwd(), "pester.config.ps1");
29910
+ const pesterConfigJsonPath = path12.join(process.cwd(), "pester.config.ps1.json");
29911
+ const pesterPs1Path = path12.join(process.cwd(), "tests.ps1");
29950
29912
  if (fs6.existsSync(pesterConfigPath) || fs6.existsSync(pesterConfigJsonPath) || fs6.existsSync(pesterPs1Path)) {
29951
29913
  return "pester";
29952
29914
  }
29953
29915
  } catch {}
29954
29916
  return "none";
29955
29917
  }
29918
+ var init_detect = () => {};
29919
+
29920
+ // src/tools/test-runner/discover.ts
29921
+ import * as fs7 from "fs";
29922
+ import * as path13 from "path";
29956
29923
  function hasCompoundTestExtension(filename) {
29957
29924
  const lower = filename.toLowerCase();
29958
29925
  return COMPOUND_TEST_EXTENSIONS.some((ext) => lower.endsWith(ext));
@@ -29960,26 +29927,26 @@ function hasCompoundTestExtension(filename) {
29960
29927
  function getTestFilesFromConvention(sourceFiles) {
29961
29928
  const testFiles = [];
29962
29929
  for (const file3 of sourceFiles) {
29963
- const basename2 = path11.basename(file3);
29964
- const dirname4 = path11.dirname(file3);
29965
- if (hasCompoundTestExtension(basename2) || basename2.includes(".spec.") || basename2.includes(".test.")) {
29930
+ const basename3 = path13.basename(file3);
29931
+ const dirname4 = path13.dirname(file3);
29932
+ if (hasCompoundTestExtension(basename3) || basename3.includes(".spec.") || basename3.includes(".test.")) {
29966
29933
  if (!testFiles.includes(file3)) {
29967
29934
  testFiles.push(file3);
29968
29935
  }
29969
29936
  continue;
29970
29937
  }
29971
29938
  for (const pattern of TEST_PATTERNS) {
29972
- const nameWithoutExt = basename2.replace(/\.[^.]+$/, "");
29973
- const ext = path11.extname(basename2);
29939
+ const nameWithoutExt = basename3.replace(/\.[^.]+$/, "");
29940
+ const ext = path13.extname(basename3);
29974
29941
  const possibleTestFiles = [
29975
- path11.join(dirname4, `${nameWithoutExt}.spec${ext}`),
29976
- path11.join(dirname4, `${nameWithoutExt}.test${ext}`),
29977
- path11.join(dirname4, "__tests__", `${nameWithoutExt}${ext}`),
29978
- path11.join(dirname4, "tests", `${nameWithoutExt}${ext}`),
29979
- path11.join(dirname4, "test", `${nameWithoutExt}${ext}`)
29942
+ path13.join(dirname4, `${nameWithoutExt}.spec${ext}`),
29943
+ path13.join(dirname4, `${nameWithoutExt}.test${ext}`),
29944
+ path13.join(dirname4, "__tests__", `${nameWithoutExt}${ext}`),
29945
+ path13.join(dirname4, "tests", `${nameWithoutExt}${ext}`),
29946
+ path13.join(dirname4, "test", `${nameWithoutExt}${ext}`)
29980
29947
  ];
29981
29948
  for (const testFile of possibleTestFiles) {
29982
- if (fs6.existsSync(testFile) && !testFiles.includes(testFile)) {
29949
+ if (fs7.existsSync(testFile) && !testFiles.includes(testFile)) {
29983
29950
  testFiles.push(testFile);
29984
29951
  }
29985
29952
  }
@@ -29995,16 +29962,16 @@ async function getTestFilesFromGraph(sourceFiles) {
29995
29962
  }
29996
29963
  for (const testFile of candidateTestFiles) {
29997
29964
  try {
29998
- const content = fs6.readFileSync(testFile, "utf-8");
29999
- const testDir = path11.dirname(testFile);
29965
+ const content = fs7.readFileSync(testFile, "utf-8");
29966
+ const testDir = path13.dirname(testFile);
30000
29967
  const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
30001
29968
  let match;
30002
29969
  while ((match = importRegex.exec(content)) !== null) {
30003
29970
  const importPath = match[1];
30004
29971
  let resolvedImport;
30005
29972
  if (importPath.startsWith(".")) {
30006
- resolvedImport = path11.resolve(testDir, importPath);
30007
- const existingExt = path11.extname(resolvedImport);
29973
+ resolvedImport = path13.resolve(testDir, importPath);
29974
+ const existingExt = path13.extname(resolvedImport);
30008
29975
  if (!existingExt) {
30009
29976
  for (const extToTry of [
30010
29977
  ".ts",
@@ -30015,7 +29982,7 @@ async function getTestFilesFromGraph(sourceFiles) {
30015
29982
  ".cjs"
30016
29983
  ]) {
30017
29984
  const withExt = resolvedImport + extToTry;
30018
- if (sourceFiles.includes(withExt) || fs6.existsSync(withExt)) {
29985
+ if (sourceFiles.includes(withExt) || fs7.existsSync(withExt)) {
30019
29986
  resolvedImport = withExt;
30020
29987
  break;
30021
29988
  }
@@ -30024,12 +29991,12 @@ async function getTestFilesFromGraph(sourceFiles) {
30024
29991
  } else {
30025
29992
  continue;
30026
29993
  }
30027
- const importBasename = path11.basename(resolvedImport, path11.extname(resolvedImport));
30028
- const importDir = path11.dirname(resolvedImport);
29994
+ const importBasename = path13.basename(resolvedImport, path13.extname(resolvedImport));
29995
+ const importDir = path13.dirname(resolvedImport);
30029
29996
  for (const sourceFile of sourceFiles) {
30030
- const sourceDir = path11.dirname(sourceFile);
30031
- const sourceBasename = path11.basename(sourceFile, path11.extname(sourceFile));
30032
- const isRelatedDir = importDir === sourceDir || importDir === path11.join(sourceDir, "__tests__") || importDir === path11.join(sourceDir, "tests") || importDir === path11.join(sourceDir, "test");
29997
+ const sourceDir = path13.dirname(sourceFile);
29998
+ const sourceBasename = path13.basename(sourceFile, path13.extname(sourceFile));
29999
+ const isRelatedDir = importDir === sourceDir || importDir === path13.join(sourceDir, "__tests__") || importDir === path13.join(sourceDir, "tests") || importDir === path13.join(sourceDir, "test");
30033
30000
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
30034
30001
  if (!testFiles.includes(testFile)) {
30035
30002
  testFiles.push(testFile);
@@ -30042,8 +30009,8 @@ async function getTestFilesFromGraph(sourceFiles) {
30042
30009
  while ((match = requireRegex.exec(content)) !== null) {
30043
30010
  const importPath = match[1];
30044
30011
  if (importPath.startsWith(".")) {
30045
- let resolvedImport = path11.resolve(testDir, importPath);
30046
- const existingExt = path11.extname(resolvedImport);
30012
+ let resolvedImport = path13.resolve(testDir, importPath);
30013
+ const existingExt = path13.extname(resolvedImport);
30047
30014
  if (!existingExt) {
30048
30015
  for (const extToTry of [
30049
30016
  ".ts",
@@ -30054,18 +30021,18 @@ async function getTestFilesFromGraph(sourceFiles) {
30054
30021
  ".cjs"
30055
30022
  ]) {
30056
30023
  const withExt = resolvedImport + extToTry;
30057
- if (sourceFiles.includes(withExt) || fs6.existsSync(withExt)) {
30024
+ if (sourceFiles.includes(withExt) || fs7.existsSync(withExt)) {
30058
30025
  resolvedImport = withExt;
30059
30026
  break;
30060
30027
  }
30061
30028
  }
30062
30029
  }
30063
- const importDir = path11.dirname(resolvedImport);
30064
- const importBasename = path11.basename(resolvedImport, path11.extname(resolvedImport));
30030
+ const importDir = path13.dirname(resolvedImport);
30031
+ const importBasename = path13.basename(resolvedImport, path13.extname(resolvedImport));
30065
30032
  for (const sourceFile of sourceFiles) {
30066
- const sourceDir = path11.dirname(sourceFile);
30067
- const sourceBasename = path11.basename(sourceFile, path11.extname(sourceFile));
30068
- const isRelatedDir = importDir === sourceDir || importDir === path11.join(sourceDir, "__tests__") || importDir === path11.join(sourceDir, "tests") || importDir === path11.join(sourceDir, "test");
30033
+ const sourceDir = path13.dirname(sourceFile);
30034
+ const sourceBasename = path13.basename(sourceFile, path13.extname(sourceFile));
30035
+ const isRelatedDir = importDir === sourceDir || importDir === path13.join(sourceDir, "__tests__") || importDir === path13.join(sourceDir, "tests") || importDir === path13.join(sourceDir, "test");
30069
30036
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
30070
30037
  if (!testFiles.includes(testFile)) {
30071
30038
  testFiles.push(testFile);
@@ -30079,40 +30046,118 @@ async function getTestFilesFromGraph(sourceFiles) {
30079
30046
  }
30080
30047
  return testFiles;
30081
30048
  }
30049
+ function findSourceFiles(dir, files = []) {
30050
+ let entries;
30051
+ try {
30052
+ entries = fs7.readdirSync(dir);
30053
+ } catch {
30054
+ return files;
30055
+ }
30056
+ entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
30057
+ for (const entry of entries) {
30058
+ if (SKIP_DIRECTORIES.has(entry))
30059
+ continue;
30060
+ const fullPath = path13.join(dir, entry);
30061
+ let stat;
30062
+ try {
30063
+ stat = fs7.statSync(fullPath);
30064
+ } catch {
30065
+ continue;
30066
+ }
30067
+ if (stat.isDirectory()) {
30068
+ findSourceFiles(fullPath, files);
30069
+ } else if (stat.isFile()) {
30070
+ const ext = path13.extname(fullPath).toLowerCase();
30071
+ if (SOURCE_EXTENSIONS.has(ext)) {
30072
+ files.push(fullPath);
30073
+ }
30074
+ }
30075
+ }
30076
+ return files;
30077
+ }
30078
+ var TEST_PATTERNS, COMPOUND_TEST_EXTENSIONS, SOURCE_EXTENSIONS, SKIP_DIRECTORIES;
30079
+ var init_discover = __esm(() => {
30080
+ TEST_PATTERNS = [
30081
+ { test: ".spec.", source: "." },
30082
+ { test: ".test.", source: "." },
30083
+ { test: "/__tests__/", source: "/" },
30084
+ { test: "/tests/", source: "/" },
30085
+ { test: "/test/", source: "/" }
30086
+ ];
30087
+ COMPOUND_TEST_EXTENSIONS = [
30088
+ ".test.ts",
30089
+ ".test.tsx",
30090
+ ".test.js",
30091
+ ".test.jsx",
30092
+ ".spec.ts",
30093
+ ".spec.tsx",
30094
+ ".spec.js",
30095
+ ".spec.jsx",
30096
+ ".test.ps1",
30097
+ ".spec.ps1"
30098
+ ];
30099
+ SOURCE_EXTENSIONS = new Set([
30100
+ ".ts",
30101
+ ".tsx",
30102
+ ".js",
30103
+ ".jsx",
30104
+ ".mjs",
30105
+ ".cjs",
30106
+ ".py",
30107
+ ".rs",
30108
+ ".ps1",
30109
+ ".psm1"
30110
+ ]);
30111
+ SKIP_DIRECTORIES = new Set([
30112
+ "node_modules",
30113
+ ".git",
30114
+ "dist",
30115
+ "build",
30116
+ "out",
30117
+ "coverage",
30118
+ ".next",
30119
+ ".nuxt",
30120
+ ".cache",
30121
+ "vendor",
30122
+ ".svn",
30123
+ ".hg",
30124
+ "__pycache__",
30125
+ ".pytest_cache",
30126
+ "target"
30127
+ ]);
30128
+ });
30129
+
30130
+ // src/tools/test-runner/run.ts
30082
30131
  function buildTestCommand(framework, scope, files, coverage) {
30083
30132
  switch (framework) {
30084
30133
  case "bun": {
30085
30134
  const args = ["bun", "test"];
30086
30135
  if (coverage)
30087
30136
  args.push("--coverage");
30088
- if (scope !== "all" && files.length > 0) {
30137
+ if (scope !== "all" && files.length > 0)
30089
30138
  args.push(...files);
30090
- }
30091
30139
  return args;
30092
30140
  }
30093
30141
  case "vitest": {
30094
30142
  const args = ["npx", "vitest", "run"];
30095
30143
  if (coverage)
30096
30144
  args.push("--coverage");
30097
- if (scope !== "all" && files.length > 0) {
30145
+ if (scope !== "all" && files.length > 0)
30098
30146
  args.push(...files);
30099
- }
30100
30147
  return args;
30101
30148
  }
30102
30149
  case "jest": {
30103
30150
  const args = ["npx", "jest"];
30104
30151
  if (coverage)
30105
30152
  args.push("--coverage");
30106
- if (scope !== "all" && files.length > 0) {
30153
+ if (scope !== "all" && files.length > 0)
30107
30154
  args.push(...files);
30108
- }
30109
30155
  return args;
30110
30156
  }
30111
30157
  case "mocha": {
30112
30158
  const args = ["npx", "mocha"];
30113
- if (scope !== "all" && files.length > 0) {
30159
+ if (scope !== "all" && files.length > 0)
30114
30160
  args.push(...files);
30115
- }
30116
30161
  return args;
30117
30162
  }
30118
30163
  case "pytest": {
@@ -30120,16 +30165,14 @@ function buildTestCommand(framework, scope, files, coverage) {
30120
30165
  const args = isWindows ? ["python", "-m", "pytest"] : ["python3", "-m", "pytest"];
30121
30166
  if (coverage)
30122
30167
  args.push("--cov=.", "--cov-report=term-missing");
30123
- if (scope !== "all" && files.length > 0) {
30168
+ if (scope !== "all" && files.length > 0)
30124
30169
  args.push(...files);
30125
- }
30126
30170
  return args;
30127
30171
  }
30128
30172
  case "cargo": {
30129
30173
  const args = ["cargo", "test"];
30130
- if (scope !== "all" && files.length > 0) {
30174
+ if (scope !== "all" && files.length > 0)
30131
30175
  args.push(...files);
30132
- }
30133
30176
  return args;
30134
30177
  }
30135
30178
  case "pester": {
@@ -30138,8 +30181,7 @@ function buildTestCommand(framework, scope, files, coverage) {
30138
30181
  const psCommand = `Invoke-Pester -Path @('${escapedFiles.join("','")}')`;
30139
30182
  const utf16Bytes = Buffer.from(psCommand, "utf16le");
30140
30183
  const base64Command = utf16Bytes.toString("base64");
30141
- const args = ["pwsh", "-EncodedCommand", base64Command];
30142
- return args;
30184
+ return ["pwsh", "-EncodedCommand", base64Command];
30143
30185
  }
30144
30186
  return ["pwsh", "-Command", "Invoke-Pester"];
30145
30187
  }
@@ -30148,12 +30190,7 @@ function buildTestCommand(framework, scope, files, coverage) {
30148
30190
  }
30149
30191
  }
30150
30192
  function parseTestOutput(framework, output) {
30151
- const totals = {
30152
- passed: 0,
30153
- failed: 0,
30154
- skipped: 0,
30155
- total: 0
30156
- };
30193
+ const totals = { passed: 0, failed: 0, skipped: 0, total: 0 };
30157
30194
  let coveragePercent;
30158
30195
  switch (framework) {
30159
30196
  case "vitest":
@@ -30169,9 +30206,8 @@ function parseTestOutput(framework, output) {
30169
30206
  totals.skipped = parsed.numPendingTests || 0;
30170
30207
  totals.total = parsed.numTotalTests || 0;
30171
30208
  }
30172
- if (parsed.coverage !== undefined) {
30209
+ if (parsed.coverage !== undefined)
30173
30210
  coveragePercent = parsed.coverage;
30174
- }
30175
30211
  } catch {}
30176
30212
  }
30177
30213
  if (totals.total === 0) {
@@ -30187,9 +30223,8 @@ function parseTestOutput(framework, output) {
30187
30223
  totals.total = totals.passed + totals.failed + totals.skipped;
30188
30224
  }
30189
30225
  const coverageMatch = output.match(/All files[^\d]*(\d+\.?\d*)\s*%/);
30190
- if (!coveragePercent && coverageMatch) {
30226
+ if (!coveragePercent && coverageMatch)
30191
30227
  coveragePercent = parseFloat(coverageMatch[1]);
30192
- }
30193
30228
  break;
30194
30229
  }
30195
30230
  case "mocha": {
@@ -30217,9 +30252,8 @@ function parseTestOutput(framework, output) {
30217
30252
  totals.skipped = parseInt(skipMatch[1], 10);
30218
30253
  totals.total = totals.passed + totals.failed + totals.skipped;
30219
30254
  const coverageMatch = output.match(/TOTAL\s+(\d+\.?\d*)\s*%/);
30220
- if (coverageMatch) {
30255
+ if (coverageMatch)
30221
30256
  coveragePercent = parseFloat(coverageMatch[1]);
30222
- }
30223
30257
  break;
30224
30258
  }
30225
30259
  case "cargo": {
@@ -30275,10 +30309,7 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30275
30309
  }
30276
30310
  const startTime = Date.now();
30277
30311
  try {
30278
- const proc = Bun.spawn(command, {
30279
- stdout: "pipe",
30280
- stderr: "pipe"
30281
- });
30312
+ const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
30282
30313
  const exitPromise = proc.exited;
30283
30314
  const timeoutPromise = new Promise((resolve7) => setTimeout(() => {
30284
30315
  proc.kill();
@@ -30291,18 +30322,16 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30291
30322
  new Response(proc.stderr).text()
30292
30323
  ]);
30293
30324
  let output = stdout;
30294
- if (stderr) {
30325
+ if (stderr)
30295
30326
  output += (output ? `
30296
30327
  ` : "") + stderr;
30297
- }
30298
30328
  const outputBytes = Buffer.byteLength(output, "utf-8");
30299
30329
  if (outputBytes > MAX_OUTPUT_BYTES3) {
30300
30330
  let truncIndex = MAX_OUTPUT_BYTES3;
30301
30331
  while (truncIndex > 0) {
30302
30332
  const truncated = output.slice(0, truncIndex);
30303
- if (Buffer.byteLength(truncated, "utf-8") <= MAX_OUTPUT_BYTES3) {
30333
+ if (Buffer.byteLength(truncated, "utf-8") <= MAX_OUTPUT_BYTES3)
30304
30334
  break;
30305
- }
30306
30335
  truncIndex--;
30307
30336
  }
30308
30337
  output = output.slice(0, truncIndex) + `
@@ -30321,13 +30350,11 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30321
30350
  totals,
30322
30351
  rawOutput: output
30323
30352
  };
30324
- if (coveragePercent !== undefined) {
30353
+ if (coveragePercent !== undefined)
30325
30354
  result.coveragePercent = coveragePercent;
30326
- }
30327
30355
  result.message = `${framework} tests passed (${totals.passed}/${totals.total})`;
30328
- if (coveragePercent !== undefined) {
30356
+ if (coveragePercent !== undefined)
30329
30357
  result.message += ` with ${coveragePercent}% coverage`;
30330
- }
30331
30358
  return result;
30332
30359
  } else {
30333
30360
  const result = {
@@ -30342,9 +30369,8 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30342
30369
  error: `Tests failed with ${totals.failed} failures`,
30343
30370
  message: `${framework} tests failed (${totals.failed}/${totals.total} failed)`
30344
30371
  };
30345
- if (coveragePercent !== undefined) {
30372
+ if (coveragePercent !== undefined)
30346
30373
  result.coveragePercent = coveragePercent;
30347
- }
30348
30374
  return result;
30349
30375
  }
30350
30376
  } catch (error93) {
@@ -30360,89 +30386,111 @@ async function runTests(framework, scope, files, coverage, timeout_ms) {
30360
30386
  };
30361
30387
  }
30362
30388
  }
30363
- function findSourceFiles(dir, files = []) {
30364
- let entries;
30365
- try {
30366
- entries = fs6.readdirSync(dir);
30367
- } catch {
30368
- return files;
30369
- }
30370
- entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
30371
- for (const entry of entries) {
30372
- if (SKIP_DIRECTORIES.has(entry))
30373
- continue;
30374
- const fullPath = path11.join(dir, entry);
30375
- let stat;
30376
- try {
30377
- stat = fs6.statSync(fullPath);
30378
- } catch {
30379
- continue;
30389
+ var init_run = __esm(() => {
30390
+ init_constants();
30391
+ });
30392
+
30393
+ // src/tools/test-runner/validate.ts
30394
+ function containsPathTraversal2(str) {
30395
+ if (/\.\.[/\\]/.test(str))
30396
+ return true;
30397
+ if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
30398
+ return true;
30399
+ if (/%2e%2e/i.test(str))
30400
+ return true;
30401
+ if (/%2e\./i.test(str))
30402
+ return true;
30403
+ if (/%2e/i.test(str) && /\.\./.test(str))
30404
+ return true;
30405
+ if (/%252e%252e/i.test(str))
30406
+ return true;
30407
+ if (/\uff0e/.test(str))
30408
+ return true;
30409
+ if (/\u3002/.test(str))
30410
+ return true;
30411
+ if (/\uff65/.test(str))
30412
+ return true;
30413
+ if (/%2f/i.test(str))
30414
+ return true;
30415
+ if (/%5c/i.test(str))
30416
+ return true;
30417
+ return false;
30418
+ }
30419
+ function isAbsolutePath(str) {
30420
+ if (str.startsWith("/"))
30421
+ return true;
30422
+ if (/^[a-zA-Z]:[/\\]/.test(str))
30423
+ return true;
30424
+ if (/^\\\\/.test(str))
30425
+ return true;
30426
+ if (/^\\\\\.\\/.test(str))
30427
+ return true;
30428
+ return false;
30429
+ }
30430
+ function containsControlChars2(str) {
30431
+ return /[\x00-\x08\x0a\x0b\x0c\x0d\x0e-\x1f\x7f\x80-\x9f]/.test(str);
30432
+ }
30433
+ function containsPowerShellMetacharacters(str) {
30434
+ return POWERSHELL_METACHARACTERS.test(str);
30435
+ }
30436
+ function validateArgs2(args) {
30437
+ if (typeof args !== "object" || args === null)
30438
+ return false;
30439
+ const obj = args;
30440
+ if (obj.scope !== undefined) {
30441
+ if (obj.scope !== "all" && obj.scope !== "convention" && obj.scope !== "graph") {
30442
+ return false;
30380
30443
  }
30381
- if (stat.isDirectory()) {
30382
- findSourceFiles(fullPath, files);
30383
- } else if (stat.isFile()) {
30384
- const ext = path11.extname(fullPath).toLowerCase();
30385
- if (SOURCE_EXTENSIONS.has(ext)) {
30386
- files.push(fullPath);
30387
- }
30444
+ }
30445
+ if (obj.files !== undefined) {
30446
+ if (!Array.isArray(obj.files))
30447
+ return false;
30448
+ for (const f of obj.files) {
30449
+ if (typeof f !== "string")
30450
+ return false;
30451
+ if (isAbsolutePath(f))
30452
+ return false;
30453
+ if (containsPathTraversal2(f))
30454
+ return false;
30455
+ if (containsControlChars2(f))
30456
+ return false;
30457
+ if (containsPowerShellMetacharacters(f))
30458
+ return false;
30388
30459
  }
30389
30460
  }
30390
- return files;
30461
+ if (obj.coverage !== undefined) {
30462
+ if (typeof obj.coverage !== "boolean")
30463
+ return false;
30464
+ }
30465
+ if (obj.timeout_ms !== undefined) {
30466
+ if (typeof obj.timeout_ms !== "number")
30467
+ return false;
30468
+ if (obj.timeout_ms < 0 || obj.timeout_ms > MAX_TIMEOUT_MS)
30469
+ return false;
30470
+ }
30471
+ return true;
30391
30472
  }
30392
- var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000, POWERSHELL_METACHARACTERS, TEST_PATTERNS, COMPOUND_TEST_EXTENSIONS, SOURCE_EXTENSIONS, SKIP_DIRECTORIES, test_runner;
30473
+ var POWERSHELL_METACHARACTERS;
30474
+ var init_validate = __esm(() => {
30475
+ init_constants();
30476
+ POWERSHELL_METACHARACTERS = /[|;&`$(){}[\]<>"'#*?\x00-\x1f]/;
30477
+ });
30478
+
30479
+ // src/tools/test-runner/index.ts
30480
+ import * as path14 from "path";
30481
+ var test_runner;
30393
30482
  var init_test_runner = __esm(() => {
30394
30483
  init_dist();
30395
- POWERSHELL_METACHARACTERS = /[|;&`$(){}[\]<>"'#*?\x00-\x1f]/;
30396
- TEST_PATTERNS = [
30397
- { test: ".spec.", source: "." },
30398
- { test: ".test.", source: "." },
30399
- { test: "/__tests__/", source: "/" },
30400
- { test: "/tests/", source: "/" },
30401
- { test: "/test/", source: "/" }
30402
- ];
30403
- COMPOUND_TEST_EXTENSIONS = [
30404
- ".test.ts",
30405
- ".test.tsx",
30406
- ".test.js",
30407
- ".test.jsx",
30408
- ".spec.ts",
30409
- ".spec.tsx",
30410
- ".spec.js",
30411
- ".spec.jsx",
30412
- ".test.ps1",
30413
- ".spec.ps1"
30414
- ];
30415
- SOURCE_EXTENSIONS = new Set([
30416
- ".ts",
30417
- ".tsx",
30418
- ".js",
30419
- ".jsx",
30420
- ".mjs",
30421
- ".cjs",
30422
- ".py",
30423
- ".rs",
30424
- ".ps1",
30425
- ".psm1"
30426
- ]);
30427
- SKIP_DIRECTORIES = new Set([
30428
- "node_modules",
30429
- ".git",
30430
- "dist",
30431
- "build",
30432
- "out",
30433
- "coverage",
30434
- ".next",
30435
- ".nuxt",
30436
- ".cache",
30437
- "vendor",
30438
- ".svn",
30439
- ".hg",
30440
- "__pycache__",
30441
- ".pytest_cache",
30442
- "target"
30443
- ]);
30484
+ init_constants();
30485
+ init_detect();
30486
+ init_discover();
30487
+ init_run();
30488
+ init_validate();
30489
+ init_constants();
30490
+ init_detect();
30491
+ init_run();
30444
30492
  test_runner = tool({
30445
- description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, and pester. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to map source files to test files, or "graph" to find related tests via imports.',
30493
+ description: 'Run project tests with framework detection. Supports bun, vitest, jest, mocha, pytest, cargo, and pester. Returns deterministic normalized JSON with framework, scope, command, totals, coverage, duration, success status, and failures. Use scope "all" for full suite, "convention" to map source files to test files by naming, or "graph" to find related tests via imports.',
30446
30494
  args: {
30447
30495
  scope: tool.schema.enum(["all", "convention", "graph"]).optional().describe('Test scope: "all" runs full suite, "convention" maps source files to test files by naming, "graph" finds related tests via imports'),
30448
30496
  files: tool.schema.array(tool.schema.string()).optional().describe("Specific files to test (used with convention or graph scope)"),
@@ -30472,12 +30520,7 @@ var init_test_runner = __esm(() => {
30472
30520
  scope,
30473
30521
  error: "No test framework detected",
30474
30522
  message: "No supported test framework found. Install bun, vitest, jest, mocha, pytest, cargo, or pester.",
30475
- totals: {
30476
- passed: 0,
30477
- failed: 0,
30478
- skipped: 0,
30479
- total: 0
30480
- }
30523
+ totals: { passed: 0, failed: 0, skipped: 0, total: 0 }
30481
30524
  };
30482
30525
  return JSON.stringify(result2, null, 2);
30483
30526
  }
@@ -30487,16 +30530,10 @@ var init_test_runner = __esm(() => {
30487
30530
  if (scope === "all") {
30488
30531
  testFiles = [];
30489
30532
  } else if (scope === "convention") {
30490
- const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => {
30491
- const ext = path11.extname(f).toLowerCase();
30492
- return SOURCE_EXTENSIONS.has(ext);
30493
- }) : findSourceFiles(process.cwd());
30533
+ const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => SOURCE_EXTENSIONS.has(path14.extname(f).toLowerCase())) : findSourceFiles(process.cwd());
30494
30534
  testFiles = getTestFilesFromConvention(sourceFiles);
30495
30535
  } else if (scope === "graph") {
30496
- const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => {
30497
- const ext = path11.extname(f).toLowerCase();
30498
- return SOURCE_EXTENSIONS.has(ext);
30499
- }) : findSourceFiles(process.cwd());
30536
+ const sourceFiles = args.files && args.files.length > 0 ? args.files.filter((f) => SOURCE_EXTENSIONS.has(path14.extname(f).toLowerCase())) : findSourceFiles(process.cwd());
30500
30537
  const graphTestFiles = await getTestFilesFromGraph(sourceFiles);
30501
30538
  if (graphTestFiles.length > 0) {
30502
30539
  testFiles = graphTestFiles;
@@ -30516,8 +30553,8 @@ var init_test_runner = __esm(() => {
30516
30553
  });
30517
30554
 
30518
30555
  // src/services/preflight-service.ts
30519
- import * as fs7 from "fs";
30520
- import * as path12 from "path";
30556
+ import * as fs8 from "fs";
30557
+ import * as path15 from "path";
30521
30558
  function validateDirectoryPath(dir) {
30522
30559
  if (!dir || typeof dir !== "string") {
30523
30560
  throw new Error("Directory path is required");
@@ -30525,8 +30562,8 @@ function validateDirectoryPath(dir) {
30525
30562
  if (dir.includes("..")) {
30526
30563
  throw new Error("Directory path must not contain path traversal sequences");
30527
30564
  }
30528
- const normalized = path12.normalize(dir);
30529
- const absolutePath = path12.isAbsolute(normalized) ? normalized : path12.resolve(normalized);
30565
+ const normalized = path15.normalize(dir);
30566
+ const absolutePath = path15.isAbsolute(normalized) ? normalized : path15.resolve(normalized);
30530
30567
  return absolutePath;
30531
30568
  }
30532
30569
  function validateTimeout(timeoutMs, defaultValue) {
@@ -30549,9 +30586,9 @@ function validateTimeout(timeoutMs, defaultValue) {
30549
30586
  }
30550
30587
  function getPackageVersion(dir) {
30551
30588
  try {
30552
- const packagePath = path12.join(dir, "package.json");
30553
- if (fs7.existsSync(packagePath)) {
30554
- const content = fs7.readFileSync(packagePath, "utf-8");
30589
+ const packagePath = path15.join(dir, "package.json");
30590
+ if (fs8.existsSync(packagePath)) {
30591
+ const content = fs8.readFileSync(packagePath, "utf-8");
30555
30592
  const pkg = JSON.parse(content);
30556
30593
  return pkg.version ?? null;
30557
30594
  }
@@ -30560,9 +30597,9 @@ function getPackageVersion(dir) {
30560
30597
  }
30561
30598
  function getChangelogVersion(dir) {
30562
30599
  try {
30563
- const changelogPath = path12.join(dir, "CHANGELOG.md");
30564
- if (fs7.existsSync(changelogPath)) {
30565
- const content = fs7.readFileSync(changelogPath, "utf-8");
30600
+ const changelogPath = path15.join(dir, "CHANGELOG.md");
30601
+ if (fs8.existsSync(changelogPath)) {
30602
+ const content = fs8.readFileSync(changelogPath, "utf-8");
30566
30603
  const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
30567
30604
  if (match) {
30568
30605
  return match[1];
@@ -30574,10 +30611,10 @@ function getChangelogVersion(dir) {
30574
30611
  function getVersionFileVersion(dir) {
30575
30612
  const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
30576
30613
  for (const file3 of possibleFiles) {
30577
- const filePath = path12.join(dir, file3);
30578
- if (fs7.existsSync(filePath)) {
30614
+ const filePath = path15.join(dir, file3);
30615
+ if (fs8.existsSync(filePath)) {
30579
30616
  try {
30580
- const content = fs7.readFileSync(filePath, "utf-8").trim();
30617
+ const content = fs8.readFileSync(filePath, "utf-8").trim();
30581
30618
  const match = content.match(/(\d+\.\d+\.\d+)/);
30582
30619
  if (match) {
30583
30620
  return match[1];
@@ -31114,7 +31151,7 @@ function createPreflightIntegration(config3) {
31114
31151
  statusArtifact = new AutomationStatusArtifact(swarmDir);
31115
31152
  }
31116
31153
  const preflightHandler = async (request) => {
31117
- console.log("[PreflightIntegration] Handling preflight request", {
31154
+ log("[PreflightIntegration] Handling preflight request", {
31118
31155
  requestId: request.id,
31119
31156
  phase: request.currentPhase,
31120
31157
  source: request.source
@@ -31123,13 +31160,13 @@ function createPreflightIntegration(config3) {
31123
31160
  if (statusArtifact) {
31124
31161
  const state = report.overall === "pass" ? "success" : "failure";
31125
31162
  statusArtifact.recordOutcome(state, request.currentPhase, report.message);
31126
- console.log("[PreflightIntegration] Status artifact updated", {
31163
+ log("[PreflightIntegration] Status artifact updated", {
31127
31164
  state,
31128
31165
  phase: request.currentPhase,
31129
31166
  message: report.message
31130
31167
  });
31131
31168
  }
31132
- console.log("[PreflightIntegration] Preflight complete", {
31169
+ log("[PreflightIntegration] Preflight complete", {
31133
31170
  requestId: request.id,
31134
31171
  overall: report.overall,
31135
31172
  message: report.message,
@@ -31152,10 +31189,11 @@ var init_preflight_integration = __esm(() => {
31152
31189
  init_status_artifact();
31153
31190
  init_trigger();
31154
31191
  init_preflight_service();
31192
+ init_utils();
31155
31193
  });
31156
31194
 
31157
31195
  // src/index.ts
31158
- import * as path25 from "path";
31196
+ import * as path28 from "path";
31159
31197
 
31160
31198
  // src/config/constants.ts
31161
31199
  var QA_AGENTS = ["reviewer", "critic"];
@@ -31213,9 +31251,9 @@ var DEFAULT_SCORING_CONFIG = {
31213
31251
  init_evidence_schema();
31214
31252
 
31215
31253
  // src/config/loader.ts
31254
+ init_utils();
31216
31255
  import * as fs from "fs";
31217
- import * as os from "os";
31218
- import * as path from "path";
31256
+ import * as path2 from "path";
31219
31257
 
31220
31258
  // src/config/schema.ts
31221
31259
  init_zod();
@@ -31504,6 +31542,10 @@ var CheckpointConfigSchema = exports_external.object({
31504
31542
  enabled: exports_external.boolean().default(true),
31505
31543
  auto_checkpoint_threshold: exports_external.number().min(1).max(20).default(3)
31506
31544
  });
31545
+ var GitingestConfigSchema = exports_external.object({
31546
+ enabled: exports_external.boolean().default(true),
31547
+ endpoint: exports_external.string().url().default("https://gitingest.com/api/ingest")
31548
+ });
31507
31549
  var AutomationModeSchema = exports_external.enum(["manual", "hybrid", "auto"]);
31508
31550
  var AutomationCapabilitiesSchema = exports_external.object({
31509
31551
  plan_sync: exports_external.boolean().default(true),
@@ -31525,7 +31567,7 @@ var AutomationConfigSchemaBase = exports_external.object({
31525
31567
  })
31526
31568
  });
31527
31569
  var AutomationConfigSchema = AutomationConfigSchemaBase;
31528
- var PluginConfigSchema = exports_external.object({
31570
+ var pluginConfigShape = {
31529
31571
  agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
31530
31572
  swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
31531
31573
  max_iterations: exports_external.number().min(1).max(10).default(5),
@@ -31539,31 +31581,25 @@ var PluginConfigSchema = exports_external.object({
31539
31581
  review_passes: ReviewPassesConfigSchema.optional(),
31540
31582
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
31541
31583
  docs: DocsConfigSchema.optional(),
31584
+ gitingest: GitingestConfigSchema.optional(),
31542
31585
  ui_review: UIReviewConfigSchema.optional(),
31543
31586
  compaction_advisory: CompactionAdvisoryConfigSchema.optional(),
31544
31587
  lint: LintConfigSchema.optional(),
31545
31588
  secretscan: SecretscanConfigSchema.optional(),
31546
31589
  checkpoint: CheckpointConfigSchema.optional(),
31547
31590
  automation: AutomationConfigSchema.optional()
31548
- });
31591
+ };
31592
+ var PLUGIN_CONFIG_ALLOWED_KEYS = new Set(Object.keys(pluginConfigShape));
31593
+ var PluginConfigSchema = exports_external.object(pluginConfigShape);
31549
31594
 
31550
31595
  // src/config/loader.ts
31551
31596
  var CONFIG_FILENAME = "opencode-swarm.json";
31552
31597
  var PROMPTS_DIR_NAME = "opencode-swarm";
31553
31598
  var MAX_CONFIG_FILE_BYTES = 102400;
31554
- function getUserConfigDir() {
31555
- return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
31556
- }
31557
31599
  function loadRawConfigFromPath(configPath) {
31558
31600
  try {
31559
- const stats = fs.statSync(configPath);
31560
- if (stats.size > MAX_CONFIG_FILE_BYTES) {
31561
- console.warn(`[opencode-swarm] Config file too large (max 100 KB): ${configPath}`);
31562
- console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
31563
- return { config: null, fileExisted: true, hadError: true };
31564
- }
31565
31601
  const content = fs.readFileSync(configPath, "utf-8");
31566
- if (content.length > MAX_CONFIG_FILE_BYTES) {
31602
+ if (Buffer.byteLength(content, "utf-8") > MAX_CONFIG_FILE_BYTES) {
31567
31603
  console.warn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
31568
31604
  console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config file exceeds size limit. Falling back to safe defaults with guardrails ENABLED.");
31569
31605
  return { config: null, fileExisted: true, hadError: true };
@@ -31579,10 +31615,10 @@ function loadRawConfigFromPath(configPath) {
31579
31615
  fileExisted: true,
31580
31616
  hadError: false
31581
31617
  };
31582
- } catch (error48) {
31583
- const isFileNotFoundError = error48 instanceof Error && "code" in error48 && error48.code === "ENOENT";
31618
+ } catch (error49) {
31619
+ const isFileNotFoundError = error49 instanceof Error && "code" in error49 && error49.code === "ENOENT";
31584
31620
  if (!isFileNotFoundError) {
31585
- const errorMessage = error48 instanceof Error ? error48.message : String(error48);
31621
+ const errorMessage = error49 instanceof Error ? error49.message : String(error49);
31586
31622
  console.warn(`[opencode-swarm] \u26A0\uFE0F CONFIG LOAD FAILURE \u2014 config exists at ${configPath} but could not be loaded: ${errorMessage}`);
31587
31623
  console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Config load failed. Falling back to safe defaults with guardrails ENABLED.");
31588
31624
  return { config: null, fileExisted: true, hadError: true };
@@ -31590,9 +31626,15 @@ function loadRawConfigFromPath(configPath) {
31590
31626
  return { config: null, fileExisted: false, hadError: false };
31591
31627
  }
31592
31628
  }
31593
- function loadPluginConfig(directory) {
31594
- const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
31595
- const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
31629
+ function warnOnUnknownPluginConfigFields(config2, context) {
31630
+ const unknownKeys = Object.keys(config2).filter((key) => !PLUGIN_CONFIG_ALLOWED_KEYS.has(key)).sort();
31631
+ if (unknownKeys.length === 0)
31632
+ return;
31633
+ warn(`${context} contains unknown plugin config fields: ${unknownKeys.join(", ")}. They will be ignored.`);
31634
+ }
31635
+ function _loadPluginConfigInternal(directory) {
31636
+ const userConfigPath = path2.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
31637
+ const projectConfigPath = path2.join(directory, ".opencode", CONFIG_FILENAME);
31596
31638
  const userResult = loadRawConfigFromPath(userConfigPath);
31597
31639
  const projectResult = loadRawConfigFromPath(projectConfigPath);
31598
31640
  const rawUserConfig = userResult.config;
@@ -31603,56 +31645,67 @@ function loadPluginConfig(directory) {
31603
31645
  if (rawProjectConfig) {
31604
31646
  mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
31605
31647
  }
31648
+ warnOnUnknownPluginConfigFields(mergedRaw, "Merged plugin configuration");
31606
31649
  const result = PluginConfigSchema.safeParse(mergedRaw);
31607
31650
  if (!result.success) {
31608
31651
  if (rawUserConfig) {
31609
31652
  const userParseResult = PluginConfigSchema.safeParse(rawUserConfig);
31610
31653
  if (userParseResult.success) {
31611
31654
  console.warn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
31612
- return userParseResult.data;
31655
+ return {
31656
+ config: userParseResult.data,
31657
+ loadedFromFile,
31658
+ configHadErrors
31659
+ };
31613
31660
  }
31614
31661
  }
31615
31662
  console.warn("[opencode-swarm] Merged config validation failed:");
31616
31663
  console.warn(result.error.format());
31617
31664
  console.warn("[opencode-swarm] \u26A0\uFE0F SECURITY: Falling back to conservative defaults with guardrails ENABLED. Fix the config file to restore custom configuration.");
31618
- return PluginConfigSchema.parse({
31619
- guardrails: { enabled: true }
31620
- });
31665
+ return {
31666
+ config: PluginConfigSchema.parse({
31667
+ guardrails: { enabled: true }
31668
+ }),
31669
+ loadedFromFile,
31670
+ configHadErrors
31671
+ };
31621
31672
  }
31622
31673
  if (loadedFromFile && configHadErrors) {
31623
- return PluginConfigSchema.parse({
31624
- ...mergedRaw,
31625
- guardrails: { enabled: true }
31626
- });
31674
+ return {
31675
+ config: PluginConfigSchema.parse({
31676
+ ...mergedRaw,
31677
+ guardrails: { enabled: true }
31678
+ }),
31679
+ loadedFromFile,
31680
+ configHadErrors
31681
+ };
31627
31682
  }
31628
- return result.data;
31683
+ return { config: result.data, loadedFromFile, configHadErrors };
31684
+ }
31685
+ function loadPluginConfig(directory) {
31686
+ return _loadPluginConfigInternal(directory).config;
31629
31687
  }
31630
31688
  function loadPluginConfigWithMeta(directory) {
31631
- const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
31632
- const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
31633
- const userResult = loadRawConfigFromPath(userConfigPath);
31634
- const projectResult = loadRawConfigFromPath(projectConfigPath);
31635
- const loadedFromFile = userResult.fileExisted || projectResult.fileExisted;
31636
- const config2 = loadPluginConfig(directory);
31637
- return { config: config2, loadedFromFile };
31689
+ const result = _loadPluginConfigInternal(directory);
31690
+ return { config: result.config, loadedFromFile: result.loadedFromFile };
31638
31691
  }
31639
31692
  function loadAgentPrompt(agentName) {
31640
- const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
31693
+ const promptsDir = path2.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
31641
31694
  const result = {};
31642
- const promptPath = path.join(promptsDir, `${agentName}.md`);
31695
+ const promptPath = path2.join(promptsDir, `${agentName}.md`);
31643
31696
  if (fs.existsSync(promptPath)) {
31644
31697
  try {
31645
31698
  result.prompt = fs.readFileSync(promptPath, "utf-8");
31646
- } catch (error48) {
31647
- console.warn(`[opencode-swarm] Error reading prompt file ${promptPath}:`, error48 instanceof Error ? error48.message : String(error48));
31699
+ } catch (error49) {
31700
+ console.warn(`[opencode-swarm] Error reading prompt file ${promptPath}:`, error49 instanceof Error ? error49.message : String(error49));
31648
31701
  }
31649
31702
  }
31650
- const appendPromptPath = path.join(promptsDir, `${agentName}_append.md`);
31703
+ const appendPromptPath = path2.join(promptsDir, `${agentName}_append.md`);
31651
31704
  if (fs.existsSync(appendPromptPath)) {
31652
31705
  try {
31653
31706
  result.appendPrompt = fs.readFileSync(appendPromptPath, "utf-8");
31654
- } catch (error48) {
31655
- console.warn(`[opencode-swarm] Error reading append prompt ${appendPromptPath}:`, error48 instanceof Error ? error48.message : String(error48));
31707
+ } catch (error49) {
31708
+ console.warn(`[opencode-swarm] Error reading append prompt ${appendPromptPath}:`, error49 instanceof Error ? error49.message : String(error49));
31656
31709
  }
31657
31710
  }
31658
31711
  return result;
@@ -31661,6 +31714,14 @@ function loadAgentPrompt(agentName) {
31661
31714
  // src/config/index.ts
31662
31715
  init_plan_schema();
31663
31716
 
31717
+ // src/agents/model.ts
31718
+ function resolveModel(model) {
31719
+ if (!model || model === "current") {
31720
+ return;
31721
+ }
31722
+ return model;
31723
+ }
31724
+
31664
31725
  // src/agents/architect.ts
31665
31726
  var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
31666
31727
 
@@ -31929,6 +31990,7 @@ Swarm: {{SWARM_ID}}
31929
31990
  - [pattern]: [usage]
31930
31991
  \`\`\``;
31931
31992
  function createArchitectAgent(model, customPrompt, customAppendPrompt) {
31993
+ const resolvedModel = resolveModel(model);
31932
31994
  let prompt = ARCHITECT_PROMPT;
31933
31995
  if (customPrompt) {
31934
31996
  prompt = customPrompt;
@@ -31941,7 +32003,7 @@ ${customAppendPrompt}`;
31941
32003
  name: "architect",
31942
32004
  description: "Central orchestrator of the development pipeline. Analyzes requests, coordinates SME consultation, manages code generation, and triages QA feedback.",
31943
32005
  config: {
31944
- model,
32006
+ ...resolvedModel !== undefined && { model: resolvedModel },
31945
32007
  temperature: 0.1,
31946
32008
  prompt
31947
32009
  }
@@ -31975,6 +32037,7 @@ OUTPUT FORMAT:
31975
32037
  DONE: [one-line summary]
31976
32038
  CHANGED: [file]: [what changed]`;
31977
32039
  function createCoderAgent(model, customPrompt, customAppendPrompt) {
32040
+ const resolvedModel = resolveModel(model);
31978
32041
  let prompt = CODER_PROMPT;
31979
32042
  if (customPrompt) {
31980
32043
  prompt = customPrompt;
@@ -31987,7 +32050,7 @@ ${customAppendPrompt}`;
31987
32050
  name: "coder",
31988
32051
  description: "Production-quality code implementation specialist. Receives unified specifications and writes complete, working code.",
31989
32052
  config: {
31990
- model,
32053
+ ...resolvedModel !== undefined && { model: resolvedModel },
31991
32054
  temperature: 0.2,
31992
32055
  prompt
31993
32056
  }
@@ -32033,6 +32096,7 @@ RULES:
32033
32096
  - Don't reject for style/formatting \u2014 focus on substance
32034
32097
  - If the plan is fundamentally sound with only minor concerns, APPROVE it`;
32035
32098
  function createCriticAgent(model, customPrompt, customAppendPrompt) {
32099
+ const resolvedModel = resolveModel(model);
32036
32100
  let prompt = CRITIC_PROMPT;
32037
32101
  if (customPrompt) {
32038
32102
  prompt = customPrompt;
@@ -32045,7 +32109,7 @@ ${customAppendPrompt}`;
32045
32109
  name: "critic",
32046
32110
  description: "Plan critic. Reviews the architect's plan before implementation begins \u2014 checks completeness, feasibility, scope, dependencies, and flags AI-slop.",
32047
32111
  config: {
32048
- model,
32112
+ ...resolvedModel !== undefined && { model: resolvedModel },
32049
32113
  temperature: 0.1,
32050
32114
  prompt,
32051
32115
  tools: {
@@ -32190,6 +32254,7 @@ RULES:
32190
32254
  - Do NOT implement business logic \u2014 leave that for the coder
32191
32255
  - Keep output under 3000 characters per component`;
32192
32256
  function createDesignerAgent(model, customPrompt, customAppendPrompt) {
32257
+ const resolvedModel = resolveModel(model);
32193
32258
  let prompt = DESIGNER_PROMPT;
32194
32259
  if (customPrompt) {
32195
32260
  prompt = customPrompt;
@@ -32202,7 +32267,7 @@ ${customAppendPrompt}`;
32202
32267
  name: "designer",
32203
32268
  description: "UI/UX design specification agent. Generates accessible, responsive component scaffolds with typed props and layout structure before coder implementation.",
32204
32269
  config: {
32205
- model,
32270
+ ...resolvedModel !== undefined && { model: resolvedModel },
32206
32271
  temperature: 0.3,
32207
32272
  prompt
32208
32273
  }
@@ -32263,6 +32328,7 @@ ADDED: [list of new sections/files created]
32263
32328
  REMOVED: [list of deprecated sections removed]
32264
32329
  SUMMARY: [one-line description of doc changes]`;
32265
32330
  function createDocsAgent(model, customPrompt, customAppendPrompt) {
32331
+ const resolvedModel = resolveModel(model);
32266
32332
  let prompt = DOCS_PROMPT;
32267
32333
  if (customPrompt) {
32268
32334
  prompt = customPrompt;
@@ -32275,7 +32341,7 @@ ${customAppendPrompt}`;
32275
32341
  name: "docs",
32276
32342
  description: "Documentation synthesizer. Updates README, API docs, and guides to reflect code changes after each phase.",
32277
32343
  config: {
32278
- model,
32344
+ ...resolvedModel !== undefined && { model: resolvedModel },
32279
32345
  temperature: 0.2,
32280
32346
  prompt
32281
32347
  }
@@ -32323,6 +32389,7 @@ DOMAINS: [relevant SME domains: powershell, security, python, etc.]
32323
32389
  REVIEW NEEDED:
32324
32390
  - [path]: [why, which SME]`;
32325
32391
  function createExplorerAgent(model, customPrompt, customAppendPrompt) {
32392
+ const resolvedModel = resolveModel(model);
32326
32393
  let prompt = EXPLORER_PROMPT;
32327
32394
  if (customPrompt) {
32328
32395
  prompt = customPrompt;
@@ -32335,7 +32402,7 @@ ${customAppendPrompt}`;
32335
32402
  name: "explorer",
32336
32403
  description: "Fast codebase discovery and analysis. Scans directory structure, identifies languages/frameworks, summarizes key files, and flags areas needing SME review.",
32337
32404
  config: {
32338
- model,
32405
+ ...resolvedModel !== undefined && { model: resolvedModel },
32339
32406
  temperature: 0.1,
32340
32407
  prompt,
32341
32408
  tools: {
@@ -32381,6 +32448,7 @@ RISK LEVELS:
32381
32448
  - HIGH: must fix
32382
32449
  - CRITICAL: blocks approval`;
32383
32450
  function createReviewerAgent(model, customPrompt, customAppendPrompt) {
32451
+ const resolvedModel = resolveModel(model);
32384
32452
  let prompt = REVIEWER_PROMPT;
32385
32453
  if (customPrompt) {
32386
32454
  prompt = customPrompt;
@@ -32393,7 +32461,7 @@ ${customAppendPrompt}`;
32393
32461
  name: "reviewer",
32394
32462
  description: "Code reviewer. Verifies correctness, finds vulnerabilities, and checks quality across architect-specified dimensions.",
32395
32463
  config: {
32396
- model,
32464
+ ...resolvedModel !== undefined && { model: resolvedModel },
32397
32465
  temperature: 0.1,
32398
32466
  prompt,
32399
32467
  tools: {
@@ -32432,6 +32500,7 @@ RULES:
32432
32500
  - Be actionable: info Coder can use directly
32433
32501
  - No code writing`;
32434
32502
  function createSMEAgent(model, customPrompt, customAppendPrompt) {
32503
+ const resolvedModel = resolveModel(model);
32435
32504
  let prompt = SME_PROMPT;
32436
32505
  if (customPrompt) {
32437
32506
  prompt = customPrompt;
@@ -32444,7 +32513,7 @@ ${customAppendPrompt}`;
32444
32513
  name: "sme",
32445
32514
  description: "Open-domain subject matter expert. Provides deep technical guidance on any domain the Architect requests \u2014 from security to iOS to Kubernetes.",
32446
32515
  config: {
32447
- model,
32516
+ ...resolvedModel !== undefined && { model: resolvedModel },
32448
32517
  temperature: 0.2,
32449
32518
  prompt,
32450
32519
  tools: {
@@ -32518,6 +32587,7 @@ COVERAGE REPORTING:
32518
32587
  - If COVERAGE_PCT < 70%, add a note: "COVERAGE_WARNING: Below 70% threshold \u2014 consider additional test cases for uncovered paths."
32519
32588
  - The architect uses this to decide whether to request an additional test pass (Rule 10 / Phase 5 step 5h).`;
32520
32589
  function createTestEngineerAgent(model, customPrompt, customAppendPrompt) {
32590
+ const resolvedModel = resolveModel(model);
32521
32591
  let prompt = TEST_ENGINEER_PROMPT;
32522
32592
  if (customPrompt) {
32523
32593
  prompt = customPrompt;
@@ -32530,7 +32600,7 @@ ${customAppendPrompt}`;
32530
32600
  name: "test_engineer",
32531
32601
  description: "Testing and validation specialist. Generates test cases, runs them, and reports structured PASS/FAIL verdicts.",
32532
32602
  config: {
32533
- model,
32603
+ ...resolvedModel !== undefined && { model: resolvedModel },
32534
32604
  temperature: 0.2,
32535
32605
  prompt
32536
32606
  }
@@ -32550,8 +32620,9 @@ function stripSwarmPrefix(agentName, swarmPrefix) {
32550
32620
  function getModelForAgent(agentName, swarmAgents, swarmPrefix) {
32551
32621
  const baseAgentName = stripSwarmPrefix(agentName, swarmPrefix);
32552
32622
  const explicit = swarmAgents?.[baseAgentName]?.model;
32553
- if (explicit)
32554
- return explicit;
32623
+ if (explicit !== undefined) {
32624
+ return resolveModel(explicit);
32625
+ }
32555
32626
  return DEFAULT_MODELS[baseAgentName] ?? DEFAULT_MODELS.default;
32556
32627
  }
32557
32628
  function isAgentDisabled(agentName, swarmAgents, swarmPrefix) {
@@ -32722,8 +32793,8 @@ class CircuitBreaker {
32722
32793
  async execute(fn) {
32723
32794
  const state = this.getState();
32724
32795
  if (state === "open") {
32725
- const error48 = new Error(`Circuit breaker '${this.name}' is open`);
32726
- throw error48;
32796
+ const error49 = new Error(`Circuit breaker '${this.name}' is open`);
32797
+ throw error49;
32727
32798
  }
32728
32799
  const startTime = Date.now();
32729
32800
  try {
@@ -32731,10 +32802,10 @@ class CircuitBreaker {
32731
32802
  const duration3 = Date.now() - startTime;
32732
32803
  this.recordSuccess(duration3);
32733
32804
  return result;
32734
- } catch (error48) {
32805
+ } catch (error49) {
32735
32806
  const duration3 = Date.now() - startTime;
32736
- this.recordFailure(error48, duration3);
32737
- throw error48;
32807
+ this.recordFailure(error49, duration3);
32808
+ throw error49;
32738
32809
  }
32739
32810
  }
32740
32811
  async executeWithTimeout(fn) {
@@ -32748,9 +32819,9 @@ class CircuitBreaker {
32748
32819
  fn().then((result) => {
32749
32820
  clearTimeout(timeout);
32750
32821
  resolve(result);
32751
- }).catch((error48) => {
32822
+ }).catch((error49) => {
32752
32823
  clearTimeout(timeout);
32753
- reject(error48);
32824
+ reject(error49);
32754
32825
  });
32755
32826
  });
32756
32827
  }
@@ -32764,7 +32835,7 @@ class CircuitBreaker {
32764
32835
  }
32765
32836
  this.onStateChange?.("callSuccess", { duration: duration3 });
32766
32837
  }
32767
- recordFailure(error48, duration3) {
32838
+ recordFailure(error49, duration3) {
32768
32839
  this.failureCount++;
32769
32840
  this.lastFailureTime = Date.now();
32770
32841
  if (this.state === "half-open") {
@@ -32772,7 +32843,7 @@ class CircuitBreaker {
32772
32843
  } else if (this.state === "closed" && this.failureCount >= this.config.failureThreshold) {
32773
32844
  this.transitionTo("open");
32774
32845
  }
32775
- this.onStateChange?.("callFailure", { error: error48, duration: duration3 });
32846
+ this.onStateChange?.("callFailure", { error: error49, duration: duration3 });
32776
32847
  }
32777
32848
  transitionTo(newState) {
32778
32849
  const oldState = this.state;
@@ -33248,7 +33319,7 @@ function createAutomationManager(automationConfig) {
33248
33319
  init_manager2();
33249
33320
  init_utils();
33250
33321
  import * as fs2 from "fs";
33251
- import * as path6 from "path";
33322
+ import * as path7 from "path";
33252
33323
 
33253
33324
  class PlanSyncWorker {
33254
33325
  directory;
@@ -33272,10 +33343,10 @@ class PlanSyncWorker {
33272
33343
  this.onSyncComplete = options.onSyncComplete;
33273
33344
  }
33274
33345
  getSwarmDir() {
33275
- return path6.resolve(this.directory, ".swarm");
33346
+ return path7.resolve(this.directory, ".swarm");
33276
33347
  }
33277
33348
  getPlanJsonPath() {
33278
- return path6.join(this.getSwarmDir(), "plan.json");
33349
+ return path7.join(this.getSwarmDir(), "plan.json");
33279
33350
  }
33280
33351
  start() {
33281
33352
  if (this.disposed) {
@@ -33510,7 +33581,7 @@ function handleAgentsCommand(agents, guardrails) {
33510
33581
  }
33511
33582
  const lines = [`## Registered Agents (${entries.length} total)`, ""];
33512
33583
  for (const [key, agent] of entries) {
33513
- const model = agent.config.model || "default";
33584
+ const model = agent.config.model === undefined ? "current session model" : agent.config.model || "default";
33514
33585
  const temp = agent.config.temperature !== undefined ? agent.config.temperature.toString() : "default";
33515
33586
  const tools = agent.config.tools || {};
33516
33587
  const isReadOnly = tools.write === false || tools.edit === false;
@@ -33622,6 +33693,8 @@ async function handleArchiveCommand(directory, args) {
33622
33693
  init_manager();
33623
33694
 
33624
33695
  // src/state.ts
33696
+ var TOOL_AGGREGATE_CAP = 1000;
33697
+ var MAX_DELEGATION_CHAIN_SESSIONS = 1000;
33625
33698
  var swarmState = {
33626
33699
  activeToolCalls: new Map,
33627
33700
  toolAggregates: new Map,
@@ -33630,6 +33703,14 @@ var swarmState = {
33630
33703
  pendingEvents: 0,
33631
33704
  agentSessions: new Map
33632
33705
  };
33706
+ function enforceToolAggregateCapacity(key) {
33707
+ if (!swarmState.toolAggregates.has(key) && swarmState.toolAggregates.size >= TOOL_AGGREGATE_CAP) {
33708
+ const firstKey = swarmState.toolAggregates.keys().next().value;
33709
+ if (firstKey !== undefined) {
33710
+ swarmState.toolAggregates.delete(firstKey);
33711
+ }
33712
+ }
33713
+ }
33633
33714
  function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
33634
33715
  const now = Date.now();
33635
33716
  const staleIds = [];
@@ -33640,6 +33721,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
33640
33721
  }
33641
33722
  for (const id of staleIds) {
33642
33723
  swarmState.agentSessions.delete(id);
33724
+ swarmState.activeAgent.delete(id);
33725
+ swarmState.delegationChains.delete(id);
33643
33726
  }
33644
33727
  const sessionState = {
33645
33728
  agentName,
@@ -33956,15 +34039,12 @@ async function handleBenchmarkCommand(directory, args) {
33956
34039
  }
33957
34040
 
33958
34041
  // src/commands/config.ts
33959
- import * as os2 from "os";
33960
- import * as path8 from "path";
33961
- function getUserConfigDir2() {
33962
- return process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config");
33963
- }
34042
+ import * as path9 from "path";
34043
+ init_utils();
33964
34044
  async function handleConfigCommand(directory, _args) {
33965
34045
  const config2 = loadPluginConfig(directory);
33966
- const userConfigPath = path8.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
33967
- const projectConfigPath = path8.join(directory, ".opencode", "opencode-swarm.json");
34046
+ const userConfigPath = path9.join(getUserConfigDir(), "opencode", "opencode-swarm.json");
34047
+ const projectConfigPath = path9.join(directory, ".opencode", "opencode-swarm.json");
33968
34048
  const lines = [
33969
34049
  "## Swarm Configuration",
33970
34050
  "",
@@ -34389,6 +34469,56 @@ async function handleEvidenceSummaryCommand(directory) {
34389
34469
  return lines.join(`
34390
34470
  `);
34391
34471
  }
34472
+ // package.json
34473
+ var package_default = {
34474
+ name: "opencode-swarm",
34475
+ version: "6.8.1",
34476
+ description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
34477
+ main: "dist/index.js",
34478
+ types: "dist/index.d.ts",
34479
+ bin: {
34480
+ "opencode-swarm": "./dist/cli/index.js"
34481
+ },
34482
+ type: "module",
34483
+ license: "MIT",
34484
+ keywords: [
34485
+ "opencode",
34486
+ "opencode-plugin",
34487
+ "ai",
34488
+ "agents",
34489
+ "orchestration",
34490
+ "swarm",
34491
+ "multi-agent",
34492
+ "llm"
34493
+ ],
34494
+ files: [
34495
+ "dist",
34496
+ "README.md",
34497
+ "LICENSE"
34498
+ ],
34499
+ scripts: {
34500
+ clean: "rm -rf dist",
34501
+ build: "rm -rf dist && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && tsc --emitDeclarationOnly",
34502
+ typecheck: "tsc --noEmit",
34503
+ test: "bun test",
34504
+ lint: "biome lint .",
34505
+ format: "biome format . --write",
34506
+ check: "biome check --write .",
34507
+ dev: "bun run build && opencode",
34508
+ prepublishOnly: "bun run build"
34509
+ },
34510
+ dependencies: {
34511
+ "@opencode-ai/plugin": "^1.1.53",
34512
+ "@opencode-ai/sdk": "^1.1.53",
34513
+ zod: "^4.1.8"
34514
+ },
34515
+ devDependencies: {
34516
+ "@biomejs/biome": "2.3.14",
34517
+ "bun-types": "latest",
34518
+ typescript: "^5.7.3"
34519
+ }
34520
+ };
34521
+
34392
34522
  // src/services/export-service.ts
34393
34523
  init_utils2();
34394
34524
  init_manager2();
@@ -34397,7 +34527,7 @@ async function getExportData(directory) {
34397
34527
  const planContent = await readSwarmFileAsync(directory, "plan.md");
34398
34528
  const contextContent = await readSwarmFileAsync(directory, "context.md");
34399
34529
  return {
34400
- version: "4.5.0",
34530
+ version: package_default.version,
34401
34531
  exported: new Date().toISOString(),
34402
34532
  plan: planStructured || planContent,
34403
34533
  context: contextContent
@@ -34693,7 +34823,7 @@ init_preflight_service();
34693
34823
 
34694
34824
  // src/commands/reset.ts
34695
34825
  init_utils2();
34696
- import * as fs8 from "fs";
34826
+ import * as fs9 from "fs";
34697
34827
  async function handleResetCommand(directory, args) {
34698
34828
  const hasConfirm = args.includes("--confirm");
34699
34829
  if (!hasConfirm) {
@@ -34713,8 +34843,8 @@ async function handleResetCommand(directory, args) {
34713
34843
  for (const filename of filesToReset) {
34714
34844
  try {
34715
34845
  const resolvedPath = validateSwarmPath(directory, filename);
34716
- if (fs8.existsSync(resolvedPath)) {
34717
- fs8.unlinkSync(resolvedPath);
34846
+ if (fs9.existsSync(resolvedPath)) {
34847
+ fs9.unlinkSync(resolvedPath);
34718
34848
  results.push(`- \u2705 Deleted ${filename}`);
34719
34849
  } else {
34720
34850
  results.push(`- \u23ED\uFE0F ${filename} not found (skipped)`);
@@ -34725,8 +34855,8 @@ async function handleResetCommand(directory, args) {
34725
34855
  }
34726
34856
  try {
34727
34857
  const summariesPath = validateSwarmPath(directory, "summaries");
34728
- if (fs8.existsSync(summariesPath)) {
34729
- fs8.rmSync(summariesPath, { recursive: true, force: true });
34858
+ if (fs9.existsSync(summariesPath)) {
34859
+ fs9.rmSync(summariesPath, { recursive: true, force: true });
34730
34860
  results.push("- \u2705 Deleted summaries/ directory");
34731
34861
  } else {
34732
34862
  results.push("- \u23ED\uFE0F summaries/ not found (skipped)");
@@ -34747,8 +34877,9 @@ async function handleResetCommand(directory, args) {
34747
34877
  // src/summaries/manager.ts
34748
34878
  init_utils2();
34749
34879
  init_utils();
34750
- import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync6 } from "fs";
34751
- import * as path13 from "path";
34880
+ import { randomBytes } from "crypto";
34881
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync4, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync5 } from "fs";
34882
+ import * as path16 from "path";
34752
34883
  var SUMMARY_ID_REGEX = /^S\d+$/;
34753
34884
  function sanitizeSummaryId(id) {
34754
34885
  if (!id || id.length === 0) {
@@ -34776,9 +34907,9 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
34776
34907
  if (outputBytes > maxStoredBytes) {
34777
34908
  throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
34778
34909
  }
34779
- const relativePath = path13.join("summaries", `${sanitizedId}.json`);
34910
+ const relativePath = path16.join("summaries", `${sanitizedId}.json`);
34780
34911
  const summaryPath = validateSwarmPath(directory, relativePath);
34781
- const summaryDir = path13.dirname(summaryPath);
34912
+ const summaryDir = path16.dirname(summaryPath);
34782
34913
  const entry = {
34783
34914
  id: sanitizedId,
34784
34915
  summaryText,
@@ -34788,7 +34919,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
34788
34919
  };
34789
34920
  const entryJson = JSON.stringify(entry);
34790
34921
  mkdirSync5(summaryDir, { recursive: true });
34791
- const tempPath = path13.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${process.pid}`);
34922
+ const tempPath = path16.join(summaryDir, `${sanitizedId}.json.tmp.${Date.now()}.${randomBytes(16).toString("hex")}`);
34792
34923
  try {
34793
34924
  await Bun.write(tempPath, entryJson);
34794
34925
  renameSync2(tempPath, summaryPath);
@@ -34801,7 +34932,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
34801
34932
  }
34802
34933
  async function loadFullOutput(directory, id) {
34803
34934
  const sanitizedId = sanitizeSummaryId(id);
34804
- const relativePath = path13.join("summaries", `${sanitizedId}.json`);
34935
+ const relativePath = path16.join("summaries", `${sanitizedId}.json`);
34805
34936
  validateSwarmPath(directory, relativePath);
34806
34937
  const content = await readSwarmFileAsync(directory, relativePath);
34807
34938
  if (content === null) {
@@ -35275,6 +35406,7 @@ function createAgentActivityHooks(config3, directory) {
35275
35406
  else
35276
35407
  existing.failureCount++;
35277
35408
  existing.totalDuration += duration5;
35409
+ enforceToolAggregateCapacity(key);
35278
35410
  swarmState.toolAggregates.set(key, existing);
35279
35411
  swarmState.pendingEvents++;
35280
35412
  if (swarmState.pendingEvents >= 20) {
@@ -35305,8 +35437,8 @@ async function doFlush(directory) {
35305
35437
  const activitySection = renderActivitySection();
35306
35438
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
35307
35439
  const flushedCount = swarmState.pendingEvents;
35308
- const path14 = `${directory}/.swarm/context.md`;
35309
- await Bun.write(path14, updated);
35440
+ const path17 = `${directory}/.swarm/context.md`;
35441
+ await Bun.write(path17, updated);
35310
35442
  swarmState.pendingEvents = Math.max(0, swarmState.pendingEvents - flushedCount);
35311
35443
  } catch (error93) {
35312
35444
  warn("Agent activity flush failed:", error93);
@@ -35578,6 +35710,11 @@ function createDelegationTrackerHook(config3, guardrailsEnabled = true) {
35578
35710
  to: agentName,
35579
35711
  timestamp: now
35580
35712
  };
35713
+ if (!swarmState.delegationChains.has(input.sessionID) && swarmState.delegationChains.size >= MAX_DELEGATION_CHAIN_SESSIONS) {
35714
+ const firstKey = swarmState.delegationChains.keys().next().value;
35715
+ if (firstKey !== undefined)
35716
+ swarmState.delegationChains.delete(firstKey);
35717
+ }
35581
35718
  if (!swarmState.delegationChains.has(input.sessionID)) {
35582
35719
  swarmState.delegationChains.set(input.sessionID, []);
35583
35720
  }
@@ -35861,15 +35998,15 @@ ${originalText}`;
35861
35998
  };
35862
35999
  }
35863
36000
  // src/hooks/system-enhancer.ts
35864
- import * as fs10 from "fs";
35865
- import * as path15 from "path";
36001
+ import * as fs11 from "fs";
36002
+ import * as path18 from "path";
35866
36003
  init_manager2();
35867
36004
 
35868
36005
  // src/services/decision-drift-analyzer.ts
35869
36006
  init_utils2();
35870
36007
  init_manager2();
35871
- import * as fs9 from "fs";
35872
- import * as path14 from "path";
36008
+ import * as fs10 from "fs";
36009
+ import * as path17 from "path";
35873
36010
  var DEFAULT_DRIFT_CONFIG = {
35874
36011
  staleThresholdPhases: 1,
35875
36012
  detectContradictions: true,
@@ -36023,11 +36160,11 @@ async function analyzeDecisionDrift(directory, config3 = {}) {
36023
36160
  currentPhase = legacyPhase;
36024
36161
  }
36025
36162
  }
36026
- const contextPath = path14.join(directory, ".swarm", "context.md");
36163
+ const contextPath = path17.join(directory, ".swarm", "context.md");
36027
36164
  let contextContent = "";
36028
36165
  try {
36029
- if (fs9.existsSync(contextPath)) {
36030
- contextContent = fs9.readFileSync(contextPath, "utf-8");
36166
+ if (fs10.existsSync(contextPath)) {
36167
+ contextContent = fs10.readFileSync(contextPath, "utf-8");
36031
36168
  }
36032
36169
  } catch {
36033
36170
  return {
@@ -36206,6 +36343,265 @@ function estimateContentType(text) {
36206
36343
  }
36207
36344
  return "prose";
36208
36345
  }
36346
+ async function loadPhaseTask(directory) {
36347
+ const plan = await loadPlan(directory);
36348
+ if (plan && plan.migration_status !== "migration_failed") {
36349
+ const phaseFromPlan = extractCurrentPhaseFromPlan(plan);
36350
+ const taskFromPlan = extractCurrentTaskFromPlan(plan);
36351
+ if (phaseFromPlan === null && taskFromPlan === null) {
36352
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
36353
+ if (planContent) {
36354
+ return {
36355
+ currentPhase: extractCurrentPhase(planContent),
36356
+ currentTask: extractCurrentTask(planContent)
36357
+ };
36358
+ }
36359
+ }
36360
+ return {
36361
+ currentPhase: phaseFromPlan,
36362
+ currentTask: taskFromPlan
36363
+ };
36364
+ } else {
36365
+ const planContent = await readSwarmFileAsync(directory, "plan.md");
36366
+ if (planContent) {
36367
+ return {
36368
+ currentPhase: extractCurrentPhase(planContent),
36369
+ currentTask: extractCurrentTask(planContent)
36370
+ };
36371
+ }
36372
+ }
36373
+ return { currentPhase: null, currentTask: null };
36374
+ }
36375
+ async function loadContextDetails(directory, sessionId, config3) {
36376
+ const contextContent = await readSwarmFileAsync(directory, "context.md");
36377
+ const decisions = contextContent ? extractDecisions(contextContent, 200) : null;
36378
+ const activeAgentForSession = sessionId ? swarmState.activeAgent.get(sessionId) : undefined;
36379
+ const isArchitect = !activeAgentForSession || stripKnownSwarmPrefix(activeAgentForSession) === "architect";
36380
+ let agentContext = null;
36381
+ if (config3.hooks?.agent_activity !== false && sessionId && contextContent && activeAgentForSession) {
36382
+ const extracted = extractAgentContext(contextContent, activeAgentForSession, config3.hooks?.agent_awareness_max_chars ?? 300);
36383
+ if (extracted) {
36384
+ agentContext = `[SWARM AGENT CONTEXT] ${extracted}`;
36385
+ }
36386
+ }
36387
+ return { contextContent, decisions, agentContext, isArchitect };
36388
+ }
36389
+ function getConfigHints(config3) {
36390
+ const hints = [];
36391
+ if (config3.review_passes?.always_security_review) {
36392
+ hints.push("[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.");
36393
+ }
36394
+ if (config3.integration_analysis?.enabled === false) {
36395
+ hints.push("[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.");
36396
+ }
36397
+ if (config3.ui_review?.enabled) {
36398
+ hints.push("[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).");
36399
+ }
36400
+ if (config3.docs?.enabled === false) {
36401
+ hints.push("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
36402
+ }
36403
+ if (config3.lint?.enabled === false) {
36404
+ hints.push("[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.");
36405
+ }
36406
+ if (config3.secretscan?.enabled === false) {
36407
+ hints.push("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
36408
+ }
36409
+ return hints;
36410
+ }
36411
+ async function getRetrospectiveHint(directory, isArchitect) {
36412
+ if (!isArchitect)
36413
+ return null;
36414
+ try {
36415
+ const evidenceDir = path18.join(directory, ".swarm", "evidence");
36416
+ if (!fs11.existsSync(evidenceDir))
36417
+ return null;
36418
+ const files = fs11.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
36419
+ for (const file3 of files.slice(0, 5)) {
36420
+ let content;
36421
+ try {
36422
+ content = JSON.parse(fs11.readFileSync(path18.join(evidenceDir, file3), "utf-8"));
36423
+ } catch {
36424
+ continue;
36425
+ }
36426
+ if (content !== null && typeof content === "object" && content.type === "retrospective") {
36427
+ const retro = content;
36428
+ const hints = [];
36429
+ if (retro.reviewer_rejections > 2) {
36430
+ hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
36431
+ }
36432
+ if (retro.top_rejection_reasons.length > 0) {
36433
+ hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
36434
+ }
36435
+ if (retro.lessons_learned.length > 0) {
36436
+ hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
36437
+ }
36438
+ if (hints.length > 0) {
36439
+ const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
36440
+ const retroText = retroHint.length <= 800 ? retroHint : retroHint.substring(0, 800) + "...";
36441
+ return retroText;
36442
+ }
36443
+ break;
36444
+ }
36445
+ }
36446
+ } catch {}
36447
+ return null;
36448
+ }
36449
+ function getCompactionHint(sessionId, config3, isArchitect) {
36450
+ if (!isArchitect)
36451
+ return null;
36452
+ const compactionConfig = config3.compaction_advisory;
36453
+ if (compactionConfig?.enabled === false)
36454
+ return null;
36455
+ if (!sessionId)
36456
+ return null;
36457
+ const session = swarmState.agentSessions.get(sessionId);
36458
+ if (!session)
36459
+ return null;
36460
+ const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
36461
+ const thresholds = compactionConfig?.thresholds ?? [50, 75, 100, 125, 150];
36462
+ const lastHint = session.lastCompactionHint || 0;
36463
+ for (const threshold of thresholds) {
36464
+ if (totalToolCalls >= threshold && lastHint < threshold) {
36465
+ const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
36466
+ const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
36467
+ session.lastCompactionHint = threshold;
36468
+ return message;
36469
+ }
36470
+ }
36471
+ return null;
36472
+ }
36473
+ async function getDecisionDriftHint(sessionId, config3, directory, isArchitect) {
36474
+ if (!isArchitect)
36475
+ return null;
36476
+ const automationCapabilities = config3.automation?.capabilities;
36477
+ if (automationCapabilities?.decision_drift_detection !== true)
36478
+ return null;
36479
+ if (!sessionId)
36480
+ return null;
36481
+ try {
36482
+ const driftResult = await analyzeDecisionDrift(directory);
36483
+ if (driftResult.hasDrift) {
36484
+ const driftText = formatDriftForContext(driftResult);
36485
+ if (driftText)
36486
+ return driftText;
36487
+ }
36488
+ } catch {}
36489
+ return null;
36490
+ }
36491
+ async function loadSwarmArtifacts(directory, sessionId, config3) {
36492
+ const [phaseTask, contextDetails] = await Promise.all([
36493
+ loadPhaseTask(directory),
36494
+ loadContextDetails(directory, sessionId, config3)
36495
+ ]);
36496
+ return {
36497
+ phaseTask,
36498
+ contextDetails,
36499
+ scoringEnabled: config3.context_budget?.scoring?.enabled === true,
36500
+ maxInjectionTokens: config3.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY
36501
+ };
36502
+ }
36503
+ async function buildInjectionCandidates(artifacts, config3, sessionId, directory) {
36504
+ const { phaseTask, contextDetails } = artifacts;
36505
+ const candidates = [];
36506
+ let idCounter = 0;
36507
+ const addCandidate = (kind, text, priority, contentType = "prose", metadata) => {
36508
+ candidates.push({
36509
+ id: `candidate-${idCounter++}`,
36510
+ kind,
36511
+ text,
36512
+ tokens: estimateTokens(text),
36513
+ priority,
36514
+ metadata: { contentType, ...metadata }
36515
+ });
36516
+ };
36517
+ if (phaseTask.currentPhase) {
36518
+ addCandidate("phase", `[SWARM CONTEXT] Current phase: ${phaseTask.currentPhase}`, 1, estimateContentType(phaseTask.currentPhase));
36519
+ }
36520
+ if (phaseTask.currentTask) {
36521
+ addCandidate("task", `[SWARM CONTEXT] Current task: ${phaseTask.currentTask}`, 2, estimateContentType(phaseTask.currentTask), { isCurrentTask: true });
36522
+ }
36523
+ if (contextDetails.decisions) {
36524
+ addCandidate("decision", `[SWARM CONTEXT] Key decisions: ${contextDetails.decisions}`, 3, estimateContentType(contextDetails.decisions));
36525
+ }
36526
+ if (contextDetails.agentContext) {
36527
+ addCandidate("agent_context", contextDetails.agentContext, 4, estimateContentType(contextDetails.agentContext));
36528
+ }
36529
+ for (const hint of getConfigHints(config3)) {
36530
+ addCandidate("phase", hint, 1, "prose");
36531
+ }
36532
+ addCandidate("phase", "[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.", 2, "prose");
36533
+ if (contextDetails.isArchitect) {
36534
+ const retroText = await getRetrospectiveHint(directory, contextDetails.isArchitect);
36535
+ if (retroText) {
36536
+ addCandidate("phase", retroText, 2, "prose");
36537
+ }
36538
+ const compactionMessage = getCompactionHint(sessionId, config3, contextDetails.isArchitect);
36539
+ if (compactionMessage) {
36540
+ addCandidate("phase", compactionMessage, 1, "prose");
36541
+ }
36542
+ const driftText = await getDecisionDriftHint(sessionId, config3, directory, contextDetails.isArchitect);
36543
+ if (driftText) {
36544
+ addCandidate("phase", driftText, 2, "prose");
36545
+ }
36546
+ }
36547
+ const userScoringConfig = config3.context_budget?.scoring;
36548
+ const effectiveConfig = userScoringConfig?.weights ? {
36549
+ ...DEFAULT_SCORING_CONFIG,
36550
+ ...userScoringConfig,
36551
+ weights: userScoringConfig.weights
36552
+ } : DEFAULT_SCORING_CONFIG;
36553
+ return { candidates, effectiveConfig };
36554
+ }
36555
+ async function applyLegacyInjection(artifacts, config3, directory, sessionId, maxInjectionTokens, output) {
36556
+ const { phaseTask, contextDetails } = artifacts;
36557
+ let injectedTokens = 0;
36558
+ const tryInject = (text) => {
36559
+ const tokens = estimateTokens(text);
36560
+ if (injectedTokens + tokens <= maxInjectionTokens) {
36561
+ output.system.push(text);
36562
+ injectedTokens += tokens;
36563
+ }
36564
+ };
36565
+ if (phaseTask.currentPhase) {
36566
+ tryInject(`[SWARM CONTEXT] Current phase: ${phaseTask.currentPhase}`);
36567
+ }
36568
+ if (phaseTask.currentTask) {
36569
+ tryInject(`[SWARM CONTEXT] Current task: ${phaseTask.currentTask}`);
36570
+ }
36571
+ if (contextDetails.decisions) {
36572
+ tryInject(`[SWARM CONTEXT] Key decisions: ${contextDetails.decisions}`);
36573
+ }
36574
+ if (contextDetails.agentContext) {
36575
+ tryInject(contextDetails.agentContext);
36576
+ }
36577
+ tryInject("[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.");
36578
+ for (const hint of getConfigHints(config3)) {
36579
+ tryInject(hint);
36580
+ }
36581
+ if (contextDetails.isArchitect) {
36582
+ const retroText = await getRetrospectiveHint(directory, true);
36583
+ if (retroText)
36584
+ tryInject(retroText);
36585
+ const compactionMessage = getCompactionHint(sessionId, config3, contextDetails.isArchitect);
36586
+ if (compactionMessage)
36587
+ tryInject(compactionMessage);
36588
+ const driftText = await getDecisionDriftHint(sessionId, config3, directory, contextDetails.isArchitect);
36589
+ if (driftText)
36590
+ tryInject(driftText);
36591
+ }
36592
+ }
36593
+ async function applyScoringInjection(artifacts, config3, directory, sessionId, maxInjectionTokens, output) {
36594
+ const { candidates, effectiveConfig } = await buildInjectionCandidates(artifacts, config3, sessionId, directory);
36595
+ const ranked = rankCandidates(candidates, effectiveConfig);
36596
+ let injectedTokens = 0;
36597
+ for (const candidate of ranked) {
36598
+ if (injectedTokens + candidate.tokens > maxInjectionTokens) {
36599
+ continue;
36600
+ }
36601
+ output.system.push(candidate.text);
36602
+ injectedTokens += candidate.tokens;
36603
+ }
36604
+ }
36209
36605
  function createSystemEnhancerHook(config3, directory) {
36210
36606
  const enabled = config3.hooks?.system_enhancer !== false;
36211
36607
  if (!enabled) {
@@ -36214,408 +36610,12 @@ function createSystemEnhancerHook(config3, directory) {
36214
36610
  return {
36215
36611
  "experimental.chat.system.transform": safeHook(async (_input, output) => {
36216
36612
  try {
36217
- let tryInject = function(text) {
36218
- const tokens = estimateTokens(text);
36219
- if (injectedTokens + tokens > maxInjectionTokens) {
36220
- return;
36221
- }
36222
- output.system.push(text);
36223
- injectedTokens += tokens;
36224
- };
36225
- const maxInjectionTokens = config3.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
36226
- let injectedTokens = 0;
36227
- const contextContent = await readSwarmFileAsync(directory, "context.md");
36228
- const scoringEnabled = config3.context_budget?.scoring?.enabled === true;
36229
- if (!scoringEnabled) {
36230
- const plan2 = await loadPlan(directory);
36231
- if (plan2 && plan2.migration_status !== "migration_failed") {
36232
- const currentPhase2 = extractCurrentPhaseFromPlan(plan2);
36233
- if (currentPhase2) {
36234
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
36235
- }
36236
- const currentTask2 = extractCurrentTaskFromPlan(plan2);
36237
- if (currentTask2) {
36238
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
36239
- }
36240
- } else {
36241
- const planContent = await readSwarmFileAsync(directory, "plan.md");
36242
- if (planContent) {
36243
- const currentPhase2 = extractCurrentPhase(planContent);
36244
- if (currentPhase2) {
36245
- tryInject(`[SWARM CONTEXT] Current phase: ${currentPhase2}`);
36246
- }
36247
- const currentTask2 = extractCurrentTask(planContent);
36248
- if (currentTask2) {
36249
- tryInject(`[SWARM CONTEXT] Current task: ${currentTask2}`);
36250
- }
36251
- }
36252
- }
36253
- if (contextContent) {
36254
- const decisions = extractDecisions(contextContent, 200);
36255
- if (decisions) {
36256
- tryInject(`[SWARM CONTEXT] Key decisions: ${decisions}`);
36257
- }
36258
- if (config3.hooks?.agent_activity !== false && _input.sessionID) {
36259
- const activeAgent = swarmState.activeAgent.get(_input.sessionID);
36260
- if (activeAgent) {
36261
- const agentContext = extractAgentContext(contextContent, activeAgent, config3.hooks?.agent_awareness_max_chars ?? 300);
36262
- if (agentContext) {
36263
- tryInject(`[SWARM AGENT CONTEXT] ${agentContext}`);
36264
- }
36265
- }
36266
- }
36267
- }
36268
- tryInject("[SWARM HINT] Large tool outputs may be auto-summarized. Use /swarm retrieve <id> to get the full content if needed.");
36269
- if (config3.review_passes?.always_security_review) {
36270
- tryInject("[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.");
36271
- }
36272
- if (config3.integration_analysis?.enabled === false) {
36273
- tryInject("[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.");
36274
- }
36275
- if (config3.ui_review?.enabled) {
36276
- tryInject("[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).");
36277
- }
36278
- if (config3.docs?.enabled === false) {
36279
- tryInject("[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.");
36280
- }
36281
- if (config3.lint?.enabled === false) {
36282
- tryInject("[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.");
36283
- }
36284
- if (config3.secretscan?.enabled === false) {
36285
- tryInject("[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.");
36286
- }
36287
- const sessionId_retro = _input.sessionID;
36288
- const activeAgent_retro = swarmState.activeAgent.get(sessionId_retro ?? "");
36289
- const isArchitect = !activeAgent_retro || stripKnownSwarmPrefix(activeAgent_retro) === "architect";
36290
- if (isArchitect) {
36291
- try {
36292
- const evidenceDir = path15.join(directory, ".swarm", "evidence");
36293
- if (fs10.existsSync(evidenceDir)) {
36294
- const files = fs10.readdirSync(evidenceDir).filter((f) => f.endsWith(".json")).sort().reverse();
36295
- for (const file3 of files.slice(0, 5)) {
36296
- let content;
36297
- try {
36298
- content = JSON.parse(fs10.readFileSync(path15.join(evidenceDir, file3), "utf-8"));
36299
- } catch {
36300
- continue;
36301
- }
36302
- if (content !== null && typeof content === "object" && content.type === "retrospective") {
36303
- const retro = content;
36304
- const hints = [];
36305
- if (retro.reviewer_rejections > 2) {
36306
- hints.push(`Phase ${retro.phase_number} had ${retro.reviewer_rejections} reviewer rejections.`);
36307
- }
36308
- if (retro.top_rejection_reasons.length > 0) {
36309
- hints.push(`Common rejection reasons: ${retro.top_rejection_reasons.join(", ")}.`);
36310
- }
36311
- if (retro.lessons_learned.length > 0) {
36312
- hints.push(`Lessons: ${retro.lessons_learned.join("; ")}.`);
36313
- }
36314
- if (hints.length > 0) {
36315
- const retroHint = `[SWARM RETROSPECTIVE] From Phase ${retro.phase_number}: ${hints.join(" ")}`;
36316
- if (retroHint.length <= 800) {
36317
- tryInject(retroHint);
36318
- } else {
36319
- tryInject(retroHint.substring(0, 800) + "...");
36320
- }
36321
- }
36322
- break;
36323
- }
36324
- }
36325
- }
36326
- } catch {}
36327
- const compactionConfig = config3.compaction_advisory;
36328
- if (compactionConfig?.enabled !== false && sessionId_retro) {
36329
- const session = swarmState.agentSessions.get(sessionId_retro);
36330
- if (session) {
36331
- const totalToolCalls = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
36332
- const thresholds = compactionConfig?.thresholds ?? [
36333
- 50,
36334
- 75,
36335
- 100,
36336
- 125,
36337
- 150
36338
- ];
36339
- const lastHint = session.lastCompactionHint || 0;
36340
- for (const threshold of thresholds) {
36341
- if (totalToolCalls >= threshold && lastHint < threshold) {
36342
- const messageTemplate = compactionConfig?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
36343
- const message = messageTemplate.replace("${totalToolCalls}", String(totalToolCalls));
36344
- tryInject(message);
36345
- session.lastCompactionHint = threshold;
36346
- break;
36347
- }
36348
- }
36349
- }
36350
- }
36351
- }
36352
- const automationCapabilities = config3.automation?.capabilities;
36353
- if (automationCapabilities?.decision_drift_detection === true && _input.sessionID) {
36354
- const activeAgentForDrift = swarmState.activeAgent.get(_input.sessionID);
36355
- const isArchitectForDrift = !activeAgentForDrift || stripKnownSwarmPrefix(activeAgentForDrift) === "architect";
36356
- if (isArchitectForDrift) {
36357
- try {
36358
- const driftResult = await analyzeDecisionDrift(directory);
36359
- if (driftResult.hasDrift) {
36360
- const driftText = formatDriftForContext(driftResult);
36361
- if (driftText) {
36362
- tryInject(driftText);
36363
- }
36364
- }
36365
- } catch {}
36366
- }
36367
- }
36368
- return;
36369
- }
36370
- const userScoringConfig = config3.context_budget?.scoring;
36371
- const candidates = [];
36372
- let idCounter = 0;
36373
- const effectiveConfig = userScoringConfig?.weights ? {
36374
- ...DEFAULT_SCORING_CONFIG,
36375
- ...userScoringConfig,
36376
- weights: userScoringConfig.weights
36377
- } : DEFAULT_SCORING_CONFIG;
36378
- const plan = await loadPlan(directory);
36379
- let currentPhase = null;
36380
- let currentTask = null;
36381
- if (plan && plan.migration_status !== "migration_failed") {
36382
- currentPhase = extractCurrentPhaseFromPlan(plan);
36383
- currentTask = extractCurrentTaskFromPlan(plan);
36613
+ const artifacts = await loadSwarmArtifacts(directory, _input.sessionID, config3);
36614
+ const { scoringEnabled, maxInjectionTokens } = artifacts;
36615
+ if (scoringEnabled) {
36616
+ await applyScoringInjection(artifacts, config3, directory, _input.sessionID, maxInjectionTokens, output);
36384
36617
  } else {
36385
- const planContent = await readSwarmFileAsync(directory, "plan.md");
36386
- if (planContent) {
36387
- currentPhase = extractCurrentPhase(planContent);
36388
- currentTask = extractCurrentTask(planContent);
36389
- }
36390
- }
36391
- if (currentPhase) {
36392
- const text = `[SWARM CONTEXT] Current phase: ${currentPhase}`;
36393
- candidates.push({
36394
- id: `candidate-${idCounter++}`,
36395
- kind: "phase",
36396
- text,
36397
- tokens: estimateTokens(text),
36398
- priority: 1,
36399
- metadata: { contentType: estimateContentType(text) }
36400
- });
36401
- }
36402
- if (currentTask) {
36403
- const text = `[SWARM CONTEXT] Current task: ${currentTask}`;
36404
- candidates.push({
36405
- id: `candidate-${idCounter++}`,
36406
- kind: "task",
36407
- text,
36408
- tokens: estimateTokens(text),
36409
- priority: 2,
36410
- metadata: {
36411
- contentType: estimateContentType(text),
36412
- isCurrentTask: true
36413
- }
36414
- });
36415
- }
36416
- if (contextContent) {
36417
- const decisions = extractDecisions(contextContent, 200);
36418
- if (decisions) {
36419
- const text = `[SWARM CONTEXT] Key decisions: ${decisions}`;
36420
- candidates.push({
36421
- id: `candidate-${idCounter++}`,
36422
- kind: "decision",
36423
- text,
36424
- tokens: estimateTokens(text),
36425
- priority: 3,
36426
- metadata: { contentType: estimateContentType(text) }
36427
- });
36428
- }
36429
- if (config3.hooks?.agent_activity !== false && _input.sessionID) {
36430
- const activeAgent = swarmState.activeAgent.get(_input.sessionID);
36431
- if (activeAgent) {
36432
- const agentContext = extractAgentContext(contextContent, activeAgent, config3.hooks?.agent_awareness_max_chars ?? 300);
36433
- if (agentContext) {
36434
- const text = `[SWARM AGENT CONTEXT] ${agentContext}`;
36435
- candidates.push({
36436
- id: `candidate-${idCounter++}`,
36437
- kind: "agent_context",
36438
- text,
36439
- tokens: estimateTokens(text),
36440
- priority: 4,
36441
- metadata: { contentType: estimateContentType(text) }
36442
- });
36443
- }
36444
- }
36445
- }
36446
- }
36447
- if (config3.review_passes?.always_security_review) {
36448
- const text = "[SWARM CONFIG] Security review pass is MANDATORY for ALL tasks. Skip file-pattern check \u2014 always run security-only reviewer pass after general review APPROVED.";
36449
- candidates.push({
36450
- id: `candidate-${idCounter++}`,
36451
- kind: "phase",
36452
- text,
36453
- tokens: estimateTokens(text),
36454
- priority: 1,
36455
- metadata: { contentType: "prose" }
36456
- });
36457
- }
36458
- if (config3.integration_analysis?.enabled === false) {
36459
- const text = "[SWARM CONFIG] Integration analysis is DISABLED. Skip diff tool and integration impact analysis after coder tasks.";
36460
- candidates.push({
36461
- id: `candidate-${idCounter++}`,
36462
- kind: "phase",
36463
- text,
36464
- tokens: estimateTokens(text),
36465
- priority: 1,
36466
- metadata: { contentType: "prose" }
36467
- });
36468
- }
36469
- if (config3.ui_review?.enabled) {
36470
- const text = "[SWARM CONFIG] UI/UX Designer agent is ENABLED. For tasks matching UI trigger keywords or file paths, delegate to designer BEFORE coder (Rule 9).";
36471
- candidates.push({
36472
- id: `candidate-${idCounter++}`,
36473
- kind: "phase",
36474
- text,
36475
- tokens: estimateTokens(text),
36476
- priority: 1,
36477
- metadata: { contentType: "prose" }
36478
- });
36479
- }
36480
- if (config3.docs?.enabled === false) {
36481
- const text = "[SWARM CONFIG] Docs agent is DISABLED. Skip docs delegation in Phase 6.";
36482
- candidates.push({
36483
- id: `candidate-${idCounter++}`,
36484
- kind: "phase",
36485
- text,
36486
- tokens: estimateTokens(text),
36487
- priority: 1,
36488
- metadata: { contentType: "prose" }
36489
- });
36490
- }
36491
- if (config3.lint?.enabled === false) {
36492
- const text = "[SWARM CONFIG] Lint gate is DISABLED. Skip lint check/fix in QA sequence.";
36493
- candidates.push({
36494
- id: `candidate-${idCounter++}`,
36495
- kind: "phase",
36496
- text,
36497
- tokens: estimateTokens(text),
36498
- priority: 1,
36499
- metadata: { contentType: "prose" }
36500
- });
36501
- }
36502
- if (config3.secretscan?.enabled === false) {
36503
- const text = "[SWARM CONFIG] Secretscan gate is DISABLED. Skip secretscan in QA sequence.";
36504
- candidates.push({
36505
- id: `candidate-${idCounter++}`,
36506
- kind: "phase",
36507
- text,
36508
- tokens: estimateTokens(text),
36509
- priority: 1,
36510
- metadata: { contentType: "prose" }
36511
- });
36512
- }
36513
- const sessionId_retro_b = _input.sessionID;
36514
- const activeAgent_retro_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
36515
- const isArchitect_b = !activeAgent_retro_b || stripKnownSwarmPrefix(activeAgent_retro_b) === "architect";
36516
- if (isArchitect_b) {
36517
- try {
36518
- const evidenceDir_b = path15.join(directory, ".swarm", "evidence");
36519
- if (fs10.existsSync(evidenceDir_b)) {
36520
- const files_b = fs10.readdirSync(evidenceDir_b).filter((f) => f.endsWith(".json")).sort().reverse();
36521
- for (const file3 of files_b.slice(0, 5)) {
36522
- let content_b;
36523
- try {
36524
- content_b = JSON.parse(fs10.readFileSync(path15.join(evidenceDir_b, file3), "utf-8"));
36525
- } catch {
36526
- continue;
36527
- }
36528
- if (content_b !== null && typeof content_b === "object" && content_b.type === "retrospective") {
36529
- const retro_b = content_b;
36530
- const hints_b = [];
36531
- if (retro_b.reviewer_rejections > 2) {
36532
- hints_b.push(`Phase ${retro_b.phase_number} had ${retro_b.reviewer_rejections} reviewer rejections.`);
36533
- }
36534
- if (retro_b.top_rejection_reasons.length > 0) {
36535
- hints_b.push(`Common rejection reasons: ${retro_b.top_rejection_reasons.join(", ")}.`);
36536
- }
36537
- if (retro_b.lessons_learned.length > 0) {
36538
- hints_b.push(`Lessons: ${retro_b.lessons_learned.join("; ")}.`);
36539
- }
36540
- if (hints_b.length > 0) {
36541
- const retroHint_b = `[SWARM RETROSPECTIVE] From Phase ${retro_b.phase_number}: ${hints_b.join(" ")}`;
36542
- const retroText = retroHint_b.length <= 800 ? retroHint_b : retroHint_b.substring(0, 800) + "...";
36543
- candidates.push({
36544
- id: `candidate-${idCounter++}`,
36545
- kind: "phase",
36546
- text: retroText,
36547
- tokens: estimateTokens(retroText),
36548
- priority: 2,
36549
- metadata: { contentType: "prose" }
36550
- });
36551
- }
36552
- break;
36553
- }
36554
- }
36555
- }
36556
- } catch {}
36557
- const compactionConfig_b = config3.compaction_advisory;
36558
- if (compactionConfig_b?.enabled !== false && sessionId_retro_b) {
36559
- const session_b = swarmState.agentSessions.get(sessionId_retro_b);
36560
- if (session_b) {
36561
- const totalToolCalls_b = Array.from(swarmState.toolAggregates.values()).reduce((sum, agg) => sum + agg.count, 0);
36562
- const thresholds_b = compactionConfig_b?.thresholds ?? [
36563
- 50,
36564
- 75,
36565
- 100,
36566
- 125,
36567
- 150
36568
- ];
36569
- const lastHint_b = session_b.lastCompactionHint || 0;
36570
- for (const threshold of thresholds_b) {
36571
- if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
36572
- const messageTemplate_b = compactionConfig_b?.message ?? "[SWARM HINT] Session has ${totalToolCalls} tool calls. Consider compacting at next phase boundary to maintain context quality.";
36573
- const compactionText = messageTemplate_b.replace("${totalToolCalls}", String(totalToolCalls_b));
36574
- candidates.push({
36575
- id: `candidate-${idCounter++}`,
36576
- kind: "phase",
36577
- text: compactionText,
36578
- tokens: estimateTokens(compactionText),
36579
- priority: 1,
36580
- metadata: { contentType: "prose" }
36581
- });
36582
- session_b.lastCompactionHint = threshold;
36583
- break;
36584
- }
36585
- }
36586
- }
36587
- }
36588
- }
36589
- const automationCapabilities_b = config3.automation?.capabilities;
36590
- if (automationCapabilities_b?.decision_drift_detection === true && sessionId_retro_b) {
36591
- const activeAgentForDrift_b = swarmState.activeAgent.get(sessionId_retro_b ?? "");
36592
- const isArchitectForDrift_b = !activeAgentForDrift_b || stripKnownSwarmPrefix(activeAgentForDrift_b) === "architect";
36593
- if (isArchitectForDrift_b) {
36594
- try {
36595
- const driftResult_b = await analyzeDecisionDrift(directory);
36596
- if (driftResult_b.hasDrift) {
36597
- const driftText_b = formatDriftForContext(driftResult_b);
36598
- if (driftText_b) {
36599
- candidates.push({
36600
- id: `candidate-${idCounter++}`,
36601
- kind: "phase",
36602
- text: driftText_b,
36603
- tokens: estimateTokens(driftText_b),
36604
- priority: 2,
36605
- metadata: { contentType: "prose" }
36606
- });
36607
- }
36608
- }
36609
- } catch {}
36610
- }
36611
- }
36612
- const ranked = rankCandidates(candidates, effectiveConfig);
36613
- for (const candidate of ranked) {
36614
- if (injectedTokens + candidate.tokens > maxInjectionTokens) {
36615
- continue;
36616
- }
36617
- output.system.push(candidate.text);
36618
- injectedTokens += candidate.tokens;
36618
+ await applyLegacyInjection(artifacts, config3, directory, _input.sessionID, maxInjectionTokens, output);
36619
36619
  }
36620
36620
  } catch (error93) {
36621
36621
  warn("System enhancer failed:", error93);
@@ -36814,8 +36814,8 @@ init_config_doctor();
36814
36814
  // src/tools/checkpoint.ts
36815
36815
  init_tool();
36816
36816
  import { spawnSync } from "child_process";
36817
- import * as fs11 from "fs";
36818
- import * as path16 from "path";
36817
+ import * as fs12 from "fs";
36818
+ import * as path19 from "path";
36819
36819
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json";
36820
36820
  var MAX_LABEL_LENGTH = 100;
36821
36821
  var GIT_TIMEOUT_MS = 30000;
@@ -36866,13 +36866,13 @@ function validateLabel(label) {
36866
36866
  return null;
36867
36867
  }
36868
36868
  function getCheckpointLogPath() {
36869
- return path16.join(process.cwd(), CHECKPOINT_LOG_PATH);
36869
+ return path19.join(process.cwd(), CHECKPOINT_LOG_PATH);
36870
36870
  }
36871
36871
  function readCheckpointLog() {
36872
36872
  const logPath = getCheckpointLogPath();
36873
36873
  try {
36874
- if (fs11.existsSync(logPath)) {
36875
- const content = fs11.readFileSync(logPath, "utf-8");
36874
+ if (fs12.existsSync(logPath)) {
36875
+ const content = fs12.readFileSync(logPath, "utf-8");
36876
36876
  const parsed = JSON.parse(content);
36877
36877
  if (!parsed.checkpoints || !Array.isArray(parsed.checkpoints)) {
36878
36878
  return { version: 1, checkpoints: [] };
@@ -36884,13 +36884,13 @@ function readCheckpointLog() {
36884
36884
  }
36885
36885
  function writeCheckpointLog(log2) {
36886
36886
  const logPath = getCheckpointLogPath();
36887
- const dir = path16.dirname(logPath);
36888
- if (!fs11.existsSync(dir)) {
36889
- fs11.mkdirSync(dir, { recursive: true });
36887
+ const dir = path19.dirname(logPath);
36888
+ if (!fs12.existsSync(dir)) {
36889
+ fs12.mkdirSync(dir, { recursive: true });
36890
36890
  }
36891
36891
  const tempPath = `${logPath}.tmp`;
36892
- fs11.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
36893
- fs11.renameSync(tempPath, logPath);
36892
+ fs12.writeFileSync(tempPath, JSON.stringify(log2, null, 2), "utf-8");
36893
+ fs12.renameSync(tempPath, logPath);
36894
36894
  }
36895
36895
  function gitExec(args) {
36896
36896
  const result = spawnSync("git", args, {
@@ -37090,8 +37090,8 @@ var checkpoint = tool({
37090
37090
  });
37091
37091
  // src/tools/complexity-hotspots.ts
37092
37092
  init_dist();
37093
- import * as fs12 from "fs";
37094
- import * as path17 from "path";
37093
+ import * as fs13 from "fs";
37094
+ import * as path20 from "path";
37095
37095
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
37096
37096
  var DEFAULT_DAYS = 90;
37097
37097
  var DEFAULT_TOP_N = 20;
@@ -37219,11 +37219,11 @@ function estimateComplexity(content) {
37219
37219
  }
37220
37220
  function getComplexityForFile(filePath) {
37221
37221
  try {
37222
- const stat = fs12.statSync(filePath);
37222
+ const stat = fs13.statSync(filePath);
37223
37223
  if (stat.size > MAX_FILE_SIZE_BYTES2) {
37224
37224
  return null;
37225
37225
  }
37226
- const content = fs12.readFileSync(filePath, "utf-8");
37226
+ const content = fs13.readFileSync(filePath, "utf-8");
37227
37227
  return estimateComplexity(content);
37228
37228
  } catch {
37229
37229
  return null;
@@ -37234,7 +37234,7 @@ async function analyzeHotspots(days, topN, extensions) {
37234
37234
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
37235
37235
  const filteredChurn = new Map;
37236
37236
  for (const [file3, count] of churnMap) {
37237
- const ext = path17.extname(file3).toLowerCase();
37237
+ const ext = path20.extname(file3).toLowerCase();
37238
37238
  if (extSet.has(ext)) {
37239
37239
  filteredChurn.set(file3, count);
37240
37240
  }
@@ -37244,8 +37244,8 @@ async function analyzeHotspots(days, topN, extensions) {
37244
37244
  let analyzedFiles = 0;
37245
37245
  for (const [file3, churnCount] of filteredChurn) {
37246
37246
  let fullPath = file3;
37247
- if (!fs12.existsSync(fullPath)) {
37248
- fullPath = path17.join(cwd, file3);
37247
+ if (!fs13.existsSync(fullPath)) {
37248
+ fullPath = path20.join(cwd, file3);
37249
37249
  }
37250
37250
  const complexity = getComplexityForFile(fullPath);
37251
37251
  if (complexity !== null) {
@@ -37403,14 +37403,14 @@ function validateBase(base) {
37403
37403
  function validatePaths(paths) {
37404
37404
  if (!paths)
37405
37405
  return null;
37406
- for (const path18 of paths) {
37407
- if (!path18 || path18.length === 0) {
37406
+ for (const path21 of paths) {
37407
+ if (!path21 || path21.length === 0) {
37408
37408
  return "empty path not allowed";
37409
37409
  }
37410
- if (path18.length > MAX_PATH_LENGTH) {
37410
+ if (path21.length > MAX_PATH_LENGTH) {
37411
37411
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
37412
37412
  }
37413
- if (SHELL_METACHARACTERS2.test(path18)) {
37413
+ if (SHELL_METACHARACTERS2.test(path21)) {
37414
37414
  return "path contains shell metacharacters";
37415
37415
  }
37416
37416
  }
@@ -37473,8 +37473,8 @@ var diff = tool({
37473
37473
  if (parts.length >= 3) {
37474
37474
  const additions = parseInt(parts[0]) || 0;
37475
37475
  const deletions = parseInt(parts[1]) || 0;
37476
- const path18 = parts[2];
37477
- files.push({ path: path18, additions, deletions });
37476
+ const path21 = parts[2];
37477
+ files.push({ path: path21, additions, deletions });
37478
37478
  }
37479
37479
  }
37480
37480
  const contractChanges = [];
@@ -37702,8 +37702,8 @@ Use these as DOMAIN values when delegating to @sme.`;
37702
37702
  });
37703
37703
  // src/tools/evidence-check.ts
37704
37704
  init_dist();
37705
- import * as fs13 from "fs";
37706
- import * as path18 from "path";
37705
+ import * as fs14 from "fs";
37706
+ import * as path21 from "path";
37707
37707
  var MAX_FILE_SIZE_BYTES3 = 1024 * 1024;
37708
37708
  var MAX_EVIDENCE_FILES = 1000;
37709
37709
  var EVIDENCE_DIR = ".swarm/evidence";
@@ -37726,9 +37726,9 @@ function validateRequiredTypes(input) {
37726
37726
  return null;
37727
37727
  }
37728
37728
  function isPathWithinSwarm(filePath, cwd) {
37729
- const normalizedCwd = path18.resolve(cwd);
37730
- const swarmPath = path18.join(normalizedCwd, ".swarm");
37731
- const normalizedPath = path18.resolve(filePath);
37729
+ const normalizedCwd = path21.resolve(cwd);
37730
+ const swarmPath = path21.join(normalizedCwd, ".swarm");
37731
+ const normalizedPath = path21.resolve(filePath);
37732
37732
  return normalizedPath.startsWith(swarmPath);
37733
37733
  }
37734
37734
  function parseCompletedTasks(planContent) {
@@ -37745,12 +37745,12 @@ function parseCompletedTasks(planContent) {
37745
37745
  }
37746
37746
  function readEvidenceFiles(evidenceDir, cwd) {
37747
37747
  const evidence = [];
37748
- if (!fs13.existsSync(evidenceDir) || !fs13.statSync(evidenceDir).isDirectory()) {
37748
+ if (!fs14.existsSync(evidenceDir) || !fs14.statSync(evidenceDir).isDirectory()) {
37749
37749
  return evidence;
37750
37750
  }
37751
37751
  let files;
37752
37752
  try {
37753
- files = fs13.readdirSync(evidenceDir);
37753
+ files = fs14.readdirSync(evidenceDir);
37754
37754
  } catch {
37755
37755
  return evidence;
37756
37756
  }
@@ -37759,14 +37759,14 @@ function readEvidenceFiles(evidenceDir, cwd) {
37759
37759
  if (!VALID_EVIDENCE_FILENAME_REGEX.test(filename)) {
37760
37760
  continue;
37761
37761
  }
37762
- const filePath = path18.join(evidenceDir, filename);
37762
+ const filePath = path21.join(evidenceDir, filename);
37763
37763
  try {
37764
- const resolvedPath = path18.resolve(filePath);
37765
- const evidenceDirResolved = path18.resolve(evidenceDir);
37764
+ const resolvedPath = path21.resolve(filePath);
37765
+ const evidenceDirResolved = path21.resolve(evidenceDir);
37766
37766
  if (!resolvedPath.startsWith(evidenceDirResolved)) {
37767
37767
  continue;
37768
37768
  }
37769
- const stat = fs13.lstatSync(filePath);
37769
+ const stat = fs14.lstatSync(filePath);
37770
37770
  if (!stat.isFile()) {
37771
37771
  continue;
37772
37772
  }
@@ -37775,7 +37775,7 @@ function readEvidenceFiles(evidenceDir, cwd) {
37775
37775
  }
37776
37776
  let fileStat;
37777
37777
  try {
37778
- fileStat = fs13.statSync(filePath);
37778
+ fileStat = fs14.statSync(filePath);
37779
37779
  if (fileStat.size > MAX_FILE_SIZE_BYTES3) {
37780
37780
  continue;
37781
37781
  }
@@ -37784,7 +37784,7 @@ function readEvidenceFiles(evidenceDir, cwd) {
37784
37784
  }
37785
37785
  let content;
37786
37786
  try {
37787
- content = fs13.readFileSync(filePath, "utf-8");
37787
+ content = fs14.readFileSync(filePath, "utf-8");
37788
37788
  } catch {
37789
37789
  continue;
37790
37790
  }
@@ -37869,7 +37869,7 @@ var evidence_check = tool({
37869
37869
  return JSON.stringify(errorResult, null, 2);
37870
37870
  }
37871
37871
  const requiredTypes = requiredTypesValue.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
37872
- const planPath = path18.join(cwd, PLAN_FILE);
37872
+ const planPath = path21.join(cwd, PLAN_FILE);
37873
37873
  if (!isPathWithinSwarm(planPath, cwd)) {
37874
37874
  const errorResult = {
37875
37875
  error: "plan file path validation failed",
@@ -37883,7 +37883,7 @@ var evidence_check = tool({
37883
37883
  }
37884
37884
  let planContent;
37885
37885
  try {
37886
- planContent = fs13.readFileSync(planPath, "utf-8");
37886
+ planContent = fs14.readFileSync(planPath, "utf-8");
37887
37887
  } catch {
37888
37888
  const result2 = {
37889
37889
  message: "No completed tasks found in plan.",
@@ -37901,7 +37901,7 @@ var evidence_check = tool({
37901
37901
  };
37902
37902
  return JSON.stringify(result2, null, 2);
37903
37903
  }
37904
- const evidenceDir = path18.join(cwd, EVIDENCE_DIR);
37904
+ const evidenceDir = path21.join(cwd, EVIDENCE_DIR);
37905
37905
  const evidence = readEvidenceFiles(evidenceDir, cwd);
37906
37906
  const { tasksWithFullEvidence, gaps } = analyzeGaps(completedTasks, evidence, requiredTypes);
37907
37907
  const completeness = completedTasks.length > 0 ? Math.round(tasksWithFullEvidence.length / completedTasks.length * 100) / 100 : 1;
@@ -37917,8 +37917,8 @@ var evidence_check = tool({
37917
37917
  });
37918
37918
  // src/tools/file-extractor.ts
37919
37919
  init_tool();
37920
- import * as fs14 from "fs";
37921
- import * as path19 from "path";
37920
+ import * as fs15 from "fs";
37921
+ import * as path22 from "path";
37922
37922
  var EXT_MAP = {
37923
37923
  python: ".py",
37924
37924
  py: ".py",
@@ -37980,8 +37980,8 @@ var extract_code_blocks = tool({
37980
37980
  execute: async (args) => {
37981
37981
  const { content, output_dir, prefix } = args;
37982
37982
  const targetDir = output_dir || process.cwd();
37983
- if (!fs14.existsSync(targetDir)) {
37984
- fs14.mkdirSync(targetDir, { recursive: true });
37983
+ if (!fs15.existsSync(targetDir)) {
37984
+ fs15.mkdirSync(targetDir, { recursive: true });
37985
37985
  }
37986
37986
  const pattern = /```(\w*)\n([\s\S]*?)```/g;
37987
37987
  const matches = [...content.matchAll(pattern)];
@@ -37996,16 +37996,16 @@ var extract_code_blocks = tool({
37996
37996
  if (prefix) {
37997
37997
  filename = `${prefix}_${filename}`;
37998
37998
  }
37999
- let filepath = path19.join(targetDir, filename);
38000
- const base = path19.basename(filepath, path19.extname(filepath));
38001
- const ext = path19.extname(filepath);
37999
+ let filepath = path22.join(targetDir, filename);
38000
+ const base = path22.basename(filepath, path22.extname(filepath));
38001
+ const ext = path22.extname(filepath);
38002
38002
  let counter = 1;
38003
- while (fs14.existsSync(filepath)) {
38004
- filepath = path19.join(targetDir, `${base}_${counter}${ext}`);
38003
+ while (fs15.existsSync(filepath)) {
38004
+ filepath = path22.join(targetDir, `${base}_${counter}${ext}`);
38005
38005
  counter++;
38006
38006
  }
38007
38007
  try {
38008
- fs14.writeFileSync(filepath, code.trim(), "utf-8");
38008
+ fs15.writeFileSync(filepath, code.trim(), "utf-8");
38009
38009
  savedFiles.push(filepath);
38010
38010
  } catch (error93) {
38011
38011
  errors5.push(`Failed to save ${filename}: ${error93 instanceof Error ? error93.message : String(error93)}`);
@@ -38034,13 +38034,15 @@ init_dist();
38034
38034
  var GITINGEST_TIMEOUT_MS = 1e4;
38035
38035
  var GITINGEST_MAX_RESPONSE_BYTES = 5242880;
38036
38036
  var GITINGEST_MAX_RETRIES = 2;
38037
+ var GITINGEST_DEFAULT_ENDPOINT = "https://gitingest.com/api/ingest";
38038
+ var DEFAULT_GITINGEST_CONFIG = GitingestConfigSchema.parse({});
38037
38039
  var delay = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
38038
- async function fetchGitingest(args) {
38040
+ async function fetchGitingest(args, endpoint = GITINGEST_DEFAULT_ENDPOINT) {
38039
38041
  for (let attempt = 0;attempt <= GITINGEST_MAX_RETRIES; attempt++) {
38040
38042
  try {
38041
38043
  const controller = new AbortController;
38042
38044
  const timeoutId = setTimeout(() => controller.abort(), GITINGEST_TIMEOUT_MS);
38043
- const response = await fetch("https://gitingest.com/api/ingest", {
38045
+ const response = await fetch(endpoint, {
38044
38046
  method: "POST",
38045
38047
  headers: { "Content-Type": "application/json" },
38046
38048
  body: JSON.stringify({
@@ -38100,21 +38102,28 @@ ${data.content}`;
38100
38102
  throw new Error("gitingest request failed after retries");
38101
38103
  }
38102
38104
  var gitingest = tool({
38103
- description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. Use when you need to understand an external repository's structure or code.",
38105
+ description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. This third-party call sends repository content to gitingest.com, so enable it explicitly and ensure you have permission before sharing code externally.",
38104
38106
  args: {
38105
38107
  url: tool.schema.string().describe("GitHub repository URL (e.g., https://github.com/owner/repo)"),
38106
38108
  maxFileSize: tool.schema.number().optional().describe("Maximum file size in bytes to include (default: 50000)"),
38107
38109
  pattern: tool.schema.string().optional().describe("Glob pattern to filter files (e.g., '*.ts' or 'src/**/*.py')"),
38108
38110
  patternType: tool.schema.enum(["include", "exclude"]).optional().describe("Whether pattern includes or excludes matching files (default: exclude)")
38109
38111
  },
38110
- async execute(args, _context) {
38111
- return fetchGitingest(args);
38112
+ async execute(args, context) {
38113
+ const directory = context.directory ?? process.cwd();
38114
+ const pluginConfig = loadPluginConfig(directory);
38115
+ const gitingestConfig = pluginConfig.gitingest ?? DEFAULT_GITINGEST_CONFIG;
38116
+ if (gitingestConfig.enabled === false) {
38117
+ throw new Error("gitingest is disabled in your config. Set gitingest.enabled=true to re-enable it.");
38118
+ }
38119
+ const endpoint = gitingestConfig.endpoint ?? GITINGEST_DEFAULT_ENDPOINT;
38120
+ return fetchGitingest(args, endpoint);
38112
38121
  }
38113
38122
  });
38114
38123
  // src/tools/imports.ts
38115
38124
  init_dist();
38116
- import * as fs15 from "fs";
38117
- import * as path20 from "path";
38125
+ import * as fs16 from "fs";
38126
+ import * as path23 from "path";
38118
38127
  var MAX_FILE_PATH_LENGTH2 = 500;
38119
38128
  var MAX_SYMBOL_LENGTH = 256;
38120
38129
  var MAX_FILE_SIZE_BYTES4 = 1024 * 1024;
@@ -38168,7 +38177,7 @@ function validateSymbolInput(symbol3) {
38168
38177
  return null;
38169
38178
  }
38170
38179
  function isBinaryFile2(filePath, buffer) {
38171
- const ext = path20.extname(filePath).toLowerCase();
38180
+ const ext = path23.extname(filePath).toLowerCase();
38172
38181
  if (ext === ".json" || ext === ".md" || ext === ".txt") {
38173
38182
  return false;
38174
38183
  }
@@ -38192,15 +38201,15 @@ function parseImports(content, targetFile, targetSymbol) {
38192
38201
  const imports = [];
38193
38202
  let resolvedTarget;
38194
38203
  try {
38195
- resolvedTarget = path20.resolve(targetFile);
38204
+ resolvedTarget = path23.resolve(targetFile);
38196
38205
  } catch {
38197
38206
  resolvedTarget = targetFile;
38198
38207
  }
38199
- const targetBasename = path20.basename(targetFile, path20.extname(targetFile));
38208
+ const targetBasename = path23.basename(targetFile, path23.extname(targetFile));
38200
38209
  const targetWithExt = targetFile;
38201
38210
  const targetWithoutExt = targetFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
38202
- const normalizedTargetWithExt = path20.normalize(targetWithExt).replace(/\\/g, "/");
38203
- const normalizedTargetWithoutExt = path20.normalize(targetWithoutExt).replace(/\\/g, "/");
38211
+ const normalizedTargetWithExt = path23.normalize(targetWithExt).replace(/\\/g, "/");
38212
+ const normalizedTargetWithoutExt = path23.normalize(targetWithoutExt).replace(/\\/g, "/");
38204
38213
  const importRegex = /import\s+(?:\{[\s\S]*?\}|(?:\*\s+as\s+\w+)|\w+)\s+from\s+['"`]([^'"`]+)['"`]|import\s+['"`]([^'"`]+)['"`]|require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
38205
38214
  let match;
38206
38215
  while ((match = importRegex.exec(content)) !== null) {
@@ -38224,9 +38233,9 @@ function parseImports(content, targetFile, targetSymbol) {
38224
38233
  }
38225
38234
  const normalizedModule = modulePath.replace(/^\.\//, "").replace(/^\.\.\\/, "../");
38226
38235
  let isMatch = false;
38227
- const targetDir = path20.dirname(targetFile);
38228
- const targetExt = path20.extname(targetFile);
38229
- const targetBasenameNoExt = path20.basename(targetFile, targetExt);
38236
+ const targetDir = path23.dirname(targetFile);
38237
+ const targetExt = path23.extname(targetFile);
38238
+ const targetBasenameNoExt = path23.basename(targetFile, targetExt);
38230
38239
  const moduleNormalized = modulePath.replace(/\\/g, "/").replace(/^\.\//, "");
38231
38240
  const moduleName = modulePath.split(/[/\\]/).pop() || "";
38232
38241
  const moduleNameNoExt = moduleName.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/i, "");
@@ -38283,7 +38292,7 @@ var SKIP_DIRECTORIES2 = new Set([
38283
38292
  function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFiles: 0, fileErrors: [] }) {
38284
38293
  let entries;
38285
38294
  try {
38286
- entries = fs15.readdirSync(dir);
38295
+ entries = fs16.readdirSync(dir);
38287
38296
  } catch (e) {
38288
38297
  stats.fileErrors.push({
38289
38298
  path: dir,
@@ -38294,13 +38303,13 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
38294
38303
  entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
38295
38304
  for (const entry of entries) {
38296
38305
  if (SKIP_DIRECTORIES2.has(entry)) {
38297
- stats.skippedDirs.push(path20.join(dir, entry));
38306
+ stats.skippedDirs.push(path23.join(dir, entry));
38298
38307
  continue;
38299
38308
  }
38300
- const fullPath = path20.join(dir, entry);
38309
+ const fullPath = path23.join(dir, entry);
38301
38310
  let stat;
38302
38311
  try {
38303
- stat = fs15.statSync(fullPath);
38312
+ stat = fs16.statSync(fullPath);
38304
38313
  } catch (e) {
38305
38314
  stats.fileErrors.push({
38306
38315
  path: fullPath,
@@ -38311,7 +38320,7 @@ function findSourceFiles2(dir, files = [], stats = { skippedDirs: [], skippedFil
38311
38320
  if (stat.isDirectory()) {
38312
38321
  findSourceFiles2(fullPath, files, stats);
38313
38322
  } else if (stat.isFile()) {
38314
- const ext = path20.extname(fullPath).toLowerCase();
38323
+ const ext = path23.extname(fullPath).toLowerCase();
38315
38324
  if (SUPPORTED_EXTENSIONS.includes(ext)) {
38316
38325
  files.push(fullPath);
38317
38326
  }
@@ -38367,8 +38376,8 @@ var imports = tool({
38367
38376
  return JSON.stringify(errorResult, null, 2);
38368
38377
  }
38369
38378
  try {
38370
- const targetFile = path20.resolve(file3);
38371
- if (!fs15.existsSync(targetFile)) {
38379
+ const targetFile = path23.resolve(file3);
38380
+ if (!fs16.existsSync(targetFile)) {
38372
38381
  const errorResult = {
38373
38382
  error: `target file not found: ${file3}`,
38374
38383
  target: file3,
@@ -38378,7 +38387,7 @@ var imports = tool({
38378
38387
  };
38379
38388
  return JSON.stringify(errorResult, null, 2);
38380
38389
  }
38381
- const targetStat = fs15.statSync(targetFile);
38390
+ const targetStat = fs16.statSync(targetFile);
38382
38391
  if (!targetStat.isFile()) {
38383
38392
  const errorResult = {
38384
38393
  error: "target must be a file, not a directory",
@@ -38389,7 +38398,7 @@ var imports = tool({
38389
38398
  };
38390
38399
  return JSON.stringify(errorResult, null, 2);
38391
38400
  }
38392
- const baseDir = path20.dirname(targetFile);
38401
+ const baseDir = path23.dirname(targetFile);
38393
38402
  const scanStats = {
38394
38403
  skippedDirs: [],
38395
38404
  skippedFiles: 0,
@@ -38404,12 +38413,12 @@ var imports = tool({
38404
38413
  if (consumers.length >= MAX_CONSUMERS)
38405
38414
  break;
38406
38415
  try {
38407
- const stat = fs15.statSync(filePath);
38416
+ const stat = fs16.statSync(filePath);
38408
38417
  if (stat.size > MAX_FILE_SIZE_BYTES4) {
38409
38418
  skippedFileCount++;
38410
38419
  continue;
38411
38420
  }
38412
- const buffer = fs15.readFileSync(filePath);
38421
+ const buffer = fs16.readFileSync(filePath);
38413
38422
  if (isBinaryFile2(filePath, buffer)) {
38414
38423
  skippedFileCount++;
38415
38424
  continue;
@@ -38478,10 +38487,107 @@ init_lint();
38478
38487
 
38479
38488
  // src/tools/pkg-audit.ts
38480
38489
  init_dist();
38481
- import * as fs16 from "fs";
38482
- import * as path21 from "path";
38483
- var MAX_OUTPUT_BYTES4 = 52428800;
38490
+ import * as fs17 from "fs";
38491
+ import * as path24 from "path";
38492
+ var MAX_OUTPUT_BYTES4 = 5242880;
38484
38493
  var AUDIT_TIMEOUT_MS = 120000;
38494
+ async function runAuditCommand(command) {
38495
+ const proc = Bun.spawn(command, {
38496
+ stdout: "pipe",
38497
+ stderr: "pipe",
38498
+ cwd: process.cwd()
38499
+ });
38500
+ const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
38501
+ const capturePromise = Promise.all([
38502
+ new Response(proc.stdout).text(),
38503
+ new Response(proc.stderr).text()
38504
+ ]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 }));
38505
+ const result = await Promise.race([capturePromise, timeoutPromise]);
38506
+ if (result === "timeout") {
38507
+ proc.kill();
38508
+ return { stdout: "", stderr: "", exitCode: -1, timedOut: true };
38509
+ }
38510
+ let { stdout, stderr } = result;
38511
+ if (stdout.length > MAX_OUTPUT_BYTES4) {
38512
+ stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
38513
+ }
38514
+ const exitCode = await proc.exited;
38515
+ return { stdout, stderr, exitCode, timedOut: false };
38516
+ }
38517
+ function buildTimeoutResult(ecosystem, command, message) {
38518
+ return {
38519
+ ecosystem,
38520
+ command,
38521
+ findings: [],
38522
+ criticalCount: 0,
38523
+ highCount: 0,
38524
+ totalCount: 0,
38525
+ clean: true,
38526
+ note: `${message} after ${AUDIT_TIMEOUT_MS / 1000}s`
38527
+ };
38528
+ }
38529
+ function handleAuditError(ecosystem, command, error93, notInstalledMatchers, notInstalledNote, errorPrefix) {
38530
+ const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
38531
+ if (notInstalledMatchers.some((re) => re.test(errorMessage))) {
38532
+ return {
38533
+ ecosystem,
38534
+ command,
38535
+ findings: [],
38536
+ criticalCount: 0,
38537
+ highCount: 0,
38538
+ totalCount: 0,
38539
+ clean: true,
38540
+ note: notInstalledNote
38541
+ };
38542
+ }
38543
+ return {
38544
+ ecosystem,
38545
+ command,
38546
+ findings: [],
38547
+ criticalCount: 0,
38548
+ highCount: 0,
38549
+ totalCount: 0,
38550
+ clean: true,
38551
+ note: `${errorPrefix}: ${errorMessage}`
38552
+ };
38553
+ }
38554
+ function buildCleanResult(ecosystem, command) {
38555
+ return {
38556
+ ecosystem,
38557
+ command,
38558
+ findings: [],
38559
+ criticalCount: 0,
38560
+ highCount: 0,
38561
+ totalCount: 0,
38562
+ clean: true
38563
+ };
38564
+ }
38565
+ function handleParseFailure(ecosystem, command, result, notePrefix, notInstalledNote, notInstalledMatchers) {
38566
+ const combined = `${result.stdout} ${result.stderr}`;
38567
+ if (notInstalledMatchers.some((re) => re.test(combined))) {
38568
+ return {
38569
+ ecosystem,
38570
+ command,
38571
+ findings: [],
38572
+ criticalCount: 0,
38573
+ highCount: 0,
38574
+ totalCount: 0,
38575
+ clean: true,
38576
+ note: notInstalledNote
38577
+ };
38578
+ }
38579
+ const snippet = result.stdout.trim() || result.stderr.trim();
38580
+ return {
38581
+ ecosystem,
38582
+ command,
38583
+ findings: [],
38584
+ criticalCount: 0,
38585
+ highCount: 0,
38586
+ totalCount: 0,
38587
+ clean: true,
38588
+ note: `${notePrefix}: ${snippet.slice(0, 200) || "unparseable output"}`
38589
+ };
38590
+ }
38485
38591
  function isValidEcosystem(value) {
38486
38592
  return typeof value === "string" && ["auto", "npm", "pip", "cargo"].includes(value);
38487
38593
  }
@@ -38497,13 +38603,13 @@ function validateArgs3(args) {
38497
38603
  function detectEcosystems() {
38498
38604
  const ecosystems = [];
38499
38605
  const cwd = process.cwd();
38500
- if (fs16.existsSync(path21.join(cwd, "package.json"))) {
38606
+ if (fs17.existsSync(path24.join(cwd, "package.json"))) {
38501
38607
  ecosystems.push("npm");
38502
38608
  }
38503
- if (fs16.existsSync(path21.join(cwd, "pyproject.toml")) || fs16.existsSync(path21.join(cwd, "requirements.txt"))) {
38609
+ if (fs17.existsSync(path24.join(cwd, "pyproject.toml")) || fs17.existsSync(path24.join(cwd, "requirements.txt"))) {
38504
38610
  ecosystems.push("pip");
38505
38611
  }
38506
- if (fs16.existsSync(path21.join(cwd, "Cargo.toml"))) {
38612
+ if (fs17.existsSync(path24.join(cwd, "Cargo.toml"))) {
38507
38613
  ecosystems.push("cargo");
38508
38614
  }
38509
38615
  return ecosystems;
@@ -38511,100 +38617,17 @@ function detectEcosystems() {
38511
38617
  async function runNpmAudit() {
38512
38618
  const command = ["npm", "audit", "--json"];
38513
38619
  try {
38514
- const proc = Bun.spawn(command, {
38515
- stdout: "pipe",
38516
- stderr: "pipe",
38517
- cwd: process.cwd()
38518
- });
38519
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
38520
- const result = await Promise.race([
38521
- Promise.all([
38522
- new Response(proc.stdout).text(),
38523
- new Response(proc.stderr).text()
38524
- ]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
38525
- timeoutPromise
38526
- ]);
38527
- if (result === "timeout") {
38528
- proc.kill();
38529
- return {
38530
- ecosystem: "npm",
38531
- command,
38532
- findings: [],
38533
- criticalCount: 0,
38534
- highCount: 0,
38535
- totalCount: 0,
38536
- clean: true,
38537
- note: `npm audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
38538
- };
38539
- }
38540
- let { stdout, stderr } = result;
38541
- if (stdout.length > MAX_OUTPUT_BYTES4) {
38542
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
38543
- }
38544
- const exitCode = await proc.exited;
38545
- if (exitCode === 0) {
38546
- return {
38547
- ecosystem: "npm",
38548
- command,
38549
- findings: [],
38550
- criticalCount: 0,
38551
- highCount: 0,
38552
- totalCount: 0,
38553
- clean: true
38554
- };
38620
+ const result = await runAuditCommand(command);
38621
+ if (result.timedOut) {
38622
+ return buildTimeoutResult("npm", command, "npm audit timed out");
38555
38623
  }
38556
- let jsonOutput = stdout;
38557
- const jsonMatch = stdout.match(/\{[\s\S]*\}/) || stderr.match(/\{[\s\S]*\}/);
38558
- if (jsonMatch) {
38559
- jsonOutput = jsonMatch[0];
38560
- }
38561
- const response = JSON.parse(jsonOutput);
38562
- const findings = [];
38563
- if (response.vulnerabilities) {
38564
- for (const [pkgName, vuln] of Object.entries(response.vulnerabilities)) {
38565
- let patchedVersion = null;
38566
- if (vuln.fixAvailable && typeof vuln.fixAvailable === "object") {
38567
- patchedVersion = vuln.fixAvailable.version;
38568
- } else if (vuln.fixAvailable === true) {
38569
- patchedVersion = "latest";
38570
- }
38571
- const severity = mapNpmSeverity(vuln.severity);
38572
- findings.push({
38573
- package: pkgName,
38574
- installedVersion: vuln.range,
38575
- patchedVersion,
38576
- severity,
38577
- title: vuln.title || `Vulnerability in ${pkgName}`,
38578
- cve: vuln.cves && vuln.cves.length > 0 ? vuln.cves[0] : null,
38579
- url: vuln.url || null
38580
- });
38581
- }
38582
- }
38583
- const criticalCount = findings.filter((f) => f.severity === "critical").length;
38584
- const highCount = findings.filter((f) => f.severity === "high").length;
38585
- return {
38586
- ecosystem: "npm",
38587
- command,
38588
- findings,
38589
- criticalCount,
38590
- highCount,
38591
- totalCount: findings.length,
38592
- clean: findings.length === 0
38593
- };
38624
+ return parseNpmAuditResult(command, result);
38594
38625
  } catch (error93) {
38595
- const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
38596
- if (errorMessage.includes("audit") || errorMessage.includes("command not found") || errorMessage.includes("'npm' is not recognized")) {
38597
- return {
38598
- ecosystem: "npm",
38599
- command,
38600
- findings: [],
38601
- criticalCount: 0,
38602
- highCount: 0,
38603
- totalCount: 0,
38604
- clean: true,
38605
- note: "npm audit not available - npm may not be installed"
38606
- };
38607
- }
38626
+ return handleAuditError("npm", command, error93, [/audit/, /command not found/, /'npm' is not recognized/], "npm audit not available - npm may not be installed", "Error running npm audit");
38627
+ }
38628
+ }
38629
+ function parseNpmAuditResult(command, result) {
38630
+ if (result.exitCode === 0) {
38608
38631
  return {
38609
38632
  ecosystem: "npm",
38610
38633
  command,
@@ -38612,10 +38635,45 @@ async function runNpmAudit() {
38612
38635
  criticalCount: 0,
38613
38636
  highCount: 0,
38614
38637
  totalCount: 0,
38615
- clean: true,
38616
- note: `Error running npm audit: ${errorMessage}`
38638
+ clean: true
38617
38639
  };
38618
38640
  }
38641
+ let jsonOutput = result.stdout;
38642
+ const match = jsonOutput.match(/\{[\s\S]*\}/) || result.stderr.match(/\{[\s\S]*\}/);
38643
+ if (match) {
38644
+ jsonOutput = match[0];
38645
+ }
38646
+ let response;
38647
+ try {
38648
+ response = JSON.parse(jsonOutput);
38649
+ } catch {
38650
+ return handleParseFailure("npm", command, result, "npm audit output could not be parsed", "npm audit not available - npm may not be installed", [/audit/, /command not found/, /'npm' is not recognized/]);
38651
+ }
38652
+ const findings = buildNpmFindings(response.vulnerabilities);
38653
+ const criticalCount = findings.filter((f) => f.severity === "critical").length;
38654
+ const highCount = findings.filter((f) => f.severity === "high").length;
38655
+ return {
38656
+ ecosystem: "npm",
38657
+ command,
38658
+ findings,
38659
+ criticalCount,
38660
+ highCount,
38661
+ totalCount: findings.length,
38662
+ clean: findings.length === 0
38663
+ };
38664
+ }
38665
+ function buildNpmFindings(response) {
38666
+ if (!response)
38667
+ return [];
38668
+ return Object.entries(response).map(([pkgName, vuln]) => ({
38669
+ package: pkgName,
38670
+ installedVersion: vuln.range,
38671
+ patchedVersion: vuln.fixAvailable && typeof vuln.fixAvailable === "object" ? vuln.fixAvailable.version : vuln.fixAvailable === true ? "latest" : null,
38672
+ severity: mapNpmSeverity(vuln.severity),
38673
+ title: vuln.title || `Vulnerability in ${pkgName}`,
38674
+ cve: vuln.cves && vuln.cves.length > 0 ? vuln.cves[0] : null,
38675
+ url: vuln.url || null
38676
+ }));
38619
38677
  }
38620
38678
  function mapNpmSeverity(severity) {
38621
38679
  switch (severity.toLowerCase()) {
@@ -38634,238 +38692,116 @@ function mapNpmSeverity(severity) {
38634
38692
  async function runPipAudit() {
38635
38693
  const command = ["pip-audit", "--format=json"];
38636
38694
  try {
38637
- const proc = Bun.spawn(command, {
38638
- stdout: "pipe",
38639
- stderr: "pipe",
38640
- cwd: process.cwd()
38641
- });
38642
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
38643
- const result = await Promise.race([
38644
- Promise.all([
38645
- new Response(proc.stdout).text(),
38646
- new Response(proc.stderr).text()
38647
- ]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
38648
- timeoutPromise
38649
- ]);
38650
- if (result === "timeout") {
38651
- proc.kill();
38652
- return {
38653
- ecosystem: "pip",
38654
- command,
38655
- findings: [],
38656
- criticalCount: 0,
38657
- highCount: 0,
38658
- totalCount: 0,
38659
- clean: true,
38660
- note: `pip-audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
38661
- };
38662
- }
38663
- let { stdout, stderr } = result;
38664
- if (stdout.length > MAX_OUTPUT_BYTES4) {
38665
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
38666
- }
38667
- const exitCode = await proc.exited;
38668
- if (exitCode === 0 && !stdout.trim()) {
38669
- return {
38670
- ecosystem: "pip",
38671
- command,
38672
- findings: [],
38673
- criticalCount: 0,
38674
- highCount: 0,
38675
- totalCount: 0,
38676
- clean: true
38677
- };
38678
- }
38679
- let packages = [];
38680
- try {
38681
- const parsed = JSON.parse(stdout);
38682
- if (Array.isArray(parsed)) {
38683
- packages = parsed;
38684
- } else if (parsed.dependencies) {
38685
- packages = parsed.dependencies;
38686
- }
38687
- } catch {
38688
- if (stderr.includes("not installed") || stdout.includes("not installed") || stderr.includes("command not found")) {
38689
- return {
38690
- ecosystem: "pip",
38691
- command,
38692
- findings: [],
38693
- criticalCount: 0,
38694
- highCount: 0,
38695
- totalCount: 0,
38696
- clean: true,
38697
- note: "pip-audit not installed. Install with: pip install pip-audit"
38698
- };
38699
- }
38700
- return {
38701
- ecosystem: "pip",
38702
- command,
38703
- findings: [],
38704
- criticalCount: 0,
38705
- highCount: 0,
38706
- totalCount: 0,
38707
- clean: true,
38708
- note: `pip-audit output could not be parsed: ${stdout.slice(0, 200)}`
38709
- };
38710
- }
38711
- const findings = [];
38712
- for (const pkg of packages) {
38713
- if (pkg.vulns && pkg.vulns.length > 0) {
38714
- for (const vuln of pkg.vulns) {
38715
- const severity = vuln.aliases && vuln.aliases.length > 0 ? "high" : "moderate";
38716
- findings.push({
38717
- package: pkg.name,
38718
- installedVersion: pkg.version,
38719
- patchedVersion: vuln.fix_versions && vuln.fix_versions.length > 0 ? vuln.fix_versions[0] : null,
38720
- severity,
38721
- title: vuln.id,
38722
- cve: vuln.aliases && vuln.aliases.length > 0 ? vuln.aliases[0] : null,
38723
- url: vuln.id.startsWith("CVE-") ? `https://nvd.nist.gov/vuln/detail/${vuln.id}` : null
38724
- });
38725
- }
38726
- }
38695
+ const result = await runAuditCommand(command);
38696
+ if (result.timedOut) {
38697
+ return buildTimeoutResult("pip", command, "pip-audit timed out");
38727
38698
  }
38728
- const criticalCount = findings.filter((f) => f.severity === "critical").length;
38729
- const highCount = findings.filter((f) => f.severity === "high").length;
38730
- return {
38731
- ecosystem: "pip",
38732
- command,
38733
- findings,
38734
- criticalCount,
38735
- highCount,
38736
- totalCount: findings.length,
38737
- clean: findings.length === 0
38738
- };
38699
+ return parsePipAuditResult(command, result);
38739
38700
  } catch (error93) {
38740
- const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
38741
- if (errorMessage.includes("not found") || errorMessage.includes("not recognized") || errorMessage.includes("pip-audit")) {
38742
- return {
38743
- ecosystem: "pip",
38744
- command,
38745
- findings: [],
38746
- criticalCount: 0,
38747
- highCount: 0,
38748
- totalCount: 0,
38749
- clean: true,
38750
- note: "pip-audit not installed. Install with: pip install pip-audit"
38751
- };
38701
+ return handleAuditError("pip", command, error93, [/not installed/, /command not found/, /pip-audit/], "pip-audit not installed. Install with: pip install pip-audit", "Error running pip-audit");
38702
+ }
38703
+ }
38704
+ function parsePipAuditResult(command, result) {
38705
+ if (result.exitCode === 0 && !result.stdout.trim()) {
38706
+ return buildCleanResult("pip", command);
38707
+ }
38708
+ let packages = [];
38709
+ try {
38710
+ const parsed = JSON.parse(result.stdout);
38711
+ if (Array.isArray(parsed)) {
38712
+ packages = parsed;
38713
+ } else if (parsed.dependencies) {
38714
+ packages = parsed.dependencies;
38715
+ }
38716
+ } catch {
38717
+ return handleParseFailure("pip", command, result, "pip-audit output could not be parsed", "pip-audit not installed. Install with: pip install pip-audit", [/not installed/, /command not found/, /pip-audit/]);
38718
+ }
38719
+ const findings = buildPipFindings(packages);
38720
+ const criticalCount = findings.filter((f) => f.severity === "critical").length;
38721
+ const highCount = findings.filter((f) => f.severity === "high").length;
38722
+ return {
38723
+ ecosystem: "pip",
38724
+ command,
38725
+ findings,
38726
+ criticalCount,
38727
+ highCount,
38728
+ totalCount: findings.length,
38729
+ clean: findings.length === 0
38730
+ };
38731
+ }
38732
+ function buildPipFindings(packages) {
38733
+ const findings = [];
38734
+ for (const pkg of packages) {
38735
+ if (!pkg.vulns)
38736
+ continue;
38737
+ for (const vuln of pkg.vulns) {
38738
+ const severity = vuln.aliases && vuln.aliases.length > 0 ? "high" : "moderate";
38739
+ findings.push({
38740
+ package: pkg.name,
38741
+ installedVersion: pkg.version,
38742
+ patchedVersion: vuln.fix_versions && vuln.fix_versions.length > 0 ? vuln.fix_versions[0] : null,
38743
+ severity,
38744
+ title: vuln.id,
38745
+ cve: vuln.aliases && vuln.aliases.length > 0 ? vuln.aliases[0] : null,
38746
+ url: vuln.id.startsWith("CVE-") ? `https://nvd.nist.gov/vuln/detail/${vuln.id}` : null
38747
+ });
38752
38748
  }
38753
- return {
38754
- ecosystem: "pip",
38755
- command,
38756
- findings: [],
38757
- criticalCount: 0,
38758
- highCount: 0,
38759
- totalCount: 0,
38760
- clean: true,
38761
- note: `Error running pip-audit: ${errorMessage}`
38762
- };
38763
38749
  }
38750
+ return findings;
38764
38751
  }
38765
38752
  async function runCargoAudit() {
38766
38753
  const command = ["cargo", "audit", "--json"];
38767
38754
  try {
38768
- const proc = Bun.spawn(command, {
38769
- stdout: "pipe",
38770
- stderr: "pipe",
38771
- cwd: process.cwd()
38772
- });
38773
- const timeoutPromise = new Promise((resolve10) => setTimeout(() => resolve10("timeout"), AUDIT_TIMEOUT_MS));
38774
- const result = await Promise.race([
38775
- Promise.all([
38776
- new Response(proc.stdout).text(),
38777
- new Response(proc.stderr).text()
38778
- ]).then(([stdout2, stderr2]) => ({ stdout: stdout2, stderr: stderr2 })),
38779
- timeoutPromise
38780
- ]);
38781
- if (result === "timeout") {
38782
- proc.kill();
38783
- return {
38784
- ecosystem: "cargo",
38785
- command,
38786
- findings: [],
38787
- criticalCount: 0,
38788
- highCount: 0,
38789
- totalCount: 0,
38790
- clean: true,
38791
- note: `cargo audit timed out after ${AUDIT_TIMEOUT_MS / 1000}s`
38792
- };
38793
- }
38794
- let { stdout, stderr } = result;
38795
- if (stdout.length > MAX_OUTPUT_BYTES4) {
38796
- stdout = stdout.slice(0, MAX_OUTPUT_BYTES4);
38797
- }
38798
- const exitCode = await proc.exited;
38799
- if (exitCode === 0) {
38800
- return {
38801
- ecosystem: "cargo",
38802
- command,
38803
- findings: [],
38804
- criticalCount: 0,
38805
- highCount: 0,
38806
- totalCount: 0,
38807
- clean: true
38808
- };
38809
- }
38810
- const findings = [];
38811
- const lines = stdout.split(`
38812
- `).filter((line) => line.trim());
38813
- for (const line of lines) {
38814
- try {
38815
- const obj = JSON.parse(line);
38816
- if (obj.vulnerabilities && obj.vulnerabilities.list) {
38817
- for (const item of obj.vulnerabilities.list) {
38818
- const cvss = item.advisory.cvss || 0;
38819
- const severity = mapCargoSeverity(cvss);
38820
- findings.push({
38821
- package: item.advisory.package,
38822
- installedVersion: item.package.version,
38823
- patchedVersion: item.versions.patched && item.versions.patched.length > 0 ? item.versions.patched[0] : null,
38824
- severity,
38825
- title: item.advisory.title,
38826
- cve: item.advisory.aliases && item.advisory.aliases.length > 0 ? item.advisory.aliases[0] : item.advisory.id ? item.advisory.id : null,
38827
- url: item.advisory.url || null
38828
- });
38829
- }
38830
- }
38831
- } catch {}
38755
+ const result = await runAuditCommand(command);
38756
+ if (result.timedOut) {
38757
+ return buildTimeoutResult("cargo", command, "cargo audit timed out");
38832
38758
  }
38833
- const criticalCount = findings.filter((f) => f.severity === "critical").length;
38834
- const highCount = findings.filter((f) => f.severity === "high").length;
38835
- return {
38836
- ecosystem: "cargo",
38837
- command,
38838
- findings,
38839
- criticalCount,
38840
- highCount,
38841
- totalCount: findings.length,
38842
- clean: findings.length === 0
38843
- };
38759
+ return parseCargoAuditResult(command, result);
38844
38760
  } catch (error93) {
38845
- const errorMessage = error93 instanceof Error ? error93.message : "Unknown error";
38846
- if (errorMessage.includes("not found") || errorMessage.includes("not recognized") || errorMessage.includes("cargo-audit")) {
38847
- return {
38848
- ecosystem: "cargo",
38849
- command,
38850
- findings: [],
38851
- criticalCount: 0,
38852
- highCount: 0,
38853
- totalCount: 0,
38854
- clean: true,
38855
- note: "cargo-audit not installed. Install with: cargo install cargo-audit"
38856
- };
38857
- }
38858
- return {
38859
- ecosystem: "cargo",
38860
- command,
38861
- findings: [],
38862
- criticalCount: 0,
38863
- highCount: 0,
38864
- totalCount: 0,
38865
- clean: true,
38866
- note: `Error running cargo audit: ${errorMessage}`
38867
- };
38761
+ return handleAuditError("cargo", command, error93, [/not found/, /not recognized/, /cargo-audit/], "cargo-audit not installed. Install with: cargo install cargo-audit", "Error running cargo audit");
38762
+ }
38763
+ }
38764
+ function parseCargoAuditResult(command, result) {
38765
+ if (result.exitCode === 0) {
38766
+ return buildCleanResult("cargo", command);
38767
+ }
38768
+ const lines = result.stdout.split("\\n").filter((line) => line.trim());
38769
+ const findings = buildCargoFindings(lines);
38770
+ const criticalCount = findings.filter((f) => f.severity === "critical").length;
38771
+ const highCount = findings.filter((f) => f.severity === "high").length;
38772
+ return {
38773
+ ecosystem: "cargo",
38774
+ command,
38775
+ findings,
38776
+ criticalCount,
38777
+ highCount,
38778
+ totalCount: findings.length,
38779
+ clean: findings.length === 0
38780
+ };
38781
+ }
38782
+ function buildCargoFindings(lines) {
38783
+ const findings = [];
38784
+ for (const line of lines) {
38785
+ try {
38786
+ const obj = JSON.parse(line);
38787
+ if (obj.vulnerabilities && obj.vulnerabilities.list) {
38788
+ for (const item of obj.vulnerabilities.list) {
38789
+ const cvss = item.advisory.cvss || 0;
38790
+ const severity = mapCargoSeverity(cvss);
38791
+ findings.push({
38792
+ package: item.advisory.package,
38793
+ installedVersion: item.package.version,
38794
+ patchedVersion: item.versions.patched && item.versions.patched.length > 0 ? item.versions.patched[0] : null,
38795
+ severity,
38796
+ title: item.advisory.title,
38797
+ cve: item.advisory.aliases && item.advisory.aliases.length > 0 ? item.advisory.aliases[0] : item.advisory.id ? item.advisory.id : null,
38798
+ url: item.advisory.url || null
38799
+ });
38800
+ }
38801
+ }
38802
+ } catch {}
38868
38803
  }
38804
+ return findings;
38869
38805
  }
38870
38806
  function mapCargoSeverity(cvss) {
38871
38807
  if (cvss >= 9)
@@ -38984,8 +38920,8 @@ var retrieve_summary = tool({
38984
38920
  });
38985
38921
  // src/tools/schema-drift.ts
38986
38922
  init_dist();
38987
- import * as fs17 from "fs";
38988
- import * as path22 from "path";
38923
+ import * as fs18 from "fs";
38924
+ import * as path25 from "path";
38989
38925
  var SPEC_CANDIDATES = [
38990
38926
  "openapi.json",
38991
38927
  "openapi.yaml",
@@ -39017,28 +38953,28 @@ function normalizePath(p) {
39017
38953
  }
39018
38954
  function discoverSpecFile(cwd, specFileArg) {
39019
38955
  if (specFileArg) {
39020
- const resolvedPath = path22.resolve(cwd, specFileArg);
39021
- const normalizedCwd = cwd.endsWith(path22.sep) ? cwd : cwd + path22.sep;
38956
+ const resolvedPath = path25.resolve(cwd, specFileArg);
38957
+ const normalizedCwd = cwd.endsWith(path25.sep) ? cwd : cwd + path25.sep;
39022
38958
  if (!resolvedPath.startsWith(normalizedCwd) && resolvedPath !== cwd) {
39023
38959
  throw new Error("Invalid spec_file: path traversal detected");
39024
38960
  }
39025
- const ext = path22.extname(resolvedPath).toLowerCase();
38961
+ const ext = path25.extname(resolvedPath).toLowerCase();
39026
38962
  if (!ALLOWED_EXTENSIONS.includes(ext)) {
39027
38963
  throw new Error(`Invalid spec_file: must end in .json, .yaml, or .yml, got ${ext}`);
39028
38964
  }
39029
- const stats = fs17.statSync(resolvedPath);
38965
+ const stats = fs18.statSync(resolvedPath);
39030
38966
  if (stats.size > MAX_SPEC_SIZE) {
39031
38967
  throw new Error(`Invalid spec_file: file exceeds ${MAX_SPEC_SIZE / 1024 / 1024}MB limit`);
39032
38968
  }
39033
- if (!fs17.existsSync(resolvedPath)) {
38969
+ if (!fs18.existsSync(resolvedPath)) {
39034
38970
  throw new Error(`Spec file not found: ${resolvedPath}`);
39035
38971
  }
39036
38972
  return resolvedPath;
39037
38973
  }
39038
38974
  for (const candidate of SPEC_CANDIDATES) {
39039
- const candidatePath = path22.resolve(cwd, candidate);
39040
- if (fs17.existsSync(candidatePath)) {
39041
- const stats = fs17.statSync(candidatePath);
38975
+ const candidatePath = path25.resolve(cwd, candidate);
38976
+ if (fs18.existsSync(candidatePath)) {
38977
+ const stats = fs18.statSync(candidatePath);
39042
38978
  if (stats.size <= MAX_SPEC_SIZE) {
39043
38979
  return candidatePath;
39044
38980
  }
@@ -39047,8 +38983,8 @@ function discoverSpecFile(cwd, specFileArg) {
39047
38983
  return null;
39048
38984
  }
39049
38985
  function parseSpec(specFile) {
39050
- const content = fs17.readFileSync(specFile, "utf-8");
39051
- const ext = path22.extname(specFile).toLowerCase();
38986
+ const content = fs18.readFileSync(specFile, "utf-8");
38987
+ const ext = path25.extname(specFile).toLowerCase();
39052
38988
  if (ext === ".json") {
39053
38989
  return parseJsonSpec(content);
39054
38990
  }
@@ -39114,12 +39050,12 @@ function extractRoutes(cwd) {
39114
39050
  function walkDir(dir) {
39115
39051
  let entries;
39116
39052
  try {
39117
- entries = fs17.readdirSync(dir, { withFileTypes: true });
39053
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
39118
39054
  } catch {
39119
39055
  return;
39120
39056
  }
39121
39057
  for (const entry of entries) {
39122
- const fullPath = path22.join(dir, entry.name);
39058
+ const fullPath = path25.join(dir, entry.name);
39123
39059
  if (entry.isSymbolicLink()) {
39124
39060
  continue;
39125
39061
  }
@@ -39129,7 +39065,7 @@ function extractRoutes(cwd) {
39129
39065
  }
39130
39066
  walkDir(fullPath);
39131
39067
  } else if (entry.isFile()) {
39132
- const ext = path22.extname(entry.name).toLowerCase();
39068
+ const ext = path25.extname(entry.name).toLowerCase();
39133
39069
  const baseName = entry.name.toLowerCase();
39134
39070
  if (![".ts", ".js", ".mjs"].includes(ext)) {
39135
39071
  continue;
@@ -39147,7 +39083,7 @@ function extractRoutes(cwd) {
39147
39083
  }
39148
39084
  function extractRoutesFromFile(filePath) {
39149
39085
  const routes = [];
39150
- const content = fs17.readFileSync(filePath, "utf-8");
39086
+ const content = fs18.readFileSync(filePath, "utf-8");
39151
39087
  const lines = content.split(/\r?\n/);
39152
39088
  const expressRegex = /(?:app|router|server|express)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]/g;
39153
39089
  const flaskRegex = /@(?:app|blueprint|bp)\.route\s*\(\s*['"]([^'"]+)['"]/g;
@@ -39294,8 +39230,8 @@ init_secretscan();
39294
39230
 
39295
39231
  // src/tools/symbols.ts
39296
39232
  init_tool();
39297
- import * as fs18 from "fs";
39298
- import * as path23 from "path";
39233
+ import * as fs19 from "fs";
39234
+ import * as path26 from "path";
39299
39235
  var MAX_FILE_SIZE_BYTES5 = 1024 * 1024;
39300
39236
  var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
39301
39237
  function containsControlCharacters(str) {
@@ -39324,11 +39260,11 @@ function containsWindowsAttacks(str) {
39324
39260
  }
39325
39261
  function isPathInWorkspace(filePath, workspace) {
39326
39262
  try {
39327
- const resolvedPath = path23.resolve(workspace, filePath);
39328
- const realWorkspace = fs18.realpathSync(workspace);
39329
- const realResolvedPath = fs18.realpathSync(resolvedPath);
39330
- const relativePath = path23.relative(realWorkspace, realResolvedPath);
39331
- if (relativePath.startsWith("..") || path23.isAbsolute(relativePath)) {
39263
+ const resolvedPath = path26.resolve(workspace, filePath);
39264
+ const realWorkspace = fs19.realpathSync(workspace);
39265
+ const realResolvedPath = fs19.realpathSync(resolvedPath);
39266
+ const relativePath = path26.relative(realWorkspace, realResolvedPath);
39267
+ if (relativePath.startsWith("..") || path26.isAbsolute(relativePath)) {
39332
39268
  return false;
39333
39269
  }
39334
39270
  return true;
@@ -39340,17 +39276,17 @@ function validatePathForRead(filePath, workspace) {
39340
39276
  return isPathInWorkspace(filePath, workspace);
39341
39277
  }
39342
39278
  function extractTSSymbols(filePath, cwd) {
39343
- const fullPath = path23.join(cwd, filePath);
39279
+ const fullPath = path26.join(cwd, filePath);
39344
39280
  if (!validatePathForRead(fullPath, cwd)) {
39345
39281
  return [];
39346
39282
  }
39347
39283
  let content;
39348
39284
  try {
39349
- const stats = fs18.statSync(fullPath);
39285
+ const stats = fs19.statSync(fullPath);
39350
39286
  if (stats.size > MAX_FILE_SIZE_BYTES5) {
39351
39287
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES5})`);
39352
39288
  }
39353
- content = fs18.readFileSync(fullPath, "utf-8");
39289
+ content = fs19.readFileSync(fullPath, "utf-8");
39354
39290
  } catch {
39355
39291
  return [];
39356
39292
  }
@@ -39492,17 +39428,17 @@ function extractTSSymbols(filePath, cwd) {
39492
39428
  });
39493
39429
  }
39494
39430
  function extractPythonSymbols(filePath, cwd) {
39495
- const fullPath = path23.join(cwd, filePath);
39431
+ const fullPath = path26.join(cwd, filePath);
39496
39432
  if (!validatePathForRead(fullPath, cwd)) {
39497
39433
  return [];
39498
39434
  }
39499
39435
  let content;
39500
39436
  try {
39501
- const stats = fs18.statSync(fullPath);
39437
+ const stats = fs19.statSync(fullPath);
39502
39438
  if (stats.size > MAX_FILE_SIZE_BYTES5) {
39503
39439
  throw new Error(`File too large: ${stats.size} bytes (max: ${MAX_FILE_SIZE_BYTES5})`);
39504
39440
  }
39505
- content = fs18.readFileSync(fullPath, "utf-8");
39441
+ content = fs19.readFileSync(fullPath, "utf-8");
39506
39442
  } catch {
39507
39443
  return [];
39508
39444
  }
@@ -39574,7 +39510,7 @@ var symbols = tool({
39574
39510
  }, null, 2);
39575
39511
  }
39576
39512
  const cwd = process.cwd();
39577
- const ext = path23.extname(file3);
39513
+ const ext = path26.extname(file3);
39578
39514
  if (containsControlCharacters(file3)) {
39579
39515
  return JSON.stringify({
39580
39516
  file: file3,
@@ -39639,8 +39575,8 @@ init_test_runner();
39639
39575
 
39640
39576
  // src/tools/todo-extract.ts
39641
39577
  init_dist();
39642
- import * as fs19 from "fs";
39643
- import * as path24 from "path";
39578
+ import * as fs20 from "fs";
39579
+ import * as path27 from "path";
39644
39580
  var MAX_TEXT_LENGTH = 200;
39645
39581
  var MAX_FILE_SIZE_BYTES6 = 1024 * 1024;
39646
39582
  var SUPPORTED_EXTENSIONS2 = new Set([
@@ -39711,9 +39647,9 @@ function validatePathsInput(paths, cwd) {
39711
39647
  return { error: "paths contains path traversal", resolvedPath: null };
39712
39648
  }
39713
39649
  try {
39714
- const resolvedPath = path24.resolve(paths);
39715
- const normalizedCwd = path24.resolve(cwd);
39716
- const normalizedResolved = path24.resolve(resolvedPath);
39650
+ const resolvedPath = path27.resolve(paths);
39651
+ const normalizedCwd = path27.resolve(cwd);
39652
+ const normalizedResolved = path27.resolve(resolvedPath);
39717
39653
  if (!normalizedResolved.startsWith(normalizedCwd)) {
39718
39654
  return {
39719
39655
  error: "paths must be within the current working directory",
@@ -39729,13 +39665,13 @@ function validatePathsInput(paths, cwd) {
39729
39665
  }
39730
39666
  }
39731
39667
  function isSupportedExtension(filePath) {
39732
- const ext = path24.extname(filePath).toLowerCase();
39668
+ const ext = path27.extname(filePath).toLowerCase();
39733
39669
  return SUPPORTED_EXTENSIONS2.has(ext);
39734
39670
  }
39735
39671
  function findSourceFiles3(dir, files = []) {
39736
39672
  let entries;
39737
39673
  try {
39738
- entries = fs19.readdirSync(dir);
39674
+ entries = fs20.readdirSync(dir);
39739
39675
  } catch {
39740
39676
  return files;
39741
39677
  }
@@ -39744,10 +39680,10 @@ function findSourceFiles3(dir, files = []) {
39744
39680
  if (SKIP_DIRECTORIES3.has(entry)) {
39745
39681
  continue;
39746
39682
  }
39747
- const fullPath = path24.join(dir, entry);
39683
+ const fullPath = path27.join(dir, entry);
39748
39684
  let stat;
39749
39685
  try {
39750
- stat = fs19.statSync(fullPath);
39686
+ stat = fs20.statSync(fullPath);
39751
39687
  } catch {
39752
39688
  continue;
39753
39689
  }
@@ -39840,7 +39776,7 @@ var todo_extract = tool({
39840
39776
  return JSON.stringify(errorResult, null, 2);
39841
39777
  }
39842
39778
  const scanPath = resolvedPath;
39843
- if (!fs19.existsSync(scanPath)) {
39779
+ if (!fs20.existsSync(scanPath)) {
39844
39780
  const errorResult = {
39845
39781
  error: `path not found: ${pathsInput}`,
39846
39782
  total: 0,
@@ -39850,13 +39786,13 @@ var todo_extract = tool({
39850
39786
  return JSON.stringify(errorResult, null, 2);
39851
39787
  }
39852
39788
  const filesToScan = [];
39853
- const stat = fs19.statSync(scanPath);
39789
+ const stat = fs20.statSync(scanPath);
39854
39790
  if (stat.isFile()) {
39855
39791
  if (isSupportedExtension(scanPath)) {
39856
39792
  filesToScan.push(scanPath);
39857
39793
  } else {
39858
39794
  const errorResult = {
39859
- error: `unsupported file extension: ${path24.extname(scanPath)}`,
39795
+ error: `unsupported file extension: ${path27.extname(scanPath)}`,
39860
39796
  total: 0,
39861
39797
  byPriority: { high: 0, medium: 0, low: 0 },
39862
39798
  entries: []
@@ -39869,11 +39805,11 @@ var todo_extract = tool({
39869
39805
  const allEntries = [];
39870
39806
  for (const filePath of filesToScan) {
39871
39807
  try {
39872
- const fileStat = fs19.statSync(filePath);
39808
+ const fileStat = fs20.statSync(filePath);
39873
39809
  if (fileStat.size > MAX_FILE_SIZE_BYTES6) {
39874
39810
  continue;
39875
39811
  }
39876
- const content = fs19.readFileSync(filePath, "utf-8");
39812
+ const content = fs20.readFileSync(filePath, "utf-8");
39877
39813
  const entries = parseTodoComments(content, filePath, tagsSet);
39878
39814
  allEntries.push(...entries);
39879
39815
  } catch {}
@@ -39944,7 +39880,7 @@ var OpenCodeSwarm = async (ctx) => {
39944
39880
  const { PreflightTriggerManager: PTM } = await Promise.resolve().then(() => (init_trigger(), exports_trigger));
39945
39881
  preflightTriggerManager = new PTM(automationConfig);
39946
39882
  const { AutomationStatusArtifact: ASA } = await Promise.resolve().then(() => (init_status_artifact(), exports_status_artifact));
39947
- const swarmDir = path25.resolve(ctx.directory, ".swarm");
39883
+ const swarmDir = path28.resolve(ctx.directory, ".swarm");
39948
39884
  statusArtifact = new ASA(swarmDir);
39949
39885
  statusArtifact.updateConfig(automationConfig.mode, automationConfig.capabilities);
39950
39886
  if (automationConfig.capabilities?.evidence_auto_summaries === true) {