opencode-swarm 6.71.1 → 6.72.0

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
@@ -19162,7 +19162,10 @@ var KnowledgeConfigSchema = exports_external.object({
19162
19162
  min_encounter_score: exports_external.number().min(0).max(1).default(0.1),
19163
19163
  initial_encounter_score: exports_external.number().min(0).max(5).default(1),
19164
19164
  encounter_increment: exports_external.number().min(0).max(1).default(0.1),
19165
- max_encounter_score: exports_external.number().min(1).max(20).default(10)
19165
+ max_encounter_score: exports_external.number().min(1).max(20).default(10),
19166
+ default_max_phases: exports_external.number().int().positive().default(10),
19167
+ todo_max_phases: exports_external.number().int().positive().default(3),
19168
+ sweep_enabled: exports_external.boolean().default(true)
19166
19169
  });
19167
19170
  var CuratorConfigSchema = exports_external.object({
19168
19171
  enabled: exports_external.boolean().default(true),
@@ -33188,7 +33191,8 @@ async function rewriteKnowledge(filePath, entries) {
33188
33191
  let release = null;
33189
33192
  try {
33190
33193
  release = await import_proper_lockfile2.default.lock(dir, {
33191
- retries: { retries: 3, minTimeout: 100 }
33194
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
33195
+ stale: 5000
33192
33196
  });
33193
33197
  const content = entries.map((e) => JSON.stringify(e)).join(`
33194
33198
  `) + (entries.length > 0 ? `
@@ -33257,6 +33261,8 @@ function computeConfidence(confirmedByCount, autoGenerated) {
33257
33261
  function inferTags(lesson) {
33258
33262
  const lower = lesson.toLowerCase();
33259
33263
  const tags = [];
33264
+ if (/(^|\s)(?:todo|remember|don't?(?:\s+)?forget)(?:\s|:|,|$)/i.test(lesson))
33265
+ tags.push("todo");
33260
33266
  if (/\b(?:typescript|ts)\b/.test(lower))
33261
33267
  tags.push("typescript");
33262
33268
  if (/\b(?:javascript|js)\b/.test(lower))
@@ -33341,6 +33347,7 @@ var VALID_CATEGORIES = new Set([
33341
33347
  "debugging",
33342
33348
  "performance",
33343
33349
  "integration",
33350
+ "todo",
33344
33351
  "other"
33345
33352
  ]);
33346
33353
  var TECH_REFERENCE_WORDS = new Set([
@@ -33659,7 +33666,8 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
33659
33666
  ["debugging", "debugging"],
33660
33667
  ["performance", "performance"],
33661
33668
  ["integration", "integration"],
33662
- ["other", "other"]
33669
+ ["other", "other"],
33670
+ ["todo", "todo"]
33663
33671
  ]);
33664
33672
  for (const lesson of lessons) {
33665
33673
  const tags = inferTags(lesson);
@@ -437,6 +437,9 @@ export declare const KnowledgeConfigSchema: z.ZodObject<{
437
437
  initial_encounter_score: z.ZodDefault<z.ZodNumber>;
438
438
  encounter_increment: z.ZodDefault<z.ZodNumber>;
439
439
  max_encounter_score: z.ZodDefault<z.ZodNumber>;
440
+ default_max_phases: z.ZodDefault<z.ZodNumber>;
441
+ todo_max_phases: z.ZodDefault<z.ZodNumber>;
442
+ sweep_enabled: z.ZodDefault<z.ZodBoolean>;
440
443
  }, z.core.$strip>;
441
444
  export type KnowledgeConfig = z.infer<typeof KnowledgeConfigSchema>;
442
445
  export declare const CuratorConfigSchema: z.ZodObject<{
@@ -826,6 +829,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
826
829
  initial_encounter_score: z.ZodDefault<z.ZodNumber>;
827
830
  encounter_increment: z.ZodDefault<z.ZodNumber>;
828
831
  max_encounter_score: z.ZodDefault<z.ZodNumber>;
832
+ default_max_phases: z.ZodDefault<z.ZodNumber>;
833
+ todo_max_phases: z.ZodDefault<z.ZodNumber>;
834
+ sweep_enabled: z.ZodDefault<z.ZodBoolean>;
829
835
  }, z.core.$strip>>;
830
836
  curator: z.ZodOptional<z.ZodObject<{
831
837
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -1,5 +1,5 @@
1
1
  /** Core storage layer for the opencode-swarm v6.17 two-tier knowledge system. */
2
- import type { RejectedLesson } from './knowledge-types.js';
2
+ import type { KnowledgeEntryBase, RejectedLesson } from './knowledge-types.js';
3
3
  export declare function getPlatformConfigDir(): string;
4
4
  export declare function resolveSwarmKnowledgePath(directory: string): string;
5
5
  export declare function resolveSwarmRejectedPath(directory: string): string;
@@ -10,6 +10,15 @@ export declare function readRejectedLessons(directory: string): Promise<Rejected
10
10
  export declare function appendKnowledge<T>(filePath: string, entry: T): Promise<void>;
11
11
  export declare function rewriteKnowledge<T>(filePath: string, entries: T[]): Promise<void>;
12
12
  export declare function enforceKnowledgeCap<T>(filePath: string, maxEntries: number): Promise<void>;
13
+ export interface SweepResult {
14
+ scanned: number;
15
+ aged: number;
16
+ archived: number;
17
+ removed: number;
18
+ skipped_promoted: number;
19
+ }
20
+ export declare function sweepAgedEntries<T extends KnowledgeEntryBase>(filePath: string, defaultMaxPhases: number): Promise<SweepResult>;
21
+ export declare function sweepStaleTodos<T extends KnowledgeEntryBase>(filePath: string, todoMaxPhases: number): Promise<SweepResult>;
13
22
  export declare function appendRejectedLesson(directory: string, lesson: RejectedLesson): Promise<void>;
14
23
  export declare function normalize(text: string): string;
15
24
  export declare function wordBigrams(text: string): Set<string>;
@@ -1,5 +1,5 @@
1
1
  /** Type definitions for the opencode-swarm v6.17 two-tier knowledge system. */
2
- export type KnowledgeCategory = 'process' | 'architecture' | 'tooling' | 'security' | 'testing' | 'debugging' | 'performance' | 'integration' | 'other';
2
+ export type KnowledgeCategory = 'process' | 'architecture' | 'tooling' | 'security' | 'testing' | 'debugging' | 'performance' | 'integration' | 'todo' | 'other';
3
3
  export interface PhaseConfirmationRecord {
4
4
  phase_number: number;
5
5
  confirmed_at: string;
@@ -32,6 +32,8 @@ export interface KnowledgeEntryBase {
32
32
  updated_at: string;
33
33
  hive_eligible?: boolean;
34
34
  auto_generated?: boolean;
35
+ phases_alive?: number;
36
+ max_phases?: number;
35
37
  }
36
38
  export interface SwarmKnowledgeEntry extends KnowledgeEntryBase {
37
39
  tier: 'swarm';
@@ -103,6 +105,12 @@ export interface KnowledgeConfig {
103
105
  encounter_increment: number;
104
106
  /** Weighted scoring: maximum encounter score cap. Default: 10.0 */
105
107
  max_encounter_score: number;
108
+ /** Default N-phase TTL for knowledge entries. Default: 10 */
109
+ default_max_phases: number;
110
+ /** N-phase TTL for 'todo' category entries. Default: 3 */
111
+ todo_max_phases: number;
112
+ /** Enable age-based sweep of knowledge entries. Default: true */
113
+ sweep_enabled: boolean;
106
114
  }
107
115
  export interface MessageInfo {
108
116
  role: string;
package/dist/index.js CHANGED
@@ -15101,7 +15101,10 @@ var init_schema = __esm(() => {
15101
15101
  min_encounter_score: exports_external.number().min(0).max(1).default(0.1),
15102
15102
  initial_encounter_score: exports_external.number().min(0).max(5).default(1),
15103
15103
  encounter_increment: exports_external.number().min(0).max(1).default(0.1),
15104
- max_encounter_score: exports_external.number().min(1).max(20).default(10)
15104
+ max_encounter_score: exports_external.number().min(1).max(20).default(10),
15105
+ default_max_phases: exports_external.number().int().positive().default(10),
15106
+ todo_max_phases: exports_external.number().int().positive().default(3),
15107
+ sweep_enabled: exports_external.boolean().default(true)
15105
15108
  });
15106
15109
  CuratorConfigSchema = exports_external.object({
15107
15110
  enabled: exports_external.boolean().default(true),
@@ -38935,7 +38938,8 @@ async function rewriteKnowledge(filePath, entries) {
38935
38938
  let release = null;
38936
38939
  try {
38937
38940
  release = await import_proper_lockfile2.default.lock(dir, {
38938
- retries: { retries: 3, minTimeout: 100 }
38941
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
38942
+ stale: 5000
38939
38943
  });
38940
38944
  const content = entries.map((e) => JSON.stringify(e)).join(`
38941
38945
  `) + (entries.length > 0 ? `
@@ -38956,6 +38960,103 @@ async function enforceKnowledgeCap(filePath, maxEntries) {
38956
38960
  await rewriteKnowledge(filePath, trimmed);
38957
38961
  }
38958
38962
  }
38963
+ async function sweepAgedEntries(filePath, defaultMaxPhases) {
38964
+ let release = null;
38965
+ try {
38966
+ const dir = path13.dirname(filePath);
38967
+ await mkdir2(dir, { recursive: true });
38968
+ release = await import_proper_lockfile2.default.lock(dir, {
38969
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
38970
+ stale: 5000
38971
+ });
38972
+ const entries = await readKnowledge(filePath);
38973
+ const result = {
38974
+ scanned: entries.length,
38975
+ aged: 0,
38976
+ archived: 0,
38977
+ removed: 0,
38978
+ skipped_promoted: 0
38979
+ };
38980
+ if (entries.length === 0)
38981
+ return result;
38982
+ const now = new Date().toISOString();
38983
+ let mutated = false;
38984
+ for (const entry of entries) {
38985
+ if (entry.status === "archived")
38986
+ continue;
38987
+ if (entry.status === "promoted") {
38988
+ result.skipped_promoted++;
38989
+ continue;
38990
+ }
38991
+ entry.phases_alive = (entry.phases_alive ?? 0) + 1;
38992
+ result.aged++;
38993
+ mutated = true;
38994
+ const ttl = entry.max_phases ?? defaultMaxPhases;
38995
+ if (entry.phases_alive > ttl) {
38996
+ entry.status = "archived";
38997
+ entry.updated_at = now;
38998
+ result.archived++;
38999
+ }
39000
+ }
39001
+ if (mutated) {
39002
+ const content = entries.map((e) => JSON.stringify(e)).join(`
39003
+ `) + (entries.length > 0 ? `
39004
+ ` : "");
39005
+ await writeFile2(filePath, content, "utf-8");
39006
+ }
39007
+ return result;
39008
+ } finally {
39009
+ if (release) {
39010
+ try {
39011
+ await release();
39012
+ } catch {}
39013
+ }
39014
+ }
39015
+ }
39016
+ async function sweepStaleTodos(filePath, todoMaxPhases) {
39017
+ let release = null;
39018
+ try {
39019
+ const dir = path13.dirname(filePath);
39020
+ await mkdir2(dir, { recursive: true });
39021
+ release = await import_proper_lockfile2.default.lock(dir, {
39022
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
39023
+ stale: 5000
39024
+ });
39025
+ const entries = await readKnowledge(filePath);
39026
+ const result = {
39027
+ scanned: entries.length,
39028
+ aged: 0,
39029
+ archived: 0,
39030
+ removed: 0,
39031
+ skipped_promoted: 0
39032
+ };
39033
+ if (entries.length === 0)
39034
+ return result;
39035
+ const kept = entries.filter((e) => {
39036
+ if (e.category !== "todo" || e.status === "promoted")
39037
+ return true;
39038
+ const age = e.phases_alive ?? 0;
39039
+ if (age > todoMaxPhases) {
39040
+ result.removed++;
39041
+ return false;
39042
+ }
39043
+ return true;
39044
+ });
39045
+ if (result.removed > 0) {
39046
+ const content = kept.map((e) => JSON.stringify(e)).join(`
39047
+ `) + (kept.length > 0 ? `
39048
+ ` : "");
39049
+ await writeFile2(filePath, content, "utf-8");
39050
+ }
39051
+ return result;
39052
+ } finally {
39053
+ if (release) {
39054
+ try {
39055
+ await release();
39056
+ } catch {}
39057
+ }
39058
+ }
39059
+ }
38959
39060
  async function appendRejectedLesson(directory, lesson) {
38960
39061
  const filePath = resolveSwarmRejectedPath(directory);
38961
39062
  const existing = await readRejectedLessons(directory);
@@ -39004,6 +39105,8 @@ function computeConfidence(confirmedByCount, autoGenerated) {
39004
39105
  function inferTags(lesson) {
39005
39106
  const lower = lesson.toLowerCase();
39006
39107
  const tags = [];
39108
+ if (/(^|\s)(?:todo|remember|don't?(?:\s+)?forget)(?:\s|:|,|$)/i.test(lesson))
39109
+ tags.push("todo");
39007
39110
  if (/\b(?:typescript|ts)\b/.test(lower))
39008
39111
  tags.push("typescript");
39009
39112
  if (/\b(?:javascript|js)\b/.test(lower))
@@ -39142,7 +39245,7 @@ async function readMergedKnowledge(directory, config3, context) {
39142
39245
  });
39143
39246
  }
39144
39247
  const scopeFilter = config3.scope_filter ?? ["global"];
39145
- const filtered = merged.filter((entry) => scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern));
39248
+ const filtered = merged.filter((entry) => scopeFilter.some((pattern) => (entry.scope ?? "global") === pattern) && entry.status !== "archived");
39146
39249
  const ranked = filtered.map((entry) => {
39147
39250
  let categoryScore = 0;
39148
39251
  if (context?.currentPhase) {
@@ -39562,6 +39665,7 @@ var init_knowledge_validator = __esm(() => {
39562
39665
  "debugging",
39563
39666
  "performance",
39564
39667
  "integration",
39668
+ "todo",
39565
39669
  "other"
39566
39670
  ]);
39567
39671
  TECH_REFERENCE_WORDS = new Set([
@@ -39759,7 +39863,8 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
39759
39863
  ["debugging", "debugging"],
39760
39864
  ["performance", "performance"],
39761
39865
  ["integration", "integration"],
39762
- ["other", "other"]
39866
+ ["other", "other"],
39867
+ ["todo", "todo"]
39763
39868
  ]);
39764
39869
  for (const lesson of lessons) {
39765
39870
  const tags = inferTags(lesson);
@@ -73586,7 +73691,7 @@ var knowledge_query = createSwarmTool({
73586
73691
  args: {
73587
73692
  tier: tool.schema.string().optional().describe("Knowledge tier to query: 'swarm', 'hive', or 'all' (default: 'all')"),
73588
73693
  status: tool.schema.string().optional().describe("Filter by status: 'candidate', 'established', or 'promoted'"),
73589
- category: tool.schema.string().optional().describe("Filter by category: 'process', 'architecture', 'tooling', 'security', 'testing', 'debugging', 'performance', 'integration', or 'other'"),
73694
+ category: tool.schema.string().optional().describe("Filter by category: 'process', 'architecture', 'tooling', 'security', 'testing', 'debugging', 'performance', 'integration', 'todo', or 'other'"),
73590
73695
  min_score: tool.schema.number().optional().describe("Minimum confidence score filter (0.0-1.0)"),
73591
73696
  limit: tool.schema.number().optional().describe(`Maximum number of results to return (default: ${DEFAULT_LIMIT}, max: 100)`)
73592
73697
  },
@@ -73749,6 +73854,7 @@ import * as fs60 from "fs";
73749
73854
  import * as path74 from "path";
73750
73855
  init_knowledge_curator();
73751
73856
  init_knowledge_reader();
73857
+ init_knowledge_store();
73752
73858
  init_review_receipt();
73753
73859
  init_utils2();
73754
73860
  init_checkpoint3();
@@ -74058,7 +74164,13 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
74058
74164
  safeWarn(`[phase_complete] Drift verifier error (non-blocking):`, driftError);
74059
74165
  }
74060
74166
  }
74061
- const knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
74167
+ let knowledgeConfig;
74168
+ try {
74169
+ knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
74170
+ } catch (parseErr) {
74171
+ warnings.push(`Knowledge config validation failed: ${String(parseErr)}`);
74172
+ knowledgeConfig = KnowledgeConfigSchema.parse({});
74173
+ }
74062
74174
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
74063
74175
  try {
74064
74176
  const projectName = path74.basename(dir);
@@ -74280,6 +74392,28 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
74280
74392
  telemetry.phaseChanged(contributorSessionId, oldPhase ?? 0, phase);
74281
74393
  }
74282
74394
  }
74395
+ try {
74396
+ if (knowledgeConfig.sweep_enabled) {
74397
+ const swarmPath = resolveSwarmKnowledgePath(dir);
74398
+ await sweepAgedEntries(swarmPath, knowledgeConfig.default_max_phases);
74399
+ await sweepStaleTodos(swarmPath, knowledgeConfig.todo_max_phases);
74400
+ if (knowledgeConfig.hive_enabled) {
74401
+ const hivePath = resolveHiveKnowledgePath();
74402
+ await sweepAgedEntries(hivePath, knowledgeConfig.default_max_phases);
74403
+ await sweepStaleTodos(hivePath, knowledgeConfig.todo_max_phases);
74404
+ }
74405
+ }
74406
+ } catch (err2) {
74407
+ let detail = String(err2);
74408
+ if (detail.includes("ELOCKED")) {
74409
+ detail = "lock timeout (stale lock detected)";
74410
+ } else if (detail.includes("ENOSPC")) {
74411
+ detail = "disk full";
74412
+ } else if (detail.includes("EACCES")) {
74413
+ detail = "permission denied";
74414
+ }
74415
+ warnings.push(`Knowledge sweep failed for phase ${phase}: ${detail}`);
74416
+ }
74283
74417
  try {
74284
74418
  const plan = await loadPlan(dir);
74285
74419
  if (plan === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.71.1",
3
+ "version": "6.72.0",
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",