opencode-swarm 7.68.1 → 7.68.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli/index.js +218 -234
  2. package/dist/index.js +218 -234
  3. 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.68.1",
55
+ version: "7.68.2",
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",
@@ -49731,7 +49731,7 @@ var init_history = __esm(() => {
49731
49731
  init_history_service();
49732
49732
  });
49733
49733
 
49734
- // src/commands/issue.ts
49734
+ // src/commands/pr-ref.ts
49735
49735
  import { execSync as execSync2 } from "child_process";
49736
49736
  function sanitizeUrl(raw) {
49737
49737
  let urlStr = raw.trim();
@@ -49750,6 +49750,22 @@ function sanitizeUrl(raw) {
49750
49750
  }
49751
49751
  return urlStr.trim();
49752
49752
  }
49753
+ function sanitizeInstructions(raw) {
49754
+ const collapsed = raw.replace(/\s+/g, " ").trim();
49755
+ const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
49756
+ const normalized = stripped.replace(/\s+/g, " ").trim();
49757
+ if (normalized.length <= MAX_INSTRUCTIONS_LEN)
49758
+ return normalized;
49759
+ return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}\u2026`;
49760
+ }
49761
+ function hasNonAsciiHostname(hostname5) {
49762
+ for (const ch of hostname5) {
49763
+ const cp = ch.codePointAt(0);
49764
+ if (cp !== undefined && cp > 127)
49765
+ return true;
49766
+ }
49767
+ return false;
49768
+ }
49753
49769
  function isPrivateHost(url3) {
49754
49770
  const host = url3.hostname.toLowerCase();
49755
49771
  if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
@@ -49782,13 +49798,182 @@ function validateAndSanitizeUrl(rawUrl) {
49782
49798
  if (!sanitized.startsWith("https://")) {
49783
49799
  return { error: "URL must use HTTPS scheme" };
49784
49800
  }
49801
+ try {
49802
+ const url3 = new URL(sanitized);
49803
+ if (hasNonAsciiHostname(url3.hostname)) {
49804
+ return { error: "Non-ASCII hostnames are not allowed" };
49805
+ }
49806
+ if (isPrivateHost(url3)) {
49807
+ return { error: "Private or localhost URLs are not allowed" };
49808
+ }
49809
+ const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
49810
+ if (!githubPrPattern.test(sanitized)) {
49811
+ return {
49812
+ error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
49813
+ };
49814
+ }
49815
+ return { sanitized };
49816
+ } catch {
49817
+ return { error: "Invalid URL format" };
49818
+ }
49819
+ }
49820
+ function parsePrRef(input, cwd) {
49821
+ const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
49822
+ if (urlMatch) {
49823
+ return {
49824
+ owner: urlMatch[1],
49825
+ repo: urlMatch[2],
49826
+ number: parseInt(urlMatch[3], 10)
49827
+ };
49828
+ }
49829
+ const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
49830
+ if (shorthandMatch) {
49831
+ return {
49832
+ owner: shorthandMatch[1],
49833
+ repo: shorthandMatch[2],
49834
+ number: parseInt(shorthandMatch[3], 10)
49835
+ };
49836
+ }
49837
+ const bareMatch = input.match(/^(\d+)$/);
49838
+ if (bareMatch) {
49839
+ const prNumber = parseInt(bareMatch[1], 10);
49840
+ const remoteUrl = detectGitRemote(cwd);
49841
+ if (!remoteUrl) {
49842
+ return null;
49843
+ }
49844
+ const parsed = parseGitRemoteUrl(remoteUrl);
49845
+ if (!parsed) {
49846
+ return null;
49847
+ }
49848
+ return {
49849
+ owner: parsed.owner,
49850
+ repo: parsed.repo,
49851
+ number: prNumber
49852
+ };
49853
+ }
49854
+ return null;
49855
+ }
49856
+ function detectGitRemote(cwd) {
49857
+ try {
49858
+ const remoteUrl = _internals23.execSync("git remote get-url origin", {
49859
+ encoding: "utf-8",
49860
+ stdio: ["pipe", "pipe", "pipe"],
49861
+ timeout: 5000,
49862
+ ...cwd ? { cwd } : {}
49863
+ }).trim();
49864
+ return remoteUrl || null;
49865
+ } catch {
49866
+ return null;
49867
+ }
49868
+ }
49869
+ function parseGitRemoteUrl(remoteUrl) {
49870
+ const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
49871
+ if (httpsMatch) {
49872
+ return {
49873
+ owner: httpsMatch[1],
49874
+ repo: httpsMatch[2].replace(/\.git$/, "")
49875
+ };
49876
+ }
49877
+ const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
49878
+ if (sshMatch) {
49879
+ return {
49880
+ owner: sshMatch[1],
49881
+ repo: sshMatch[2].replace(/\.git$/, "")
49882
+ };
49883
+ }
49884
+ const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
49885
+ if (pathMatch) {
49886
+ return {
49887
+ owner: pathMatch[1],
49888
+ repo: pathMatch[2].replace(/\.git$/, "")
49889
+ };
49890
+ }
49891
+ return null;
49892
+ }
49893
+ function looksLikePrRef(token) {
49894
+ return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
49895
+ }
49896
+ function resolvePrCommandInput(rest, cwd) {
49897
+ if (rest.length === 0) {
49898
+ return null;
49899
+ }
49900
+ const refToken = rest[0];
49901
+ const instructions = sanitizeInstructions(rest.slice(1).join(" "));
49902
+ const isFullUrl = /^https?:\/\//i.test(refToken);
49903
+ const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
49904
+ if (!prInfo) {
49905
+ return { error: `Could not parse PR reference from "${refToken}"` };
49906
+ }
49907
+ const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
49908
+ const result = validateAndSanitizeUrl(prUrl);
49909
+ if ("error" in result) {
49910
+ return { error: result.error };
49911
+ }
49912
+ return { prUrl: result.sanitized, instructions };
49913
+ }
49914
+ var _internals23, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
49915
+ var init_pr_ref = __esm(() => {
49916
+ _internals23 = { execSync: execSync2 };
49917
+ });
49918
+
49919
+ // src/commands/issue.ts
49920
+ import { execSync as execSync3 } from "child_process";
49921
+ function sanitizeUrl2(raw) {
49922
+ let urlStr = raw.trim();
49923
+ urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
49924
+ const fragmentIdx = urlStr.indexOf("#");
49925
+ if (fragmentIdx !== -1) {
49926
+ urlStr = urlStr.slice(0, fragmentIdx);
49927
+ }
49928
+ const queryIdx = urlStr.indexOf("?");
49929
+ if (queryIdx !== -1) {
49930
+ urlStr = urlStr.slice(0, queryIdx);
49931
+ }
49932
+ urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
49933
+ if (urlStr.length > MAX_URL_LEN2) {
49934
+ urlStr = urlStr.slice(0, MAX_URL_LEN2);
49935
+ }
49936
+ return urlStr.trim();
49937
+ }
49938
+ function isPrivateHost2(url3) {
49939
+ const host = url3.hostname.toLowerCase();
49940
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
49941
+ return true;
49942
+ }
49943
+ if (host.startsWith("localhost") || host === "localhost.com") {
49944
+ return true;
49945
+ }
49946
+ const ipv4Private = /^10\./;
49947
+ const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
49948
+ const ipv4192 = /^192\.168\./;
49949
+ const ipv6Private = /^fe80:/i;
49950
+ const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
49951
+ if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
49952
+ return true;
49953
+ }
49954
+ if (host.startsWith("::ffff:")) {
49955
+ const inner = host.slice(7);
49956
+ if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
49957
+ return true;
49958
+ }
49959
+ }
49960
+ return false;
49961
+ }
49962
+ function validateAndSanitizeUrl2(rawUrl) {
49963
+ const sanitized = sanitizeUrl2(rawUrl);
49964
+ if (!sanitized) {
49965
+ return { error: "Empty URL" };
49966
+ }
49967
+ if (!sanitized.startsWith("https://")) {
49968
+ return { error: "URL must use HTTPS scheme" };
49969
+ }
49785
49970
  try {
49786
49971
  const url3 = new URL(sanitized);
49787
49972
  const hostname5 = url3.hostname;
49788
49973
  if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
49789
49974
  return { error: "Non-ASCII hostnames are not allowed" };
49790
49975
  }
49791
- if (isPrivateHost(url3)) {
49976
+ if (isPrivateHost2(url3)) {
49792
49977
  return { error: "Private or localhost URLs are not allowed" };
49793
49978
  }
49794
49979
  const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
@@ -49847,7 +50032,7 @@ function parseIssueRef(input) {
49847
50032
  const bareMatch = input.match(/^(\d+)$/);
49848
50033
  if (bareMatch) {
49849
50034
  const issueNumber = parseInt(bareMatch[1], 10);
49850
- const remoteUrl = detectGitRemote();
50035
+ const remoteUrl = detectGitRemote2();
49851
50036
  if (!remoteUrl) {
49852
50037
  return null;
49853
50038
  }
@@ -49863,9 +50048,9 @@ function parseIssueRef(input) {
49863
50048
  }
49864
50049
  return null;
49865
50050
  }
49866
- function detectGitRemote() {
50051
+ function detectGitRemote2() {
49867
50052
  try {
49868
- const remoteUrl = execSync2("git remote get-url origin", {
50053
+ const remoteUrl = execSync3("git remote get-url origin", {
49869
50054
  encoding: "utf-8",
49870
50055
  stdio: ["pipe", "pipe", "pipe"],
49871
50056
  timeout: 5000
@@ -49875,23 +50060,6 @@ function detectGitRemote() {
49875
50060
  return null;
49876
50061
  }
49877
50062
  }
49878
- function parseGitRemoteUrl(remoteUrl) {
49879
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
49880
- if (httpsMatch) {
49881
- return {
49882
- owner: httpsMatch[1],
49883
- repo: httpsMatch[2].replace(/\.git$/, "")
49884
- };
49885
- }
49886
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
49887
- if (sshMatch) {
49888
- return {
49889
- owner: sshMatch[1],
49890
- repo: sshMatch[2].replace(/\.git$/, "")
49891
- };
49892
- }
49893
- return null;
49894
- }
49895
50063
  function handleIssueCommand(_directory, args) {
49896
50064
  const parsed = parseArgs5(args);
49897
50065
  const rawInput = parsed.rest.join(" ").trim();
@@ -49899,14 +50067,14 @@ function handleIssueCommand(_directory, args) {
49899
50067
  return USAGE5;
49900
50068
  }
49901
50069
  const isFullUrl = /^https?:\/\//i.test(rawInput);
49902
- const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput);
50070
+ const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl2(rawInput) : rawInput);
49903
50071
  if (!issueInfo) {
49904
50072
  return `Error: Could not parse issue reference from "${rawInput}"
