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/.opencode/skills/swarm-pr-feedback/SKILL.md +19 -0
- package/dist/cli/index.js +194 -195
- package/dist/commands/_shared/url-security.d.ts +44 -0
- package/dist/commands/issue.d.ts +1 -1
- package/dist/commands/pr-ref.d.ts +2 -45
- package/dist/hooks/skill-usage-log.d.ts +13 -1
- package/dist/index.js +226 -205
- package/package.json +1 -1
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.
|
|
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(
|
|
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 ??
|
|
42666
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
-
|
|
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 === "
|
|
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 !== "
|
|
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 === "
|
|
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 === "
|
|
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 === "
|
|
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 === "
|
|
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 ??
|
|
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:
|
|
68811
|
-
max:
|
|
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/
|
|
75197
|
-
import {
|
|
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
|
|
75216
|
-
|
|
75217
|
-
const
|
|
75218
|
-
|
|
75219
|
-
|
|
75220
|
-
|
|
75221
|
-
|
|
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 === "
|
|
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
|
-
|
|
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 (
|
|
75300
|
+
if (isIpv4MappedPrivateHost(inner)) {
|
|
75250
75301
|
return true;
|
|
75251
75302
|
}
|
|
75252
75303
|
}
|
|
75253
75304
|
return false;
|
|
75254
75305
|
}
|
|
75255
|
-
function
|
|
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
|
|
75272
|
-
if (!
|
|
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
|
|
75335
|
+
const result = _internals39.spawnSync("git", ["remote", "get-url", "origin"], {
|
|
75321
75336
|
encoding: "utf-8",
|
|
75322
|
-
stdio: ["
|
|
75337
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
75323
75338
|
timeout: 5000,
|
|
75324
75339
|
...cwd ? { cwd } : {}
|
|
75325
|
-
})
|
|
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
|
-
|
|
75335
|
-
|
|
75336
|
-
|
|
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
|
-
|
|
75342
|
-
|
|
75343
|
-
|
|
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
|
-
|
|
75349
|
-
|
|
75350
|
-
|
|
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
|
-
|
|
75356
|
-
|
|
75357
|
-
|
|
75358
|
-
|
|
75359
|
-
|
|
75360
|
-
|
|
75361
|
-
|
|
75362
|
-
|
|
75363
|
-
|
|
75364
|
-
|
|
75365
|
-
|
|
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
|
-
|
|
75383
|
-
|
|
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 =
|
|
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
|
|
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 ?
|
|
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 =
|
|
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
|
|
75494
|
+
var USAGE6;
|
|
75556
75495
|
var init_issue = __esm(() => {
|
|
75557
|
-
|
|
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
|
|
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:
|
|
128047
|
+
spawnSync: spawnSync11
|
|
128027
128048
|
};
|
|
128028
128049
|
async function executeMutation(patch, testCommand, testFiles, workingDir) {
|
|
128029
128050
|
const startTime = Date.now();
|