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/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.0",
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
  "",
@@ -73929,6 +74098,7 @@ var KNOWLEDGE_SCHEMA_VERSION = 2;
73929
74098
  import { randomUUID as randomUUID6 } from "node:crypto";
73930
74099
  import { existsSync as existsSync32, readFileSync as readFileSync17 } from "node:fs";
73931
74100
  import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile12 } from "node:fs/promises";
74101
+ import * as os13 from "node:os";
73932
74102
  import * as path53 from "node:path";
73933
74103
  async function migrateKnowledgeToExternal(_directory, _config) {
73934
74104
  return {
@@ -73971,9 +74141,9 @@ async function migrateContextToKnowledge(directory, config3) {
73971
74141
  skippedReason: "empty-context"
73972
74142
  };
73973
74143
  }
73974
- const rawEntries = _internals35.parseContextMd(contextContent);
74144
+ const rawEntries = _internals36.parseContextMd(contextContent);
73975
74145
  if (rawEntries.length === 0) {
73976
- await _internals35.writeSentinel(sentinelPath, 0, 0);
74146
+ await _internals36.writeSentinel(sentinelPath, 0, 0);
73977
74147
  return {
73978
74148
  migrated: true,
73979
74149
  entriesMigrated: 0,
@@ -73984,10 +74154,10 @@ async function migrateContextToKnowledge(directory, config3) {
73984
74154
  const existing = await readKnowledge(knowledgePath);
73985
74155
  let migrated = 0;
73986
74156
  let dropped = 0;
73987
- const projectName = _internals35.inferProjectName(directory);
74157
+ const projectName = _internals36.inferProjectName(directory);
73988
74158
  for (const raw of rawEntries) {
73989
74159
  if (config3.validation_enabled !== false) {
73990
- const category = raw.categoryHint ?? _internals35.inferCategoryFromText(raw.text);
74160
+ const category = raw.categoryHint ?? _internals36.inferCategoryFromText(raw.text);
73991
74161
  const result = validateLesson(raw.text, existing.map((e) => e.lesson), {
73992
74162
  category,
73993
74163
  scope: "global",
@@ -74007,8 +74177,8 @@ async function migrateContextToKnowledge(directory, config3) {
74007
74177
  const entry = {
74008
74178
  id: randomUUID6(),
74009
74179
  tier: "swarm",
74010
- lesson: _internals35.truncateLesson(raw.text),
74011
- category: raw.categoryHint ?? _internals35.inferCategoryFromText(raw.text),
74180
+ lesson: _internals36.truncateLesson(raw.text),
74181
+ category: raw.categoryHint ?? _internals36.inferCategoryFromText(raw.text),
74012
74182
  tags: [...inferredTags, `migration:${raw.sourceSection}`],
74013
74183
  scope: "global",
74014
74184
  confidence: 0.3,
@@ -74031,7 +74201,7 @@ async function migrateContextToKnowledge(directory, config3) {
74031
74201
  if (migrated > 0) {
74032
74202
  await rewriteKnowledge(knowledgePath, existing);
74033
74203
  }
74034
- await _internals35.writeSentinel(sentinelPath, migrated, dropped);
74204
+ await _internals36.writeSentinel(sentinelPath, migrated, dropped);
74035
74205
  log(`[knowledge-migrator] Migrated ${migrated} entries, dropped ${dropped}`);
74036
74206
  return {
74037
74207
  migrated: true,
@@ -74040,8 +74210,125 @@ async function migrateContextToKnowledge(directory, config3) {
74040
74210
  entriesTotal: rawEntries.length
74041
74211
  };
74042
74212
  }
74213
+ async function migrateHiveKnowledgeLegacy(config3) {
74214
+ const legacyHivePath = _internals36.resolveLegacyHiveKnowledgePath();
74215
+ const canonicalHivePath = resolveHiveKnowledgePath();
74216
+ const sentinelPath = path53.join(path53.dirname(canonicalHivePath), ".hive-knowledge-migrated");
74217
+ if (existsSync32(sentinelPath)) {
74218
+ return {
74219
+ migrated: false,
74220
+ entriesMigrated: 0,
74221
+ entriesDropped: 0,
74222
+ entriesTotal: 0,
74223
+ skippedReason: "sentinel-exists"
74224
+ };
74225
+ }
74226
+ if (!existsSync32(legacyHivePath)) {
74227
+ return {
74228
+ migrated: false,
74229
+ entriesMigrated: 0,
74230
+ entriesDropped: 0,
74231
+ entriesTotal: 0,
74232
+ skippedReason: "no-context-file"
74233
+ };
74234
+ }
74235
+ const legacyEntries = await readKnowledge(legacyHivePath);
74236
+ if (legacyEntries.length === 0) {
74237
+ await _internals36.writeSentinel(sentinelPath, 0, 0);
74238
+ return {
74239
+ migrated: true,
74240
+ entriesMigrated: 0,
74241
+ entriesDropped: 0,
74242
+ entriesTotal: 0
74243
+ };
74244
+ }
74245
+ const existingHiveEntries = await readKnowledge(canonicalHivePath);
74246
+ let migrated = 0;
74247
+ let dropped = 0;
74248
+ const entryErrors = [];
74249
+ for (const legacyEntry of legacyEntries) {
74250
+ try {
74251
+ const lesson = legacyEntry.lesson;
74252
+ if (!lesson || typeof lesson !== "string" || lesson.length < 15) {
74253
+ dropped++;
74254
+ continue;
74255
+ }
74256
+ const dup = findNearDuplicate(lesson, existingHiveEntries, config3.dedup_threshold ?? 0.6);
74257
+ if (dup) {
74258
+ dropped++;
74259
+ continue;
74260
+ }
74261
+ const category = legacyEntry.category || "process";
74262
+ const validationResult = validateLesson(lesson, existingHiveEntries.map((e) => e.lesson), {
74263
+ category,
74264
+ scope: "global",
74265
+ confidence: 0.3
74266
+ });
74267
+ if (!validationResult.valid) {
74268
+ const errorMsg = `Validation failed for legacy entry: ${validationResult.reason}`;
74269
+ entryErrors.push(errorMsg);
74270
+ warn(`[knowledge-migrator] ${errorMsg}`);
74271
+ dropped++;
74272
+ continue;
74273
+ }
74274
+ const confidence = legacyEntry.confidence ?? 0.8;
74275
+ const scopeTag = legacyEntry.scope_tag || "global";
74276
+ const legacyId = legacyEntry.id;
74277
+ const existingIds = new Set(existingHiveEntries.map((e) => e.id));
74278
+ const resolvedId = legacyId && existingIds.has(legacyId) ? randomUUID6() : legacyId || randomUUID6();
74279
+ if (legacyId && existingIds.has(legacyId)) {
74280
+ warn(`[knowledge-migrator] Legacy entry ID collision for "${legacyId}", generating new UUID`);
74281
+ }
74282
+ const newHiveEntry = {
74283
+ id: resolvedId,
74284
+ tier: "hive",
74285
+ lesson: _internals36.truncateLesson(lesson),
74286
+ category,
74287
+ tags: ["migration:legacy-hive"],
74288
+ scope: scopeTag,
74289
+ confidence: Math.min(Math.max(confidence, 0), 1),
74290
+ status: "established",
74291
+ confirmed_by: [],
74292
+ retrieval_outcomes: {
74293
+ applied_count: 0,
74294
+ succeeded_after_count: 0,
74295
+ failed_after_count: 0
74296
+ },
74297
+ schema_version: KNOWLEDGE_SCHEMA_VERSION,
74298
+ created_at: legacyEntry.created_at || new Date().toISOString(),
74299
+ updated_at: legacyEntry.updated_at || new Date().toISOString(),
74300
+ source_project: "legacy-promotion",
74301
+ encounter_score: 1
74302
+ };
74303
+ try {
74304
+ await _internals36.appendKnowledge(canonicalHivePath, newHiveEntry);
74305
+ existingHiveEntries.push(newHiveEntry);
74306
+ migrated++;
74307
+ } catch (appendError) {
74308
+ const errorMsg = `Failed to append entry: ${appendError instanceof Error ? appendError.message : String(appendError)}`;
74309
+ entryErrors.push(errorMsg);
74310
+ warn(`[knowledge-migrator] ${errorMsg}`);
74311
+ dropped++;
74312
+ }
74313
+ } catch (entryError) {
74314
+ const errorMsg = `Unexpected error processing legacy entry: ${entryError instanceof Error ? entryError.message : String(entryError)}`;
74315
+ entryErrors.push(errorMsg);
74316
+ warn(`[knowledge-migrator] ${errorMsg}`);
74317
+ dropped++;
74318
+ }
74319
+ }
74320
+ await _internals36.writeSentinel(sentinelPath, migrated, dropped);
74321
+ log(`[knowledge-migrator] Migrated ${migrated} legacy hive entries, dropped ${dropped}`);
74322
+ return {
74323
+ migrated: true,
74324
+ entriesMigrated: migrated,
74325
+ entriesDropped: dropped,
74326
+ entriesTotal: legacyEntries.length,
74327
+ ...entryErrors.length > 0 && { entryErrors }
74328
+ };
74329
+ }
74043
74330
  function parseContextMd(content) {
74044
- const sections = _internals35.splitIntoSections(content);
74331
+ const sections = _internals36.splitIntoSections(content);
74045
74332
  const entries = [];
74046
74333
  const seen = new Set;
74047
74334
  const sectionPatterns = [
@@ -74057,7 +74344,7 @@ function parseContextMd(content) {
74057
74344
  const match = sectionPatterns.find((sp) => sp.pattern.test(section.heading));
74058
74345
  if (!match)
74059
74346
  continue;
74060
- const bullets = _internals35.extractBullets(section.body);
74347
+ const bullets = _internals36.extractBullets(section.body);
74061
74348
  for (const bullet of bullets) {
74062
74349
  if (bullet.length < 15)
74063
74350
  continue;
@@ -74066,9 +74353,9 @@ function parseContextMd(content) {
74066
74353
  continue;
74067
74354
  seen.add(normalized);
74068
74355
  entries.push({
74069
- text: _internals35.truncateLesson(bullet),
74356
+ text: _internals36.truncateLesson(bullet),
74070
74357
  sourceSection: match.sourceSection,
74071
- categoryHint: _internals35.inferCategoryFromText(bullet)
74358
+ categoryHint: _internals36.inferCategoryFromText(bullet)
74072
74359
  });
74073
74360
  }
74074
74361
  }
@@ -74161,21 +74448,37 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
74161
74448
  await mkdir14(path53.dirname(sentinelPath), { recursive: true });
74162
74449
  await writeFile12(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
74163
74450
  }
74164
- var _internals35;
74451
+ function resolveLegacyHiveKnowledgePath() {
74452
+ const platform = process.platform;
74453
+ const home = process.env.HOME || os13.homedir();
74454
+ let dataDir;
74455
+ if (platform === "win32") {
74456
+ dataDir = path53.join(process.env.LOCALAPPDATA || path53.join(home, "AppData", "Local"), "opencode-swarm", "Data");
74457
+ } else if (platform === "darwin") {
74458
+ dataDir = path53.join(home, "Library", "Application Support", "opencode-swarm");
74459
+ } else {
74460
+ dataDir = path53.join(process.env.XDG_DATA_HOME || path53.join(home, ".local", "share"), "opencode-swarm");
74461
+ }
74462
+ return path53.join(dataDir, "hive-knowledge.jsonl");
74463
+ }
74464
+ var _internals36;
74165
74465
  var init_knowledge_migrator = __esm(() => {
74166
74466
  init_logger();
74167
74467
  init_knowledge_store();
74168
74468
  init_knowledge_validator();
74169
- _internals35 = {
74469
+ _internals36 = {
74470
+ appendKnowledge,
74170
74471
  migrateContextToKnowledge,
74171
74472
  migrateKnowledgeToExternal,
74473
+ migrateHiveKnowledgeLegacy,
74172
74474
  parseContextMd,
74173
74475
  splitIntoSections,
74174
74476
  extractBullets,
74175
74477
  inferCategoryFromText,
74176
74478
  truncateLesson: truncateLesson2,
74177
74479
  inferProjectName,
74178
- writeSentinel
74480
+ writeSentinel,
74481
+ resolveLegacyHiveKnowledgePath
74179
74482
  };
74180
74483
  });
74181
74484
 
@@ -74247,24 +74550,43 @@ async function handleKnowledgeRestoreCommand(directory, args2) {
74247
74550
  }
74248
74551
  async function handleKnowledgeMigrateCommand(directory, args2) {
74249
74552
  const targetDir = args2[0] || directory;
74553
+ const config3 = KnowledgeConfigSchema.parse({});
74250
74554
  try {
74251
- const result = await migrateContextToKnowledge(targetDir, KnowledgeConfigSchema.parse({}));
74252
- if (result.skippedReason) {
74253
- switch (result.skippedReason) {
74555
+ const contextResult = await migrateContextToKnowledge(targetDir, config3);
74556
+ const hiveResult = await migrateHiveKnowledgeLegacy(config3);
74557
+ const messages = [];
74558
+ if (contextResult.skippedReason) {
74559
+ switch (contextResult.skippedReason) {
74254
74560
  case "sentinel-exists":
74255
- return "⏭ Migration already completed for this project. Delete .swarm/.knowledge-migrated to re-run.";
74561
+ messages.push("⏭ Context migration already completed. Delete .swarm/.knowledge-migrated to re-run.");
74562
+ break;
74256
74563
  case "no-context-file":
74257
- return "ℹ️ No .swarm/context.md found — nothing to migrate.";
74564
+ messages.push("ℹ️ No .swarm/context.md found — nothing to migrate.");
74565
+ break;
74258
74566
  case "empty-context":
74259
- return "ℹ️ .swarm/context.md is empty — nothing to migrate.";
74260
- default:
74261
- return "⚠️ Migration skipped for an unknown reason.";
74567
+ messages.push("ℹ️ .swarm/context.md is empty — nothing to migrate.");
74568
+ break;
74262
74569
  }
74570
+ } else {
74571
+ messages.push(`✅ Context migration: ${contextResult.entriesMigrated} entries added, ${contextResult.entriesDropped} dropped`);
74263
74572
  }
74264
- return `✅ Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
74573
+ if (hiveResult.skippedReason) {
74574
+ switch (hiveResult.skippedReason) {
74575
+ case "sentinel-exists":
74576
+ messages.push("⏭ Hive legacy migration already completed. Delete the sentinel in the hive data dir to re-run.");
74577
+ break;
74578
+ case "no-context-file":
74579
+ messages.push("ℹ️ No legacy hive-knowledge.jsonl found — nothing to migrate.");
74580
+ break;
74581
+ }
74582
+ } else if (hiveResult.migrated) {
74583
+ messages.push(`✅ Hive legacy migration: ${hiveResult.entriesMigrated} entries added, ${hiveResult.entriesDropped} dropped`);
74584
+ }
74585
+ return messages.join(`
74586
+ `);
74265
74587
  } catch (error93) {
74266
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
74267
- return "❌ Migration failed. Check .swarm/context.md is readable.";
74588
+ console.warn("[knowledge-command] migration error:", error93 instanceof Error ? error93.message : String(error93));
74589
+ return "❌ Migration failed. Check that knowledge source files are readable.";
74268
74590
  }
74269
74591
  }
74270
74592
  async function handleKnowledgeListCommand(directory, _args) {
@@ -77324,7 +77646,7 @@ var init_gateway = __esm(() => {
77324
77646
 
77325
77647
  // src/memory/evaluation.ts
77326
77648
  import * as fs27 from "node:fs/promises";
77327
- import * as os13 from "node:os";
77649
+ import * as os14 from "node:os";
77328
77650
  import * as path58 from "node:path";
77329
77651
  async function evaluateMemoryRecallFixtures(options) {
77330
77652
  const fixtureDirectory = path58.resolve(options.fixtureDirectory);
@@ -77336,7 +77658,7 @@ async function evaluateMemoryRecallFixtures(options) {
77336
77658
  for (const fixture of fixtures) {
77337
77659
  const materialized = materializeFixture(fixture);
77338
77660
  for (const providerName of providers) {
77339
- const tempRoot = await fs27.realpath(await fs27.mkdtemp(path58.join(os13.tmpdir(), "swarm-memory-eval-")));
77661
+ const tempRoot = await fs27.realpath(await fs27.mkdtemp(path58.join(os14.tmpdir(), "swarm-memory-eval-")));
77340
77662
  const provider = createEvaluationProvider(providerName, tempRoot);
77341
77663
  try {
77342
77664
  await provider.initialize?.();
@@ -78652,9 +78974,9 @@ var init_memory2 = __esm(() => {
78652
78974
 
78653
78975
  // src/services/plan-service.ts
78654
78976
  async function getPlanData(directory, phaseArg) {
78655
- const plan = await _internals36.loadPlanJsonOnly(directory);
78977
+ const plan = await _internals37.loadPlanJsonOnly(directory);
78656
78978
  if (plan) {
78657
- const fullMarkdown = _internals36.derivePlanMarkdown(plan);
78979
+ const fullMarkdown = _internals37.derivePlanMarkdown(plan);
78658
78980
  if (phaseArg === undefined || phaseArg === null || phaseArg === "") {
78659
78981
  return {
78660
78982
  hasPlan: true,
@@ -78697,7 +79019,7 @@ async function getPlanData(directory, phaseArg) {
78697
79019
  isLegacy: false
78698
79020
  };
78699
79021
  }
78700
- const planContent = await _internals36.readSwarmFileAsync(directory, "plan.md");
79022
+ const planContent = await _internals37.readSwarmFileAsync(directory, "plan.md");
78701
79023
  if (!planContent) {
78702
79024
  return {
78703
79025
  hasPlan: false,
@@ -78793,11 +79115,11 @@ async function handlePlanCommand(directory, args2) {
78793
79115
  const planData = await getPlanData(directory, phaseArg);
78794
79116
  return formatPlanMarkdown(planData);
78795
79117
  }
78796
- var _internals36;
79118
+ var _internals37;
78797
79119
  var init_plan_service = __esm(() => {
78798
79120
  init_utils2();
78799
79121
  init_manager();
78800
- _internals36 = {
79122
+ _internals37 = {
78801
79123
  loadPlanJsonOnly,
78802
79124
  derivePlanMarkdown,
78803
79125
  readSwarmFileAsync
@@ -78809,191 +79131,6 @@ var init_plan = __esm(() => {
78809
79131
  init_plan_service();
78810
79132
  });
78811
79133
 
78812
- // src/commands/pr-ref.ts
78813
- import { execSync as execSync3 } from "node:child_process";
78814
- function sanitizeUrl2(raw) {
78815
- let urlStr = raw.trim();
78816
- urlStr = urlStr.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
78817
- const fragmentIdx = urlStr.indexOf("#");
78818
- if (fragmentIdx !== -1) {
78819
- urlStr = urlStr.slice(0, fragmentIdx);
78820
- }
78821
- const queryIdx = urlStr.indexOf("?");
78822
- if (queryIdx !== -1) {
78823
- urlStr = urlStr.slice(0, queryIdx);
78824
- }
78825
- urlStr = urlStr.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^@/]+@/, "https://");
78826
- if (urlStr.length > MAX_URL_LEN2) {
78827
- urlStr = urlStr.slice(0, MAX_URL_LEN2);
78828
- }
78829
- return urlStr.trim();
78830
- }
78831
- function sanitizeInstructions(raw) {
78832
- const collapsed = raw.replace(/\s+/g, " ").trim();
78833
- const stripped = collapsed.replace(/\[\s*MODE\s*:[^\]]*\]/gi, "");
78834
- const normalized = stripped.replace(/\s+/g, " ").trim();
78835
- if (normalized.length <= MAX_INSTRUCTIONS_LEN)
78836
- return normalized;
78837
- return `${normalized.slice(0, MAX_INSTRUCTIONS_LEN)}…`;
78838
- }
78839
- function hasNonAsciiHostname(hostname5) {
78840
- for (const ch of hostname5) {
78841
- const cp = ch.codePointAt(0);
78842
- if (cp !== undefined && cp > 127)
78843
- return true;
78844
- }
78845
- return false;
78846
- }
78847
- function isPrivateHost2(url3) {
78848
- const host = url3.hostname.toLowerCase();
78849
- if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "0.0.0.0") {
78850
- return true;
78851
- }
78852
- if (host.startsWith("localhost") || host === "localhost.com") {
78853
- return true;
78854
- }
78855
- const ipv4Private = /^10\./;
78856
- const ipv4172 = /^172\.(1[6-9]|2\d|3[0-1])\./;
78857
- const ipv4192 = /^192\.168\./;
78858
- const ipv6Private = /^fe80:/i;
78859
- const ipv6Unique = /^f[cd][0-9a-f]{2}:/i;
78860
- if (ipv4Private.test(host) || ipv4172.test(host) || ipv4192.test(host) || ipv6Private.test(host) || ipv6Unique.test(host)) {
78861
- return true;
78862
- }
78863
- if (host.startsWith("::ffff:")) {
78864
- const inner = host.slice(7);
78865
- if (ipv4Private.test(inner) || ipv4172.test(inner) || ipv4192.test(inner)) {
78866
- return true;
78867
- }
78868
- }
78869
- return false;
78870
- }
78871
- function validateAndSanitizeUrl2(rawUrl) {
78872
- const sanitized = sanitizeUrl2(rawUrl);
78873
- if (!sanitized) {
78874
- return { error: "Empty URL" };
78875
- }
78876
- if (!sanitized.startsWith("https://")) {
78877
- return { error: "URL must use HTTPS scheme" };
78878
- }
78879
- try {
78880
- const url3 = new URL(sanitized);
78881
- if (hasNonAsciiHostname(url3.hostname)) {
78882
- return { error: "Non-ASCII hostnames are not allowed" };
78883
- }
78884
- if (isPrivateHost2(url3)) {
78885
- return { error: "Private or localhost URLs are not allowed" };
78886
- }
78887
- const githubPrPattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/([0-9]+)\/?$/;
78888
- if (!githubPrPattern.test(sanitized)) {
78889
- return {
78890
- error: "URL must be a GitHub pull request URL (https://github.com/owner/repo/pull/N)"
78891
- };
78892
- }
78893
- return { sanitized };
78894
- } catch {
78895
- return { error: "Invalid URL format" };
78896
- }
78897
- }
78898
- function parsePrRef(input, cwd) {
78899
- const urlMatch = input.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
78900
- if (urlMatch) {
78901
- return {
78902
- owner: urlMatch[1],
78903
- repo: urlMatch[2],
78904
- number: parseInt(urlMatch[3], 10)
78905
- };
78906
- }
78907
- const shorthandMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
78908
- if (shorthandMatch) {
78909
- return {
78910
- owner: shorthandMatch[1],
78911
- repo: shorthandMatch[2],
78912
- number: parseInt(shorthandMatch[3], 10)
78913
- };
78914
- }
78915
- const bareMatch = input.match(/^(\d+)$/);
78916
- if (bareMatch) {
78917
- const prNumber = parseInt(bareMatch[1], 10);
78918
- const remoteUrl = detectGitRemote2(cwd);
78919
- if (!remoteUrl) {
78920
- return null;
78921
- }
78922
- const parsed = parseGitRemoteUrl2(remoteUrl);
78923
- if (!parsed) {
78924
- return null;
78925
- }
78926
- return {
78927
- owner: parsed.owner,
78928
- repo: parsed.repo,
78929
- number: prNumber
78930
- };
78931
- }
78932
- return null;
78933
- }
78934
- function detectGitRemote2(cwd) {
78935
- try {
78936
- const remoteUrl = _internals37.execSync("git remote get-url origin", {
78937
- encoding: "utf-8",
78938
- stdio: ["pipe", "pipe", "pipe"],
78939
- timeout: 5000,
78940
- ...cwd ? { cwd } : {}
78941
- }).trim();
78942
- return remoteUrl || null;
78943
- } catch {
78944
- return null;
78945
- }
78946
- }
78947
- function parseGitRemoteUrl2(remoteUrl) {
78948
- const httpsMatch = remoteUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/i);
78949
- if (httpsMatch) {
78950
- return {
78951
- owner: httpsMatch[1],
78952
- repo: httpsMatch[2].replace(/\.git$/, "")
78953
- };
78954
- }
78955
- const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
78956
- if (sshMatch) {
78957
- return {
78958
- owner: sshMatch[1],
78959
- repo: sshMatch[2].replace(/\.git$/, "")
78960
- };
78961
- }
78962
- const pathMatch = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
78963
- if (pathMatch) {
78964
- return {
78965
- owner: pathMatch[1],
78966
- repo: pathMatch[2].replace(/\.git$/, "")
78967
- };
78968
- }
78969
- return null;
78970
- }
78971
- function looksLikePrRef(token) {
78972
- return /^https?:\/\//i.test(token) || /^[^/]+\/[^#]+#\d+$/.test(token) || /^\d+$/.test(token);
78973
- }
78974
- function resolvePrCommandInput(rest, cwd) {
78975
- if (rest.length === 0) {
78976
- return null;
78977
- }
78978
- const refToken = rest[0];
78979
- const instructions = sanitizeInstructions(rest.slice(1).join(" "));
78980
- const isFullUrl = /^https?:\/\//i.test(refToken);
78981
- const prInfo = parsePrRef(isFullUrl ? sanitizeUrl2(refToken) : refToken, cwd);
78982
- if (!prInfo) {
78983
- return { error: `Could not parse PR reference from "${refToken}"` };
78984
- }
78985
- const prUrl = `https://github.com/${prInfo.owner}/${prInfo.repo}/pull/${prInfo.number}`;
78986
- const result = validateAndSanitizeUrl2(prUrl);
78987
- if ("error" in result) {
78988
- return { error: result.error };
78989
- }
78990
- return { prUrl: result.sanitized, instructions };
78991
- }
78992
- var _internals37, MAX_URL_LEN2 = 2048, MAX_INSTRUCTIONS_LEN = 1000;
78993
- var init_pr_ref = __esm(() => {
78994
- _internals37 = { execSync: execSync3 };
78995
- });
78996
-
78997
79134
  // src/commands/pr-feedback.ts
78998
79135
  function handlePrFeedbackCommand(directory, args2) {
78999
79136
  const rest = args2.filter((t) => t.trim().length > 0);
@@ -104023,7 +104160,7 @@ init_path_security();
104023
104160
  import * as fsSync5 from "node:fs";
104024
104161
  import { existsSync as existsSync61, realpathSync as realpathSync13 } from "node:fs";
104025
104162
  import * as fsPromises5 from "node:fs/promises";
104026
- import * as os14 from "node:os";
104163
+ import * as os15 from "node:os";
104027
104164
  import * as path101 from "node:path";
104028
104165
 
104029
104166
  // src/tools/symbols.ts
@@ -104777,8 +104914,8 @@ function isRefusedWorkspaceRoot(target) {
104777
104914
  refused.add(path101.resolve(p));
104778
104915
  }
104779
104916
  };
104780
- add2(os14.homedir());
104781
- add2(os14.tmpdir());
104917
+ add2(os15.homedir());
104918
+ add2(os15.tmpdir());
104782
104919
  add2("/");
104783
104920
  add2("/Users");
104784
104921
  add2("/home");