49905
50073
 
49906
50074
  ${USAGE5}`;
49907
50075
  }
49908
50076
  const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
49909
- const result = validateAndSanitizeUrl(issueUrl);
50077
+ const result = validateAndSanitizeUrl2(issueUrl);
49910
50078
  if ("error" in result) {
49911
50079
  return `Error: ${result.error}
49912
50080
 
@@ -49922,8 +50090,9 @@ ${USAGE5}`;
49922
50090
  const flagsStr = flags.length > 0 ? ` ${flags.join(" ")}` : "";
49923
50091
  return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
49924
50092
  }
49925
- var MAX_URL_LEN = 2048, USAGE5;
50093
+ var MAX_URL_LEN2 = 2048, USAGE5;
49926
50094
  var init_issue = __esm(() => {
50095
+ init_pr_ref();
49927
50096
  USAGE5 = [
49928
50097
  "Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
49929
50098
  "",
@@ -49991,9 +50160,9 @@ async function migrateContextToKnowledge(directory, config3) {
49991
50160
  skippedReason: "empty-context"
49992
50161
  };
49993
50162
  }
49994
- const rawEntries = _internals23.parseContextMd(contextContent);
50163
+ const rawEntries = _internals24.parseContextMd(contextContent);
49995
50164
  if (rawEntries.length === 0) {
49996
- await _internals23.writeSentinel(sentinelPath, 0, 0);
50165
+ await _internals24.writeSentinel(sentinelPath, 0, 0);
49997
50166
  return {
49998
50167
  migrated: true,
49999
50168
  entriesMigrated: 0,
@@ -50004,10 +50173,10 @@ async function migrateContextToKnowledge(directory, config3) {
50004
50173
  const existing = await readKnowledge(knowledgePath);
50005
50174
  let migrated = 0;
50006
50175
  let dropped = 0;
50007
- const projectName = _internals23.inferProjectName(directory);
50176
+ const projectName = _internals24.inferProjectName(directory);
50008
50177
  for (const raw of rawEntries) {
50009
50178
  if (config3.validation_enabled !== false) {
50010
- const category = raw.categoryHint ?? _internals23.inferCategoryFromText(raw.text);
50179
+ const category = raw.categoryHint ?? _internals24.inferCategoryFromText(raw.text);
50011
50180
  const result = validateLesson(raw.text, existing.map((e) => e.lesson), {
50012
50181
  category,
50013
50182
  scope: "global",
@@ -50027,8 +50196,8 @@ async function migrateContextToKnowledge(directory, config3) {
50027
50196
  const entry = {
50028
50197
  id: randomUUID3(),
50029
50198
  tier: "swarm",
50030
- lesson: _internals23.truncateLesson(raw.text),
50031
- category: raw.categoryHint ?? _internals23.inferCategoryFromText(raw.text),
50199
+ lesson: _internals24.truncateLesson(raw.text),
50200
+ category: raw.categoryHint ?? _internals24.inferCategoryFromText(raw.text),
50032
50201
  tags: [...inferredTags, `migration:${raw.sourceSection}`],
50033
50202
  scope: "global",
50034
50203
  confidence: 0.3,
@@ -50051,7 +50220,7 @@ async function migrateContextToKnowledge(directory, config3) {
50051
50220
  if (migrated > 0) {
50052
50221
  await rewriteKnowledge(knowledgePath, existing);
50053
50222
  }
50054
- await _internals23.writeSentinel(sentinelPath, migrated, dropped);
50223
+ await _internals24.writeSentinel(sentinelPath, migrated, dropped);
50055
50224
  log(`[knowledge-migrator] Migrated ${migrated} entries, dropped ${dropped}`);
50056
50225
  return {
50057
50226
  migrated: true,
@@ -50061,7 +50230,7 @@ async function migrateContextToKnowledge(directory, config3) {
50061
50230
  };
50062
50231
  }
50063
50232
  async function migrateHiveKnowledgeLegacy(config3) {
50064
- const legacyHivePath = _internals23.resolveLegacyHiveKnowledgePath();
50233
+ const legacyHivePath = _internals24.resolveLegacyHiveKnowledgePath();
50065
50234
  const canonicalHivePath = resolveHiveKnowledgePath();
50066
50235
  const sentinelPath = path37.join(path37.dirname(canonicalHivePath), ".hive-knowledge-migrated");
50067
50236
  if (existsSync25(sentinelPath)) {
@@ -50084,7 +50253,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
50084
50253
  }
50085
50254
  const legacyEntries = await readKnowledge(legacyHivePath);
50086
50255
  if (legacyEntries.length === 0) {
50087
- await _internals23.writeSentinel(sentinelPath, 0, 0);
50256
+ await _internals24.writeSentinel(sentinelPath, 0, 0);
50088
50257
  return {
50089
50258
  migrated: true,
50090
50259
  entriesMigrated: 0,
@@ -50132,7 +50301,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
50132
50301
  const newHiveEntry = {
50133
50302
  id: resolvedId,
50134
50303
  tier: "hive",
50135
- lesson: _internals23.truncateLesson(lesson),
50304
+ lesson: _internals24.truncateLesson(lesson),
50136
50305
  category,
50137
50306
  tags: ["migration:legacy-hive"],
50138
50307
  scope: scopeTag,
@@ -50151,7 +50320,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
50151
50320
  encounter_score: 1
50152
50321
  };
50153
50322
  try {
50154
- await _internals23.appendKnowledge(canonicalHivePath, newHiveEntry);
50323
+ await _internals24.appendKnowledge(canonicalHivePath, newHiveEntry);
50155
50324
  existingHiveEntries.push(newHiveEntry);
50156
50325
  migrated++;
50157
50326
  } catch (appendError) {
@@ -50167,7 +50336,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
50167
50336
  dropped++;
50168
50337
  }
50169
50338
  }
50170
- await _internals23.writeSentinel(sentinelPath, migrated, dropped);
50339
+ await _internals24.writeSentinel(sentinelPath, migrated, dropped);
50171
50340
  log(`[knowledge-migrator] Migrated ${migrated} legacy hive entries, dropped ${dropped}`);
50172
50341
  return {
50173
50342
  migrated: true,
@@ -50178,7 +50347,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
50178
50347
  };
50179
50348
  }
50180
50349
  function parseContextMd(content) {
50181
- const sections = _internals23.splitIntoSections(content);
50350
+ const sections = _internals24.splitIntoSections(content);
50182
50351
  const entries = [];
50183
50352
  const seen = new Set;
50184
50353
  const sectionPatterns = [
@@ -50194,7 +50363,7 @@ function parseContextMd(content) {
50194
50363
  const match = sectionPatterns.find((sp) => sp.pattern.test(section.heading));
50195
50364
  if (!match)
50196
50365
  continue;
50197
- const bullets = _internals23.extractBullets(section.body);
50366
+ const bullets = _internals24.extractBullets(section.body);
50198
50367
  for (const bullet of bullets) {
50199
50368
  if (bullet.length < 15)
50200
50369
  continue;
@@ -50203,9 +50372,9 @@ function parseContextMd(content) {
50203
50372
  continue;
50204
50373
  seen.add(normalized);
50205
50374
  entries.push({
50206
- text: _internals23.truncateLesson(bullet),
50375
+ text: _internals24.truncateLesson(bullet),
50207
50376
  sourceSection: match.sourceSection,
50208
- categoryHint: _internals23.inferCategoryFromText(bullet)
50377
+ categoryHint: _internals24.inferCategoryFromText(bullet)
50209
50378
  });
50210
50379
  }
50211
50380
  }
@@ -50311,12 +50480,12 @@ function resolveLegacyHiveKnowledgePath() {
50311
50480
  }
50312
50481
  return path37.join(dataDir, "hive-knowledge.jsonl");
50313
50482
  }
50314
- var _internals23;
50483
+ var _internals24;
50315
50484
  var init_knowledge_migrator = __esm(() => {
50316
50485
  init_logger();
50317
50486
  init_knowledge_store();
50318
50487
  init_knowledge_validator();
50319
- _internals23 = {
50488
+ _internals24 = {
50320
50489
  appendKnowledge,
50321
50490
  migrateContextToKnowledge,
50322
50491
  migrateKnowledgeToExternal,
@@ -53833,9 +54002,9 @@ var init_memory2 = __esm(() => {
53833
54002
 
53834
54003
  // src/services/plan-service.ts
53835
54004
  async function getPlanData(directory, phaseArg) {
53836
- const plan = await _internals24.loadPlanJsonOnly(directory);
54005
+ const plan = await _internals25.loadPlanJsonOnly(directory);
53837
54006
  if (plan) {
53838
- const fullMarkdown = _internals24.derivePlanMarkdown(plan);
54007
+ const fullMarkdown = _internals25.derivePlanMarkdown(plan);
53839
54008
  if (phaseArg === undefined || phaseArg === null || phaseArg === "") {
53840
54009
  return {
53841
54010
  hasPlan: true,
@@ -53878,7 +54047,7 @@ async function getPlanData(directory, phaseArg) {
53878
54047
  isLegacy: false
53879
54048
  };
53880
54049
  }
53881
- const planContent = await _internals24.readSwarmFileAsync(directory, "plan.md");
54050
+ const planContent = await _internals25.readSwarmFileAsync(directory, "plan.md");
53882
54051
  if (!planContent) {
53883
54052
  return {
53884
54053
  hasPlan: false,
@@ -53974,11 +54143,11 @@ async function handlePlanCommand(directory, args) {
53974
54143
  const planData = await getPlanData(directory, phaseArg);
53975
54144
  return formatPlanMarkdown(planData);
53976
54145
  }
53977
- var _internals24;
54146
+ var _internals25;
53978
54147
  var init_plan_service = __esm(() => {
53979
54148
  init_utils2();
53980
54149
  init_manager();
53981
- _internals24 = {
54150
+ _internals25 = {
53982
54151
  loadPlanJsonOnly,
53983
54152
  derivePlanMarkdown,
53984
54153
  readSwarmFileAsync
@@ -53990,191 +54159,6 @@ var init_plan = __esm(() => {
53990
54159
  init_plan_service();
53991
54160
  });
53992
54161
 
53993
- // src/commands/pr-ref.ts
53994
- import { execSync as execSync3 } from "child_process";
53995
- function sanitizeUrl2(raw) {
53996
- let urlStr = raw.trim();
53997
- urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
53998
- const fragmentIdx = urlStr.indexOf("#");
53999
- if (fragmentIdx !== -1) {
54000
- urlStr = urlStr.slice(0, fragmentIdx);
54001
- }
54002
- const queryIdx = urlStr.indexOf("?");
54003
- if (queryIdx !== -1) {
54004
- urlStr = urlStr.slice(0, queryIdx);
54005
- }
54006
- urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
54007
- if (urlStr.length > MAX_URL_LEN2) {
54008
- urlStr = urlStr.slice(0, MAX_URL_LEN2);
54009
- }
54010
- return urlStr.trim();
54011
- }
54012
- function sanitizeInstructions(raw) {
54013
- const collapsed = raw.replace(/\s+/g, " ").trim();
54014
- const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
54015
- const normalized = stripped.replace(/\s+/g, " ").trim();
54016
- if (normalized.length <= MAX_INSTRUCTIONS_LEN)
54017
- return normalized;
54018
- return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}\u2026`;
54019
- }
54020
- function hasNonAsciiHostname(hostname5) {
54021
- for (const ch of hostname5) {
54022
- const cp = ch.codePointAt(0);
54023
- if (cp !== undefined && cp > 127)
54024
- return true;
54025
- }
54026
- return false;
54027
- }
54028
- function isPrivateHost2(url3) {
54029
- const host = url3.hostname.toLowerCase();
54030
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
54031
- return true;
54032
- }
54033
- if (host.startsWith("localhost") || host === "localhost.com") {
54034
- return true;
54035
- }
54036
- const ipv4Private = /^10\./;
54037
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
54038
- const ipv4192 = /^192\.168\./;
54039
- const ipv6Private = /^fe80:/i;
54040
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
54041
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
54042
- return true;
54043
- }
54044
- if (host.startsWith("::ffff:")) {
54045
- const inner = host.slice(7);
54046
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
54047
- return true;
54048
- }
54049
- }
54050
- return false;
54051
- }
54052
- function validateAndSanitizeUrl2(rawUrl) {
54053
- const sanitized = sanitizeUrl2(rawUrl);
54054
- if (!sanitized) {
54055
- return { error: "Empty URL" };
54056
- }
54057
- if (!sanitized.startsWith("https://")) {
54058
- return { error: "URL must use HTTPS scheme" };
54059
- }
54060
- try {
54061
- const url3 = new URL(sanitized);
54062
- if (hasNonAsciiHostname(url3.hostname)) {
54063
- return { error: "Non-ASCII hostnames are not allowed" };
54064
- }
54065
- if (isPrivateHost2(url3)) {
54066
- return { error: "Private or localhost URLs are not allowed" };
54067
- }
54068
- const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
54069
- if (!githubPrPattern.test(sanitized)) {
54070
- return {
54071
- error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
54072
- };
54073
- }
54074
- return { sanitized };
54075
- } catch {
54076
- return { error: "Invalid URL format" };
54077
- }
54078
- }
54079
- function parsePrRef(input, cwd) {
54080
- const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
54081
- if (urlMatch) {
54082
- return {
54083
- owner: urlMatch[1],
54084
- repo: urlMatch[2],
54085
- number: parseInt(urlMatch[3], 10)
54086
- };
54087
- }
54088
- const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
54089
- if (shorthandMatch) {
54090
- return {
54091
- owner: shorthandMatch[1],
54092
- repo: shorthandMatch[2],
54093
- number: parseInt(shorthandMatch[3], 10)
54094
- };
54095
- }
54096
- const bareMatch = input.match(/^(\d+)$/);
54097
- if (bareMatch) {
54098
- const prNumber = parseInt(bareMatch[1], 10);
54099
- const remoteUrl = detectGitRemote2(cwd);
54100
- if (!remoteUrl) {
54101
- return null;
54102
- }
54103
- const parsed = parseGitRemoteUrl2(remoteUrl);
54104
- if (!parsed) {
54105
- return null;
54106
- }
54107
- return {
54108
- owner: parsed.owner,
54109
- repo: parsed.repo,
54110
- number: prNumber
54111
- };
54112
- }
54113
- return null;
54114
- }
54115
- function detectGitRemote2(cwd) {
54116
- try {
54117
- const remoteUrl = _internals25.execSync("git remote get-url origin", {
54118
- encoding: "utf-8",
54119
- stdio: ["pipe", "pipe", "pipe"],
54120
- timeout: 5000,
54121
- ...cwd ? { cwd } : {}
54122
- }).trim();
54123
- return remoteUrl || null;
54124
- } catch {
54125
- return null;
54126
- }
54127
- }
54128
- function parseGitRemoteUrl2(remoteUrl) {
54129
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
54130
- if (httpsMatch) {
54131
- return {
54132
- owner: httpsMatch[1],
54133
- repo: httpsMatch[2].replace(/\.git$/, "")
54134
- };
54135
- }
54136
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
54137
- if (sshMatch) {
54138
- return {
54139
- owner: sshMatch[1],
54140
- repo: sshMatch[2].replace(/\.git$/, "")
54141
- };
54142
- }
54143
- const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
54144
- if (pathMatch) {
54145
- return {
54146
- owner: pathMatch[1],
54147
- repo: pathMatch[2].replace(/\.git$/, "")
54148
- };
54149
- }
54150
- return null;
54151
- }
54152
- function looksLikePrRef(token) {
54153
- return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
54154
- }
54155
- function resolvePrCommandInput(rest, cwd) {
54156
- if (rest.length === 0) {
54157
- return null;
54158
- }
54159
- const refToken = rest[0];
54160
- const instructions = sanitizeInstructions(rest.slice(1).join(" "));
54161
- const isFullUrl = /^https?:\/\//i.test(refToken);
54162
- const prInfo = parsePrRef(isFullUrl ? sanitizeUrl2(refToken) : refToken, cwd);
54163
- if (!prInfo) {
54164
- return { error: `Could not parse PR reference from "${refToken}"` };
54165
- }
54166
- const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
54167
- const result = validateAndSanitizeUrl2(prUrl);
54168
- if ("error" in result) {
54169
- return { error: result.error };
54170
- }
54171
- return { prUrl: result.sanitized, instructions };
54172
- }
54173
- var _internals25, MAX_URL_LEN2 = 2048, MAX_INSTRUCTIONS_LEN = 1000;
54174
- var init_pr_ref = __esm(() => {
54175
- _internals25 = { execSync: execSync3 };
54176
- });
54177
-
54178
54162
  // src/commands/pr-feedback.ts
