opencode-swarm 7.73.2 → 7.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ var package_default;
69
69
  var init_package = __esm(() => {
70
70
  package_default = {
71
71
  name: "opencode-swarm",
72
- version: "7.73.2",
72
+ version: "7.74.0",
73
73
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
74
74
  main: "dist/index.js",
75
75
  types: "dist/index.d.ts",
@@ -16736,7 +16736,7 @@ var init_plan_schema = __esm(() => {
16736
16736
  init_zod();
16737
16737
  ExecutionProfileSchema = exports_external.object({
16738
16738
  parallelization_enabled: exports_external.boolean().default(false),
16739
- max_concurrent_tasks: exports_external.number().int().min(1).max(64).default(1),
16739
+ max_concurrent_tasks: exports_external.number().int().min(1).max(64).default(10),
16740
16740
  council_parallel: exports_external.boolean().default(true),
16741
16741
  locked: exports_external.boolean().default(false),
16742
16742
  auto_proceed: exports_external.boolean().default(false)
@@ -42662,8 +42662,30 @@ async function buildParallelExecutionGuidance(directory, sessionID, session) {
42662
42662
  }
42663
42663
  const profile = plan.execution_profile;
42664
42664
  const enabled = profile?.parallelization_enabled === true;
42665
- const maxConcurrent = profile?.max_concurrent_tasks ?? 1;
42666
- const effectiveMaxConcurrent = session?.maxConcurrencyOverride ?? maxConcurrent;
42665
+ const maxConcurrent = profile?.max_concurrent_tasks ?? 10;
42666
+ let effectiveMaxConcurrent = session?.maxConcurrencyOverride ?? maxConcurrent;
42667
+ const allTasks = plan.phases.flatMap((phase) => phase.tasks);
42668
+ const blockedTasks = allTasks.filter((task) => {
42669
+ if (task.status !== "blocked")
42670
+ return false;
42671
+ const reason = (task.blocked_reason ?? "").toLowerCase();
42672
+ return reason.includes("fail") || reason.includes("error") || reason.includes("exception");
42673
+ });
42674
+ const totalTasks = allTasks.length;
42675
+ let backoffTriggered = false;
42676
+ if (totalTasks > 0 && blockedTasks.length > 0) {
42677
+ const failureRate = blockedTasks.length / totalTasks;
42678
+ const FAILURE_RATE_THRESHOLD = 0.2;
42679
+ const BACKOFF_MULTIPLIER = 0.5;
42680
+ if (failureRate > FAILURE_RATE_THRESHOLD && blockedTasks.length >= 2) {
42681
+ const newConcurrency = Math.max(1, Math.floor(effectiveMaxConcurrent * BACKOFF_MULTIPLIER));
42682
+ if (newConcurrency < effectiveMaxConcurrent) {
42683
+ effectiveMaxConcurrent = newConcurrency;
42684
+ session.maxConcurrencyOverride = newConcurrency;
42685
+ backoffTriggered = true;
42686
+ }
42687
+ }
42688
+ }
42667
42689
  if (!enabled || effectiveMaxConcurrent <= 1)
42668
42690
  return null;
42669
42691
  if (hasActiveLeanTurbo(sessionID)) {
@@ -42675,7 +42697,6 @@ async function buildParallelExecutionGuidance(directory, sessionID, session) {
42675
42697
  const tasks = currentPhase.tasks;
42676
42698
  if (tasks.length === 0)
42677
42699
  return null;
42678
- const allTasks = plan.phases.flatMap((phase) => phase.tasks);
42679
42700
  const completed = new Set;
42680
42701
  for (const task of allTasks) {
42681
42702
  const taskId = task.id;
@@ -42709,7 +42730,8 @@ async function buildParallelExecutionGuidance(directory, sessionID, session) {
42709
42730
  if (eligible.length === 0) {
42710
42731
  return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${effectiveMaxConcurrent}; no dependency-ready pending tasks are available for a new coder slot. Continue the current task/gate.`;
42711
42732
  }
42712
- return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${effectiveMaxConcurrent}; ${occupied.size} slot(s) occupied. Eligible now: ${eligible.join(", ")}. [NEXT] dispatch up to ${availableSlots} eligible coder task(s) before waiting; preserve ONE task per coder call and call declare_scope for each task.`;
42733
+ const failureWarning = backoffTriggered ? ` (${blockedTasks.length} blocked task(s) detected concurrency auto-reduced due to failures)` : "";
42734
+ return `[PARALLEL EXECUTION PROFILE] parallelization_enabled=true max_concurrent_tasks=${effectiveMaxConcurrent}; ${occupied.size} slot(s) occupied. Eligible now: ${eligible.join(", ")}. [NEXT] dispatch up to ${availableSlots} eligible coder task(s) before waiting; preserve ONE task per coder call and call declare_scope for each task.${failureWarning}`;
42713
42735
  }
42714
42736
  function isParallelGuidancePhaseComplete(phase) {
42715
42737
  return phase.status === "complete" || phase.status === "completed" || phase.status === "closed";
@@ -42873,7 +42895,7 @@ function createDelegationGateHook(config2, directory) {
42873
42895
  return;
42874
42896
  const profile = plan.execution_profile;
42875
42897
  const parallelEnabled = profile?.parallelization_enabled === true;
42876
- const maxConcurrent = profile?.max_concurrent_tasks ?? 1;
42898
+ const maxConcurrent = profile?.max_concurrent_tasks ?? 10;
42877
42899
  const effectiveMaxConcurrent = session.maxConcurrencyOverride ?? maxConcurrent;
42878
42900
  if (!parallelEnabled || effectiveMaxConcurrent <= 1)
42879
42901
  return;
@@ -62433,6 +62455,9 @@ import * as path37 from "node:path";
62433
62455
  function resolveLogPath(directory) {
62434
62456
  return validateSwarmPath(directory, "skill-usage.jsonl");
62435
62457
  }
62458
+ function normalizeComplianceVerdict(verdict) {
62459
+ return verdict === "violation" ? "violated" : verdict;
62460
+ }
62436
62461
  function appendSkillUsageEntry(directory, entry) {
62437
62462
  const {
62438
62463
  skillPath,
@@ -62503,7 +62528,9 @@ function readSkillUsageEntries(directory, options) {
62503
62528
  if (!trimmed)
62504
62529
  continue;
62505
62530
  try {
62506
- entries.push(JSON.parse(trimmed));
62531
+ const entry = JSON.parse(trimmed);
62532
+ entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
62533
+ entries.push(entry);
62507
62534
  } catch {}
62508
62535
  }
62509
62536
  if (!options)
@@ -62562,6 +62589,7 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
62562
62589
  continue;
62563
62590
  try {
62564
62591
  const entry = JSON.parse(line);
62592
+ entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
62565
62593
  if (filters.sessionID !== undefined && entry.sessionID !== filters.sessionID) {
62566
62594
  continue;
62567
62595
  }
@@ -62596,7 +62624,7 @@ function computeComplianceByVersion(entries, skillPath) {
62596
62624
  stats.total += 1;
62597
62625
  if (e.complianceVerdict === "compliant")
62598
62626
  stats.compliant += 1;
62599
- if (e.complianceVerdict === "violation")
62627
+ if (e.complianceVerdict === "violated")
62600
62628
  stats.violation += 1;
62601
62629
  }
62602
62630
  for (const stats of map3.values()) {
@@ -62709,7 +62737,7 @@ async function applySkillUsageFeedback(directory, options) {
62709
62737
  try {
62710
62738
  const allEntries = readSkillUsageEntries(directory);
62711
62739
  const actionable = allEntries.filter((e) => {
62712
- if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "violation") {
62740
+ if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "violated") {
62713
62741
  return false;
62714
62742
  }
62715
62743
  if (options?.sinceTimestamp && e.timestamp <= options.sinceTimestamp) {
@@ -62735,7 +62763,7 @@ async function applySkillUsageFeedback(directory, options) {
62735
62763
  for (const entry of entries) {
62736
62764
  if (entry.complianceVerdict === "compliant")
62737
62765
  compliantCount++;
62738
- else if (entry.complianceVerdict === "violation")
62766
+ else if (entry.complianceVerdict === "violated")
62739
62767
  violationCount++;
62740
62768
  }
62741
62769
  if (compliantCount === 0 && violationCount === 0)
@@ -62819,7 +62847,7 @@ async function autoRetireSkills(directory, curatorKnowledgePath, excludeSlugs) {
62819
62847
  return true;
62820
62848
  return false;
62821
62849
  });
62822
- const violations = skillUsage.filter((e) => e.complianceVerdict === "violation").length;
62850
+ const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
62823
62851
  const violationRate = skillUsage.length > 0 ? violations / skillUsage.length : 0;
62824
62852
  let allArchived = false;
62825
62853
  try {
@@ -63484,7 +63512,7 @@ ${phaseDigest.summary}`,
63484
63512
  });
63485
63513
  if (skillUsage.length === 0)
63486
63514
  continue;
63487
- const violations = skillUsage.filter((e) => e.complianceVerdict === "violation").length;
63515
+ const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
63488
63516
  const violationRate = violations / skillUsage.length;
63489
63517
  if (violationRate > REVISION_VIOLATION_THRESHOLD && violationRate <= 0.3) {
63490
63518
  const content = await _internals27.readFileAsync(active.path, "utf-8");
@@ -63492,7 +63520,7 @@ ${phaseDigest.summary}`,
63492
63520
  if (fm && fm.skillOrigin === "promoted_external")
63493
63521
  continue;
63494
63522
  const currentVersion = fm?.version ?? 1;
63495
- const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "violation").slice(-10).map((e) => ({
63523
+ const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "violated").slice(-10).map((e) => ({
63496
63524
  taskId: e.taskID,
63497
63525
  agent: e.agentName,
63498
63526
  verdict: e.complianceVerdict,
@@ -68778,7 +68806,7 @@ function buildStatusMessage(session, plan) {
68778
68806
  const overrideActive = session.maxConcurrencyOverride !== undefined;
68779
68807
  const configuredOverride = session.maxConcurrencyOverride ?? "absent";
68780
68808
  const hasPlan = plan !== null && plan !== undefined;
68781
- const planBaseline = hasPlan ? plan.execution_profile?.max_concurrent_tasks ?? 1 : 1;
68809
+ const planBaseline = hasPlan ? plan.execution_profile?.max_concurrent_tasks ?? 10 : 10;
68782
68810
  const parallelizationEnabled = hasPlan ? plan.execution_profile?.parallelization_enabled ?? false : false;
68783
68811
  const operationalEffective = !parallelizationEnabled ? 1 : session.maxConcurrencyOverride ?? planBaseline;
68784
68812
  let description;
@@ -68807,8 +68835,8 @@ var init_concurrency = __esm(() => {
68807
68835
  init_state();
68808
68836
  PRESETS = {
68809
68837
  min: 1,
68810
- medium: 3,
68811
- max: 8
68838
+ medium: 8,
68839
+ max: 16
68812
68840
  };
68813
68841
  });
68814
68842
 
@@ -75193,8 +75221,8 @@ var init_history = __esm(() => {
75193
75221
  init_history_service();
75194
75222
  });
75195
75223
 
75196
- // src/commands/pr-ref.ts
75197
- import { execSync as execSync2 } from "node:child_process";
75224
+ // src/commands/_shared/url-security.ts
75225
+ import { spawnSync as spawnSync8 } from "node:child_process";
75198
75226
  function sanitizeUrl(raw) {
75199
75227
  let urlStr = raw.trim();
75200
75228
  urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
@@ -75212,13 +75240,29 @@ function sanitizeUrl(raw) {
75212
75240
  }
75213
75241
  return urlStr.trim();
75214
75242
  }
75215
- function sanitizeInstructions(raw) {
75216
- const collapsed = raw.replace(/\s+/g, " ").trim();
75217
- const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
75218
- const normalized = stripped.replace(/\s+/g, " ").trim();
75219
- if (normalized.length <= MAX_INSTRUCTIONS_LEN)
75220
- return normalized;
75221
- return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
75243
+ function sanitizeErrorEcho(raw, maxLength = 80) {
75244
+ let stripped = "";
75245
+ for (const ch of raw) {
75246
+ const cp = ch.codePointAt(0);
75247
+ if (cp !== undefined && (cp <= 31 || cp === 127)) {
75248
+ stripped += " ";
75249
+ continue;
75250
+ }
75251
+ stripped += ch;
75252
+ }
75253
+ const collapsed = stripped.replace(/\s+/g, " ").trim();
75254
+ if (collapsed.length <= maxLength)
75255
+ return collapsed;
75256
+ return `${collapsed.slice(0, maxLength)}…`;
75257
+ }
75258
+ function containsControlCharacters(value) {
75259
+ for (const ch of value) {
75260
+ const cp = ch.codePointAt(0);
75261
+ if (cp !== undefined && (cp <= 31 || cp === 127)) {
75262
+ return true;
75263
+ }
75264
+ }
75265
+ return false;
75222
75266
  }
75223
75267
  function hasNonAsciiHostname(hostname5) {
75224
75268
  for (const ch of hostname5) {
@@ -75228,31 +75272,38 @@ function hasNonAsciiHostname(hostname5) {
75228
75272
  }
75229
75273
  return false;
75230
75274
  }
75275
+ function isIpv4MappedPrivateHost(inner) {
75276
+ if (IPV4_PRIVATE.test(inner) || IPV4_LOOPBACK.test(inner) || IPV4_LINK_LOCAL.test(inner) || IPV4_PRIVATE_172.test(inner) || IPV4_PRIVATE_192.test(inner) || IPV4_ZERO_NETWORK.test(inner)) {
75277
+ return true;
75278
+ }
75279
+ const firstSegment = inner.split(":", 1)[0];
75280
+ if (!firstSegment)
75281
+ return false;
75282
+ const firstWord = Number.parseInt(firstSegment, 16);
75283
+ if (!Number.isFinite(firstWord))
75284
+ return false;
75285
+ return firstWord >= 0 && firstWord <= 255 || firstWord >= 2560 && firstWord <= 2815 || firstWord >= 32512 && firstWord <= 32767 || firstWord === 43518 || firstWord >= 44048 && firstWord <= 44063 || firstWord === 49320;
75286
+ }
75231
75287
  function isPrivateHost(url3) {
75232
- const host = url3.hostname.toLowerCase();
75233
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
75288
+ const host = url3.hostname.toLowerCase().replace(/^\[|\]$/g, "");
75289
+ if (host === "localhost" || host === "::1" || host === "0.0.0.0" || IPV4_LOOPBACK.test(host) || IPV4_ZERO_NETWORK.test(host)) {
75234
75290
  return true;
75235
75291
  }
75236
75292
  if (host.startsWith("localhost") || host === "localhost.com") {
75237
75293
  return true;
75238
75294
  }
75239
- const ipv4Private = /^10\./;
75240
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
75241
- const ipv4192 = /^192\.168\./;
75242
- const ipv6Private = /^fe80:/i;
75243
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
75244
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
75295
+ if (IPV4_PRIVATE.test(host) || IPV4_LINK_LOCAL.test(host) || IPV4_PRIVATE_172.test(host) || IPV4_PRIVATE_192.test(host) || IPV6_LINK_LOCAL.test(host) || IPV6_UNIQUE_LOCAL.test(host)) {
75245
75296
  return true;
75246
75297
  }
75247
75298
  if (host.startsWith("::ffff:")) {
75248
75299
  const inner = host.slice(7);
75249
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
75300
+ if (isIpv4MappedPrivateHost(inner)) {
75250
75301
  return true;
75251
75302
  }
75252
75303
  }
75253
75304
  return false;
75254
75305
  }
75255
- function validateAndSanitizeUrl(rawUrl) {
75306
+ function validateAndSanitizeGithubUrl(rawUrl, resource) {
75256
75307
  const sanitized = sanitizeUrl(rawUrl);
75257
75308
  if (!sanitized) {
75258
75309
  return { error: "Empty URL" };
@@ -75268,10 +75319,10 @@ function validateAndSanitizeUrl(rawUrl) {
75268
75319
  if (isPrivateHost(url3)) {
75269
75320
  return { error: "Private or localhost URLs are not allowed" };
75270
75321
  }
75271
- const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
75272
- if (!githubPrPattern.test(sanitized)) {
75322
+ const githubPattern = new RegExp(`^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/${resource}\\/([0-9]+)\\/?$`);
75323
+ if (!githubPattern.test(sanitized)) {
75273
75324
  return {
75274
- error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
75325
+ error: resource === "issues" ? "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)" : "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
75275
75326
  };
75276
75327
  }
75277
75328
  return { sanitized };
@@ -75279,50 +75330,18 @@ function validateAndSanitizeUrl(rawUrl) {
75279
75330
  return { error: "Invalid URL format" };
75280
75331
  }
75281
75332
  }
75282
- function parsePrRef(input, cwd) {
75283
- const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
75284
- if (urlMatch) {
75285
- return {
75286
- owner: urlMatch[1],
75287
- repo: urlMatch[2],
75288
- number: parseInt(urlMatch[3], 10)
75289
- };
75290
- }
75291
- const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
75292
- if (shorthandMatch) {
75293
- return {
75294
- owner: shorthandMatch[1],
75295
- repo: shorthandMatch[2],
75296
- number: parseInt(shorthandMatch[3], 10)
75297
- };
75298
- }
75299
- const bareMatch = input.match(/^(\d+)$/);
75300
- if (bareMatch) {
75301
- const prNumber = parseInt(bareMatch[1], 10);
75302
- const remoteUrl = detectGitRemote(cwd);
75303
- if (!remoteUrl) {
75304
- return null;
75305
- }
75306
- const parsed = parseGitRemoteUrl(remoteUrl);
75307
- if (!parsed) {
75308
- return null;
75309
- }
75310
- return {
75311
- owner: parsed.owner,
75312
- repo: parsed.repo,
75313
- number: prNumber
75314
- };
75315
- }
75316
- return null;
75317
- }
75318
75333
  function detectGitRemote(cwd) {
75319
75334
  try {
75320
- const remoteUrl = _internals39.execSync("git remote get-url origin", {
75335
+ const result = _internals39.spawnSync("git", ["remote", "get-url", "origin"], {
75321
75336
  encoding: "utf-8",
75322
- stdio: ["pipe", "pipe", "pipe"],
75337
+ stdio: ["ignore", "pipe", "pipe"],
75323
75338
  timeout: 5000,
75324
75339
  ...cwd ? { cwd } : {}
75325
- }).trim();
75340
+ });
75341
+ if (result.status !== 0 || result.error) {
75342
+ return null;
75343
+ }
75344
+ const remoteUrl = (result.stdout ?? "").trim();
75326
75345
  return remoteUrl || null;
75327
75346
  } catch {
75328
75347
  return null;
@@ -75331,123 +75350,49 @@ function detectGitRemote(cwd) {
75331
75350
  function parseGitRemoteUrl(remoteUrl) {
75332
75351
  const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
75333
75352
  if (httpsMatch) {
75334
- return {
75335
- owner: httpsMatch[1],
75336
- repo: httpsMatch[2].replace(/\.git$/, "")
75337
- };
75353
+ const owner = httpsMatch[1];
75354
+ const repo = httpsMatch[2].replace(/\.git$/, "");
75355
+ if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
75356
+ return null;
75357
+ }
75358
+ return { owner, repo };
75338
75359
  }
75339
75360
  const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
75340
75361
  if (sshMatch) {
75341
- return {
75342
- owner: sshMatch[1],
75343
- repo: sshMatch[2].replace(/\.git$/, "")
75344
- };
75362
+ const owner = sshMatch[1];
75363
+ const repo = sshMatch[2].replace(/\.git$/, "");
75364
+ if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
75365
+ return null;
75366
+ }
75367
+ return { owner, repo };
75345
75368
  }
75346
75369
  const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
75347
75370
  if (pathMatch) {
75348
- return {
75349
- owner: pathMatch[1],
75350
- repo: pathMatch[2].replace(/\.git$/, "")
75351
- };
75371
+ const owner = pathMatch[1];
75372
+ const repo = pathMatch[2].replace(/\.git$/, "");
75373
+ if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
75374
+ return null;
75375
+ }
75376
+ return { owner, repo };
75352
75377
  }
75353
75378
  return null;
75354
75379
  }
75355
- function looksLikePrRef(token) {
75356
- return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
75357
- }
75358
- function resolvePrCommandInput(rest, cwd) {
75359
- if (rest.length === 0) {
75360
- return null;
75361
- }
75362
- const refToken = rest[0];
75363
- const instructions = sanitizeInstructions(rest.slice(1).join(" "));
75364
- const isFullUrl = /^https?:\/\//i.test(refToken);
75365
- const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
75366
- if (!prInfo) {
75367
- return { error: `Could not parse PR reference from "${refToken}"` };
75368
- }
75369
- const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
75370
- const result = validateAndSanitizeUrl(prUrl);
75371
- if ("error" in result) {
75372
- return { error: result.error };
75373
- }
75374
- return { prUrl: result.sanitized, instructions };
75375
- }
75376
- var _internals39, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
75377
- var init_pr_ref = __esm(() => {
75378
- _internals39 = { execSync: execSync2 };
75380
+ var MAX_URL_LEN = 2048, IPV4_PRIVATE, IPV4_LOOPBACK, IPV4_LINK_LOCAL, IPV4_PRIVATE_172, IPV4_PRIVATE_192, IPV4_ZERO_NETWORK, IPV6_LINK_LOCAL, IPV6_UNIQUE_LOCAL, _internals39;
75381
+ var init_url_security = __esm(() => {
75382
+ IPV4_PRIVATE = /^10\./;
75383
+ IPV4_LOOPBACK = /^127\./;
75384
+ IPV4_LINK_LOCAL = /^169\.254\./;
75385
+ IPV4_PRIVATE_172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
75386
+ IPV4_PRIVATE_192 = /^192\.168\./;
75387
+ IPV4_ZERO_NETWORK = /^0\./;
75388
+ IPV6_LINK_LOCAL = /^fe80:/i;
75389
+ IPV6_UNIQUE_LOCAL = /^f[cd][0-9a-f]{2}:/i;
75390
+ _internals39 = { spawnSync: spawnSync8 };
75379
75391
  });
75380
75392
 
75381
75393
  // src/commands/issue.ts
75382
- import { execSync as execSync3 } from "node:child_process";
75383
- function sanitizeUrl2(raw) {
75384
- let urlStr = raw.trim();
75385
- urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
75386
- const fragmentIdx = urlStr.indexOf("#");
75387
- if (fragmentIdx !== -1) {
75388
- urlStr = urlStr.slice(0, fragmentIdx);
75389
- }
75390
- const queryIdx = urlStr.indexOf("?");
75391
- if (queryIdx !== -1) {
75392
- urlStr = urlStr.slice(0, queryIdx);
75393
- }
75394
- urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
75395
- if (urlStr.length > MAX_URL_LEN2) {
75396
- urlStr = urlStr.slice(0, MAX_URL_LEN2);
75397
- }
75398
- return urlStr.trim();
75399
- }
75400
- function isPrivateHost2(url3) {
75401
- const host = url3.hostname.toLowerCase();
75402
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
75403
- return true;
75404
- }
75405
- if (host.startsWith("localhost") || host === "localhost.com") {
75406
- return true;
75407
- }
75408
- const ipv4Private = /^10\./;
75409
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
75410
- const ipv4192 = /^192\.168\./;
75411
- const ipv6Private = /^fe80:/i;
75412
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
75413
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
75414
- return true;
75415
- }
75416
- if (host.startsWith("::ffff:")) {
75417
- const inner = host.slice(7);
75418
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
75419
- return true;
75420
- }
75421
- }
75422
- return false;
75423
- }
75424
- function validateAndSanitizeUrl2(rawUrl) {
75425
- const sanitized = sanitizeUrl2(rawUrl);
75426
- if (!sanitized) {
75427
- return { error: "Empty URL" };
75428
- }
75429
- if (!sanitized.startsWith("https://")) {
75430
- return { error: "URL must use HTTPS scheme" };
75431
- }
75432
- try {
75433
- const url3 = new URL(sanitized);
75434
- const hostname5 = url3.hostname;
75435
- if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
75436
- return { error: "Non-ASCII hostnames are not allowed" };
75437
- }
75438
- if (isPrivateHost2(url3)) {
75439
- return { error: "Private or localhost URLs are not allowed" };
75440
- }
75441
- const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
75442
- if (!githubIssuePattern.test(sanitized)) {
75443
- return {
75444
- error: "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)"
75445
- };
75446
- }
75447
- return { sanitized };
75448
- } catch {
75449
- return { error: "Invalid URL format" };
75450
- }
75394
+ function validateAndSanitizeUrl(rawUrl) {
75395
+ return validateAndSanitizeGithubUrl(rawUrl, "issues");
75451
75396
  }
75452
75397
  function parseArgs6(args2) {
75453
75398
  const out2 = {
@@ -75474,9 +75419,12 @@ function parseArgs6(args2) {
75474
75419
  }
75475
75420
  return out2;
75476
75421
  }
75477
- function parseIssueRef(input) {
75422
+ function parseIssueRef(input, directory) {
75478
75423
  const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/i);
75479
75424
  if (urlMatch) {
75425
+ if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
75426
+ return null;
75427
+ }
75480
75428
  return {
75481
75429
  owner: urlMatch[1],
75482
75430
  repo: urlMatch[2],
@@ -75485,6 +75433,9 @@ function parseIssueRef(input) {
75485
75433
  }
75486
75434
  const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
75487
75435
  if (shorthandMatch) {
75436
+ if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
75437
+ return null;
75438
+ }
75488
75439
  return {
75489
75440
  owner: shorthandMatch[1],
75490
75441
  repo: shorthandMatch[2],
@@ -75494,7 +75445,7 @@ function parseIssueRef(input) {
75494
75445
  const bareMatch = input.match(/^(\d+)$/);
75495
75446
  if (bareMatch) {
75496
75447
  const issueNumber = parseInt(bareMatch[1], 10);
75497
- const remoteUrl = detectGitRemote2();
75448
+ const remoteUrl = detectGitRemote(directory);
75498
75449
  if (!remoteUrl) {
75499
75450
  return null;
75500
75451
  }
@@ -75510,33 +75461,21 @@ function parseIssueRef(input) {
75510
75461
  }
75511
75462
  return null;
75512
75463
  }
75513
- function detectGitRemote2() {
75514
- try {
75515
- const remoteUrl = execSync3("git remote get-url origin", {
75516
- encoding: "utf-8",
75517
- stdio: ["pipe", "pipe", "pipe"],
75518
- timeout: 5000
75519
- }).trim();
75520
- return remoteUrl || null;
75521
- } catch {
75522
- return null;
75523
- }
75524
- }
75525
- function handleIssueCommand(_directory, args2) {
75464
+ function handleIssueCommand(directory, args2) {
75526
75465
  const parsed = parseArgs6(args2);
75527
75466
  const rawInput = parsed.rest.join(" ").trim();
75528
75467
  if (!rawInput) {
75529
75468
  return USAGE6;
75530
75469
  }
75531
75470
  const isFullUrl = /^https?:\/\//i.test(rawInput);
75532
- const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl2(rawInput) : rawInput);
75471
+ const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput, directory);
75533
75472
  if (!issueInfo) {
75534
- return `Error: Could not parse issue reference from "${rawInput}"
75473
+ return `Error: Could not parse issue reference from "${sanitizeErrorEcho(rawInput)}"
75535
75474
 
75536
75475
  ${USAGE6}`;
75537
75476
  }
75538
75477
  const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
75539
- const result = validateAndSanitizeUrl2(issueUrl);
75478
+ const result = validateAndSanitizeUrl(issueUrl);
75540
75479
  if ("error" in result) {
75541
75480
  return `Error: ${result.error}
75542
75481
 
@@ -75552,9 +75491,9 @@ ${USAGE6}`;
75552
75491
  const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
75553
75492
  return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
75554
75493
  }
75555
- var MAX_URL_LEN2 = 2048, USAGE6;
75494
+ var USAGE6;
75556
75495
  var init_issue = __esm(() => {
75557
- init_pr_ref();
75496
+ init_url_security();
75558
75497
  USAGE6 = [
75559
75498
  "Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
75560
75499
  "",
@@ -80737,6 +80676,88 @@ var init_post_mortem = __esm(() => {
80737
80676
  init_curator_postmortem();
80738
80677
  });
80739
80678
 
80679
+ // src/commands/pr-ref.ts
80680
+ function sanitizeInstructions(raw) {
80681
+ const collapsed = raw.replace(/\s+/g, " ").trim();
80682
+ const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
80683
+ const normalized = stripped.replace(/\s+/g, " ").trim();
80684
+ if (normalized.length <= MAX_INSTRUCTIONS_LEN)
80685
+ return normalized;
80686
+ return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
80687
+ }
80688
+ function validateAndSanitizeUrl2(rawUrl) {
80689
+ return validateAndSanitizeGithubUrl(rawUrl, "pull");
80690
+ }
80691
+ function parsePrRef(input, cwd) {
80692
+ const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
80693
+ if (urlMatch) {
80694
+ if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
80695
+ return null;
80696
+ }
80697
+ return {
80698
+ owner: urlMatch[1],
80699
+ repo: urlMatch[2],
80700
+ number: parseInt(urlMatch[3], 10)
80701
+ };
80702
+ }
80703
+ const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
80704
+ if (shorthandMatch) {
80705
+ if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
80706
+ return null;
80707
+ }
80708
+ return {
80709
+ owner: shorthandMatch[1],
80710
+ repo: shorthandMatch[2],
80711
+ number: parseInt(shorthandMatch[3], 10)
80712
+ };
80713
+ }
80714
+ const bareMatch = input.match(/^(\d+)$/);
80715
+ if (bareMatch) {
80716
+ const prNumber = parseInt(bareMatch[1], 10);
80717
+ const remoteUrl = detectGitRemote(cwd);
80718
+ if (!remoteUrl) {
80719
+ return null;
80720
+ }
80721
+ const parsed = parseGitRemoteUrl(remoteUrl);
80722
+ if (!parsed) {
80723
+ return null;
80724
+ }
80725
+ return {
80726
+ owner: parsed.owner,
80727
+ repo: parsed.repo,
80728
+ number: prNumber
80729
+ };
80730
+ }
80731
+ return null;
80732
+ }
80733
+ function looksLikePrRef(token) {
80734
+ return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
80735
+ }
80736
+ function resolvePrCommandInput(rest, cwd) {
80737
+ if (rest.length === 0) {
80738
+ return null;
80739
+ }
80740
+ const refToken = rest[0];
80741
+ const instructions = sanitizeInstructions(rest.slice(1).join(" "));
80742
+ const isFullUrl = /^https?:\/\//i.test(refToken);
80743
+ const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
80744
+ if (!prInfo) {
80745
+ return {
80746
+ error: `Could not parse PR reference from "${sanitizeErrorEcho(refToken)}"`
80747
+ };
80748
+ }
80749
+ const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
80750
+ const result = validateAndSanitizeUrl2(prUrl);
80751
+ if ("error" in result) {
80752
+ return { error: result.error };
80753
+ }
80754
+ return { prUrl: result.sanitized, instructions };
80755
+ }
80756
+ var MAX_INSTRUCTIONS_LEN = 1000;
80757
+ var init_pr_ref = __esm(() => {
80758
+ init_url_security();
80759
+ });
80760
+
80740
80761
  // src/commands/pr-feedback.ts
80741
80762
  function handlePrFeedbackCommand(directory, args2) {
80742
80763
  const rest = args2.filter((t) => t.trim().length > 0);
@@ -127845,7 +127866,7 @@ import * as fs103 from "node:fs";
127845
127866
  import * as path156 from "node:path";
127846
127867
 
127847
127868
  // src/mutation/engine.ts
127848
- import { spawnSync as spawnSync10 } from "node:child_process";
127869
+ import { spawnSync as spawnSync11 } from "node:child_process";
127849
127870
  import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync27 } from "node:fs";
127850
127871
  import * as path155 from "node:path";
127851
127872
 
@@ -128023,7 +128044,7 @@ var _internals88 = {
128023
128044
  executeMutation,
128024
128045
  computeReport,
128025
128046
  executeMutationSuite,
128026
- spawnSync: spawnSync10
128047
+ spawnSync: spawnSync11
128027
128048
  };
128028
128049
  async function executeMutation(patch, testCommand, testFiles, workingDir) {
128029
128050
  const startTime = Date.now();