opencode-swarm 7.73.1 → 7.73.3
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 +282 -202
- 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/knowledge-curator.d.ts +1 -0
- package/dist/hooks/knowledge-reinforcement.d.ts +10 -0
- package/dist/hooks/skill-usage-log.d.ts +13 -1
- package/dist/index.js +345 -223
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "opencode-swarm",
|
|
55
|
-
version: "7.73.
|
|
55
|
+
version: "7.73.3",
|
|
56
56
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
57
57
|
main: "dist/index.js",
|
|
58
58
|
types: "dist/index.d.ts",
|
|
@@ -40093,6 +40093,9 @@ import * as path19 from "path";
|
|
|
40093
40093
|
function resolveLogPath(directory) {
|
|
40094
40094
|
return validateSwarmPath(directory, "skill-usage.jsonl");
|
|
40095
40095
|
}
|
|
40096
|
+
function normalizeComplianceVerdict(verdict) {
|
|
40097
|
+
return verdict === "violation" ? "violated" : verdict;
|
|
40098
|
+
}
|
|
40096
40099
|
function appendSkillUsageEntry(directory, entry) {
|
|
40097
40100
|
const {
|
|
40098
40101
|
skillPath,
|
|
@@ -40163,7 +40166,9 @@ function readSkillUsageEntries(directory, options) {
|
|
|
40163
40166
|
if (!trimmed)
|
|
40164
40167
|
continue;
|
|
40165
40168
|
try {
|
|
40166
|
-
|
|
40169
|
+
const entry = JSON.parse(trimmed);
|
|
40170
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
40171
|
+
entries.push(entry);
|
|
40167
40172
|
} catch {}
|
|
40168
40173
|
}
|
|
40169
40174
|
if (!options)
|
|
@@ -40222,6 +40227,7 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
|
|
|
40222
40227
|
continue;
|
|
40223
40228
|
try {
|
|
40224
40229
|
const entry = JSON.parse(line);
|
|
40230
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
40225
40231
|
if (filters.sessionID !== undefined && entry.sessionID !== filters.sessionID) {
|
|
40226
40232
|
continue;
|
|
40227
40233
|
}
|
|
@@ -40256,7 +40262,7 @@ function computeComplianceByVersion(entries, skillPath) {
|
|
|
40256
40262
|
stats.total += 1;
|
|
40257
40263
|
if (e.complianceVerdict === "compliant")
|
|
40258
40264
|
stats.compliant += 1;
|
|
40259
|
-
if (e.complianceVerdict === "
|
|
40265
|
+
if (e.complianceVerdict === "violated")
|
|
40260
40266
|
stats.violation += 1;
|
|
40261
40267
|
}
|
|
40262
40268
|
for (const stats of map3.values()) {
|
|
@@ -40369,7 +40375,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
40369
40375
|
try {
|
|
40370
40376
|
const allEntries = readSkillUsageEntries(directory);
|
|
40371
40377
|
const actionable = allEntries.filter((e) => {
|
|
40372
|
-
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "
|
|
40378
|
+
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "violated") {
|
|
40373
40379
|
return false;
|
|
40374
40380
|
}
|
|
40375
40381
|
if (options?.sinceTimestamp && e.timestamp <= options.sinceTimestamp) {
|
|
@@ -40395,7 +40401,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
40395
40401
|
for (const entry of entries) {
|
|
40396
40402
|
if (entry.complianceVerdict === "compliant")
|
|
40397
40403
|
compliantCount++;
|
|
40398
|
-
else if (entry.complianceVerdict === "
|
|
40404
|
+
else if (entry.complianceVerdict === "violated")
|
|
40399
40405
|
violationCount++;
|
|
40400
40406
|
}
|
|
40401
40407
|
if (compliantCount === 0 && violationCount === 0)
|
|
@@ -40921,6 +40927,49 @@ var init_synonym_map = __esm(() => {
|
|
|
40921
40927
|
MIN_READ_CEILING_BYTES = 64 * 1024;
|
|
40922
40928
|
});
|
|
40923
40929
|
|
|
40930
|
+
// src/hooks/knowledge-reinforcement.ts
|
|
40931
|
+
function isActiveSwarmKnowledgeEntry(entry) {
|
|
40932
|
+
return !INACTIVE_STATUSES.has(entry.status);
|
|
40933
|
+
}
|
|
40934
|
+
function findActiveSwarmNearDuplicate(lesson, entries, threshold) {
|
|
40935
|
+
return findNearDuplicate(lesson, entries.filter(isActiveSwarmKnowledgeEntry), threshold);
|
|
40936
|
+
}
|
|
40937
|
+
function distinctPhaseCount(records) {
|
|
40938
|
+
const phases = new Set;
|
|
40939
|
+
for (const record3 of records ?? []) {
|
|
40940
|
+
if (Number.isInteger(record3.phase_number)) {
|
|
40941
|
+
phases.add(record3.phase_number);
|
|
40942
|
+
}
|
|
40943
|
+
}
|
|
40944
|
+
return phases.size;
|
|
40945
|
+
}
|
|
40946
|
+
function reinforceSwarmKnowledgeEntry(entry, confirmation) {
|
|
40947
|
+
if (!isActiveSwarmKnowledgeEntry(entry)) {
|
|
40948
|
+
return { entryId: entry.id, reinforced: false, reason: "inactive" };
|
|
40949
|
+
}
|
|
40950
|
+
if ((entry.confirmed_by ?? []).some((record3) => record3.phase_number === confirmation.phase_number)) {
|
|
40951
|
+
return {
|
|
40952
|
+
entryId: entry.id,
|
|
40953
|
+
reinforced: false,
|
|
40954
|
+
reason: "already_confirmed_phase"
|
|
40955
|
+
};
|
|
40956
|
+
}
|
|
40957
|
+
entry.confirmed_by = [...entry.confirmed_by ?? [], confirmation];
|
|
40958
|
+
entry.updated_at = confirmation.confirmed_at;
|
|
40959
|
+
entry.phases_alive = 0;
|
|
40960
|
+
entry.confidence = computeConfidence(distinctPhaseCount(entry.confirmed_by), entry.auto_generated ?? false);
|
|
40961
|
+
return { entryId: entry.id, reinforced: true, reason: "reinforced" };
|
|
40962
|
+
}
|
|
40963
|
+
var INACTIVE_STATUSES;
|
|
40964
|
+
var init_knowledge_reinforcement = __esm(() => {
|
|
40965
|
+
init_knowledge_store();
|
|
40966
|
+
INACTIVE_STATUSES = new Set([
|
|
40967
|
+
"archived",
|
|
40968
|
+
"quarantined",
|
|
40969
|
+
"quarantined_unactionable"
|
|
40970
|
+
]);
|
|
40971
|
+
});
|
|
40972
|
+
|
|
40924
40973
|
// src/hooks/skill-scoring.ts
|
|
40925
40974
|
import * as fs10 from "fs";
|
|
40926
40975
|
import * as path22 from "path";
|
|
@@ -42244,6 +42293,7 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
42244
42293
|
]);
|
|
42245
42294
|
const snapshotPlusNew = [...snapshot];
|
|
42246
42295
|
const toAdd = [];
|
|
42296
|
+
const pendingReinforcementIds = new Set;
|
|
42247
42297
|
for (const lesson of lessons) {
|
|
42248
42298
|
const tags = inferTags(lesson);
|
|
42249
42299
|
let category = "process";
|
|
@@ -42273,8 +42323,9 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
42273
42323
|
continue;
|
|
42274
42324
|
}
|
|
42275
42325
|
}
|
|
42276
|
-
const duplicate =
|
|
42326
|
+
const duplicate = findActiveSwarmNearDuplicate(lesson, snapshotPlusNew, config3.dedup_threshold);
|
|
42277
42327
|
if (duplicate) {
|
|
42328
|
+
pendingReinforcementIds.add(duplicate.id);
|
|
42278
42329
|
skipped++;
|
|
42279
42330
|
continue;
|
|
42280
42331
|
}
|
|
@@ -42355,7 +42406,9 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
42355
42406
|
} catch {}
|
|
42356
42407
|
continue;
|
|
42357
42408
|
}
|
|
42358
|
-
|
|
42409
|
+
const duplicate = findActiveSwarmNearDuplicate(entry.lesson, snapshotPlusNew, config3.dedup_threshold);
|
|
42410
|
+
if (duplicate) {
|
|
42411
|
+
pendingReinforcementIds.add(duplicate.id);
|
|
42359
42412
|
skipped++;
|
|
42360
42413
|
continue;
|
|
42361
42414
|
}
|
|
@@ -42364,15 +42417,48 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
42364
42417
|
}
|
|
42365
42418
|
} catch {}
|
|
42366
42419
|
let stored = 0;
|
|
42367
|
-
|
|
42420
|
+
let reinforced = 0;
|
|
42421
|
+
if (toAdd.length > 0 || pendingReinforcementIds.size > 0) {
|
|
42368
42422
|
await transactKnowledge(knowledgePath, (current) => {
|
|
42369
|
-
|
|
42370
|
-
const
|
|
42371
|
-
|
|
42372
|
-
|
|
42373
|
-
|
|
42374
|
-
|
|
42375
|
-
|
|
42423
|
+
let changed = false;
|
|
42424
|
+
for (const id of pendingReinforcementIds) {
|
|
42425
|
+
const existing = current.find((entry) => entry.id === id);
|
|
42426
|
+
if (!existing)
|
|
42427
|
+
continue;
|
|
42428
|
+
const result = reinforceSwarmKnowledgeEntry(existing, {
|
|
42429
|
+
phase_number: phaseInfo.phase_number,
|
|
42430
|
+
confirmed_at: new Date().toISOString(),
|
|
42431
|
+
project_name: projectName
|
|
42432
|
+
});
|
|
42433
|
+
if (result.reinforced) {
|
|
42434
|
+
reinforced++;
|
|
42435
|
+
changed = true;
|
|
42436
|
+
}
|
|
42437
|
+
}
|
|
42438
|
+
const trulyNew = [];
|
|
42439
|
+
for (const entry of toAdd) {
|
|
42440
|
+
const duplicate = findActiveSwarmNearDuplicate(entry.lesson, current, config3.dedup_threshold);
|
|
42441
|
+
if (duplicate) {
|
|
42442
|
+
skipped++;
|
|
42443
|
+
const result = reinforceSwarmKnowledgeEntry(duplicate, {
|
|
42444
|
+
phase_number: phaseInfo.phase_number,
|
|
42445
|
+
confirmed_at: new Date().toISOString(),
|
|
42446
|
+
project_name: projectName
|
|
42447
|
+
});
|
|
42448
|
+
if (result.reinforced) {
|
|
42449
|
+
reinforced++;
|
|
42450
|
+
changed = true;
|
|
42451
|
+
}
|
|
42452
|
+
continue;
|
|
42453
|
+
}
|
|
42454
|
+
trulyNew.push(entry);
|
|
42455
|
+
}
|
|
42456
|
+
if (trulyNew.length > 0) {
|
|
42457
|
+
current.push(...trulyNew);
|
|
42458
|
+
stored = trulyNew.length;
|
|
42459
|
+
changed = true;
|
|
42460
|
+
}
|
|
42461
|
+
return changed ? current : null;
|
|
42376
42462
|
});
|
|
42377
42463
|
}
|
|
42378
42464
|
await enforceKnowledgeCap(knowledgePath, config3.swarm_max_entries);
|
|
@@ -42390,7 +42476,7 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
42390
42476
|
if (!options?.skipAutoPromotion) {
|
|
42391
42477
|
await _internals19.runAutoPromotion(directory, config3);
|
|
42392
42478
|
}
|
|
42393
|
-
return { stored, skipped, rejected, quarantined };
|
|
42479
|
+
return { stored, reinforced, skipped, rejected, quarantined };
|
|
42394
42480
|
}
|
|
42395
42481
|
async function runAutoPromotion(directory, config3) {
|
|
42396
42482
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
@@ -42503,6 +42589,7 @@ var init_knowledge_curator = __esm(() => {
|
|
|
42503
42589
|
init_synonym_map();
|
|
42504
42590
|
init_logger();
|
|
42505
42591
|
init_knowledge_events();
|
|
42592
|
+
init_knowledge_reinforcement();
|
|
42506
42593
|
init_knowledge_store();
|
|
42507
42594
|
init_knowledge_validator();
|
|
42508
42595
|
init_micro_reflector();
|
|
@@ -51391,8 +51478,8 @@ var init_history = __esm(() => {
|
|
|
51391
51478
|
init_history_service();
|
|
51392
51479
|
});
|
|
51393
51480
|
|
|
51394
|
-
// src/commands/
|
|
51395
|
-
import {
|
|
51481
|
+
// src/commands/_shared/url-security.ts
|
|
51482
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
51396
51483
|
function sanitizeUrl(raw) {
|
|
51397
51484
|
let urlStr = raw.trim();
|
|
51398
51485
|
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
@@ -51410,13 +51497,29 @@ function sanitizeUrl(raw) {
|
|
|
51410
51497
|
}
|
|
51411
51498
|
return urlStr.trim();
|
|
51412
51499
|
}
|
|
51413
|
-
function
|
|
51414
|
-
|
|
51415
|
-
const
|
|
51416
|
-
|
|
51417
|
-
|
|
51418
|
-
|
|
51419
|
-
|
|
51500
|
+
function sanitizeErrorEcho(raw, maxLength = 80) {
|
|
51501
|
+
let stripped = "";
|
|
51502
|
+
for (const ch of raw) {
|
|
51503
|
+
const cp = ch.codePointAt(0);
|
|
51504
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
51505
|
+
stripped += " ";
|
|
51506
|
+
continue;
|
|
51507
|
+
}
|
|
51508
|
+
stripped += ch;
|
|
51509
|
+
}
|
|
51510
|
+
const collapsed = stripped.replace(/\s+/g, " ").trim();
|
|
51511
|
+
if (collapsed.length <= maxLength)
|
|
51512
|
+
return collapsed;
|
|
51513
|
+
return `${collapsed.slice(0, maxLength)}\u2026`;
|
|
51514
|
+
}
|
|
51515
|
+
function containsControlCharacters(value) {
|
|
51516
|
+
for (const ch of value) {
|
|
51517
|
+
const cp = ch.codePointAt(0);
|
|
51518
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
51519
|
+
return true;
|
|
51520
|
+
}
|
|
51521
|
+
}
|
|
51522
|
+
return false;
|
|
51420
51523
|
}
|
|
51421
51524
|
function hasNonAsciiHostname(hostname5) {
|
|
51422
51525
|
for (const ch of hostname5) {
|
|
@@ -51426,31 +51529,38 @@ function hasNonAsciiHostname(hostname5) {
|
|
|
51426
51529
|
}
|
|
51427
51530
|
return false;
|
|
51428
51531
|
}
|
|
51532
|
+
function isIpv4MappedPrivateHost(inner) {
|
|
51533
|
+
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)) {
|
|
51534
|
+
return true;
|
|
51535
|
+
}
|
|
51536
|
+
const firstSegment = inner.split(":", 1)[0];
|
|
51537
|
+
if (!firstSegment)
|
|
51538
|
+
return false;
|
|
51539
|
+
const firstWord = Number.parseInt(firstSegment, 16);
|
|
51540
|
+
if (!Number.isFinite(firstWord))
|
|
51541
|
+
return false;
|
|
51542
|
+
return firstWord >= 0 && firstWord <= 255 || firstWord >= 2560 && firstWord <= 2815 || firstWord >= 32512 && firstWord <= 32767 || firstWord === 43518 || firstWord >= 44048 && firstWord <= 44063 || firstWord === 49320;
|
|
51543
|
+
}
|
|
51429
51544
|
function isPrivateHost(url3) {
|
|
51430
|
-
const host = url3.hostname.toLowerCase();
|
|
51431
|
-
if (host === "localhost" || host === "
|
|
51545
|
+
const host = url3.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
51546
|
+
if (host === "localhost" || host === "::1" || host === "0.0.0.0" || IPV4_LOOPBACK.test(host) || IPV4_ZERO_NETWORK.test(host)) {
|
|
51432
51547
|
return true;
|
|
51433
51548
|
}
|
|
51434
51549
|
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
51435
51550
|
return true;
|
|
51436
51551
|
}
|
|
51437
|
-
|
|
51438
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
51439
|
-
const ipv4192 = /^192\.168\./;
|
|
51440
|
-
const ipv6Private = /^fe80:/i;
|
|
51441
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
51442
|
-
if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
|
|
51552
|
+
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)) {
|
|
51443
51553
|
return true;
|
|
51444
51554
|
}
|
|
51445
51555
|
if (host.startsWith("::ffff:")) {
|
|
51446
51556
|
const inner = host.slice(7);
|
|
51447
|
-
if (
|
|
51557
|
+
if (isIpv4MappedPrivateHost(inner)) {
|
|
51448
51558
|
return true;
|
|
51449
51559
|
}
|
|
51450
51560
|
}
|
|
51451
51561
|
return false;
|
|
51452
51562
|
}
|
|
51453
|
-
function
|
|
51563
|
+
function validateAndSanitizeGithubUrl(rawUrl, resource) {
|
|
51454
51564
|
const sanitized = sanitizeUrl(rawUrl);
|
|
51455
51565
|
if (!sanitized) {
|
|
51456
51566
|
return { error: "Empty URL" };
|
|
@@ -51466,10 +51576,10 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
51466
51576
|
if (isPrivateHost(url3)) {
|
|
51467
51577
|
return { error: "Private or localhost URLs are not allowed" };
|
|
51468
51578
|
}
|
|
51469
|
-
const
|
|
51470
|
-
if (!
|
|
51579
|
+
const githubPattern = new RegExp(`^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/${resource}\\/([0-9]+)\\/?$`);
|
|
51580
|
+
if (!githubPattern.test(sanitized)) {
|
|
51471
51581
|
return {
|
|
51472
|
-
error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
|
|
51582
|
+
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)"
|
|
51473
51583
|
};
|
|
51474
51584
|
}
|
|
51475
51585
|
return { sanitized };
|
|
@@ -51477,50 +51587,18 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
51477
51587
|
return { error: "Invalid URL format" };
|
|
51478
51588
|
}
|
|
51479
51589
|
}
|
|
51480
|
-
function parsePrRef(input, cwd) {
|
|
51481
|
-
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
51482
|
-
if (urlMatch) {
|
|
51483
|
-
return {
|
|
51484
|
-
owner: urlMatch[1],
|
|
51485
|
-
repo: urlMatch[2],
|
|
51486
|
-
number: parseInt(urlMatch[3], 10)
|
|
51487
|
-
};
|
|
51488
|
-
}
|
|
51489
|
-
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
51490
|
-
if (shorthandMatch) {
|
|
51491
|
-
return {
|
|
51492
|
-
owner: shorthandMatch[1],
|
|
51493
|
-
repo: shorthandMatch[2],
|
|
51494
|
-
number: parseInt(shorthandMatch[3], 10)
|
|
51495
|
-
};
|
|
51496
|
-
}
|
|
51497
|
-
const bareMatch = input.match(/^(\d+)$/);
|
|
51498
|
-
if (bareMatch) {
|
|
51499
|
-
const prNumber = parseInt(bareMatch[1], 10);
|
|
51500
|
-
const remoteUrl = detectGitRemote(cwd);
|
|
51501
|
-
if (!remoteUrl) {
|
|
51502
|
-
return null;
|
|
51503
|
-
}
|
|
51504
|
-
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
51505
|
-
if (!parsed) {
|
|
51506
|
-
return null;
|
|
51507
|
-
}
|
|
51508
|
-
return {
|
|
51509
|
-
owner: parsed.owner,
|
|
51510
|
-
repo: parsed.repo,
|
|
51511
|
-
number: prNumber
|
|
51512
|
-
};
|
|
51513
|
-
}
|
|
51514
|
-
return null;
|
|
51515
|
-
}
|
|
51516
51590
|
function detectGitRemote(cwd) {
|
|
51517
51591
|
try {
|
|
51518
|
-
const
|
|
51592
|
+
const result = _internals28.spawnSync("git", ["remote", "get-url", "origin"], {
|
|
51519
51593
|
encoding: "utf-8",
|
|
51520
|
-
stdio: ["
|
|
51594
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51521
51595
|
timeout: 5000,
|
|
51522
51596
|
...cwd ? { cwd } : {}
|
|
51523
|
-
})
|
|
51597
|
+
});
|
|
51598
|
+
if (result.status !== 0 || result.error) {
|
|
51599
|
+
return null;
|
|
51600
|
+
}
|
|
51601
|
+
const remoteUrl = (result.stdout ?? "").trim();
|
|
51524
51602
|
return remoteUrl || null;
|
|
51525
51603
|
} catch {
|
|
51526
51604
|
return null;
|
|
@@ -51529,123 +51607,49 @@ function detectGitRemote(cwd) {
|
|
|
51529
51607
|
function parseGitRemoteUrl(remoteUrl) {
|
|
51530
51608
|
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
|
|
51531
51609
|
if (httpsMatch) {
|
|
51532
|
-
|
|
51533
|
-
|
|
51534
|
-
|
|
51535
|
-
|
|
51610
|
+
const owner = httpsMatch[1];
|
|
51611
|
+
const repo = httpsMatch[2].replace(/\.git$/, "");
|
|
51612
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
51613
|
+
return null;
|
|
51614
|
+
}
|
|
51615
|
+
return { owner, repo };
|
|
51536
51616
|
}
|
|
51537
51617
|
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
51538
51618
|
if (sshMatch) {
|
|
51539
|
-
|
|
51540
|
-
|
|
51541
|
-
|
|
51542
|
-
|
|
51619
|
+
const owner = sshMatch[1];
|
|
51620
|
+
const repo = sshMatch[2].replace(/\.git$/, "");
|
|
51621
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
51622
|
+
return null;
|
|
51623
|
+
}
|
|
51624
|
+
return { owner, repo };
|
|
51543
51625
|
}
|
|
51544
51626
|
const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
51545
51627
|
if (pathMatch) {
|
|
51546
|
-
|
|
51547
|
-
|
|
51548
|
-
|
|
51549
|
-
|
|
51628
|
+
const owner = pathMatch[1];
|
|
51629
|
+
const repo = pathMatch[2].replace(/\.git$/, "");
|
|
51630
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
51631
|
+
return null;
|
|
51632
|
+
}
|
|
51633
|
+
return { owner, repo };
|
|
51550
51634
|
}
|
|
51551
51635
|
return null;
|
|
51552
51636
|
}
|
|
51553
|
-
|
|
51554
|
-
|
|
51555
|
-
|
|
51556
|
-
|
|
51557
|
-
|
|
51558
|
-
|
|
51559
|
-
|
|
51560
|
-
|
|
51561
|
-
|
|
51562
|
-
|
|
51563
|
-
|
|
51564
|
-
if (!prInfo) {
|
|
51565
|
-
return { error: `Could not parse PR reference from "${refToken}"` };
|
|
51566
|
-
}
|
|
51567
|
-
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
51568
|
-
const result = validateAndSanitizeUrl(prUrl);
|
|
51569
|
-
if ("error" in result) {
|
|
51570
|
-
return { error: result.error };
|
|
51571
|
-
}
|
|
51572
|
-
return { prUrl: result.sanitized, instructions };
|
|
51573
|
-
}
|
|
51574
|
-
var _internals28, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
|
|
51575
|
-
var init_pr_ref = __esm(() => {
|
|
51576
|
-
_internals28 = { execSync: execSync2 };
|
|
51637
|
+
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, _internals28;
|
|
51638
|
+
var init_url_security = __esm(() => {
|
|
51639
|
+
IPV4_PRIVATE = /^10\./;
|
|
51640
|
+
IPV4_LOOPBACK = /^127\./;
|
|
51641
|
+
IPV4_LINK_LOCAL = /^169\.254\./;
|
|
51642
|
+
IPV4_PRIVATE_172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
51643
|
+
IPV4_PRIVATE_192 = /^192\.168\./;
|
|
51644
|
+
IPV4_ZERO_NETWORK = /^0\./;
|
|
51645
|
+
IPV6_LINK_LOCAL = /^fe80:/i;
|
|
51646
|
+
IPV6_UNIQUE_LOCAL = /^f[cd][0-9a-f]{2}:/i;
|
|
51647
|
+
_internals28 = { spawnSync: spawnSync3 };
|
|
51577
51648
|
});
|
|
51578
51649
|
|
|
51579
51650
|
// src/commands/issue.ts
|
|
51580
|
-
|
|
51581
|
-
|
|
51582
|
-
let urlStr = raw.trim();
|
|
51583
|
-
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
51584
|
-
const fragmentIdx = urlStr.indexOf("#");
|
|
51585
|
-
if (fragmentIdx !== -1) {
|
|
51586
|
-
urlStr = urlStr.slice(0, fragmentIdx);
|
|
51587
|
-
}
|
|
51588
|
-
const queryIdx = urlStr.indexOf("?");
|
|
51589
|
-
if (queryIdx !== -1) {
|
|
51590
|
-
urlStr = urlStr.slice(0, queryIdx);
|
|
51591
|
-
}
|
|
51592
|
-
urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
|
|
51593
|
-
if (urlStr.length > MAX_URL_LEN2) {
|
|
51594
|
-
urlStr = urlStr.slice(0, MAX_URL_LEN2);
|
|
51595
|
-
}
|
|
51596
|
-
return urlStr.trim();
|
|
51597
|
-
}
|
|
51598
|
-
function isPrivateHost2(url3) {
|
|
51599
|
-
const host = url3.hostname.toLowerCase();
|
|
51600
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
|
|
51601
|
-
return true;
|
|
51602
|
-
}
|
|
51603
|
-
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
51604
|
-
return true;
|
|
51605
|
-
}
|
|
51606
|
-
const ipv4Private = /^10\./;
|
|
51607
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
51608
|
-
const ipv4192 = /^192\.168\./;
|
|
51609
|
-
const ipv6Private = /^fe80:/i;
|
|
51610
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
51611
|
-
if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
|
|
51612
|
-
return true;
|
|
51613
|
-
}
|
|
51614
|
-
if (host.startsWith("::ffff:")) {
|
|
51615
|
-
const inner = host.slice(7);
|
|
51616
|
-
if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
|
|
51617
|
-
return true;
|
|
51618
|
-
}
|
|
51619
|
-
}
|
|
51620
|
-
return false;
|
|
51621
|
-
}
|
|
51622
|
-
function validateAndSanitizeUrl2(rawUrl) {
|
|
51623
|
-
const sanitized = sanitizeUrl2(rawUrl);
|
|
51624
|
-
if (!sanitized) {
|
|
51625
|
-
return { error: "Empty URL" };
|
|
51626
|
-
}
|
|
51627
|
-
if (!sanitized.startsWith("https://")) {
|
|
51628
|
-
return { error: "URL must use HTTPS scheme" };
|
|
51629
|
-
}
|
|
51630
|
-
try {
|
|
51631
|
-
const url3 = new URL(sanitized);
|
|
51632
|
-
const hostname5 = url3.hostname;
|
|
51633
|
-
if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
|
|
51634
|
-
return { error: "Non-ASCII hostnames are not allowed" };
|
|
51635
|
-
}
|
|
51636
|
-
if (isPrivateHost2(url3)) {
|
|
51637
|
-
return { error: "Private or localhost URLs are not allowed" };
|
|
51638
|
-
}
|
|
51639
|
-
const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
|
|
51640
|
-
if (!githubIssuePattern.test(sanitized)) {
|
|
51641
|
-
return {
|
|
51642
|
-
error: "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)"
|
|
51643
|
-
};
|
|
51644
|
-
}
|
|
51645
|
-
return { sanitized };
|
|
51646
|
-
} catch {
|
|
51647
|
-
return { error: "Invalid URL format" };
|
|
51648
|
-
}
|
|
51651
|
+
function validateAndSanitizeUrl(rawUrl) {
|
|
51652
|
+
return validateAndSanitizeGithubUrl(rawUrl, "issues");
|
|
51649
51653
|
}
|
|
51650
51654
|
function parseArgs6(args) {
|
|
51651
51655
|
const out = {
|
|
@@ -51672,9 +51676,12 @@ function parseArgs6(args) {
|
|
|
51672
51676
|
}
|
|
51673
51677
|
return out;
|
|
51674
51678
|
}
|
|
51675
|
-
function parseIssueRef(input) {
|
|
51679
|
+
function parseIssueRef(input, directory) {
|
|
51676
51680
|
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/i);
|
|
51677
51681
|
if (urlMatch) {
|
|
51682
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
51683
|
+
return null;
|
|
51684
|
+
}
|
|
51678
51685
|
return {
|
|
51679
51686
|
owner: urlMatch[1],
|
|
51680
51687
|
repo: urlMatch[2],
|
|
@@ -51683,6 +51690,9 @@ function parseIssueRef(input) {
|
|
|
51683
51690
|
}
|
|
51684
51691
|
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
51685
51692
|
if (shorthandMatch) {
|
|
51693
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
51694
|
+
return null;
|
|
51695
|
+
}
|
|
51686
51696
|
return {
|
|
51687
51697
|
owner: shorthandMatch[1],
|
|
51688
51698
|
repo: shorthandMatch[2],
|
|
@@ -51692,7 +51702,7 @@ function parseIssueRef(input) {
|
|
|
51692
51702
|
const bareMatch = input.match(/^(\d+)$/);
|
|
51693
51703
|
if (bareMatch) {
|
|
51694
51704
|
const issueNumber = parseInt(bareMatch[1], 10);
|
|
51695
|
-
const remoteUrl =
|
|
51705
|
+
const remoteUrl = detectGitRemote(directory);
|
|
51696
51706
|
if (!remoteUrl) {
|
|
51697
51707
|
return null;
|
|
51698
51708
|
}
|
|
@@ -51708,33 +51718,21 @@ function parseIssueRef(input) {
|
|
|
51708
51718
|
}
|
|
51709
51719
|
return null;
|
|
51710
51720
|
}
|
|
51711
|
-
function
|
|
51712
|
-
try {
|
|
51713
|
-
const remoteUrl = execSync3("git remote get-url origin", {
|
|
51714
|
-
encoding: "utf-8",
|
|
51715
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
51716
|
-
timeout: 5000
|
|
51717
|
-
}).trim();
|
|
51718
|
-
return remoteUrl || null;
|
|
51719
|
-
} catch {
|
|
51720
|
-
return null;
|
|
51721
|
-
}
|
|
51722
|
-
}
|
|
51723
|
-
function handleIssueCommand(_directory, args) {
|
|
51721
|
+
function handleIssueCommand(directory, args) {
|
|
51724
51722
|
const parsed = parseArgs6(args);
|
|
51725
51723
|
const rawInput = parsed.rest.join(" ").trim();
|
|
51726
51724
|
if (!rawInput) {
|
|
51727
51725
|
return USAGE6;
|
|
51728
51726
|
}
|
|
51729
51727
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
51730
|
-
const issueInfo = parseIssueRef(isFullUrl ?
|
|
51728
|
+
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput, directory);
|
|
51731
51729
|
if (!issueInfo) {
|
|
51732
|
-
return `Error: Could not parse issue reference from "${rawInput}"
|
|
51730
|
+
return `Error: Could not parse issue reference from "${sanitizeErrorEcho(rawInput)}"
|
|
51733
51731
|
|
|
51734
51732
|
${USAGE6}`;
|
|
51735
51733
|
}
|
|
51736
51734
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
51737
|
-
const result =
|
|
51735
|
+
const result = validateAndSanitizeUrl(issueUrl);
|
|
51738
51736
|
if ("error" in result) {
|
|
51739
51737
|
return `Error: ${result.error}
|
|
51740
51738
|
|
|
@@ -51750,9 +51748,9 @@ ${USAGE6}`;
|
|
|
51750
51748
|
const flagsStr = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
|
51751
51749
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
51752
51750
|
}
|
|
51753
|
-
var
|
|
51751
|
+
var USAGE6;
|
|
51754
51752
|
var init_issue = __esm(() => {
|
|
51755
|
-
|
|
51753
|
+
init_url_security();
|
|
51756
51754
|
USAGE6 = [
|
|
51757
51755
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
51758
51756
|
"",
|
|
@@ -55944,6 +55942,88 @@ var init_post_mortem = __esm(() => {
|
|
|
55944
55942
|
init_curator_postmortem();
|
|
55945
55943
|
});
|
|
55946
55944
|
|
|
55945
|
+
// src/commands/pr-ref.ts
|
|
55946
|
+
function sanitizeInstructions(raw) {
|
|
55947
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
55948
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
55949
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
55950
|
+
if (normalized.length <= MAX_INSTRUCTIONS_LEN)
|
|
55951
|
+
return normalized;
|
|
55952
|
+
return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}\u2026`;
|
|
55953
|
+
}
|
|
55954
|
+
function validateAndSanitizeUrl2(rawUrl) {
|
|
55955
|
+
return validateAndSanitizeGithubUrl(rawUrl, "pull");
|
|
55956
|
+
}
|
|
55957
|
+
function parsePrRef(input, cwd) {
|
|
55958
|
+
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
55959
|
+
if (urlMatch) {
|
|
55960
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
55961
|
+
return null;
|
|
55962
|
+
}
|
|
55963
|
+
return {
|
|
55964
|
+
owner: urlMatch[1],
|
|
55965
|
+
repo: urlMatch[2],
|
|
55966
|
+
number: parseInt(urlMatch[3], 10)
|
|
55967
|
+
};
|
|
55968
|
+
}
|
|
55969
|
+
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
55970
|
+
if (shorthandMatch) {
|
|
55971
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
55972
|
+
return null;
|
|
55973
|
+
}
|
|
55974
|
+
return {
|
|
55975
|
+
owner: shorthandMatch[1],
|
|
55976
|
+
repo: shorthandMatch[2],
|
|
55977
|
+
number: parseInt(shorthandMatch[3], 10)
|
|
55978
|
+
};
|
|
55979
|
+
}
|
|
55980
|
+
const bareMatch = input.match(/^(\d+)$/);
|
|
55981
|
+
if (bareMatch) {
|
|
55982
|
+
const prNumber = parseInt(bareMatch[1], 10);
|
|
55983
|
+
const remoteUrl = detectGitRemote(cwd);
|
|
55984
|
+
if (!remoteUrl) {
|
|
55985
|
+
return null;
|
|
55986
|
+
}
|
|
55987
|
+
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
55988
|
+
if (!parsed) {
|
|
55989
|
+
return null;
|
|
55990
|
+
}
|
|
55991
|
+
return {
|
|
55992
|
+
owner: parsed.owner,
|
|
55993
|
+
repo: parsed.repo,
|
|
55994
|
+
number: prNumber
|
|
55995
|
+
};
|
|
55996
|
+
}
|
|
55997
|
+
return null;
|
|
55998
|
+
}
|
|
55999
|
+
function looksLikePrRef(token) {
|
|
56000
|
+
return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
|
|
56001
|
+
}
|
|
56002
|
+
function resolvePrCommandInput(rest, cwd) {
|
|
56003
|
+
if (rest.length === 0) {
|
|
56004
|
+
return null;
|
|
56005
|
+
}
|
|
56006
|
+
const refToken = rest[0];
|
|
56007
|
+
const instructions = sanitizeInstructions(rest.slice(1).join(" "));
|
|
56008
|
+
const isFullUrl = /^https?:\/\//i.test(refToken);
|
|
56009
|
+
const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
|
|
56010
|
+
if (!prInfo) {
|
|
56011
|
+
return {
|
|
56012
|
+
error: `Could not parse PR reference from "${sanitizeErrorEcho(refToken)}"`
|
|
56013
|
+
};
|
|
56014
|
+
}
|
|
56015
|
+
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
56016
|
+
const result = validateAndSanitizeUrl2(prUrl);
|
|
56017
|
+
if ("error" in result) {
|
|
56018
|
+
return { error: result.error };
|
|
56019
|
+
}
|
|
56020
|
+
return { prUrl: result.sanitized, instructions };
|
|
56021
|
+
}
|
|
56022
|
+
var MAX_INSTRUCTIONS_LEN = 1000;
|
|
56023
|
+
var init_pr_ref = __esm(() => {
|
|
56024
|
+
init_url_security();
|
|
56025
|
+
});
|
|
56026
|
+
|
|
55947
56027
|
// src/commands/pr-feedback.ts
|
|
55948
56028
|
function handlePrFeedbackCommand(directory, args) {
|
|
55949
56029
|
const rest = args.filter((t) => t.trim().length > 0);
|