54179
54163
  function handlePrFeedbackCommand(directory, args) {
54180
54164
  const rest = args.filter((t) => t.trim().length > 0);
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.68.1",
72
+ version: "7.68.2",
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",
@@ -73712,7 +73712,7 @@ var init_history = __esm(() => {
73712
73712
  init_history_service();
73713
73713
  });
73714
73714
 
73715
- // src/commands/issue.ts
73715
+ // src/commands/pr-ref.ts
73716
73716
  import { execSync as execSync2 } from "node:child_process";
73717
73717
  function sanitizeUrl(raw) {
73718
73718
  let urlStr = raw.trim();
@@ -73731,6 +73731,22 @@ function sanitizeUrl(raw) {
73731
73731
  }
73732
73732
  return urlStr.trim();
73733
73733
  }
73734
+ function sanitizeInstructions(raw) {
73735
+ const collapsed = raw.replace(/\s+/g, " ").trim();
73736
+ const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
73737
+ const normalized = stripped.replace(/\s+/g, " ").trim();
73738
+ if (normalized.length <= MAX_INSTRUCTIONS_LEN)
73739
+ return normalized;
73740
+ return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
73741
+ }
73742
+ function hasNonAsciiHostname(hostname5) {
73743
+ for (const ch of hostname5) {
73744
+ const cp = ch.codePointAt(0);
73745
+ if (cp !== undefined && cp > 127)
73746
+ return true;
73747
+ }
73748
+ return false;
73749
+ }
73734
73750
  function isPrivateHost(url3) {
73735
73751
  const host = url3.hostname.toLowerCase();
73736
73752
  if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
@@ -73763,13 +73779,182 @@ function validateAndSanitizeUrl(rawUrl) {
73763
73779
  if (!sanitized.startsWith("https://")) {
73764
73780
  return { error: "URL must use HTTPS scheme" };
73765
73781
  }
73782
+ try {
73783
+ const url3 = new URL(sanitized);
73784
+ if (hasNonAsciiHostname(url3.hostname)) {
73785
+ return { error: "Non-ASCII hostnames are not allowed" };
73786
+ }
73787
+ if (isPrivateHost(url3)) {
73788
+ return { error: "Private or localhost URLs are not allowed" };
73789
+ }
73790
+ const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
73791
+ if (!githubPrPattern.test(sanitized)) {
73792
+ return {
73793
+ error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
73794
+ };
73795
+ }
73796
+ return { sanitized };
73797
+ } catch {
73798
+ return { error: "Invalid URL format" };
73799
+ }
73800
+ }
73801
+ function parsePrRef(input, cwd) {
73802
+ const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
73803
+ if (urlMatch) {
73804
+ return {
73805
+ owner: urlMatch[1],
73806
+ repo: urlMatch[2],
73807
+ number: parseInt(urlMatch[3], 10)
73808
+ };
73809
+ }
73810
+ const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
73811
+ if (shorthandMatch) {
73812
+ return {
73813
+ owner: shorthandMatch[1],
73814
+ repo: shorthandMatch[2],
73815
+ number: parseInt(shorthandMatch[3], 10)
73816
+ };
73817
+ }
73818
+ const bareMatch = input.match(/^(\d+)$/);
73819
+ if (bareMatch) {
73820
+ const prNumber = parseInt(bareMatch[1], 10);
73821
+ const remoteUrl = detectGitRemote(cwd);
73822
+ if (!remoteUrl) {
73823
+ return null;
73824
+ }
73825
+ const parsed = parseGitRemoteUrl(remoteUrl);
73826
+ if (!parsed) {
73827
+ return null;
73828
+ }
73829
+ return {
73830
+ owner: parsed.owner,
73831
+ repo: parsed.repo,
73832
+ number: prNumber
73833
+ };
73834
+ }
73835
+ return null;
73836
+ }
73837
+ function detectGitRemote(cwd) {
73838
+ try {
73839
+ const remoteUrl = _internals35.execSync("git remote get-url origin", {
73840
+ encoding: "utf-8",
73841
+ stdio: ["pipe", "pipe", "pipe"],
73842
+ timeout: 5000,
73843
+ ...cwd ? { cwd } : {}
73844
+ }).trim();
73845
+ return remoteUrl || null;
73846
+ } catch {
73847
+ return null;
73848
+ }
73849
+ }
73850
+ function parseGitRemoteUrl(remoteUrl) {
73851
+ const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
73852
+ if (httpsMatch) {
73853
+ return {
73854
+ owner: httpsMatch[1],
73855
+ repo: httpsMatch[2].replace(/\.git$/, "")
73856
+ };
73857
+ }
73858
+ const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
73859
+ if (sshMatch) {
73860
+ return {
73861
+ owner: sshMatch[1],
73862
+ repo: sshMatch[2].replace(/\.git$/, "")
73863
+ };
73864
+ }
73865
+ const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
73866
+ if (pathMatch) {
73867
+ return {
73868
+ owner: pathMatch[1],
73869
+ repo: pathMatch[2].replace(/\.git$/, "")
73870
+ };
73871
+ }
73872
+ return null;
73873
+ }
73874
+ function looksLikePrRef(token) {
73875
+ return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
73876
+ }
73877
+ function resolvePrCommandInput(rest, cwd) {
73878
+ if (rest.length === 0) {
73879
+ return null;
73880
+ }
73881
+ const refToken = rest[0];
73882
+ const instructions = sanitizeInstructions(rest.slice(1).join(" "));
73883
+ const isFullUrl = /^https?:\/\//i.test(refToken);
73884
+ const prInfo = parsePrRef(isFullUrl ? sanitizeUrl(refToken) : refToken, cwd);
73885
+ if (!prInfo) {
73886
+ return { error: `Could not parse PR reference from "${refToken}"` };
73887
+ }
73888
+ const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
73889
+ const result = validateAndSanitizeUrl(prUrl);
73890
+ if ("error" in result) {
73891
+ return { error: result.error };
73892
+ }
73893
+ return { prUrl: result.sanitized, instructions };
73894
+ }
73895
+ var _internals35, MAX_URL_LEN = 2048, MAX_INSTRUCTIONS_LEN = 1000;
73896
+ var init_pr_ref = __esm(() => {
73897
+ _internals35 = { execSync: execSync2 };
73898
+ });
73899
+
73900
+ // src/commands/issue.ts
73901
+ import { execSync as execSync3 } from "node:child_process";
73902
+ function sanitizeUrl2(raw) {
73903
+ let urlStr = raw.trim();
73904
+ urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
73905
+ const fragmentIdx = urlStr.indexOf("#");
73906
+ if (fragmentIdx !== -1) {
73907
+ urlStr = urlStr.slice(0, fragmentIdx);
73908
+ }
73909
+ const queryIdx = urlStr.indexOf("?");
73910
+ if (queryIdx !== -1) {
73911
+ urlStr = urlStr.slice(0, queryIdx);
73912
+ }
73913
+ urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
73914
+ if (urlStr.length > MAX_URL_LEN2) {
73915
+ urlStr = urlStr.slice(0, MAX_URL_LEN2);
73916
+ }
73917
+ return urlStr.trim();
73918
+ }
73919
+ function isPrivateHost2(url3) {
73920
+ const host = url3.hostname.toLowerCase();
73921
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
73922
+ return true;
73923
+ }
73924
+ if (host.startsWith("localhost") || host === "localhost.com") {
73925
+ return true;
73926
+ }
73927
+ const ipv4Private = /^10\./;
73928
+ const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
73929
+ const ipv4192 = /^192\.168\./;
73930
+ const ipv6Private = /^fe80:/i;
73931
+ const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
73932
+ if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
73933
+ return true;
73934
+ }
73935
+ if (host.startsWith("::ffff:")) {
73936
+ const inner = host.slice(7);
73937
+ if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
73938
+ return true;
73939
+ }
73940
+ }
73941
+ return false;
73942
+ }
73943
+ function validateAndSanitizeUrl2(rawUrl) {
73944
+ const sanitized = sanitizeUrl2(rawUrl);
73945
+ if (!sanitized) {
73946
+ return { error: "Empty URL" };
73947
+ }
73948
+ if (!sanitized.startsWith("https://")) {
73949
+ return { error: "URL must use HTTPS scheme" };
73950
+ }
73766
73951
  try {
73767
73952
  const url3 = new URL(sanitized);
73768
73953
  const hostname5 = url3.hostname;
73769
73954
  if (/[\u0080-\u{10FFFF}]/u.test(hostname5)) {
73770
73955
  return { error: "Non-ASCII hostnames are not allowed" };
73771
73956
  }
73772
- if (isPrivateHost(url3)) {
73957
+ if (isPrivateHost2(url3)) {
73773
73958
  return { error: "Private or localhost URLs are not allowed" };
73774
73959
  }
73775
73960
  const githubIssuePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/([0-9]+)\/?$/;
@@ -73828,7 +74013,7 @@ function parseIssueRef(input) {
73828
74013
  const bareMatch = input.match(/^(\d+)$/);
73829
74014
  if (bareMatch) {
73830
74015
  const issueNumber = parseInt(bareMatch[1], 10);
73831
- const remoteUrl = detectGitRemote();
74016
+ const remoteUrl = detectGitRemote2();
73832
74017
  if (!remoteUrl) {
73833
74018
  return null;
73834
74019
  }
@@ -73844,9 +74029,9 @@ function parseIssueRef(input) {
73844
74029
  }
73845
74030
  return null;
73846
74031
  }
73847
- function detectGitRemote() {
74032
+ function detectGitRemote2() {
73848
74033
  try {
73849
- const remoteUrl = execSync2("git remote get-url origin", {
74034
+ const remoteUrl = execSync3("git remote get-url origin", {
73850
74035
  encoding: "utf-8",
73851
74036
  stdio: ["pipe", "pipe", "pipe"],
73852
74037
  timeout: 5000
@@ -73856,23 +74041,6 @@ function detectGitRemote() {
73856
74041
  return null;
73857
74042
  }
73858
74043
  }
73859
- function parseGitRemoteUrl(remoteUrl) {
73860
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
73861
- if (httpsMatch) {
73862
- return {
73863
- owner: httpsMatch[1],
73864
- repo: httpsMatch[2].replace(/\.git$/, "")
73865
- };
73866
- }
73867
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
73868
- if (sshMatch) {
73869
- return {
73870
- owner: sshMatch[1],
73871
- repo: sshMatch[2].replace(/\.git$/, "")
73872
- };
73873
- }
73874
- return null;
73875
- }
73876
74044
  function handleIssueCommand(_directory, args2) {
73877
74045
  const parsed = parseArgs5(args2);
73878
74046
  const rawInput = parsed.rest.join(" ").trim();
@@ -73880,14 +74048,14 @@ function handleIssueCommand(_directory, args2) {
73880
74048
  return USAGE5;
73881
74049
  }
73882
74050
  const isFullUrl = /^https?:\/\//i.test(rawInput);
73883
- const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl(rawInput) : rawInput);
74051
+ const issueInfo = parseIssueRef(isFullUrl ? sanitizeUrl2(rawInput) : rawInput);
73884
74052
  if (!issueInfo) {
73885
74053
  return `Error: Could not parse issue reference from "${rawInput}"
73886
74054
 
73887
74055
  ${USAGE5}`;
73888
74056
  }
73889
74057
  const issueUrl = `https://github.com/${issueInfo.owner}/${issueInfo.repo}/issues/${issueInfo.number}`;
73890
- const result = validateAndSanitizeUrl(issueUrl);
74058
+ const result = validateAndSanitizeUrl2(issueUrl);
73891
74059
  if ("error" in result) {
73892
74060
  return `Error: ${result.error}
73893
74061
 
@@ -73903,8 +74071,9 @@ ${USAGE5}`;
73903
74071
  const flagsStr = flags2.length > 0 ? ` ${flags2.join(" ")}` : "";
73904
74072
  return `[MODE: ISSUE_INGEST issue="${result.sanitized}"${flagsStr}]`;
73905
74073
  }
73906
- var MAX_URL_LEN = 2048, USAGE5;
74074
+ var MAX_URL_LEN2 = 2048, USAGE5;
73907
74075
  var init_issue = __esm(() => {
74076
+ init_pr_ref();
73908
74077
  USAGE5 = [
73909
74078
  "Usage: /swarm issue <url|owner/repo#N|N> [--plan] [--trace] [--no-repro]",
73910
74079
  "",
@@ -73972,9 +74141,9 @@ async function migrateContextToKnowledge(directory, config3) {
73972
74141
  skippedReason: "empty-context"
73973
74142
  };
73974
74143
  }
73975
- const rawEntries = _internals35.parseContextMd(contextContent);
74144
+ const rawEntries = _internals36.parseContextMd(contextContent);
73976
74145
  if (rawEntries.length === 0) {
73977
- await _internals35.writeSentinel(sentinelPath, 0, 0);
74146
+ await _internals36.writeSentinel(sentinelPath, 0, 0);
73978
74147
  return {
73979
74148
  migrated: true,
73980
74149
  entriesMigrated: 0,
@@ -73985,10 +74154,10 @@ async function migrateContextToKnowledge(directory, config3) {
73985
74154
  const existing = await readKnowledge(knowledgePath);
73986
74155
  let migrated = 0;
73987
74156
  let dropped = 0;
73988
- const projectName = _internals35.inferProjectName(directory);
74157
+ const projectName = _internals36.inferProjectName(directory);
73989
74158
  for (const raw of rawEntries) {
73990
74159
  if (config3.validation_enabled !== false) {
73991
- const category = raw.categoryHint ?? _internals35.inferCategoryFromText(raw.text);
74160
+ const category = raw.categoryHint ?? _internals36.inferCategoryFromText(raw.text);
73992
74161
  const result = validateLesson(raw.text, existing.map((e) => e.lesson), {
73993
74162
  category,
73994
74163
  scope: "global",
@@ -74008,8 +74177,8 @@ async function migrateContextToKnowledge(directory, config3) {
74008
74177
  const entry = {
74009
74178
  id: randomUUID6(),
74010
74179
  tier: "swarm",
74011
- lesson: _internals35.truncateLesson(raw.text),
74012
- category: raw.categoryHint ?? _internals35.inferCategoryFromText(raw.text),
74180
+ lesson: _internals36.truncateLesson(raw.text),
74181
+ category: raw.categoryHint ?? _internals36.inferCategoryFromText(raw.text),
74013
74182
  tags: [...inferredTags, `migration:${raw.sourceSection}`],
74014
74183
  scope: "global",
74015
74184
  confidence: 0.3,
@@ -74032,7 +74201,7 @@ async function migrateContextToKnowledge(directory, config3) {
74032
74201
  if (migrated > 0) {
74033
74202
  await rewriteKnowledge(knowledgePath, existing);
74034
74203
  }
74035
- await _internals35.writeSentinel(sentinelPath, migrated, dropped);
74204
+ await _internals36.writeSentinel(sentinelPath, migrated, dropped);
74036
74205
  log(`[knowledge-migrator] Migrated ${migrated} entries, dropped ${dropped}`);
74037
74206
  return {
74038
74207
  migrated: true,
@@ -74042,7 +74211,7 @@ async function migrateContextToKnowledge(directory, config3) {
74042
74211
  };
74043
74212
  }
74044
74213
  async function migrateHiveKnowledgeLegacy(config3) {
74045
- const legacyHivePath = _internals35.resolveLegacyHiveKnowledgePath();
74214
+ const legacyHivePath = _internals36.resolveLegacyHiveKnowledgePath();
74046
74215
  const canonicalHivePath = resolveHiveKnowledgePath();
74047
74216
  const sentinelPath = path53.join(path53.dirname(canonicalHivePath), ".hive-knowledge-migrated");
74048
74217
  if (existsSync32(sentinelPath)) {
@@ -74065,7 +74234,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
74065
74234
  }
74066
74235
  const legacyEntries = await readKnowledge(legacyHivePath);
74067
74236
  if (legacyEntries.length === 0) {
74068
- await _internals35.writeSentinel(sentinelPath, 0, 0);
74237
+ await _internals36.writeSentinel(sentinelPath, 0, 0);
74069
74238
  return {
74070
74239
  migrated: true,
74071
74240
  entriesMigrated: 0,
@@ -74113,7 +74282,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
74113
74282
  const newHiveEntry = {
74114
74283
  id: resolvedId,
74115
74284
  tier: "hive",
74116
- lesson: _internals35.truncateLesson(lesson),
74285
+ lesson: _internals36.truncateLesson(lesson),
74117
74286
  category,
74118
74287
  tags: ["migration:legacy-hive"],
74119
74288
  scope: scopeTag,
@@ -74132,7 +74301,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
74132
74301
  encounter_score: 1
74133
74302
  };
74134
74303
  try {
74135
- await _internals35.appendKnowledge(canonicalHivePath, newHiveEntry);
74304
+ await _internals36.appendKnowledge(canonicalHivePath, newHiveEntry);
74136
74305
  existingHiveEntries.push(newHiveEntry);
74137
74306
  migrated++;
74138
74307
  } catch (appendError) {
@@ -74148,7 +74317,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
74148
74317
  dropped++;
74149
74318
  }
74150
74319
  }
74151
- await _internals35.writeSentinel(sentinelPath, migrated, dropped);
74320
+ await _internals36.writeSentinel(sentinelPath, migrated, dropped);
74152
74321
  log(`[knowledge-migrator] Migrated ${migrated} legacy hive entries, dropped ${dropped}`);
74153
74322
  return {
74154
74323
  migrated: true,
@@ -74159,7 +74328,7 @@ async function migrateHiveKnowledgeLegacy(config3) {
74159
74328
  };
74160
74329
  }
74161
74330
  function parseContextMd(content) {
74162
- const sections = _internals35.splitIntoSections(content);
74331
+ const sections = _internals36.splitIntoSections(content);
74163
74332
  const entries = [];
74164
74333
  const seen = new Set;
74165
74334
  const sectionPatterns = [
@@ -74175,7 +74344,7 @@ function parseContextMd(content) {
74175
74344
  const match = sectionPatterns.find((sp) => sp.pattern.test(section.heading));
74176
74345
  if (!match)
74177
74346
  continue;
74178
- const bullets = _internals35.extractBullets(section.body);
74347
+ const bullets = _internals36.extractBullets(section.body);
74179
74348
  for (const bullet of bullets) {
74180
74349
  if (bullet.length < 15)
74181
74350
  continue;
@@ -74184,9 +74353,9 @@ function parseContextMd(content) {
74184
74353
  continue;
74185
74354
  seen.add(normalized);
74186
74355
  entries.push({
74187
- text: _internals35.truncateLesson(bullet),
74356
+ text: _internals36.truncateLesson(bullet),
74188
74357
  sourceSection: match.sourceSection,
74189
- categoryHint: _internals35.inferCategoryFromText(bullet)
74358
+ categoryHint: _internals36.inferCategoryFromText(bullet)
74190
74359
  });
74191
74360
  }
74192
74361
  }
@@ -74292,12 +74461,12 @@ function resolveLegacyHiveKnowledgePath() {
74292
74461
  }
74293
74462
  return path53.join(dataDir, "hive-knowledge.jsonl");
74294
74463
  }
74295
- var _internals35;
74464
+ var _internals36;
74296
74465
  var init_knowledge_migrator = __esm(() => {
74297
74466
  init_logger();
74298
74467
  init_knowledge_store();
74299
74468
  init_knowledge_validator();
74300
- _internals35 = {
74469
+ _internals36 = {
74301
74470
  appendKnowledge,
74302
74471
  migrateContextToKnowledge,
74303
74472
  migrateKnowledgeToExternal,
@@ -78805,9 +78974,9 @@ var init_memory2 = __esm(() => {
78805
78974
 
78806
78975
  // src/services/plan-service.ts
78807
78976
  async function getPlanData(directory, phaseArg) {
78808
- const plan = await _internals36.loadPlanJsonOnly(directory);
78977
+ const plan = await _internals37.loadPlanJsonOnly(directory);
78809
78978
  if (plan) {
78810
- const fullMarkdown = _internals36.derivePlanMarkdown(plan);
78979
+ const fullMarkdown = _internals37.derivePlanMarkdown(plan);
78811
78980
  if (phaseArg === undefined || phaseArg === null || phaseArg === "") {
78812
78981
  return {
78813
78982
  hasPlan: true,
@@ -78850,7 +79019,7 @@ async function getPlanData(directory, phaseArg) {
78850
79019
  isLegacy: false
78851
79020
  };
78852
79021
  }
78853
- const planContent = await _internals36.readSwarmFileAsync(directory, "plan.md");
79022
+ const planContent = await _internals37.readSwarmFileAsync(directory, "plan.md");
78854
79023
  if (!planContent) {
78855
79024
  return {
78856
79025
  hasPlan: false,
@@ -78946,11 +79115,11 @@ async function handlePlanCommand(directory, args2) {
78946
79115
  const planData = await getPlanData(directory, phaseArg);
78947
79116
  return formatPlanMarkdown(planData);
78948
79117
  }
78949
- var _internals36;
79118
+ var _internals37;
78950
79119
  var init_plan_service = __esm(() => {
78951
79120
  init_utils2();
78952
79121
  init_manager();
78953
- _internals36 = {
79122
+ _internals37 = {
78954
79123
  loadPlanJsonOnly,
78955
79124
  derivePlanMarkdown,
78956
79125
  readSwarmFileAsync
@@ -78962,191 +79131,6 @@ var init_plan = __esm(() => {
78962
79131
  init_plan_service();
78963
79132
  });
78964
79133
 
78965
- // src/commands/pr-ref.ts
78966
- import { execSync as execSync3 } from "node:child_process";
78967
- function sanitizeUrl2(raw) {
78968
- let urlStr = raw.trim();
78969
- urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
78970
- const fragmentIdx = urlStr.indexOf("#");
78971
- if (fragmentIdx !== -1) {
78972
- urlStr = urlStr.slice(0, fragmentIdx);
78973
- }
78974
- const queryIdx = urlStr.indexOf("?");
78975
- if (queryIdx !== -1) {
78976
- urlStr = urlStr.slice(0, queryIdx);
78977
- }
78978
- urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
78979
- if (urlStr.length > MAX_URL_LEN2) {
78980
- urlStr = urlStr.slice(0, MAX_URL_LEN2);
78981
- }
78982
- return urlStr.trim();
78983
- }
78984
- function sanitizeInstructions(raw) {
78985
- const collapsed = raw.replace(/\s+/g, " ").trim();
78986
- const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
78987
- const normalized = stripped.replace(/\s+/g, " ").trim();
78988
- if (normalized.length <= MAX_INSTRUCTIONS_LEN)
78989
- return normalized;
78990
- return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
78991
- }
78992
- function hasNonAsciiHostname(hostname5) {
78993
- for (const ch of hostname5) {
78994
- const cp = ch.codePointAt(0);
78995
- if (cp !== undefined && cp > 127)
78996
- return true;
78997
- }
78998
- return false;
78999
- }
79000
- function isPrivateHost2(url3) {
79001
- const host = url3.hostname.toLowerCase();
79002
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
79003
- return true;
79004
- }
79005
- if (host.startsWith("localhost") || host === "localhost.com") {
79006
- return true;
79007
- }
79008
- const ipv4Private = /^10\./;
79009
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
79010
- const ipv4192 = /^192\.168\./;
79011
- const ipv6Private = /^fe80:/i;
79012
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
79013
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
79014
- return true;
79015
- }
79016
- if (host.startsWith("::ffff:")) {
79017
- const inner = host.slice(7);
79018
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
79019
- return true;
79020
- }
79021
- }
79022
- return false;
79023
- }
79024
- function validateAndSanitizeUrl2(rawUrl) {
79025
- const sanitized = sanitizeUrl2(rawUrl);
79026
- if (!sanitized) {
79027
- return { error: "Empty URL" };
79028
- }
79029
- if (!sanitized.startsWith("https://")) {
79030
- return { error: "URL must use HTTPS scheme" };
79031
- }
79032
- try {
79033
- const url3 = new URL(sanitized);
79034
- if (hasNonAsciiHostname(url3.hostname)) {
79035
- return { error: "Non-ASCII hostnames are not allowed" };
79036
- }
79037
- if (isPrivateHost2(url3)) {
79038
- return { error: "Private or localhost URLs are not allowed" };
79039
- }
79040
- const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
79041
- if (!githubPrPattern.test(sanitized)) {
79042
- return {
79043
- error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
79044
- };
79045
- }
79046
- return { sanitized };
79047
- } catch {
79048
- return { error: "Invalid URL format" };
79049
- }
79050
- }
79051
- function parsePrRef(input, cwd) {
79052
- const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
79053
- if (urlMatch) {
79054
- return {
79055
- owner: urlMatch[1],
79056
- repo: urlMatch[2],
79057
- number: parseInt(urlMatch[3], 10)
79058
- };
79059
- }
79060
- const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
79061
- if (shorthandMatch) {
79062
- return {
79063
- owner: shorthandMatch[1],
79064
- repo: shorthandMatch[2],
79065
- number: parseInt(shorthandMatch[3], 10)
79066
- };
79067
- }
79068
- const bareMatch = input.match(/^(\d+)$/);
79069
- if (bareMatch) {
79070
- const prNumber = parseInt(bareMatch[1], 10);
79071
- const remoteUrl = detectGitRemote2(cwd);
79072
- if (!remoteUrl) {
79073
- return null;
79074
- }
79075
- const parsed = parseGitRemoteUrl2(remoteUrl);
79076
- if (!parsed) {
79077
- return null;
79078
- }
79079
- return {
79080
- owner: parsed.owner,
79081
- repo: parsed.repo,
79082
- number: prNumber
79083
- };
79084
- }
79085
- return null;
79086
- }
79087
- function detectGitRemote2(cwd) {
79088
- try {
79089
- const remoteUrl = _internals37.execSync("git remote get-url origin", {
79090
- encoding: "utf-8",
79091
- stdio: ["pipe", "pipe", "pipe"],
79092
- timeout: 5000,
79093
- ...cwd ? { cwd } : {}
79094
- }).trim();
79095
- return remoteUrl || null;
79096
- } catch {
79097
- return null;
79098
- }
79099
- }
79100
- function parseGitRemoteUrl2(remoteUrl) {
79101
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
79102
- if (httpsMatch) {
79103
- return {
79104
- owner: httpsMatch[1],
79105
- repo: httpsMatch[2].replace(/\.git$/, "")
79106
- };
79107
- }
79108
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
79109
- if (sshMatch) {
79110
- return {
79111
- owner: sshMatch[1],
79112
- repo: sshMatch[2].replace(/\.git$/, "")
79113
- };
79114
- }
79115
- const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
79116
- if (pathMatch) {
79117
- return {
79118
- owner: pathMatch[1],
79119
- repo: pathMatch[2].replace(/\.git$/, "")
79120
- };
79121
- }
79122
- return null;
79123
- }
79124
- function looksLikePrRef(token) {
79125
- return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
79126
- }
79127
- function resolvePrCommandInput(rest, cwd) {
79128
- if (rest.length === 0) {
79129
- return null;
79130
- }
79131
- const refToken = rest[0];
79132
- const instructions = sanitizeInstructions(rest.slice(1).join(" "));
79133
- const isFullUrl = /^https?:\/\//i.test(refToken);
79134
- const prInfo = parsePrRef(isFullUrl ? sanitizeUrl2(refToken) : refToken, cwd);
79135
- if (!prInfo) {
79136
- return { error: `Could not parse PR reference from "${refToken}"` };
79137
- }
79138
- const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
79139
- const result = validateAndSanitizeUrl2(prUrl);
79140
- if ("error" in result) {
79141
- return { error: result.error };
79142
- }
79143
- return { prUrl: result.sanitized, instructions };
79144
- }
79145
- var _internals37, MAX_URL_LEN2 = 2048, MAX_INSTRUCTIONS_LEN = 1000;
79146
- var init_pr_ref = __esm(() => {
79147
- _internals37 = { execSync: execSync3 };
79148
- });
79149
-
79150
79134
  // src/commands/pr-feedback.ts
79151
79135
  function handlePrFeedbackCommand(directory, args2) {
79152
79136
  const rest = args2.filter((t) => t.trim().length > 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.68.1",
3
+ "version": "7.68.2",
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",