opencode-swarm 7.79.5 → 7.79.6

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.79.5",
55
+ version: "7.79.6",
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",
@@ -39424,6 +39424,51 @@ import * as path18 from "path";
39424
39424
  function normalizeText(text) {
39425
39425
  return text.normalize("NFKC").toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
39426
39426
  }
39427
+ function extractContextWords(text, word, contextWindow = 3) {
39428
+ const words = text.split(" ");
39429
+ const context = new Set;
39430
+ if (!word.includes(" ")) {
39431
+ let from = 0;
39432
+ let idx = words.indexOf(word, from);
39433
+ while (idx !== -1) {
39434
+ const start = Math.max(0, idx - contextWindow);
39435
+ const end = Math.min(words.length, idx + contextWindow + 1);
39436
+ for (let i2 = start;i2 < end; i2++) {
39437
+ if (i2 !== idx && words[i2] && words[i2].length > 0) {
39438
+ context.add(words[i2]);
39439
+ }
39440
+ }
39441
+ from = idx + 1;
39442
+ idx = words.indexOf(word, from);
39443
+ }
39444
+ return context;
39445
+ }
39446
+ const termLen = word.split(" ").length;
39447
+ let i = 0;
39448
+ while (i <= words.length - termLen) {
39449
+ const slice = words.slice(i, i + termLen).join(" ");
39450
+ if (slice === word) {
39451
+ const start = Math.max(0, i - contextWindow);
39452
+ const end = Math.min(words.length, i + termLen + contextWindow);
39453
+ for (let j = start;j < end; j++) {
39454
+ if (j < i || j >= i + termLen) {
39455
+ if (words[j] && words[j].length > 0) {
39456
+ context.add(words[j]);
39457
+ }
39458
+ }
39459
+ }
39460
+ i += termLen;
39461
+ } else {
39462
+ i += 1;
39463
+ }
39464
+ }
39465
+ return context;
39466
+ }
39467
+ function hasSignificantOverlap(set1, set22) {
39468
+ if (set1.size === 0 || set22.size === 0)
39469
+ return false;
39470
+ return [...set1].some((word) => set22.has(word));
39471
+ }
39427
39472
  function detectContradiction(candidate, existingLessons) {
39428
39473
  const candidateTags = inferTags(candidate);
39429
39474
  if (candidateTags.length === 0)
@@ -39436,10 +39481,20 @@ function detectContradiction(candidate, existingLessons) {
39436
39481
  continue;
39437
39482
  const existingNorm = normalizeText(existing);
39438
39483
  for (const [wordA, wordB] of NEGATION_PAIRS) {
39439
- const hasA = candidateNorm.includes(wordA) && existingNorm.includes(wordB);
39440
- const hasB = candidateNorm.includes(wordB) && existingNorm.includes(wordA);
39441
- if (hasA || hasB)
39442
- return true;
39484
+ if (candidateNorm.includes(wordA) && existingNorm.includes(wordB)) {
39485
+ const contextA = extractContextWords(candidateNorm, wordA);
39486
+ const contextB = extractContextWords(existingNorm, wordB);
39487
+ if (hasSignificantOverlap(contextA, contextB)) {
39488
+ return true;
39489
+ }
39490
+ }
39491
+ if (candidateNorm.includes(wordB) && existingNorm.includes(wordA)) {
39492
+ const contextB = extractContextWords(candidateNorm, wordB);
39493
+ const contextA = extractContextWords(existingNorm, wordA);
39494
+ if (hasSignificantOverlap(contextA, contextB)) {
39495
+ return true;
39496
+ }
39497
+ }
39443
39498
  }
39444
39499
  }
39445
39500
  return false;
@@ -41771,12 +41826,52 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
41771
41826
  if (!_internals17.existsSync(resolved)) {
41772
41827
  return { pruned: 0, remaining: 0 };
41773
41828
  }
41774
- const allEntries = readSkillUsageEntries(directory);
41775
- if (allEntries.length === 0) {
41829
+ const raw = _internals17.readFileSync(resolved, "utf-8");
41830
+ const lines = raw.split(`
41831
+ `);
41832
+ const entries = [];
41833
+ const preservedMarkers = [];
41834
+ for (const line of lines) {
41835
+ const trimmed = line.trim();
41836
+ if (!trimmed)
41837
+ continue;
41838
+ try {
41839
+ const parsed = JSON.parse(trimmed);
41840
+ const marker = parseFeedbackMarker(parsed);
41841
+ if (marker) {
41842
+ preservedMarkers.push(trimmed);
41843
+ continue;
41844
+ }
41845
+ const entry = parseSkillUsageEntry(parsed);
41846
+ if (entry) {
41847
+ entries.push(entry);
41848
+ }
41849
+ } catch {}
41850
+ }
41851
+ if (entries.length === 0) {
41852
+ if (preservedMarkers.length > 0) {
41853
+ const dir2 = path23.dirname(resolved);
41854
+ const tmpPath2 = path23.join(dir2, `skill-usage-${Date.now()}.tmp`);
41855
+ const content2 = preservedMarkers.join(`
41856
+ `).concat(preservedMarkers.length > 0 ? `
41857
+ ` : "");
41858
+ try {
41859
+ _internals17.writeFileSync(tmpPath2, content2, "utf-8");
41860
+ _internals17.renameSync(tmpPath2, resolved);
41861
+ } catch (writeErr) {
41862
+ const msg = writeErr instanceof Error ? writeErr.message : String(writeErr);
41863
+ try {
41864
+ if (_internals17.existsSync(tmpPath2)) {
41865
+ _internals17.writeFileSync(tmpPath2, "", "utf-8");
41866
+ }
41867
+ } catch {}
41868
+ return { pruned: 0, remaining: 0, error: msg };
41869
+ }
41870
+ }
41776
41871
  return { pruned: 0, remaining: 0 };
41777
41872
  }
41778
41873
  const groups = new Map;
41779
- for (const entry of allEntries) {
41874
+ for (const entry of entries) {
41780
41875
  const list = groups.get(entry.skillPath);
41781
41876
  if (list)
41782
41877
  list.push(entry);
@@ -41785,22 +41880,24 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
41785
41880
  }
41786
41881
  let pruned = 0;
41787
41882
  const surviving = [];
41788
- groups.forEach((entries) => {
41789
- if (entries.length <= maxEntriesPerSkill) {
41790
- surviving.push(...entries);
41883
+ groups.forEach((skillEntries) => {
41884
+ if (skillEntries.length <= maxEntriesPerSkill) {
41885
+ surviving.push(...skillEntries);
41791
41886
  return;
41792
41887
  }
41793
- entries.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
41794
- const kept = entries.slice(0, maxEntriesPerSkill);
41795
- pruned += entries.length - kept.length;
41888
+ skillEntries.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
41889
+ const kept = skillEntries.slice(0, maxEntriesPerSkill);
41890
+ pruned += skillEntries.length - kept.length;
41796
41891
  surviving.push(...kept);
41797
41892
  });
41798
41893
  if (pruned === 0) {
41799
- return { pruned: 0, remaining: allEntries.length };
41894
+ return { pruned: 0, remaining: entries.length };
41800
41895
  }
41801
41896
  const dir = path23.dirname(resolved);
41802
41897
  const tmpPath = path23.join(dir, `skill-usage-${Date.now()}.tmp`);
41803
- const content = surviving.map((e) => JSON.stringify(e)).join(`
41898
+ const entryLines = surviving.map((e) => JSON.stringify(e));
41899
+ const allLines = [...entryLines, ...preservedMarkers];
41900
+ const content = allLines.join(`
41804
41901
  `).concat(`
41805
41902
  `);
41806
41903
  try {
@@ -41813,7 +41910,7 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
41813
41910
  _internals17.writeFileSync(tmpPath, "", "utf-8");
41814
41911
  }
41815
41912
  } catch {}
41816
- return { pruned: 0, remaining: allEntries.length, error: msg };
41913
+ return { pruned: 0, remaining: entries.length, error: msg };
41817
41914
  }
41818
41915
  return { pruned, remaining: surviving.length };
41819
41916
  }
@@ -12,6 +12,20 @@ export declare const DANGEROUS_COMMAND_PATTERNS: RegExp[];
12
12
  export declare const SECURITY_DEGRADING_PATTERNS: RegExp[];
13
13
  export declare const INVISIBLE_FORMAT_CHARS: RegExp;
14
14
  export declare const INJECTION_PATTERNS: RegExp[];
15
+ /**
16
+ * Extract context words around each occurrence of a target word (single tokens within a window).
17
+ * Context window is 3 words before and after, excluding the target word itself.
18
+ * Handles multi-word terms (e.g. "must not", "don't use") by scanning word slices.
19
+ *
20
+ * Note: this is an exact-word-match heuristic. Synonyms (e.g. "use" / "utilize")
21
+ * will not match across lessons. The 3-token window is bounded; more distant
22
+ * negation attachments will not be detected as contradictions.
23
+ */
24
+ declare function extractContextWords(text: string, word: string, contextWindow?: number): Set<string>;
25
+ /**
26
+ * Check if two sets of words have significant overlap (at least one word in common).
27
+ */
28
+ declare function hasSignificantOverlap(set1: Set<string>, set2: Set<string>): boolean;
15
29
  export declare function validateLesson(candidate: string, existingLessons: string[], meta: {
16
30
  category: KnowledgeCategory;
17
31
  scope: string;
@@ -90,4 +104,6 @@ export declare const _internals: {
90
104
  auditEntryHealth: typeof auditEntryHealth;
91
105
  quarantineEntry: typeof quarantineEntry;
92
106
  restoreEntry: typeof restoreEntry;
107
+ extractContextWords: typeof extractContextWords;
108
+ hasSignificantOverlap: typeof hasSignificantOverlap;
93
109
  };
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.79.5",
72
+ version: "7.79.6",
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",
@@ -61771,6 +61771,51 @@ import * as path36 from "node:path";
61771
61771
  function normalizeText(text) {
61772
61772
  return text.normalize("NFKC").toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
61773
61773
  }
61774
+ function extractContextWords(text, word, contextWindow = 3) {
61775
+ const words = text.split(" ");
61776
+ const context = new Set;
61777
+ if (!word.includes(" ")) {
61778
+ let from = 0;
61779
+ let idx = words.indexOf(word, from);
61780
+ while (idx !== -1) {
61781
+ const start2 = Math.max(0, idx - contextWindow);
61782
+ const end = Math.min(words.length, idx + contextWindow + 1);
61783
+ for (let i3 = start2;i3 < end; i3++) {
61784
+ if (i3 !== idx && words[i3] && words[i3].length > 0) {
61785
+ context.add(words[i3]);
61786
+ }
61787
+ }
61788
+ from = idx + 1;
61789
+ idx = words.indexOf(word, from);
61790
+ }
61791
+ return context;
61792
+ }
61793
+ const termLen = word.split(" ").length;
61794
+ let i2 = 0;
61795
+ while (i2 <= words.length - termLen) {
61796
+ const slice = words.slice(i2, i2 + termLen).join(" ");
61797
+ if (slice === word) {
61798
+ const start2 = Math.max(0, i2 - contextWindow);
61799
+ const end = Math.min(words.length, i2 + termLen + contextWindow);
61800
+ for (let j = start2;j < end; j++) {
61801
+ if (j < i2 || j >= i2 + termLen) {
61802
+ if (words[j] && words[j].length > 0) {
61803
+ context.add(words[j]);
61804
+ }
61805
+ }
61806
+ }
61807
+ i2 += termLen;
61808
+ } else {
61809
+ i2 += 1;
61810
+ }
61811
+ }
61812
+ return context;
61813
+ }
61814
+ function hasSignificantOverlap(set1, set22) {
61815
+ if (set1.size === 0 || set22.size === 0)
61816
+ return false;
61817
+ return [...set1].some((word) => set22.has(word));
61818
+ }
61774
61819
  function detectContradiction(candidate, existingLessons) {
61775
61820
  const candidateTags = inferTags(candidate);
61776
61821
  if (candidateTags.length === 0)
@@ -61783,10 +61828,20 @@ function detectContradiction(candidate, existingLessons) {
61783
61828
  continue;
61784
61829
  const existingNorm = normalizeText(existing);
61785
61830
  for (const [wordA, wordB] of NEGATION_PAIRS) {
61786
- const hasA = candidateNorm.includes(wordA) && existingNorm.includes(wordB);
61787
- const hasB = candidateNorm.includes(wordB) && existingNorm.includes(wordA);
61788
- if (hasA || hasB)
61789
- return true;
61831
+ if (candidateNorm.includes(wordA) && existingNorm.includes(wordB)) {
61832
+ const contextA = extractContextWords(candidateNorm, wordA);
61833
+ const contextB = extractContextWords(existingNorm, wordB);
61834
+ if (hasSignificantOverlap(contextA, contextB)) {
61835
+ return true;
61836
+ }
61837
+ }
61838
+ if (candidateNorm.includes(wordB) && existingNorm.includes(wordA)) {
61839
+ const contextB = extractContextWords(candidateNorm, wordB);
61840
+ const contextA = extractContextWords(existingNorm, wordA);
61841
+ if (hasSignificantOverlap(contextA, contextB)) {
61842
+ return true;
61843
+ }
61844
+ }
61790
61845
  }
61791
61846
  }
61792
61847
  return false;
@@ -64410,12 +64465,52 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
64410
64465
  if (!_internals27.existsSync(resolved)) {
64411
64466
  return { pruned: 0, remaining: 0 };
64412
64467
  }
64413
- const allEntries = readSkillUsageEntries(directory);
64414
- if (allEntries.length === 0) {
64468
+ const raw = _internals27.readFileSync(resolved, "utf-8");
64469
+ const lines = raw.split(`
64470
+ `);
64471
+ const entries = [];
64472
+ const preservedMarkers = [];
64473
+ for (const line of lines) {
64474
+ const trimmed = line.trim();
64475
+ if (!trimmed)
64476
+ continue;
64477
+ try {
64478
+ const parsed = JSON.parse(trimmed);
64479
+ const marker = parseFeedbackMarker(parsed);
64480
+ if (marker) {
64481
+ preservedMarkers.push(trimmed);
64482
+ continue;
64483
+ }
64484
+ const entry = parseSkillUsageEntry(parsed);
64485
+ if (entry) {
64486
+ entries.push(entry);
64487
+ }
64488
+ } catch {}
64489
+ }
64490
+ if (entries.length === 0) {
64491
+ if (preservedMarkers.length > 0) {
64492
+ const dir2 = path41.dirname(resolved);
64493
+ const tmpPath2 = path41.join(dir2, `skill-usage-${Date.now()}.tmp`);
64494
+ const content2 = preservedMarkers.join(`
64495
+ `).concat(preservedMarkers.length > 0 ? `
64496
+ ` : "");
64497
+ try {
64498
+ _internals27.writeFileSync(tmpPath2, content2, "utf-8");
64499
+ _internals27.renameSync(tmpPath2, resolved);
64500
+ } catch (writeErr) {
64501
+ const msg = writeErr instanceof Error ? writeErr.message : String(writeErr);
64502
+ try {
64503
+ if (_internals27.existsSync(tmpPath2)) {
64504
+ _internals27.writeFileSync(tmpPath2, "", "utf-8");
64505
+ }
64506
+ } catch {}
64507
+ return { pruned: 0, remaining: 0, error: msg };
64508
+ }
64509
+ }
64415
64510
  return { pruned: 0, remaining: 0 };
64416
64511
  }
64417
64512
  const groups = new Map;
64418
- for (const entry of allEntries) {
64513
+ for (const entry of entries) {
64419
64514
  const list = groups.get(entry.skillPath);
64420
64515
  if (list)
64421
64516
  list.push(entry);
@@ -64424,22 +64519,24 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
64424
64519
  }
64425
64520
  let pruned = 0;
64426
64521
  const surviving = [];
64427
- groups.forEach((entries) => {
64428
- if (entries.length <= maxEntriesPerSkill) {
64429
- surviving.push(...entries);
64522
+ groups.forEach((skillEntries) => {
64523
+ if (skillEntries.length <= maxEntriesPerSkill) {
64524
+ surviving.push(...skillEntries);
64430
64525
  return;
64431
64526
  }
64432
- entries.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
64433
- const kept = entries.slice(0, maxEntriesPerSkill);
64434
- pruned += entries.length - kept.length;
64527
+ skillEntries.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
64528
+ const kept = skillEntries.slice(0, maxEntriesPerSkill);
64529
+ pruned += skillEntries.length - kept.length;
64435
64530
  surviving.push(...kept);
64436
64531
  });
64437
64532
  if (pruned === 0) {
64438
- return { pruned: 0, remaining: allEntries.length };
64533
+ return { pruned: 0, remaining: entries.length };
64439
64534
  }
64440
64535
  const dir = path41.dirname(resolved);
64441
64536
  const tmpPath = path41.join(dir, `skill-usage-${Date.now()}.tmp`);
64442
- const content = surviving.map((e) => JSON.stringify(e)).join(`
64537
+ const entryLines = surviving.map((e) => JSON.stringify(e));
64538
+ const allLines = [...entryLines, ...preservedMarkers];
64539
+ const content = allLines.join(`
64443
64540
  `).concat(`
64444
64541
  `);
64445
64542
  try {
@@ -64452,7 +64549,7 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
64452
64549
  _internals27.writeFileSync(tmpPath, "", "utf-8");
64453
64550
  }
64454
64551
  } catch {}
64455
- return { pruned: 0, remaining: allEntries.length, error: msg };
64552
+ return { pruned: 0, remaining: entries.length, error: msg };
64456
64553
  }
64457
64554
  return { pruned, remaining: surviving.length };
64458
64555
  }
@@ -98327,6 +98424,11 @@ Every listed directive ID MUST appear exactly once. If a directive carries a ver
98327
98424
  FIXES: required changes if rejected
98328
98425
  Use INFO only inside ISSUES for non-blocking suggestions. RISK reflects the highest blocking severity, so it never uses INFO.
98329
98426
 
98427
+ ## OUTPUT ORDER FOR SKILL COMPLIANCE (when applicable)
98428
+ When SKILLS_USED_BY_CODER is provided, output TASK: immediately followed by SKILL_COMPLIANCE to ensure proper attribution:
98429
+ TASK: <task-id-or-unknown>
98430
+ SKILL_COMPLIANCE: <verdict> — <details>
98431
+
98330
98432
  ## RULES
98331
98433
  - Be specific with line numbers
98332
98434
  - Only flag real issues, not theoretical
@@ -131064,7 +131166,7 @@ async function withTurboStateLock(directory, sessionID, fn2, timeoutMs = 30000)
131064
131166
  } catch (acquireErr) {
131065
131167
  console.warn(`[lean-turbo] state lock acquisition error for ${sessionID} (${lockPath}), will retry: ${acquireErr instanceof Error ? acquireErr.message : String(acquireErr)}`);
131066
131168
  }
131067
- if (result && result.acquired) {
131169
+ if (result?.acquired) {
131068
131170
  const lock = result.lock;
131069
131171
  try {
131070
131172
  return await fn2();
@@ -134666,18 +134768,7 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
134666
134768
  safeWarn("[phase_complete] Design-doc drift check error (non-blocking):", docDriftError);
134667
134769
  }
134668
134770
  try {
134669
- const markerPath = validateSwarmPath(dir, "skill-usage-last-processed.json");
134670
- let sinceTimestamp;
134671
- try {
134672
- const markerData = JSON.parse(fs111.readFileSync(markerPath, "utf-8"));
134673
- sinceTimestamp = markerData.lastProcessedTimestamp;
134674
- } catch {}
134675
- const feedbackResult = await applySkillUsageFeedback(dir, {
134676
- sinceTimestamp
134677
- });
134678
- try {
134679
- fs111.writeFileSync(markerPath, JSON.stringify({ lastProcessedTimestamp: new Date().toISOString() }), "utf-8");
134680
- } catch {}
134771
+ const feedbackResult = await applySkillUsageFeedback(dir);
134681
134772
  if (feedbackResult.processed > 0) {
134682
134773
  const sessionState = swarmState.agentSessions.get(sessionID);
134683
134774
  if (sessionState) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.79.5",
3
+ "version": "7.79.6",
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",