opencode-swarm 7.68.0 → 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.
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.0",
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
  "",
@@ -49948,6 +50117,7 @@ var KNOWLEDGE_SCHEMA_VERSION = 2;
49948
50117
  import { randomUUID as randomUUID3 } from "crypto";
49949
50118
  import { existsSync as existsSync25, readFileSync as readFileSync15 } from "fs";
49950
50119
  import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
50120
+ import * as os8 from "os";
49951
50121
  import * as path37 from "path";
49952
50122
  async function migrateKnowledgeToExternal(_directory, _config) {
49953
50123
  return {
@@ -49990,9 +50160,9 @@ async function migrateContextToKnowledge(directory, config3) {
49990
50160
  skippedReason: "empty-context"
49991
50161
  };
49992
50162
  }
49993
- const rawEntries = _internals23.parseContextMd(contextContent);
50163
+ const rawEntries = _internals24.parseContextMd(contextContent);
49994
50164
  if (rawEntries.length === 0) {
49995
- await _internals23.writeSentinel(sentinelPath, 0, 0);
50165
+ await _internals24.writeSentinel(sentinelPath, 0, 0);
49996
50166
  return {
49997
50167
  migrated: true,
49998
50168
  entriesMigrated: 0,
@@ -50003,10 +50173,10 @@ async function migrateContextToKnowledge(directory, config3) {
50003
50173
  const existing = await readKnowledge(knowledgePath);
50004
50174
  let migrated = 0;
50005
50175
  let dropped = 0;
50006
- const projectName = _internals23.inferProjectName(directory);
50176
+ const projectName = _internals24.inferProjectName(directory);
50007
50177
  for (const raw of rawEntries) {
50008
50178
  if (config3.validation_enabled !== false) {
50009
- const category = raw.categoryHint ?? _internals23.inferCategoryFromText(raw.text);
50179
+ const category = raw.categoryHint ?? _internals24.inferCategoryFromText(raw.text);
50010
50180
  const result = validateLesson(raw.text, existing.map((e) => e.lesson), {
50011
50181
  category,
50012
50182
  scope: "global",
@@ -50026,8 +50196,8 @@ async function migrateContextToKnowledge(directory, config3) {
50026
50196
  const entry = {
50027
50197
  id: randomUUID3(),
50028
50198
  tier: "swarm",
50029
- lesson: _internals23.truncateLesson(raw.text),
50030
- category: raw.categoryHint ?? _internals23.inferCategoryFromText(raw.text),
50199
+ lesson: _internals24.truncateLesson(raw.text),
50200
+ category: raw.categoryHint ?? _internals24.inferCategoryFromText(raw.text),
50031
50201
  tags: [...inferredTags, `migration:${raw.sourceSection}`],
50032
50202
  scope: "global",
50033
50203
  confidence: 0.3,
@@ -50050,7 +50220,7 @@ async function migrateContextToKnowledge(directory, config3) {
50050
50220
  if (migrated > 0) {
50051
50221
  await rewriteKnowledge(knowledgePath, existing);
50052
50222
  }
50053
- await _internals23.writeSentinel(sentinelPath, migrated, dropped);
50223
+ await _internals24.writeSentinel(sentinelPath, migrated, dropped);
50054
50224
  log(`[knowledge-migrator] Migrated ${migrated} entries, dropped ${dropped}`);
50055
50225
  return {
50056
50226
  migrated: true,
@@ -50059,8 +50229,125 @@ async function migrateContextToKnowledge(directory, config3) {
50059
50229
  entriesTotal: rawEntries.length
50060
50230
  };
50061
50231
  }
50232
+ async function migrateHiveKnowledgeLegacy(config3) {
50233
+ const legacyHivePath = _internals24.resolveLegacyHiveKnowledgePath();
50234
+ const canonicalHivePath = resolveHiveKnowledgePath();
50235
+ const sentinelPath = path37.join(path37.dirname(canonicalHivePath), ".hive-knowledge-migrated");
50236
+ if (existsSync25(sentinelPath)) {
50237
+ return {
50238
+ migrated: false,
50239
+ entriesMigrated: 0,
50240
+ entriesDropped: 0,
50241
+ entriesTotal: 0,
50242
+ skippedReason: "sentinel-exists"
50243
+ };
50244
+ }
50245
+ if (!existsSync25(legacyHivePath)) {
50246
+ return {
50247
+ migrated: false,
50248
+ entriesMigrated: 0,
50249
+ entriesDropped: 0,
50250
+ entriesTotal: 0,
50251
+ skippedReason: "no-context-file"
50252
+ };
50253
+ }
50254
+ const legacyEntries = await readKnowledge(legacyHivePath);
50255
+ if (legacyEntries.length === 0) {
50256
+ await _internals24.writeSentinel(sentinelPath, 0, 0);
50257
+ return {
50258
+ migrated: true,
50259
+ entriesMigrated: 0,
50260
+ entriesDropped: 0,
50261
+ entriesTotal: 0
50262
+ };
50263
+ }
50264
+ const existingHiveEntries = await readKnowledge(canonicalHivePath);
50265
+ let migrated = 0;
50266
+ let dropped = 0;
50267
+ const entryErrors = [];
50268
+ for (const legacyEntry of legacyEntries) {
50269
+ try {
50270
+ const lesson = legacyEntry.lesson;
50271
+ if (!lesson || typeof lesson !== "string" || lesson.length < 15) {
50272
+ dropped++;
50273
+ continue;
50274
+ }
50275
+ const dup = findNearDuplicate(lesson, existingHiveEntries, config3.dedup_threshold ?? 0.6);
50276
+ if (dup) {
50277
+ dropped++;
50278
+ continue;
50279
+ }
50280
+ const category = legacyEntry.category || "process";
50281
+ const validationResult = validateLesson(lesson, existingHiveEntries.map((e) => e.lesson), {
50282
+ category,
50283
+ scope: "global",
50284
+ confidence: 0.3
50285
+ });
50286
+ if (!validationResult.valid) {
50287
+ const errorMsg = `Validation failed for legacy entry: ${validationResult.reason}`;
50288
+ entryErrors.push(errorMsg);
50289
+ warn(`[knowledge-migrator] ${errorMsg}`);
50290
+ dropped++;
50291
+ continue;
50292
+ }
50293
+ const confidence = legacyEntry.confidence ?? 0.8;
50294
+ const scopeTag = legacyEntry.scope_tag || "global";
50295
+ const legacyId = legacyEntry.id;
50296
+ const existingIds = new Set(existingHiveEntries.map((e) => e.id));
50297
+ const resolvedId = legacyId && existingIds.has(legacyId) ? randomUUID3() : legacyId || randomUUID3();
50298
+ if (legacyId && existingIds.has(legacyId)) {
50299
+ warn(`[knowledge-migrator] Legacy entry ID collision for "${legacyId}", generating new UUID`);
50300
+ }
50301
+ const newHiveEntry = {
50302
+ id: resolvedId,
50303
+ tier: "hive",
50304
+ lesson: _internals24.truncateLesson(lesson),
50305
+ category,
50306
+ tags: ["migration:legacy-hive"],
50307
+ scope: scopeTag,
50308
+ confidence: Math.min(Math.max(confidence, 0), 1),
50309
+ status: "established",
50310
+ confirmed_by: [],
50311
+ retrieval_outcomes: {
50312
+ applied_count: 0,
50313
+ succeeded_after_count: 0,
50314
+ failed_after_count: 0
50315
+ },
50316
+ schema_version: KNOWLEDGE_SCHEMA_VERSION,
50317
+ created_at: legacyEntry.created_at || new Date().toISOString(),
50318
+ updated_at: legacyEntry.updated_at || new Date().toISOString(),
50319
+ source_project: "legacy-promotion",
50320
+ encounter_score: 1
50321
+ };
50322
+ try {
50323
+ await _internals24.appendKnowledge(canonicalHivePath, newHiveEntry);
50324
+ existingHiveEntries.push(newHiveEntry);
50325
+ migrated++;
50326
+ } catch (appendError) {
50327
+ const errorMsg = `Failed to append entry: ${appendError instanceof Error ? appendError.message : String(appendError)}`;
50328
+ entryErrors.push(errorMsg);
50329
+ warn(`[knowledge-migrator] ${errorMsg}`);
50330
+ dropped++;
50331
+ }
50332
+ } catch (entryError) {
50333
+ const errorMsg = `Unexpected error processing legacy entry: ${entryError instanceof Error ? entryError.message : String(entryError)}`;
50334
+ entryErrors.push(errorMsg);
50335
+ warn(`[knowledge-migrator] ${errorMsg}`);
50336
+ dropped++;
50337
+ }
50338
+ }
50339
+ await _internals24.writeSentinel(sentinelPath, migrated, dropped);
50340
+ log(`[knowledge-migrator] Migrated ${migrated} legacy hive entries, dropped ${dropped}`);
50341
+ return {
50342
+ migrated: true,
50343
+ entriesMigrated: migrated,
50344
+ entriesDropped: dropped,
50345
+ entriesTotal: legacyEntries.length,
50346
+ ...entryErrors.length > 0 && { entryErrors }
50347
+ };
50348
+ }
50062
50349
  function parseContextMd(content) {
50063
- const sections = _internals23.splitIntoSections(content);
50350
+ const sections = _internals24.splitIntoSections(content);
50064
50351
  const entries = [];
50065
50352
  const seen = new Set;
50066
50353
  const sectionPatterns = [
@@ -50076,7 +50363,7 @@ function parseContextMd(content) {
50076
50363
  const match = sectionPatterns.find((sp) => sp.pattern.test(section.heading));
50077
50364
  if (!match)
50078
50365
  continue;
50079
- const bullets = _internals23.extractBullets(section.body);
50366
+ const bullets = _internals24.extractBullets(section.body);
50080
50367
  for (const bullet of bullets) {
50081
50368
  if (bullet.length < 15)
50082
50369
  continue;
@@ -50085,9 +50372,9 @@ function parseContextMd(content) {
50085
50372
  continue;
50086
50373
  seen.add(normalized);
50087
50374
  entries.push({
50088
- text: _internals23.truncateLesson(bullet),
50375
+ text: _internals24.truncateLesson(bullet),
50089
50376
  sourceSection: match.sourceSection,
50090
- categoryHint: _internals23.inferCategoryFromText(bullet)
50377
+ categoryHint: _internals24.inferCategoryFromText(bullet)
50091
50378
  });
50092
50379
  }
50093
50380
  }
@@ -50180,21 +50467,37 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
50180
50467
  await mkdir12(path37.dirname(sentinelPath), { recursive: true });
50181
50468
  await writeFile10(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
50182
50469
  }
50183
- var _internals23;
50470
+ function resolveLegacyHiveKnowledgePath() {
50471
+ const platform = process.platform;
50472
+ const home = process.env.HOME || os8.homedir();
50473
+ let dataDir;
50474
+ if (platform === "win32") {
50475
+ dataDir = path37.join(process.env.LOCALAPPDATA || path37.join(home, "AppData", "Local"), "opencode-swarm", "Data");
50476
+ } else if (platform === "darwin") {
50477
+ dataDir = path37.join(home, "Library", "Application Support", "opencode-swarm");
50478
+ } else {
50479
+ dataDir = path37.join(process.env.XDG_DATA_HOME || path37.join(home, ".local", "share"), "opencode-swarm");
50480
+ }
50481
+ return path37.join(dataDir, "hive-knowledge.jsonl");
50482
+ }
50483
+ var _internals24;
50184
50484
  var init_knowledge_migrator = __esm(() => {
50185
50485
  init_logger();
50186
50486
  init_knowledge_store();
50187
50487
  init_knowledge_validator();
50188
- _internals23 = {
50488
+ _internals24 = {
50489
+ appendKnowledge,
50189
50490
  migrateContextToKnowledge,
50190
50491
  migrateKnowledgeToExternal,
50492
+ migrateHiveKnowledgeLegacy,
50191
50493
  parseContextMd,
50192
50494
  splitIntoSections,
50193
50495
  extractBullets,
50194
50496
  inferCategoryFromText,
50195
50497
  truncateLesson: truncateLesson2,
50196
50498
  inferProjectName,
50197
- writeSentinel
50499
+ writeSentinel,
50500
+ resolveLegacyHiveKnowledgePath
50198
50501
  };
50199
50502
  });
50200
50503
 
@@ -50266,24 +50569,43 @@ async function handleKnowledgeRestoreCommand(directory, args) {
50266
50569
  }
50267
50570
  async function handleKnowledgeMigrateCommand(directory, args) {
50268
50571
  const targetDir = args[0] || directory;
50572
+ const config3 = KnowledgeConfigSchema.parse({});
50269
50573
  try {
50270
- const result = await migrateContextToKnowledge(targetDir, KnowledgeConfigSchema.parse({}));
50271
- if (result.skippedReason) {
50272
- switch (result.skippedReason) {
50574
+ const contextResult = await migrateContextToKnowledge(targetDir, config3);
50575
+ const hiveResult = await migrateHiveKnowledgeLegacy(config3);
50576
+ const messages = [];
50577
+ if (contextResult.skippedReason) {
50578
+ switch (contextResult.skippedReason) {
50273
50579
  case "sentinel-exists":
50274
- return "\u23ED Migration already completed for this project. Delete .swarm/.knowledge-migrated to re-run.";
50580
+ messages.push("\u23ED Context migration already completed. Delete .swarm/.knowledge-migrated to re-run.");
50581
+ break;
50275
50582
  case "no-context-file":
50276
- return "\u2139\uFE0F No .swarm/context.md found \u2014 nothing to migrate.";
50583
+ messages.push("\u2139\uFE0F No .swarm/context.md found \u2014 nothing to migrate.");
50584
+ break;
50277
50585
  case "empty-context":
50278
- return "\u2139\uFE0F .swarm/context.md is empty \u2014 nothing to migrate.";
50279
- default:
50280
- return "\u26A0\uFE0F Migration skipped for an unknown reason.";
50586
+ messages.push("\u2139\uFE0F .swarm/context.md is empty \u2014 nothing to migrate.");
50587
+ break;
50281
50588
  }
50589
+ } else {
50590
+ messages.push(`\u2705 Context migration: ${contextResult.entriesMigrated} entries added, ${contextResult.entriesDropped} dropped`);
50282
50591
  }
50283
- return `\u2705 Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
50592
+ if (hiveResult.skippedReason) {
50593
+ switch (hiveResult.skippedReason) {
50594
+ case "sentinel-exists":
50595
+ messages.push("\u23ED Hive legacy migration already completed. Delete the sentinel in the hive data dir to re-run.");
50596
+ break;
50597
+ case "no-context-file":
50598
+ messages.push("\u2139\uFE0F No legacy hive-knowledge.jsonl found \u2014 nothing to migrate.");
50599
+ break;
50600
+ }
50601
+ } else if (hiveResult.migrated) {
50602
+ messages.push(`\u2705 Hive legacy migration: ${hiveResult.entriesMigrated} entries added, ${hiveResult.entriesDropped} dropped`);
50603
+ }
50604
+ return messages.join(`
50605
+ `);
50284
50606
  } catch (error93) {
50285
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
50286
- return "\u274C Migration failed. Check .swarm/context.md is readable.";
50607
+ console.warn("[knowledge-command] migration error:", error93 instanceof Error ? error93.message : String(error93));
50608
+ return "\u274C Migration failed. Check that knowledge source files are readable.";
50287
50609
  }
50288
50610
  }
50289
50611
  async function handleKnowledgeListCommand(directory, _args) {
@@ -52836,7 +53158,7 @@ var init_gateway = __esm(() => {
52836
53158
 
52837
53159
  // src/memory/evaluation.ts
52838
53160
  import * as fs16 from "fs/promises";
52839
- import * as os8 from "os";
53161
+ import * as os9 from "os";
52840
53162
  import * as path41 from "path";
52841
53163
  async function evaluateMemoryRecallFixtures(options) {
52842
53164
  const fixtureDirectory = path41.resolve(options.fixtureDirectory);
@@ -52848,7 +53170,7 @@ async function evaluateMemoryRecallFixtures(options) {
52848
53170
  for (const fixture of fixtures) {
52849
53171
  const materialized = materializeFixture(fixture);
52850
53172
  for (const providerName of providers) {
52851
- const tempRoot = await fs16.realpath(await fs16.mkdtemp(path41.join(os8.tmpdir(), "swarm-memory-eval-")));
53173
+ const tempRoot = await fs16.realpath(await fs16.mkdtemp(path41.join(os9.tmpdir(), "swarm-memory-eval-")));
52852
53174
  const provider = createEvaluationProvider(providerName, tempRoot);
52853
53175
  try {
52854
53176
  await provider.initialize?.();
@@ -53680,9 +54002,9 @@ var init_memory2 = __esm(() => {
53680
54002
 
53681
54003
  // src/services/plan-service.ts
53682
54004
  async function getPlanData(directory, phaseArg) {
53683
- const plan = await _internals24.loadPlanJsonOnly(directory);
54005
+ const plan = await _internals25.loadPlanJsonOnly(directory);
53684
54006
  if (plan) {
53685
- const fullMarkdown = _internals24.derivePlanMarkdown(plan);
54007
+ const fullMarkdown = _internals25.derivePlanMarkdown(plan);
53686
54008
  if (phaseArg === undefined || phaseArg === null || phaseArg === "") {
53687
54009
  return {
53688
54010
  hasPlan: true,
@@ -53725,7 +54047,7 @@ async function getPlanData(directory, phaseArg) {
53725
54047
  isLegacy: false
53726
54048
  };
53727
54049
  }
53728
- const planContent = await _internals24.readSwarmFileAsync(directory, "plan.md");
54050
+ const planContent = await _internals25.readSwarmFileAsync(directory, "plan.md");
53729
54051
  if (!planContent) {
53730
54052
  return {
53731
54053
  hasPlan: false,
@@ -53821,11 +54143,11 @@ async function handlePlanCommand(directory, args) {
53821
54143
  const planData = await getPlanData(directory, phaseArg);
53822
54144
  return formatPlanMarkdown(planData);
53823
54145
  }
53824
- var _internals24;
54146
+ var _internals25;
53825
54147
  var init_plan_service = __esm(() => {
53826
54148
  init_utils2();
53827
54149
  init_manager();
53828
- _internals24 = {
54150
+ _internals25 = {
53829
54151
  loadPlanJsonOnly,
53830
54152
  derivePlanMarkdown,
53831
54153
  readSwarmFileAsync
@@ -53837,191 +54159,6 @@ var init_plan = __esm(() => {
53837
54159
  init_plan_service();
53838
54160
  });
53839
54161
 
53840
- // src/commands/pr-ref.ts
53841
- import { execSync as execSync3 } from "child_process";
53842
- function sanitizeUrl2(raw) {
53843
- let urlStr = raw.trim();
53844
- urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
53845
- const fragmentIdx = urlStr.indexOf("#");
53846
- if (fragmentIdx !== -1) {
53847
- urlStr = urlStr.slice(0, fragmentIdx);
53848
- }
53849
- const queryIdx = urlStr.indexOf("?");
53850
- if (queryIdx !== -1) {
53851
- urlStr = urlStr.slice(0, queryIdx);
53852
- }
53853
- urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
53854
- if (urlStr.length > MAX_URL_LEN2) {
53855
- urlStr = urlStr.slice(0, MAX_URL_LEN2);
53856
- }
53857
- return urlStr.trim();
53858
- }
53859
- function sanitizeInstructions(raw) {
53860
- const collapsed = raw.replace(/\s+/g, " ").trim();
53861
- const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
53862
- const normalized = stripped.replace(/\s+/g, " ").trim();
53863
- if (normalized.length <= MAX_INSTRUCTIONS_LEN)
53864
- return normalized;
53865
- return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}\u2026`;
53866
- }
53867
- function hasNonAsciiHostname(hostname5) {
53868
- for (const ch of hostname5) {
53869
- const cp = ch.codePointAt(0);
53870
- if (cp !== undefined && cp > 127)
53871
- return true;
53872
- }
53873
- return false;
53874
- }
53875
- function isPrivateHost2(url3) {
53876
- const host = url3.hostname.toLowerCase();
53877
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
53878
- return true;
53879
- }
53880
- if (host.startsWith("localhost") || host === "localhost.com") {
53881
- return true;
53882
- }
53883
- const ipv4Private = /^10\./;
53884
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
53885
- const ipv4192 = /^192\.168\./;
53886
- const ipv6Private = /^fe80:/i;
53887
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
53888
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
53889
- return true;
53890
- }
53891
- if (host.startsWith("::ffff:")) {
53892
- const inner = host.slice(7);
53893
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
53894
- return true;
53895
- }
53896
- }
53897
- return false;
53898
- }
53899
- function validateAndSanitizeUrl2(rawUrl) {
53900
- const sanitized = sanitizeUrl2(rawUrl);
53901
- if (!sanitized) {
53902
- return { error: "Empty URL" };
53903
- }
53904
- if (!sanitized.startsWith("https://")) {
53905
- return { error: "URL must use HTTPS scheme" };
53906
- }
53907
- try {
53908
- const url3 = new URL(sanitized);
53909
- if (hasNonAsciiHostname(url3.hostname)) {
53910
- return { error: "Non-ASCII hostnames are not allowed" };
53911
- }
53912
- if (isPrivateHost2(url3)) {
53913
- return { error: "Private or localhost URLs are not allowed" };
53914
- }
53915
- const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
53916
- if (!githubPrPattern.test(sanitized)) {
53917
- return {
53918
- error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
53919
- };
53920
- }
53921
- return { sanitized };
53922
- } catch {
53923
- return { error: "Invalid URL format" };
53924
- }
53925
- }
53926
- function parsePrRef(input, cwd) {
53927
- const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
53928
- if (urlMatch) {
53929
- return {
53930
- owner: urlMatch[1],
53931
- repo: urlMatch[2],
53932
- number: parseInt(urlMatch[3], 10)
53933
- };
53934
- }
53935
- const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
53936
- if (shorthandMatch) {
53937
- return {
53938
- owner: shorthandMatch[1],
53939
- repo: shorthandMatch[2],
53940
- number: parseInt(shorthandMatch[3], 10)
53941
- };
53942
- }
53943
- const bareMatch = input.match(/^(\d+)$/);
53944
- if (bareMatch) {
53945
- const prNumber = parseInt(bareMatch[1], 10);
53946
- const remoteUrl = detectGitRemote2(cwd);
53947
- if (!remoteUrl) {
53948
- return null;
53949
- }
53950
- const parsed = parseGitRemoteUrl2(remoteUrl);
53951
- if (!parsed) {
53952
- return null;
53953
- }
53954
- return {
53955
- owner: parsed.owner,
53956
- repo: parsed.repo,
53957
- number: prNumber
53958
- };
53959
- }
53960
- return null;
53961
- }
53962
- function detectGitRemote2(cwd) {
53963
- try {
53964
- const remoteUrl = _internals25.execSync("git remote get-url origin", {
53965
- encoding: "utf-8",
53966
- stdio: ["pipe", "pipe", "pipe"],
53967
- timeout: 5000,
53968
- ...cwd ? { cwd } : {}
53969
- }).trim();
53970
- return remoteUrl || null;
53971
- } catch {
53972
- return null;
53973
- }
53974
- }
53975
- function parseGitRemoteUrl2(remoteUrl) {
53976
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
53977
- if (httpsMatch) {
53978
- return {
53979
- owner: httpsMatch[1],
53980
- repo: httpsMatch[2].replace(/\.git$/, "")
53981
- };
53982
- }
53983
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
53984
- if (sshMatch) {
53985
- return {
53986
- owner: sshMatch[1],
53987
- repo: sshMatch[2].replace(/\.git$/, "")
53988
- };
53989
- }
53990
- const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
53991
- if (pathMatch) {
53992
- return {
53993
- owner: pathMatch[1],
53994
- repo: pathMatch[2].replace(/\.git$/, "")
53995
- };
53996
- }
53997
- return null;
53998
- }
53999
- function looksLikePrRef(token) {
54000
- return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
54001
- }
54002
- function resolvePrCommandInput(rest, cwd) {
54003
- if (rest.length === 0) {
54004
- return null;
54005
- }
54006
- const refToken = rest[0];
54007
- const instructions = sanitizeInstructions(rest.slice(1).join(" "));
54008
- const isFullUrl = /^https?:\/\//i.test(refToken);
54009
- const prInfo = parsePrRef(isFullUrl ? sanitizeUrl2(refToken) : refToken, cwd);
54010
- if (!prInfo) {
54011
- return { error: `Could not parse PR reference from "${refToken}"` };
54012
- }
54013
- const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
54014
- const result = validateAndSanitizeUrl2(prUrl);
54015
- if ("error" in result) {
54016
- return { error: result.error };
54017
- }
54018
- return { prUrl: result.sanitized, instructions };
54019
- }
54020
- var _internals25, MAX_URL_LEN2 = 2048, MAX_INSTRUCTIONS_LEN = 1000;
54021
- var init_pr_ref = __esm(() => {
54022
- _internals25 = { execSync: execSync3 };
54023
- });
54024
-
54025
54162
  // src/commands/pr-feedback.ts
54026
54163
  function handlePrFeedbackCommand(directory, args) {
54027
54164
  const rest = args.filter((t) => t.trim().length > 0);
@@ -63980,7 +64117,7 @@ init_registry();
63980
64117
  init_cache_paths();
63981
64118
  init_constants();
63982
64119
  import * as fs36 from "fs";
63983
- import * as os9 from "os";
64120
+ import * as os10 from "os";
63984
64121
  import * as path64 from "path";
63985
64122
  var { version: version5 } = package_default;
63986
64123
  var CONFIG_DIR = getPluginConfigDir();
@@ -63991,7 +64128,7 @@ var OPENCODE_PLUGIN_CACHE_PATHS = getPluginCachePaths();
63991
64128
  var OPENCODE_PLUGIN_LOCK_FILE_PATHS = getPluginLockFilePaths();
63992
64129
  function isSafeCachePath(p) {
63993
64130
  const resolved = path64.resolve(p);
63994
- const home = path64.resolve(os9.homedir());
64131
+ const home = path64.resolve(os10.homedir());
63995
64132
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
63996
64133
  return false;
63997
64134
  }
@@ -64015,7 +64152,7 @@ function isSafeCachePath(p) {
64015
64152
  }
64016
64153
  function isSafeLockFilePath(p) {
64017
64154
  const resolved = path64.resolve(p);
64018
- const home = path64.resolve(os9.homedir());
64155
+ const home = path64.resolve(os10.homedir());
64019
64156
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
64020
64157
  return false;
64021
64158
  }