opencode-swarm 7.73.2 → 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 +190 -191
- 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 +195 -196
- package/package.json +1 -1
|
@@ -61,6 +61,25 @@ final state. Expect N rounds of review for N pushes, and budget for it.
|
|
|
61
61
|
each round's work is bounded by new findings + carried-forward items only.
|
|
62
62
|
This matches how the bot actually behaves and avoids wasted cycles.
|
|
63
63
|
|
|
64
|
+
### Bot Review Verification Traps
|
|
65
|
+
|
|
66
|
+
When a bot or pasted review cites a code fact, verify the fact against the
|
|
67
|
+
current branch before editing:
|
|
68
|
+
|
|
69
|
+
- **Import/export claims:** Check the exact import path used by the changed file.
|
|
70
|
+
A symbol may be missing from an internal submodule but correctly exported by the
|
|
71
|
+
public barrel the tests or runtime actually import.
|
|
72
|
+
- **Line numbers:** Treat bot line references as approximate after any follow-up
|
|
73
|
+
push or local edit. Re-locate the symbol or block with `rg` before patching.
|
|
74
|
+
- **Ordering claims:** If the concern is about rule precedence, add or run a
|
|
75
|
+
direct precedence test that would fail under the wrong ordering; comments alone
|
|
76
|
+
are not enough.
|
|
77
|
+
- **Disproved findings:** Do not change unrelated code to satisfy a false claim.
|
|
78
|
+
Keep the finding in the closure ledger with the source or test evidence that
|
|
79
|
+
disproves it.
|
|
80
|
+
- **Cache/state claims:** Test both relevant state orders when the behavior
|
|
81
|
+
depends on cache priming, singleton state, or prior calls.
|
|
82
|
+
|
|
64
83
|
## Operating Stance
|
|
65
84
|
|
|
66
85
|
Treat every review comment, CI failure, bot summary, PR body claim, and pasted note
|
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)
|
|
@@ -51472,8 +51478,8 @@ var init_history = __esm(() => {
|
|
|
51472
51478
|
init_history_service();
|
|
51473
51479
|
});
|
|
51474
51480
|
|
|
51475
|
-
// src/commands/
|
|
51476
|
-
import {
|
|
51481
|
+
// src/commands/_shared/url-security.ts
|
|
51482
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
51477
51483
|
function sanitizeUrl(raw) {
|
|
51478
51484
|
let urlStr = raw.trim();
|
|
51479
51485
|
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
@@ -51491,13 +51497,29 @@ function sanitizeUrl(raw) {
|
|
|
51491
51497
|
}
|
|
51492
51498
|
return urlStr.trim();
|
|
51493
51499
|
}
|
|
51494
|
-
function
|
|
51495
|
-
|
|
51496
|
-
const
|
|
51497
|
-
|
|
51498
|
-
|
|
51499
|
-
|
|
51500
|
-
|
|
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;
|
|
51501
51523
|
}
|
|
51502
51524
|
function hasNonAsciiHostname(hostname5) {
|
|
51503
51525
|
for (const ch of hostname5) {
|
|
@@ -51507,31 +51529,38 @@ function hasNonAsciiHostname(hostname5) {
|
|
|
51507
51529
|
}
|
|
51508
51530
|
return false;
|
|
51509
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
|
+
}
|
|
51510
51544
|
function isPrivateHost(url3) {
|
|
51511
|
-
const host = url3.hostname.toLowerCase();
|
|
51512
|
-
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)) {
|
|
51513
51547
|
return true;
|
|
51514
51548
|
}
|
|
51515
51549
|
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
51516
51550
|
return true;
|
|
51517
51551
|
}
|
|
51518
|
-
|
|
51519
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
51520
|
-
const ipv4192 = /^192\.168\./;
|
|
51521
|
-
const ipv6Private = /^fe80:/i;
|
|
51522
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
51523
|
-
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)) {
|
|
51524
51553
|
return true;
|
|
51525
51554
|
}
|
|
51526
51555
|
if (host.startsWith("::ffff:")) {
|
|
51527
51556
|
const inner = host.slice(7);
|
|
51528
|
-
if (
|
|
51557
|
+
if (isIpv4MappedPrivateHost(inner)) {
|
|
51529
51558
|
return true;
|
|
51530
51559
|
}
|
|
51531
51560
|
}
|
|
51532
51561
|
return false;
|
|
51533
51562
|
}
|
|
51534
|
-
function
|
|
51563
|
+
function validateAndSanitizeGithubUrl(rawUrl, resource) {
|
|
51535
51564
|
const sanitized = sanitizeUrl(rawUrl);
|
|
51536
51565
|
if (!sanitized) {
|
|
51537
51566
|
return { error: "Empty URL" };
|
|
@@ -51547,10 +51576,10 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
51547
51576
|
if (isPrivateHost(url3)) {
|
|
51548
51577
|
return { error: "Private or localhost URLs are not allowed" };
|
|
51549
51578
|
}
|
|
51550
|
-
const
|
|
51551
|
-
if (!
|
|
51579
|
+
const githubPattern = new RegExp(`^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/${resource}\\/([0-9]+)\\/?$`);
|
|
51580
|
+
if (!githubPattern.test(sanitized)) {
|
|
51552
51581
|
return {
|
|
51553
|
-
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)"
|
|
51554
51583
|
};
|
|
51555
51584
|
}
|
|
51556
51585
|
return { sanitized };
|
|
@@ -51558,50 +51587,18 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
51558
51587
|
return { error: "Invalid URL format" };
|
|
51559
51588
|
}
|
|
51560
51589
|
}
|
|
51561
|
-
function parsePrRef(input, cwd) {
|
|
51562
|
-
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
51563
|
-
if (urlMatch) {
|
|
51564
|
-
return {
|
|
51565
|
-
owner: urlMatch[1],
|
|
51566
|
-
repo: urlMatch[2],
|
|
51567
|
-
number: parseInt(urlMatch[3], 10)
|
|
51568
|
-
};
|
|
51569
|
-
}
|
|
51570
|
-
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
51571
|
-
if (shorthandMatch) {
|
|
51572
|
-
return {
|
|
51573
|
-
owner: shorthandMatch[1],
|
|
51574
|
-
repo: shorthandMatch[2],
|
|
51575
|
-
number: parseInt(shorthandMatch[3], 10)
|
|
51576
|
-
};
|
|
51577
|
-
}
|
|
51578
|
-
const bareMatch = input.match(/^(\d+)$/);
|
|
51579
|
-
if (bareMatch) {
|
|
51580
|
-
const prNumber = parseInt(bareMatch[1], 10);
|
|
51581
|
-
const remoteUrl = detectGitRemote(cwd);
|
|
51582
|
-
if (!remoteUrl) {
|
|
51583
|
-
return null;
|
|
51584
|
-
}
|
|
51585
|
-
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
51586
|
-
if (!parsed) {
|
|
51587
|
-
return null;
|
|
51588
|
-
}
|
|
51589
|
-
return {
|
|
51590
|
-
owner: parsed.owner,
|
|
51591
|
-
repo: parsed.repo,
|
|
51592
|
-
number: prNumber
|
|
51593
|
-
};
|
|
51594
|
-
}
|
|
51595
|
-
return null;
|
|
51596
|
-
}
|
|
51597
51590
|
function detectGitRemote(cwd) {
|
|
51598
51591
|
try {
|
|
51599
|
-
const
|
|
51592
|
+
const result = _internals28.spawnSync("git", ["remote", "get-url", "origin"], {
|
|
51600
51593
|
encoding: "utf-8",
|
|
51601
|
-
stdio: ["
|
|
51594
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51602
51595
|
timeout: 5000,
|
|
51603
51596
|
...cwd ? { cwd } : {}
|
|
51604
|
-
})
|
|
51597
|
+
});
|
|
51598
|
+
if (result.status !== 0 || result.error) {
|
|
51599
|
+
return null;
|
|
51600
|
+
}
|
|
51601
|
+
const remoteUrl = (result.stdout ?? "").trim();
|
|
51605
51602
|
return remoteUrl || null;
|
|
51606
51603
|
} catch {
|
|
51607
51604
|
return null;
|
|
@@ -51610,123 +51607,49 @@ function detectGitRemote(cwd) {
|
|
|
51610
51607
|
function parseGitRemoteUrl(remoteUrl) {
|
|
51611
51608
|
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
|
|
51612
51609
|
if (httpsMatch) {
|
|
51613
|
-
|
|
51614
|
-
|
|
51615
|
-
|
|
51616
|
-
|
|
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 };
|
|
51617
51616
|
}
|
|
51618
51617
|
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
51619
51618
|
if (sshMatch) {
|
|
51620
|
-
|
|
51621
|
-
|
|
51622
|
-
|
|
51623
|
-
|
|
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 };
|
|
51624
51625
|
}
|
|
51625
51626
|
const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
51626
51627
|
if (pathMatch) {
|
|
51627
|
-
|
|
51628
|
-
|
|
51629
|
-
|
|
51630
|
-
|
|
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 };
|
|
51631
51634
|
}
|
|
51632
51635
|
return null;
|
|
51633
51636
|
}
|
|
51634
|
-
|
|
51635
|
-
|
|
51636
|
-
|
|
51637
|
-
|
|
51638
|
-
|
|
51639
|
-
|
|
51640
|
-
|
|
51641
|
-
|
|
51642
|
-
|
|
51643
|
-
|
|
51644
|
-
|
|
51645
|
-
if (!prInfo) {
|
|
51646
|
-
return { error: `Could not parse PR reference from "${refToken}"` };
|
|
51647
|
-
}
|
|
51648
|
-
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
51649
|
-
const result = validateAndSanitizeUrl(prUrl);
|
|
51650
|
-
if ("error" in result) {
|
|
51651
|
-
return { error: result.error };
|
|
51652
|
-
}
|
|
51653
|
-
return { prUrl: result.sanitized, instructions };
|
|
51654
|
-
}
|
|
51655
|
-
var _internals28, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
|
|
51656
|
-
var init_pr_ref = __esm(() => {
|
|
51657
|
-
_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 };
|
|
51658
51648
|
});
|
|
51659
51649
|
|
|
51660
51650
|
// src/commands/issue.ts
|
|
51661
|
-
|
|
51662
|
-
|
|
51663
|
-
let urlStr = raw.trim();
|
|
51664
|
-
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
51665
|
-
const fragmentIdx = urlStr.indexOf("#");
|
|
51666
|
-
if (fragmentIdx !== -1) {
|
|
51667
|
-
urlStr = urlStr.slice(0, fragmentIdx);
|
|
51668
|
-
}
|
|
51669
|
-
const queryIdx = urlStr.indexOf("?");
|
|
51670
|
-
if (queryIdx !== -1) {
|
|
51671
|
-
urlStr = urlStr.slice(0, queryIdx);
|
|
51672
|
-
}
|
|
51673
|
-
urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
|
|
51674
|
-
if (urlStr.length > MAX_URL_LEN2) {
|
|
51675
|
-
urlStr = urlStr.slice(0, MAX_URL_LEN2);
|
|
51676
|
-
}
|
|
51677
|
-
return urlStr.trim();
|
|
51678
|
-
}
|
|
51679
|
-
function isPrivateHost2(url3) {
|
|
51680
|
-
const host = url3.hostname.toLowerCase();
|
|
51681
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
|
|
51682
|
-
return true;
|
|
51683
|
-
}
|
|
51684
|
-
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
51685
|
-
return true;
|
|
51686
|
-
}
|
|
51687
|
-
const ipv4Private = /^10\./;
|
|
51688
|
-
const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
51689
|
-
const ipv4192 = /^192\.168\./;
|
|
51690
|
-
const ipv6Private = /^fe80:/i;
|
|
51691
|
-
const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
|
|
51692
|
-
if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
|
|
51693
|
-
return true;
|
|
51694
|
-
}
|
|
51695
|
-
if (host.startsWith("::ffff:")) {
|
|
51696
|
-
const inner = host.slice(7);
|
|
51697
|
-
if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
|
|
51698
|
-
return true;
|
|
51699
|
-
}
|
|
51700
|
-
}
|
|
51701
|
-
return false;
|
|
51702
|
-
}
|
|
51703
|
-
function validateAndSanitizeUrl2(rawUrl) {
|
|
51704
|
-
const sanitized = sanitizeUrl2(rawUrl);
|
|
51705
|
-
if (!sanitized) {
|
|
51706
|
-
return { error: "Empty URL" };
|
|
51707
|
-
}
|
|
51708
|
-
if (!sanitized.startsWith("https://")) {
|
|
51709
|
-
return { error: "URL must use HTTPS scheme" };
|
|
51710
|
-
}
|
|
51711
|
-
try {
|
|
51712
|
-
const url3 = new URL(sanitized);
|
|
51713
|
-
const hostname5 = url3.hostname;
|
|
51714
|
-
if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
|
|
51715
|
-
return { error: "Non-ASCII hostnames are not allowed" };
|
|
51716
|
-
}
|
|
51717
|
-
if (isPrivateHost2(url3)) {
|
|
51718
|
-
return { error: "Private or localhost URLs are not allowed" };
|
|
51719
|
-
}
|
|
51720
|
-
const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
|
|
51721
|
-
if (!githubIssuePattern.test(sanitized)) {
|
|
51722
|
-
return {
|
|
51723
|
-
error: "URL must be a GitHub issue URL (https://github.com/owner/repo/issues/N)"
|
|
51724
|
-
};
|
|
51725
|
-
}
|
|
51726
|
-
return { sanitized };
|
|
51727
|
-
} catch {
|
|
51728
|
-
return { error: "Invalid URL format" };
|
|
51729
|
-
}
|
|
51651
|
+
function validateAndSanitizeUrl(rawUrl) {
|
|
51652
|
+
return validateAndSanitizeGithubUrl(rawUrl, "issues");
|
|
51730
51653
|
}
|
|
51731
51654
|
function parseArgs6(args) {
|
|
51732
51655
|
const out = {
|
|
@@ -51753,9 +51676,12 @@ function parseArgs6(args) {
|
|
|
51753
51676
|
}
|
|
51754
51677
|
return out;
|
|
51755
51678
|
}
|
|
51756
|
-
function parseIssueRef(input) {
|
|
51679
|
+
function parseIssueRef(input, directory) {
|
|
51757
51680
|
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/i);
|
|
51758
51681
|
if (urlMatch) {
|
|
51682
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
51683
|
+
return null;
|
|
51684
|
+
}
|
|
51759
51685
|
return {
|
|
51760
51686
|
owner: urlMatch[1],
|
|
51761
51687
|
repo: urlMatch[2],
|
|
@@ -51764,6 +51690,9 @@ function parseIssueRef(input) {
|
|
|
51764
51690
|
}
|
|
51765
51691
|
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
51766
51692
|
if (shorthandMatch) {
|
|
51693
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
51694
|
+
return null;
|
|
51695
|
+
}
|
|
51767
51696
|
return {
|
|
51768
51697
|
owner: shorthandMatch[1],
|
|
51769
51698
|
repo: shorthandMatch[2],
|
|
@@ -51773,7 +51702,7 @@ function parseIssueRef(input) {
|
|
|
51773
51702
|
const bareMatch = input.match(/^(\d+)$/);
|
|
51774
51703
|
if (bareMatch) {
|
|
51775
51704
|
const issueNumber = parseInt(bareMatch[1], 10);
|
|
51776
|
-
const remoteUrl =
|
|
51705
|
+
const remoteUrl = detectGitRemote(directory);
|
|
51777
51706
|
if (!remoteUrl) {
|
|
51778
51707
|
return null;
|
|
51779
51708
|
}
|
|
@@ -51789,33 +51718,21 @@ function parseIssueRef(input) {
|
|
|
51789
51718
|
}
|
|
51790
51719
|
return null;
|
|
51791
51720
|
}
|
|
51792
|
-
function
|
|
51793
|
-
try {
|
|
51794
|
-
const remoteUrl = execSync3("git remote get-url origin", {
|
|
51795
|
-
encoding: "utf-8",
|
|
51796
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
51797
|
-
timeout: 5000
|
|
51798
|
-
}).trim();
|
|
51799
|
-
return remoteUrl || null;
|
|
51800
|
-
} catch {
|
|
51801
|
-
return null;
|
|
51802
|
-
}
|
|
51803
|
-
}
|
|
51804
|
-
function handleIssueCommand(_directory, args) {
|
|
51721
|
+
function handleIssueCommand(directory, args) {
|
|
51805
51722
|
const parsed = parseArgs6(args);
|
|
51806
51723
|
const rawInput = parsed.rest.join(" ").trim();
|
|
51807
51724
|
if (!rawInput) {
|
|
51808
51725
|
return USAGE6;
|
|
51809
51726
|
}
|
|
51810
51727
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
51811
|
-
const issueInfo = parseIssueRef(isFullUrl ?
|
|
51728
|
+
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput, directory);
|
|
51812
51729
|
if (!issueInfo) {
|
|
51813
|
-
return `Error: Could not parse issue reference from "${rawInput}"
|
|
51730
|
+
return `Error: Could not parse issue reference from "${sanitizeErrorEcho(rawInput)}"
|
|
51814
51731
|
|
|
51815
51732
|
${USAGE6}`;
|
|
51816
51733
|
}
|
|
51817
51734
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
51818
|
-
const result =
|
|
51735
|
+
const result = validateAndSanitizeUrl(issueUrl);
|
|
51819
51736
|
if ("error" in result) {
|
|
51820
51737
|
return `Error: ${result.error}
|
|
51821
51738
|
|
|
@@ -51831,9 +51748,9 @@ ${USAGE6}`;
|
|
|
51831
51748
|
const flagsStr = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
|
51832
51749
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
51833
51750
|
}
|
|
51834
|
-
var
|
|
51751
|
+
var USAGE6;
|
|
51835
51752
|
var init_issue = __esm(() => {
|
|
51836
|
-
|
|
51753
|
+
init_url_security();
|
|
51837
51754
|
USAGE6 = [
|
|
51838
51755
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
51839
51756
|
"",
|
|
@@ -56025,6 +55942,88 @@ var init_post_mortem = __esm(() => {
|
|
|
56025
55942
|
init_curator_postmortem();
|
|
56026
55943
|
});
|
|
56027
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
|
+
|
|
56028
56027
|
// src/commands/pr-feedback.ts
|
|
56029
56028
|
function handlePrFeedbackCommand(directory, args) {
|
|
56030
56029
|
const rest = args.filter((t) => t.trim().length > 0);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
export declare const MAX_URL_LEN = 2048;
|
|
3
|
+
export type ValidationResult = {
|
|
4
|
+
sanitized: string;
|
|
5
|
+
} | {
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* File-scoped indirection seam for git remote lookups.
|
|
10
|
+
*/
|
|
11
|
+
export declare const _internals: {
|
|
12
|
+
spawnSync: typeof spawnSync;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Strip query strings, fragments, injected MODE headers, and credentials from
|
|
16
|
+
* a URL string.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sanitizeUrl(raw: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Strip control characters from user-visible error echoes and bound the result.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sanitizeErrorEcho(raw: string, maxLength?: number): string;
|
|
23
|
+
export declare function containsControlCharacters(value: string): boolean;
|
|
24
|
+
export declare function isIpv4MappedPrivateHost(inner: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Blocklist of private/localhost hostnames and IP ranges.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isPrivateHost(url: URL): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Validate and sanitize a GitHub URL for a specific resource kind.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateAndSanitizeGithubUrl(rawUrl: string, resource: 'issues' | 'pull'): ValidationResult;
|
|
33
|
+
/**
|
|
34
|
+
* Detect the `origin` remote URL from git config.
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectGitRemote(cwd?: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Parse owner/repo from a git remote URL.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseGitRemoteUrl(remoteUrl: string): {
|
|
41
|
+
owner: string;
|
|
42
|
+
repo: string;
|
|
43
|
+
} | null;
|
|
44
|
+
export declare function isIPv4ZeroNetwork(host: string): boolean;
|
package/dist/commands/issue.d.ts
CHANGED
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
* --no-repro → appends noRepro=true to emitted signal
|
|
11
11
|
* no args → returns usage string (no throw)
|
|
12
12
|
*/
|
|
13
|
-
export declare function handleIssueCommand(
|
|
13
|
+
export declare function handleIssueCommand(directory: string, args: string[]): string;
|
|
@@ -10,20 +10,8 @@
|
|
|
10
10
|
* fragments, and embedded credentials are stripped before the value is ever
|
|
11
11
|
* placed back into a signal string.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
* File-scoped indirection seam for the subprocess call. Tests override
|
|
16
|
-
* `_internals.execSync` (no `mock.module`) to assert the working directory is
|
|
17
|
-
* threaded through and to simulate a missing `origin` remote.
|
|
18
|
-
*/
|
|
19
|
-
export declare const _internals: {
|
|
20
|
-
execSync: typeof execSync;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Strip query strings, fragments, injected MODE headers, and credentials from
|
|
24
|
-
* a URL string.
|
|
25
|
-
*/
|
|
26
|
-
export declare function sanitizeUrl(raw: string): string;
|
|
13
|
+
import { _internals, detectGitRemote, parseGitRemoteUrl, sanitizeUrl, type ValidationResult } from './_shared/url-security.js';
|
|
14
|
+
export { _internals, detectGitRemote, parseGitRemoteUrl, sanitizeUrl };
|
|
27
15
|
/**
|
|
28
16
|
* Sanitize free-text instructions so they cannot forge a competing MODE
|
|
29
17
|
* header, inject control sequences, or break out of the signal line.
|
|
@@ -31,19 +19,6 @@ export declare function sanitizeUrl(raw: string): string;
|
|
|
31
19
|
* headers, and truncates to a bounded length.
|
|
32
20
|
*/
|
|
33
21
|
export declare function sanitizeInstructions(raw: string): string;
|
|
34
|
-
/**
|
|
35
|
-
* Blocklist of private/localhost hostnames and IP ranges.
|
|
36
|
-
*/
|
|
37
|
-
export declare function isPrivateHost(url: URL): boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Validate and sanitize a GitHub PR URL.
|
|
40
|
-
* Returns the sanitized URL on success, or an error message on failure.
|
|
41
|
-
*/
|
|
42
|
-
export type ValidationResult = {
|
|
43
|
-
sanitized: string;
|
|
44
|
-
} | {
|
|
45
|
-
error: string;
|
|
46
|
-
};
|
|
47
22
|
export declare function validateAndSanitizeUrl(rawUrl: string): ValidationResult;
|
|
48
23
|
export interface ParsedPr {
|
|
49
24
|
owner: string;
|
|
@@ -57,24 +32,6 @@ export interface ParsedPr {
|
|
|
57
32
|
* 3. Bare number: N (resolved against the `origin` git remote in `cwd`)
|
|
58
33
|
*/
|
|
59
34
|
export declare function parsePrRef(input: string, cwd?: string): ParsedPr | null;
|
|
60
|
-
/**
|
|
61
|
-
* Detect the `origin` remote URL from git config.
|
|
62
|
-
*
|
|
63
|
-
* `cwd` should be the project directory the command was invoked for. Without it
|
|
64
|
-
* the lookup runs in `process.cwd()`, which in a plugin host is frequently not
|
|
65
|
-
* the repository root — so bare-number PR resolution would silently fail or
|
|
66
|
-
* resolve against the wrong repo (invariant #3: subprocesses run in an explicit
|
|
67
|
-
* working directory).
|
|
68
|
-
*/
|
|
69
|
-
export declare function detectGitRemote(cwd?: string): string | null;
|
|
70
|
-
/**
|
|
71
|
-
* Parse owner/repo from a git remote URL.
|
|
72
|
-
* Supports HTTPS (https://github.com/owner/repo.git) and SSH (git@github.com:owner/repo.git).
|
|
73
|
-
*/
|
|
74
|
-
export declare function parseGitRemoteUrl(remoteUrl: string): {
|
|
75
|
-
owner: string;
|
|
76
|
-
repo: string;
|
|
77
|
-
} | null;
|
|
78
35
|
/**
|
|
79
36
|
* Whether a token is *shaped* like a PR reference — a full `http(s)` URL, an
|
|
80
37
|
* `owner/repo#N` shorthand, or a bare number. This is intent detection, not
|
|
@@ -17,7 +17,9 @@ export interface SkillUsageEntry {
|
|
|
17
17
|
taskID: string;
|
|
18
18
|
/** ISO 8601 timestamp of the event. */
|
|
19
19
|
timestamp: string;
|
|
20
|
-
/** Compliance outcome — 'compliant' | '
|
|
20
|
+
/** Compliance outcome — 'compliant' | 'partial' | 'violated' | 'not_checked' | custom.
|
|
21
|
+
* Legacy on-disk entries may carry the pre-fix spelling 'violation'; these are
|
|
22
|
+
* normalized to 'violated' on the read path (see normalizeComplianceVerdict). */
|
|
21
23
|
complianceVerdict: string;
|
|
22
24
|
/** Optional free-text notes from the reviewer. */
|
|
23
25
|
reviewerNotes?: string;
|
|
@@ -51,6 +53,16 @@ export interface PruneResult {
|
|
|
51
53
|
/** Error message when the write/rename step fails; absent on success. */
|
|
52
54
|
error?: string;
|
|
53
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Normalize a compliance verdict to the canonical spelling.
|
|
58
|
+
* The sole producer (`skill-propagation-gate.ts`) lowercases the regex
|
|
59
|
+
* capture, yielding 'violated'. Pre-fix on-disk entries may carry the
|
|
60
|
+
* legacy spelling 'violation'; this maps them to the canonical form so
|
|
61
|
+
* that every downstream comparison can use a single string.
|
|
62
|
+
*
|
|
63
|
+
* Exported for unit-testing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function normalizeComplianceVerdict(verdict: string): string;
|
|
54
66
|
/**
|
|
55
67
|
* Test-only dependency-injection seam. Tests override these without
|
|
56
68
|
* `mock.module` (which leaks across files in Bun's shared test-runner).
|
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.
|
|
72
|
+
version: "7.73.3",
|
|
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",
|
|
@@ -62433,6 +62433,9 @@ import * as path37 from "node:path";
|
|
|
62433
62433
|
function resolveLogPath(directory) {
|
|
62434
62434
|
return validateSwarmPath(directory, "skill-usage.jsonl");
|
|
62435
62435
|
}
|
|
62436
|
+
function normalizeComplianceVerdict(verdict) {
|
|
62437
|
+
return verdict === "violation" ? "violated" : verdict;
|
|
62438
|
+
}
|
|
62436
62439
|
function appendSkillUsageEntry(directory, entry) {
|
|
62437
62440
|
const {
|
|
62438
62441
|
skillPath,
|
|
@@ -62503,7 +62506,9 @@ function readSkillUsageEntries(directory, options) {
|
|
|
62503
62506
|
if (!trimmed)
|
|
62504
62507
|
continue;
|
|
62505
62508
|
try {
|
|
62506
|
-
|
|
62509
|
+
const entry = JSON.parse(trimmed);
|
|
62510
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
62511
|
+
entries.push(entry);
|
|
62507
62512
|
} catch {}
|
|
62508
62513
|
}
|
|
62509
62514
|
if (!options)
|
|
@@ -62562,6 +62567,7 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
|
|
|
62562
62567
|
continue;
|
|
62563
62568
|
try {
|
|
62564
62569
|
const entry = JSON.parse(line);
|
|
62570
|
+
entry.complianceVerdict = normalizeComplianceVerdict(entry.complianceVerdict);
|
|
62565
62571
|
if (filters.sessionID !== undefined && entry.sessionID !== filters.sessionID) {
|
|
62566
62572
|
continue;
|
|
62567
62573
|
}
|
|
@@ -62596,7 +62602,7 @@ function computeComplianceByVersion(entries, skillPath) {
|
|
|
62596
62602
|
stats.total += 1;
|
|
62597
62603
|
if (e.complianceVerdict === "compliant")
|
|
62598
62604
|
stats.compliant += 1;
|
|
62599
|
-
if (e.complianceVerdict === "
|
|
62605
|
+
if (e.complianceVerdict === "violated")
|
|
62600
62606
|
stats.violation += 1;
|
|
62601
62607
|
}
|
|
62602
62608
|
for (const stats of map3.values()) {
|
|
@@ -62709,7 +62715,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
62709
62715
|
try {
|
|
62710
62716
|
const allEntries = readSkillUsageEntries(directory);
|
|
62711
62717
|
const actionable = allEntries.filter((e) => {
|
|
62712
|
-
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "
|
|
62718
|
+
if (e.complianceVerdict !== "compliant" && e.complianceVerdict !== "violated") {
|
|
62713
62719
|
return false;
|
|
62714
62720
|
}
|
|
62715
62721
|
if (options?.sinceTimestamp && e.timestamp <= options.sinceTimestamp) {
|
|
@@ -62735,7 +62741,7 @@ async function applySkillUsageFeedback(directory, options) {
|
|
|
62735
62741
|
for (const entry of entries) {
|
|
62736
62742
|
if (entry.complianceVerdict === "compliant")
|
|
62737
62743
|
compliantCount++;
|
|
62738
|
-
else if (entry.complianceVerdict === "
|
|
62744
|
+
else if (entry.complianceVerdict === "violated")
|
|
62739
62745
|
violationCount++;
|
|
62740
62746
|
}
|
|
62741
62747
|
if (compliantCount === 0 && violationCount === 0)
|
|
@@ -62819,7 +62825,7 @@ async function autoRetireSkills(directory, curatorKnowledgePath, excludeSlugs) {
|
|
|
62819
62825
|
return true;
|
|
62820
62826
|
return false;
|
|
62821
62827
|
});
|
|
62822
|
-
const violations = skillUsage.filter((e) => e.complianceVerdict === "
|
|
62828
|
+
const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
|
|
62823
62829
|
const violationRate = skillUsage.length > 0 ? violations / skillUsage.length : 0;
|
|
62824
62830
|
let allArchived = false;
|
|
62825
62831
|
try {
|
|
@@ -63484,7 +63490,7 @@ ${phaseDigest.summary}`,
|
|
|
63484
63490
|
});
|
|
63485
63491
|
if (skillUsage.length === 0)
|
|
63486
63492
|
continue;
|
|
63487
|
-
const violations = skillUsage.filter((e) => e.complianceVerdict === "
|
|
63493
|
+
const violations = skillUsage.filter((e) => e.complianceVerdict === "violated").length;
|
|
63488
63494
|
const violationRate = violations / skillUsage.length;
|
|
63489
63495
|
if (violationRate > REVISION_VIOLATION_THRESHOLD && violationRate <= 0.3) {
|
|
63490
63496
|
const content = await _internals27.readFileAsync(active.path, "utf-8");
|
|
@@ -63492,7 +63498,7 @@ ${phaseDigest.summary}`,
|
|
|
63492
63498
|
if (fm && fm.skillOrigin === "promoted_external")
|
|
63493
63499
|
continue;
|
|
63494
63500
|
const currentVersion = fm?.version ?? 1;
|
|
63495
|
-
const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "
|
|
63501
|
+
const violationContexts = skillUsage.filter((e) => e.complianceVerdict === "violated").slice(-10).map((e) => ({
|
|
63496
63502
|
taskId: e.taskID,
|
|
63497
63503
|
agent: e.agentName,
|
|
63498
63504
|
verdict: e.complianceVerdict,
|
|
@@ -75193,8 +75199,8 @@ var init_history = __esm(() => {
|
|
|
75193
75199
|
init_history_service();
|
|
75194
75200
|
});
|
|
75195
75201
|
|
|
75196
|
-
// src/commands/
|
|
75197
|
-
import {
|
|
75202
|
+
// src/commands/_shared/url-security.ts
|
|
75203
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
75198
75204
|
function sanitizeUrl(raw) {
|
|
75199
75205
|
let urlStr = raw.trim();
|
|
75200
75206
|
urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
@@ -75212,13 +75218,29 @@ function sanitizeUrl(raw) {
|
|
|
75212
75218
|
}
|
|
75213
75219
|
return urlStr.trim();
|
|
75214
75220
|
}
|
|
75215
|
-
function
|
|
75216
|
-
|
|
75217
|
-
const
|
|
75218
|
-
|
|
75219
|
-
|
|
75220
|
-
|
|
75221
|
-
|
|
75221
|
+
function sanitizeErrorEcho(raw, maxLength = 80) {
|
|
75222
|
+
let stripped = "";
|
|
75223
|
+
for (const ch of raw) {
|
|
75224
|
+
const cp = ch.codePointAt(0);
|
|
75225
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
75226
|
+
stripped += " ";
|
|
75227
|
+
continue;
|
|
75228
|
+
}
|
|
75229
|
+
stripped += ch;
|
|
75230
|
+
}
|
|
75231
|
+
const collapsed = stripped.replace(/\s+/g, " ").trim();
|
|
75232
|
+
if (collapsed.length <= maxLength)
|
|
75233
|
+
return collapsed;
|
|
75234
|
+
return `${collapsed.slice(0, maxLength)}…`;
|
|
75235
|
+
}
|
|
75236
|
+
function containsControlCharacters(value) {
|
|
75237
|
+
for (const ch of value) {
|
|
75238
|
+
const cp = ch.codePointAt(0);
|
|
75239
|
+
if (cp !== undefined && (cp <= 31 || cp === 127)) {
|
|
75240
|
+
return true;
|
|
75241
|
+
}
|
|
75242
|
+
}
|
|
75243
|
+
return false;
|
|
75222
75244
|
}
|
|
75223
75245
|
function hasNonAsciiHostname(hostname5) {
|
|
75224
75246
|
for (const ch of hostname5) {
|
|
@@ -75228,31 +75250,38 @@ function hasNonAsciiHostname(hostname5) {
|
|
|
75228
75250
|
}
|
|
75229
75251
|
return false;
|
|
75230
75252
|
}
|
|
75253
|
+
function isIpv4MappedPrivateHost(inner) {
|
|
75254
|
+
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)) {
|
|
75255
|
+
return true;
|
|
75256
|
+
}
|
|
75257
|
+
const firstSegment = inner.split(":", 1)[0];
|
|
75258
|
+
if (!firstSegment)
|
|
75259
|
+
return false;
|
|
75260
|
+
const firstWord = Number.parseInt(firstSegment, 16);
|
|
75261
|
+
if (!Number.isFinite(firstWord))
|
|
75262
|
+
return false;
|
|
75263
|
+
return firstWord >= 0 && firstWord <= 255 || firstWord >= 2560 && firstWord <= 2815 || firstWord >= 32512 && firstWord <= 32767 || firstWord === 43518 || firstWord >= 44048 && firstWord <= 44063 || firstWord === 49320;
|
|
75264
|
+
}
|
|
75231
75265
|
function isPrivateHost(url3) {
|
|
75232
|
-
const host = url3.hostname.toLowerCase();
|
|
75233
|
-
if (host === "localhost" || host === "
|
|
75266
|
+
const host = url3.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
75267
|
+
if (host === "localhost" || host === "::1" || host === "0.0.0.0" || IPV4_LOOPBACK.test(host) || IPV4_ZERO_NETWORK.test(host)) {
|
|
75234
75268
|
return true;
|
|
75235
75269
|
}
|
|
75236
75270
|
if (host.startsWith("localhost") || host === "localhost.com") {
|
|
75237
75271
|
return true;
|
|
75238
75272
|
}
|
|
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)) {
|
|
75273
|
+
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
75274
|
return true;
|
|
75246
75275
|
}
|
|
75247
75276
|
if (host.startsWith("::ffff:")) {
|
|
75248
75277
|
const inner = host.slice(7);
|
|
75249
|
-
if (
|
|
75278
|
+
if (isIpv4MappedPrivateHost(inner)) {
|
|
75250
75279
|
return true;
|
|
75251
75280
|
}
|
|
75252
75281
|
}
|
|
75253
75282
|
return false;
|
|
75254
75283
|
}
|
|
75255
|
-
function
|
|
75284
|
+
function validateAndSanitizeGithubUrl(rawUrl, resource) {
|
|
75256
75285
|
const sanitized = sanitizeUrl(rawUrl);
|
|
75257
75286
|
if (!sanitized) {
|
|
75258
75287
|
return { error: "Empty URL" };
|
|
@@ -75268,10 +75297,10 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
75268
75297
|
if (isPrivateHost(url3)) {
|
|
75269
75298
|
return { error: "Private or localhost URLs are not allowed" };
|
|
75270
75299
|
}
|
|
75271
|
-
const
|
|
75272
|
-
if (!
|
|
75300
|
+
const githubPattern = new RegExp(`^https:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/${resource}\\/([0-9]+)\\/?$`);
|
|
75301
|
+
if (!githubPattern.test(sanitized)) {
|
|
75273
75302
|
return {
|
|
75274
|
-
error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
|
|
75303
|
+
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
75304
|
};
|
|
75276
75305
|
}
|
|
75277
75306
|
return { sanitized };
|
|
@@ -75279,50 +75308,18 @@ function validateAndSanitizeUrl(rawUrl) {
|
|
|
75279
75308
|
return { error: "Invalid URL format" };
|
|
75280
75309
|
}
|
|
75281
75310
|
}
|
|
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
75311
|
function detectGitRemote(cwd) {
|
|
75319
75312
|
try {
|
|
75320
|
-
const
|
|
75313
|
+
const result = _internals39.spawnSync("git", ["remote", "get-url", "origin"], {
|
|
75321
75314
|
encoding: "utf-8",
|
|
75322
|
-
stdio: ["
|
|
75315
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
75323
75316
|
timeout: 5000,
|
|
75324
75317
|
...cwd ? { cwd } : {}
|
|
75325
|
-
})
|
|
75318
|
+
});
|
|
75319
|
+
if (result.status !== 0 || result.error) {
|
|
75320
|
+
return null;
|
|
75321
|
+
}
|
|
75322
|
+
const remoteUrl = (result.stdout ?? "").trim();
|
|
75326
75323
|
return remoteUrl || null;
|
|
75327
75324
|
} catch {
|
|
75328
75325
|
return null;
|
|
@@ -75331,123 +75328,49 @@ function detectGitRemote(cwd) {
|
|
|
75331
75328
|
function parseGitRemoteUrl(remoteUrl) {
|
|
75332
75329
|
const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
|
|
75333
75330
|
if (httpsMatch) {
|
|
75334
|
-
|
|
75335
|
-
|
|
75336
|
-
|
|
75337
|
-
|
|
75331
|
+
const owner = httpsMatch[1];
|
|
75332
|
+
const repo = httpsMatch[2].replace(/\.git$/, "");
|
|
75333
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75334
|
+
return null;
|
|
75335
|
+
}
|
|
75336
|
+
return { owner, repo };
|
|
75338
75337
|
}
|
|
75339
75338
|
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
75340
75339
|
if (sshMatch) {
|
|
75341
|
-
|
|
75342
|
-
|
|
75343
|
-
|
|
75344
|
-
|
|
75340
|
+
const owner = sshMatch[1];
|
|
75341
|
+
const repo = sshMatch[2].replace(/\.git$/, "");
|
|
75342
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75343
|
+
return null;
|
|
75344
|
+
}
|
|
75345
|
+
return { owner, repo };
|
|
75345
75346
|
}
|
|
75346
75347
|
const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
75347
75348
|
if (pathMatch) {
|
|
75348
|
-
|
|
75349
|
-
|
|
75350
|
-
|
|
75351
|
-
|
|
75349
|
+
const owner = pathMatch[1];
|
|
75350
|
+
const repo = pathMatch[2].replace(/\.git$/, "");
|
|
75351
|
+
if (containsControlCharacters(owner) || containsControlCharacters(repo)) {
|
|
75352
|
+
return null;
|
|
75353
|
+
}
|
|
75354
|
+
return { owner, repo };
|
|
75352
75355
|
}
|
|
75353
75356
|
return null;
|
|
75354
75357
|
}
|
|
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 };
|
|
75358
|
+
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;
|
|
75359
|
+
var init_url_security = __esm(() => {
|
|
75360
|
+
IPV4_PRIVATE = /^10\./;
|
|
75361
|
+
IPV4_LOOPBACK = /^127\./;
|
|
75362
|
+
IPV4_LINK_LOCAL = /^169\.254\./;
|
|
75363
|
+
IPV4_PRIVATE_172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
|
|
75364
|
+
IPV4_PRIVATE_192 = /^192\.168\./;
|
|
75365
|
+
IPV4_ZERO_NETWORK = /^0\./;
|
|
75366
|
+
IPV6_LINK_LOCAL = /^fe80:/i;
|
|
75367
|
+
IPV6_UNIQUE_LOCAL = /^f[cd][0-9a-f]{2}:/i;
|
|
75368
|
+
_internals39 = { spawnSync: spawnSync8 };
|
|
75379
75369
|
});
|
|
75380
75370
|
|
|
75381
75371
|
// 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
|
-
}
|
|
75372
|
+
function validateAndSanitizeUrl(rawUrl) {
|
|
75373
|
+
return validateAndSanitizeGithubUrl(rawUrl, "issues");
|
|
75451
75374
|
}
|
|
75452
75375
|
function parseArgs6(args2) {
|
|
75453
75376
|
const out2 = {
|
|
@@ -75474,9 +75397,12 @@ function parseArgs6(args2) {
|
|
|
75474
75397
|
}
|
|
75475
75398
|
return out2;
|
|
75476
75399
|
}
|
|
75477
|
-
function parseIssueRef(input) {
|
|
75400
|
+
function parseIssueRef(input, directory) {
|
|
75478
75401
|
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)\/?$/i);
|
|
75479
75402
|
if (urlMatch) {
|
|
75403
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
75404
|
+
return null;
|
|
75405
|
+
}
|
|
75480
75406
|
return {
|
|
75481
75407
|
owner: urlMatch[1],
|
|
75482
75408
|
repo: urlMatch[2],
|
|
@@ -75485,6 +75411,9 @@ function parseIssueRef(input) {
|
|
|
75485
75411
|
}
|
|
75486
75412
|
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
75487
75413
|
if (shorthandMatch) {
|
|
75414
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
75415
|
+
return null;
|
|
75416
|
+
}
|
|
75488
75417
|
return {
|
|
75489
75418
|
owner: shorthandMatch[1],
|
|
75490
75419
|
repo: shorthandMatch[2],
|
|
@@ -75494,7 +75423,7 @@ function parseIssueRef(input) {
|
|
|
75494
75423
|
const bareMatch = input.match(/^(\d+)$/);
|
|
75495
75424
|
if (bareMatch) {
|
|
75496
75425
|
const issueNumber = parseInt(bareMatch[1], 10);
|
|
75497
|
-
const remoteUrl =
|
|
75426
|
+
const remoteUrl = detectGitRemote(directory);
|
|
75498
75427
|
if (!remoteUrl) {
|
|
75499
75428
|
return null;
|
|
75500
75429
|
}
|
|
@@ -75510,33 +75439,21 @@ function parseIssueRef(input) {
|
|
|
75510
75439
|
}
|
|
75511
75440
|
return null;
|
|
75512
75441
|
}
|
|
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) {
|
|
75442
|
+
function handleIssueCommand(directory, args2) {
|
|
75526
75443
|
const parsed = parseArgs6(args2);
|
|
75527
75444
|
const rawInput = parsed.rest.join(" ").trim();
|
|
75528
75445
|
if (!rawInput) {
|
|
75529
75446
|
return USAGE6;
|
|
75530
75447
|
}
|
|
75531
75448
|
const isFullUrl = /^https?:\/\//i.test(rawInput);
|
|
75532
|
-
const issueInfo = parseIssueRef(isFullUrl ?
|
|
75449
|
+
const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput, directory);
|
|
75533
75450
|
if (!issueInfo) {
|
|
75534
|
-
return `Error: Could not parse issue reference from "${rawInput}"
|
|
75451
|
+
return `Error: Could not parse issue reference from "${sanitizeErrorEcho(rawInput)}"
|
|
75535
75452
|
|
|
75536
75453
|
${USAGE6}`;
|
|
75537
75454
|
}
|
|
75538
75455
|
const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
|
|
75539
|
-
const result =
|
|
75456
|
+
const result = validateAndSanitizeUrl(issueUrl);
|
|
75540
75457
|
if ("error" in result) {
|
|
75541
75458
|
return `Error: ${result.error}
|
|
75542
75459
|
|
|
@@ -75552,9 +75469,9 @@ ${USAGE6}`;
|
|
|
75552
75469
|
const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
|
|
75553
75470
|
return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
|
|
75554
75471
|
}
|
|
75555
|
-
var
|
|
75472
|
+
var USAGE6;
|
|
75556
75473
|
var init_issue = __esm(() => {
|
|
75557
|
-
|
|
75474
|
+
init_url_security();
|
|
75558
75475
|
USAGE6 = [
|
|
75559
75476
|
"Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
|
|
75560
75477
|
"",
|
|
@@ -80737,6 +80654,88 @@ var init_post_mortem = __esm(() => {
|
|
|
80737
80654
|
init_curator_postmortem();
|
|
80738
80655
|
});
|
|
80739
80656
|
|
|
80657
|
+
// src/commands/pr-ref.ts
|
|
80658
|
+
function sanitizeInstructions(raw) {
|
|
80659
|
+
const collapsed = raw.replace(/\s+/g, " ").trim();
|
|
80660
|
+
const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
|
|
80661
|
+
const normalized = stripped.replace(/\s+/g, " ").trim();
|
|
80662
|
+
if (normalized.length <= MAX_INSTRUCTIONS_LEN)
|
|
80663
|
+
return normalized;
|
|
80664
|
+
return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
|
|
80665
|
+
}
|
|
80666
|
+
function validateAndSanitizeUrl2(rawUrl) {
|
|
80667
|
+
return validateAndSanitizeGithubUrl(rawUrl, "pull");
|
|
80668
|
+
}
|
|
80669
|
+
function parsePrRef(input, cwd) {
|
|
80670
|
+
const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
80671
|
+
if (urlMatch) {
|
|
80672
|
+
if (containsControlCharacters(urlMatch[1]) || containsControlCharacters(urlMatch[2])) {
|
|
80673
|
+
return null;
|
|
80674
|
+
}
|
|
80675
|
+
return {
|
|
80676
|
+
owner: urlMatch[1],
|
|
80677
|
+
repo: urlMatch[2],
|
|
80678
|
+
number: parseInt(urlMatch[3], 10)
|
|
80679
|
+
};
|
|
80680
|
+
}
|
|
80681
|
+
const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
80682
|
+
if (shorthandMatch) {
|
|
80683
|
+
if (containsControlCharacters(shorthandMatch[1]) || containsControlCharacters(shorthandMatch[2])) {
|
|
80684
|
+
return null;
|
|
80685
|
+
}
|
|
80686
|
+
return {
|
|
80687
|
+
owner: shorthandMatch[1],
|
|
80688
|
+
repo: shorthandMatch[2],
|
|
80689
|
+
number: parseInt(shorthandMatch[3], 10)
|
|
80690
|
+
};
|
|
80691
|
+
}
|
|
80692
|
+
const bareMatch = input.match(/^(\d+)$/);
|
|
80693
|
+
if (bareMatch) {
|
|
80694
|
+
const prNumber = parseInt(bareMatch[1], 10);
|
|
80695
|
+
const remoteUrl = detectGitRemote(cwd);
|
|
80696
|
+
if (!remoteUrl) {
|
|
80697
|
+
return null;
|
|
80698
|
+
}
|
|
80699
|
+
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
80700
|
+
if (!parsed) {
|
|
80701
|
+
return null;
|
|
80702
|
+
}
|
|
80703
|
+
return {
|
|
80704
|
+
owner: parsed.owner,
|
|
80705
|
+
repo: parsed.repo,
|
|
80706
|
+
number: prNumber
|
|
80707
|
+
};
|
|
80708
|
+
}
|
|
80709
|
+
return null;
|
|
80710
|
+
}
|
|
80711
|
+
function looksLikePrRef(token) {
|
|
80712
|
+
return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
|
|
80713
|
+
}
|
|
80714
|
+
function resolvePrCommandInput(rest, cwd) {
|
|
80715
|
+
if (rest.length === 0) {
|
|
80716
|
+
return null;
|
|
80717
|
+
}
|
|
80718
|
+
const refToken = rest[0];
|
|
80719
|
+
const instructions = sanitizeInstructions(rest.slice(1).join(" "));
|
|
80720
|
+
const isFullUrl = /^https?:\/\//i.test(refToken);
|
|
80721
|
+
const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
|
|
80722
|
+
if (!prInfo) {
|
|
80723
|
+
return {
|
|
80724
|
+
error: `Could not parse PR reference from "${sanitizeErrorEcho(refToken)}"`
|
|
80725
|
+
};
|
|
80726
|
+
}
|
|
80727
|
+
const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
|
|
80728
|
+
const result = validateAndSanitizeUrl2(prUrl);
|
|
80729
|
+
if ("error" in result) {
|
|
80730
|
+
return { error: result.error };
|
|
80731
|
+
}
|
|
80732
|
+
return { prUrl: result.sanitized, instructions };
|
|
80733
|
+
}
|
|
80734
|
+
var MAX_INSTRUCTIONS_LEN = 1000;
|
|
80735
|
+
var init_pr_ref = __esm(() => {
|
|
80736
|
+
init_url_security();
|
|
80737
|
+
});
|
|
80738
|
+
|
|
80740
80739
|
// src/commands/pr-feedback.ts
|
|
80741
80740
|
function handlePrFeedbackCommand(directory, args2) {
|
|
80742
80741
|
const rest = args2.filter((t) => t.trim().length > 0);
|
|
@@ -127845,7 +127844,7 @@ import * as fs103 from "node:fs";
|
|
|
127845
127844
|
import * as path156 from "node:path";
|
|
127846
127845
|
|
|
127847
127846
|
// src/mutation/engine.ts
|
|
127848
|
-
import { spawnSync as
|
|
127847
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
127849
127848
|
import { unlinkSync as unlinkSync19, writeFileSync as writeFileSync27 } from "node:fs";
|
|
127850
127849
|
import * as path155 from "node:path";
|
|
127851
127850
|
|
|
@@ -128023,7 +128022,7 @@ var _internals88 = {
|
|
|
128023
128022
|
executeMutation,
|
|
128024
128023
|
computeReport,
|
|
128025
128024
|
executeMutationSuite,
|
|
128026
|
-
spawnSync:
|
|
128025
|
+
spawnSync: spawnSync11
|
|
128027
128026
|
};
|
|
128028
128027
|
async function executeMutation(patch, testCommand, testFiles, workingDir) {
|
|
128029
128028
|
const startTime = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "7.73.
|
|
3
|
+
"version": "7.73.3",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|