opencode-swarm 7.67.0 → 7.68.1

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.67.0",
55
+ version: "7.68.1",
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",
@@ -19221,7 +19221,7 @@ var init_loader = __esm(() => {
19221
19221
  });
19222
19222
 
19223
19223
  // src/config/evidence-schema.ts
19224
- var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
19224
+ var EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceTypeSchema, EvidenceVerdictSchema, BaseEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, DiffEvidenceSchema, ApprovalEvidenceSchema, NoteEvidenceSchema, RetrospectiveEvidenceSchema, SyntaxEvidenceSchema, PlaceholderEvidenceSchema, SastFindingSchema, SastEvidenceSchema, SbomEvidenceSchema, BuildEvidenceSchema, QualityBudgetEvidenceSchema, SecretscanEvidenceSchema, GateEvidenceBaseSchema, MutationGateEvidenceSchema, DriftVerificationEvidenceSchema, HallucinationVerificationEvidenceSchema, EvidenceSchema, EvidenceBundleSchema;
19225
19225
  var init_evidence_schema = __esm(() => {
19226
19226
  init_zod();
19227
19227
  EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
@@ -19240,11 +19240,16 @@ var init_evidence_schema = __esm(() => {
19240
19240
  "sbom",
19241
19241
  "build",
19242
19242
  "quality_budget",
19243
- "secretscan"
19243
+ "secretscan",
19244
+ "mutation-gate",
19245
+ "drift-verification",
19246
+ "hallucination-verification"
19244
19247
  ]);
19245
19248
  EvidenceVerdictSchema = exports_external.enum([
19246
19249
  "pass",
19247
19250
  "fail",
19251
+ "warn",
19252
+ "skip",
19248
19253
  "approved",
19249
19254
  "rejected",
19250
19255
  "info"
@@ -19450,6 +19455,30 @@ var init_evidence_schema = __esm(() => {
19450
19455
  files_scanned: exports_external.number().int().min(0).default(0),
19451
19456
  skipped_files: exports_external.number().int().min(0).default(0)
19452
19457
  });
19458
+ GateEvidenceBaseSchema = exports_external.object({
19459
+ task_id: exports_external.string().optional(),
19460
+ agent: exports_external.string().optional(),
19461
+ timestamp: exports_external.string().datetime(),
19462
+ summary: exports_external.string().min(1),
19463
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
19464
+ });
19465
+ MutationGateEvidenceSchema = GateEvidenceBaseSchema.extend({
19466
+ type: exports_external.literal("mutation-gate"),
19467
+ verdict: exports_external.enum(["pass", "warn", "fail", "skip"]),
19468
+ killRate: exports_external.number().min(0).max(1).optional(),
19469
+ adjustedKillRate: exports_external.number().min(0).max(1).optional(),
19470
+ survivedMutants: exports_external.string().optional()
19471
+ });
19472
+ DriftVerificationEvidenceSchema = GateEvidenceBaseSchema.extend({
19473
+ type: exports_external.literal("drift-verification"),
19474
+ verdict: exports_external.enum(["approved", "rejected"]),
19475
+ requirementCoverage: exports_external.string().optional()
19476
+ });
19477
+ HallucinationVerificationEvidenceSchema = GateEvidenceBaseSchema.extend({
19478
+ type: exports_external.literal("hallucination-verification"),
19479
+ verdict: exports_external.enum(["approved", "rejected"]),
19480
+ findings: exports_external.string().optional()
19481
+ });
19453
19482
  EvidenceSchema = exports_external.discriminatedUnion("type", [
19454
19483
  ReviewEvidenceSchema,
19455
19484
  TestEvidenceSchema,
@@ -19463,7 +19492,10 @@ var init_evidence_schema = __esm(() => {
19463
19492
  SbomEvidenceSchema,
19464
19493
  BuildEvidenceSchema,
19465
19494
  QualityBudgetEvidenceSchema,
19466
- SecretscanEvidenceSchema
19495
+ SecretscanEvidenceSchema,
19496
+ MutationGateEvidenceSchema,
19497
+ DriftVerificationEvidenceSchema,
19498
+ HallucinationVerificationEvidenceSchema
19467
19499
  ]);
19468
19500
  EvidenceBundleSchema = exports_external.object({
19469
19501
  schema_version: exports_external.literal("1.0.0"),
@@ -36950,14 +36982,217 @@ var init_event_bus = __esm(() => {
36950
36982
  init_utils();
36951
36983
  });
36952
36984
 
36985
+ // src/hooks/knowledge-events.ts
36986
+ import { existsSync as existsSync9 } from "fs";
36987
+ import { appendFile as appendFile2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
36988
+ import * as path12 from "path";
36989
+ function resolveKnowledgeEventsPath(directory) {
36990
+ return path12.join(directory, ".swarm", "knowledge-events.jsonl");
36991
+ }
36992
+ function resolveLegacyApplicationLogPath(directory) {
36993
+ return path12.join(directory, ".swarm", "knowledge-application.jsonl");
36994
+ }
36995
+ async function readKnowledgeEvents(directory) {
36996
+ const filePath = resolveKnowledgeEventsPath(directory);
36997
+ if (!existsSync9(filePath))
36998
+ return [];
36999
+ const content = await readFile2(filePath, "utf-8");
37000
+ const out = [];
37001
+ for (const line of content.split(`
37002
+ `)) {
37003
+ const trimmed = line.trim();
37004
+ if (!trimmed)
37005
+ continue;
37006
+ try {
37007
+ out.push(JSON.parse(trimmed));
37008
+ } catch {
37009
+ warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
37010
+ }
37011
+ }
37012
+ return out;
37013
+ }
37014
+ async function readLegacyApplicationRecords(directory) {
37015
+ const filePath = resolveLegacyApplicationLogPath(directory);
37016
+ if (!existsSync9(filePath))
37017
+ return [];
37018
+ const content = await readFile2(filePath, "utf-8");
37019
+ const out = [];
37020
+ for (const line of content.split(`
37021
+ `)) {
37022
+ const trimmed = line.trim();
37023
+ if (!trimmed)
37024
+ continue;
37025
+ try {
37026
+ out.push(JSON.parse(trimmed));
37027
+ } catch {
37028
+ warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
37029
+ }
37030
+ }
37031
+ return out;
37032
+ }
37033
+ function emptyRollup() {
37034
+ return {
37035
+ shown_count: 0,
37036
+ acknowledged_count: 0,
37037
+ applied_explicit_count: 0,
37038
+ ignored_count: 0,
37039
+ violated_count: 0,
37040
+ contradicted_count: 0,
37041
+ n_a_count: 0,
37042
+ succeeded_after_shown_count: 0,
37043
+ failed_after_shown_count: 0,
37044
+ partial_after_shown_count: 0,
37045
+ violation_timestamps: []
37046
+ };
37047
+ }
37048
+ function get(map3, id) {
37049
+ let r = map3.get(id);
37050
+ if (!r) {
37051
+ r = emptyRollup();
37052
+ map3.set(id, r);
37053
+ }
37054
+ return r;
37055
+ }
37056
+ function maxIso(current, candidate) {
37057
+ if (!current)
37058
+ return candidate;
37059
+ return candidate > current ? candidate : current;
37060
+ }
37061
+ function recomputeCounters(events, legacyRecords = []) {
37062
+ const map3 = new Map;
37063
+ const retrievedIds = new Set;
37064
+ for (const e of events) {
37065
+ switch (e.type) {
37066
+ case "retrieved": {
37067
+ for (const id of e.result_ids) {
37068
+ retrievedIds.add(id);
37069
+ get(map3, id).shown_count += 1;
37070
+ }
37071
+ break;
37072
+ }
37073
+ case "acknowledged": {
37074
+ const r = get(map3, e.knowledge_id);
37075
+ r.acknowledged_count += 1;
37076
+ r.last_acknowledged_at = maxIso(r.last_acknowledged_at, e.timestamp);
37077
+ break;
37078
+ }
37079
+ case "applied": {
37080
+ const r = get(map3, e.knowledge_id);
37081
+ r.applied_explicit_count += 1;
37082
+ r.last_applied_at = maxIso(r.last_applied_at, e.timestamp);
37083
+ break;
37084
+ }
37085
+ case "ignored":
37086
+ get(map3, e.knowledge_id).ignored_count += 1;
37087
+ break;
37088
+ case "violated": {
37089
+ const r = get(map3, e.knowledge_id);
37090
+ r.violated_count += 1;
37091
+ r.violation_timestamps.push(e.timestamp);
37092
+ break;
37093
+ }
37094
+ case "contradicted":
37095
+ get(map3, e.knowledge_id).contradicted_count += 1;
37096
+ break;
37097
+ case "n_a":
37098
+ get(map3, e.knowledge_id).n_a_count += 1;
37099
+ break;
37100
+ case "outcome": {
37101
+ if (!e.knowledge_id)
37102
+ break;
37103
+ const r = get(map3, e.knowledge_id);
37104
+ if (e.outcome === "success")
37105
+ r.succeeded_after_shown_count += 1;
37106
+ else if (e.outcome === "failure")
37107
+ r.failed_after_shown_count += 1;
37108
+ else if (e.outcome === "partial")
37109
+ r.partial_after_shown_count += 1;
37110
+ break;
37111
+ }
37112
+ }
37113
+ }
37114
+ for (const rec of legacyRecords) {
37115
+ const r = get(map3, rec.knowledgeId);
37116
+ switch (rec.result) {
37117
+ case "shown":
37118
+ if (!retrievedIds.has(rec.knowledgeId))
37119
+ r.shown_count += 1;
37120
+ break;
37121
+ case "acknowledged":
37122
+ r.acknowledged_count += 1;
37123
+ r.last_acknowledged_at = maxIso(r.last_acknowledged_at, rec.timestamp);
37124
+ break;
37125
+ case "applied":
37126
+ r.applied_explicit_count += 1;
37127
+ r.last_applied_at = maxIso(r.last_applied_at, rec.timestamp);
37128
+ break;
37129
+ case "ignored":
37130
+ r.ignored_count += 1;
37131
+ break;
37132
+ case "violated":
37133
+ r.violated_count += 1;
37134
+ r.violation_timestamps.push(rec.timestamp);
37135
+ break;
37136
+ }
37137
+ }
37138
+ for (const r of map3.values()) {
37139
+ if (r.violation_timestamps.length > 1) {
37140
+ r.violation_timestamps.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
37141
+ }
37142
+ if (r.violation_timestamps.length > MAX_VIOLATION_TIMESTAMPS) {
37143
+ r.violation_timestamps = r.violation_timestamps.slice(0, MAX_VIOLATION_TIMESTAMPS);
37144
+ }
37145
+ }
37146
+ return map3;
37147
+ }
37148
+ async function readKnowledgeCounterRollups(directory) {
37149
+ try {
37150
+ const [events, legacyRecords] = await Promise.all([
37151
+ readKnowledgeEvents(directory),
37152
+ readLegacyApplicationRecords(directory)
37153
+ ]);
37154
+ return recomputeCounters(events, legacyRecords);
37155
+ } catch (err) {
37156
+ warn(`[knowledge-events] readKnowledgeCounterRollups failed: ${err instanceof Error ? err.message : String(err)}`);
37157
+ return new Map;
37158
+ }
37159
+ }
37160
+ function effectiveRetrievalOutcomes(stored, rollup) {
37161
+ const base = stored ?? {
37162
+ applied_count: 0,
37163
+ succeeded_after_count: 0,
37164
+ failed_after_count: 0
37165
+ };
37166
+ if (!rollup)
37167
+ return base;
37168
+ return {
37169
+ ...base,
37170
+ ...rollup
37171
+ };
37172
+ }
37173
+ var import_proper_lockfile3, RECEIPT_EVENT_TYPES, MAX_VIOLATION_TIMESTAMPS = 10;
37174
+ var init_knowledge_events = __esm(() => {
37175
+ init_logger();
37176
+ import_proper_lockfile3 = __toESM(require_proper_lockfile(), 1);
37177
+ RECEIPT_EVENT_TYPES = new Set([
37178
+ "acknowledged",
37179
+ "applied",
37180
+ "ignored",
37181
+ "contradicted",
37182
+ "violated",
37183
+ "n_a",
37184
+ "override"
37185
+ ]);
37186
+ });
37187
+
36953
37188
  // src/evidence/task-file.ts
36954
37189
  import { renameSync as renameSync6, unlinkSync as unlinkSync4 } from "fs";
36955
- import * as path12 from "path";
37190
+ import * as path13 from "path";
36956
37191
  function taskEvidenceRelPath(taskId) {
36957
- return path12.join("evidence", `${taskId}.json`);
37192
+ return path13.join("evidence", `${taskId}.json`);
36958
37193
  }
36959
37194
  function taskEvidencePath(directory, taskId) {
36960
- return path12.join(directory, ".swarm", taskEvidenceRelPath(taskId));
37195
+ return path13.join(directory, ".swarm", taskEvidenceRelPath(taskId));
36961
37196
  }
36962
37197
  async function atomicWriteFile(targetPath, content) {
36963
37198
  const tempPath = `${targetPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
@@ -36981,40 +37216,40 @@ var init_task_file = __esm(() => {
36981
37216
  });
36982
37217
 
36983
37218
  // src/hooks/knowledge-store.ts
36984
- import { existsSync as existsSync9 } from "fs";
36985
- import { appendFile as appendFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
37219
+ import { existsSync as existsSync10 } from "fs";
37220
+ import { appendFile as appendFile3, mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
36986
37221
  import * as os4 from "os";
36987
- import * as path13 from "path";
37222
+ import * as path14 from "path";
36988
37223
  function resolveSwarmKnowledgePath(directory) {
36989
- return path13.join(directory, ".swarm", "knowledge.jsonl");
37224
+ return path14.join(directory, ".swarm", "knowledge.jsonl");
36990
37225
  }
36991
37226
  function resolveSwarmRejectedPath(directory) {
36992
- return path13.join(directory, ".swarm", "knowledge-rejected.jsonl");
37227
+ return path14.join(directory, ".swarm", "knowledge-rejected.jsonl");
36993
37228
  }
36994
37229
  function resolveSwarmRetractionsPath(directory) {
36995
- return path13.join(directory, ".swarm", "knowledge-retractions.jsonl");
37230
+ return path14.join(directory, ".swarm", "knowledge-retractions.jsonl");
36996
37231
  }
36997
37232
  function resolveHiveKnowledgePath() {
36998
37233
  const platform = process.platform;
36999
37234
  const home = process.env.HOME || os4.homedir();
37000
37235
  let dataDir;
37001
37236
  if (platform === "win32") {
37002
- dataDir = path13.join(process.env.LOCALAPPDATA || path13.join(home, "AppData", "Local"), "opencode-swarm", "Data");
37237
+ dataDir = path14.join(process.env.LOCALAPPDATA || path14.join(home, "AppData", "Local"), "opencode-swarm", "Data");
37003
37238
  } else if (platform === "darwin") {
37004
- dataDir = path13.join(home, "Library", "Application Support", "opencode-swarm");
37239
+ dataDir = path14.join(home, "Library", "Application Support", "opencode-swarm");
37005
37240
  } else {
37006
- dataDir = path13.join(process.env.XDG_DATA_HOME || path13.join(home, ".local", "share"), "opencode-swarm");
37241
+ dataDir = path14.join(process.env.XDG_DATA_HOME || path14.join(home, ".local", "share"), "opencode-swarm");
37007
37242
  }
37008
- return path13.join(dataDir, "shared-learnings.jsonl");
37243
+ return path14.join(dataDir, "shared-learnings.jsonl");
37009
37244
  }
37010
37245
  function resolveHiveRejectedPath() {
37011
37246
  const hivePath = resolveHiveKnowledgePath();
37012
- return path13.join(path13.dirname(hivePath), "shared-learnings-rejected.jsonl");
37247
+ return path14.join(path14.dirname(hivePath), "shared-learnings-rejected.jsonl");
37013
37248
  }
37014
37249
  async function readKnowledge(filePath) {
37015
- if (!existsSync9(filePath))
37250
+ if (!existsSync10(filePath))
37016
37251
  return [];
37017
- const content = await readFile2(filePath, "utf-8");
37252
+ const content = await readFile3(filePath, "utf-8");
37018
37253
  const results = [];
37019
37254
  for (const line of content.split(`
37020
37255
  `)) {
@@ -37106,15 +37341,15 @@ async function appendRetractionRecord(directory, record3) {
37106
37341
  await appendKnowledge(resolveSwarmRetractionsPath(directory), record3);
37107
37342
  }
37108
37343
  async function appendKnowledge(filePath, entry) {
37109
- const dir = path13.dirname(filePath);
37110
- await mkdir2(dir, { recursive: true });
37344
+ const dir = path14.dirname(filePath);
37345
+ await mkdir3(dir, { recursive: true });
37111
37346
  let release = null;
37112
37347
  try {
37113
- release = await import_proper_lockfile3.default.lock(dir, {
37348
+ release = await import_proper_lockfile4.default.lock(dir, {
37114
37349
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37115
37350
  stale: 5000
37116
37351
  });
37117
- await appendFile2(filePath, `${JSON.stringify(entry)}
37352
+ await appendFile3(filePath, `${JSON.stringify(entry)}
37118
37353
  `, "utf-8");
37119
37354
  } finally {
37120
37355
  if (release) {
@@ -37125,11 +37360,11 @@ async function appendKnowledge(filePath, entry) {
37125
37360
  }
37126
37361
  }
37127
37362
  async function rewriteKnowledge(filePath, entries) {
37128
- const dir = path13.dirname(filePath);
37129
- await mkdir2(dir, { recursive: true });
37363
+ const dir = path14.dirname(filePath);
37364
+ await mkdir3(dir, { recursive: true });
37130
37365
  let release = null;
37131
37366
  try {
37132
- release = await import_proper_lockfile3.default.lock(dir, {
37367
+ release = await import_proper_lockfile4.default.lock(dir, {
37133
37368
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37134
37369
  stale: 5000
37135
37370
  });
@@ -37146,15 +37381,15 @@ async function rewriteKnowledge(filePath, entries) {
37146
37381
  }
37147
37382
  }
37148
37383
  async function transactFile(filePath, read, write, mutate) {
37149
- const dir = path13.dirname(filePath);
37384
+ const dir = path14.dirname(filePath);
37150
37385
  try {
37151
- await mkdir2(dir, { recursive: true });
37386
+ await mkdir3(dir, { recursive: true });
37152
37387
  } catch {
37153
37388
  return false;
37154
37389
  }
37155
37390
  let release = null;
37156
37391
  try {
37157
- release = await import_proper_lockfile3.default.lock(dir, {
37392
+ release = await import_proper_lockfile4.default.lock(dir, {
37158
37393
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37159
37394
  stale: 5000
37160
37395
  });
@@ -37301,9 +37536,9 @@ async function applyConfidenceDeltas(filePath, deltas) {
37301
37536
  }
37302
37537
  let release = null;
37303
37538
  try {
37304
- const dir = path13.dirname(filePath);
37305
- await mkdir2(dir, { recursive: true });
37306
- release = await import_proper_lockfile3.default.lock(dir, {
37539
+ const dir = path14.dirname(filePath);
37540
+ await mkdir3(dir, { recursive: true });
37541
+ release = await import_proper_lockfile4.default.lock(dir, {
37307
37542
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37308
37543
  stale: 5000
37309
37544
  });
@@ -37336,15 +37571,432 @@ async function applyConfidenceDeltas(filePath, deltas) {
37336
37571
  }
37337
37572
  }
37338
37573
  }
37339
- var import_proper_lockfile3, OUTCOME_SIGNAL_SMOOTHING = 4, CONFIDENCE_FLOOR = 0.1, CONFIDENCE_CEILING = 1;
37574
+ var import_proper_lockfile4, OUTCOME_SIGNAL_SMOOTHING = 4, CONFIDENCE_FLOOR = 0.1, CONFIDENCE_CEILING = 1;
37340
37575
  var init_knowledge_store = __esm(() => {
37341
37576
  init_task_file();
37342
- import_proper_lockfile3 = __toESM(require_proper_lockfile(), 1);
37577
+ import_proper_lockfile4 = __toESM(require_proper_lockfile(), 1);
37578
+ });
37579
+
37580
+ // src/hooks/knowledge-escalator.ts
37581
+ async function readRecentEscalations(directory, windowDays = ESCALATION_DISPLAY_WINDOW_DAYS, now = new Date) {
37582
+ try {
37583
+ const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;
37584
+ const events = await readKnowledgeEvents(directory);
37585
+ const out = [];
37586
+ for (const e of events) {
37587
+ if (e.type !== "escalation")
37588
+ continue;
37589
+ const t = Date.parse(e.timestamp);
37590
+ if (Number.isNaN(t) || t < cutoff)
37591
+ continue;
37592
+ out.push({
37593
+ entry_id: e.entry_id,
37594
+ from: e.from,
37595
+ to: e.to,
37596
+ reason: e.reason,
37597
+ at: e.timestamp
37598
+ });
37599
+ }
37600
+ out.sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
37601
+ return out;
37602
+ } catch {
37603
+ return [];
37604
+ }
37605
+ }
37606
+ var ESCALATION_DISPLAY_WINDOW_DAYS = 7;
37607
+ var init_knowledge_escalator = __esm(() => {
37608
+ init_knowledge_events();
37609
+ init_knowledge_store();
37610
+ });
37611
+
37612
+ // src/services/learning-metrics.ts
37613
+ function safeDivide(numerator, denominator) {
37614
+ return denominator === 0 ? 0 : numerator / denominator;
37615
+ }
37616
+ function truncateLesson(lesson) {
37617
+ if (lesson.length <= MAX_LESSON_DISPLAY_CHARS)
37618
+ return lesson;
37619
+ return `${lesson.slice(0, MAX_LESSON_DISPLAY_CHARS - 3)}...`;
37620
+ }
37621
+ function isReceiptType(event) {
37622
+ return event.type === "acknowledged" || event.type === "applied" || event.type === "ignored" || event.type === "contradicted" || event.type === "violated" || event.type === "n_a" || event.type === "override";
37623
+ }
37624
+ function countWindowedReceipts(events, entryId, windowMs, nowMs) {
37625
+ const cutoff = nowMs - windowMs;
37626
+ let violations = 0;
37627
+ let totalReceipts = 0;
37628
+ for (const e of events) {
37629
+ if (!isReceiptType(e))
37630
+ continue;
37631
+ if (e.knowledge_id !== entryId)
37632
+ continue;
37633
+ const t = Date.parse(e.timestamp);
37634
+ if (Number.isNaN(t) || t < cutoff)
37635
+ continue;
37636
+ totalReceipts++;
37637
+ if (e.type === "violated")
37638
+ violations++;
37639
+ }
37640
+ return { violations, totalReceipts };
37641
+ }
37642
+ function determineTrend(rate7d, rate30d, hasData) {
37643
+ if (!hasData)
37644
+ return "no_data";
37645
+ if (rate7d < rate30d)
37646
+ return "improving";
37647
+ if (rate7d > rate30d)
37648
+ return "worsening";
37649
+ return "stable";
37650
+ }
37651
+ function classifyROI(rollup) {
37652
+ if (rollup.applied_explicit_count > 0 && rollup.succeeded_after_shown_count > rollup.failed_after_shown_count) {
37653
+ return "high";
37654
+ }
37655
+ if (rollup.applied_explicit_count > 0) {
37656
+ return "medium";
37657
+ }
37658
+ if (rollup.shown_count > 0) {
37659
+ return "low";
37660
+ }
37661
+ return "unused";
37662
+ }
37663
+ async function computeLearningMetrics(directory, options) {
37664
+ const now = options?.now ?? new Date;
37665
+ const nowMs = now.getTime();
37666
+ const phasesThreshold = options?.currentPhase ?? DEFAULT_PHASES_ALIVE_THRESHOLD;
37667
+ const [events, entries] = await Promise.all([
37668
+ readKnowledgeEvents(directory),
37669
+ readKnowledge(resolveSwarmKnowledgePath(directory))
37670
+ ]);
37671
+ if (events.length === 0 && entries.length === 0) {
37672
+ return emptyMetrics();
37673
+ }
37674
+ const rollups = recomputeCounters(events);
37675
+ const entryMap = new Map;
37676
+ for (const entry of entries) {
37677
+ entryMap.set(entry.id, entry);
37678
+ }
37679
+ const sessionIds = new Set;
37680
+ for (const e of events) {
37681
+ if ("session_id" in e && typeof e.session_id === "string") {
37682
+ sessionIds.add(e.session_id);
37683
+ }
37684
+ }
37685
+ const sessionCount = sessionIds.size;
37686
+ const violationTrends = [];
37687
+ for (const [entryId, rollup] of rollups) {
37688
+ if (rollup.violated_count === 0)
37689
+ continue;
37690
+ const entry = entryMap.get(entryId);
37691
+ const lesson = entry?.lesson ?? entryId;
37692
+ const priority = entry?.directive_priority ?? "medium";
37693
+ const w7 = countWindowedReceipts(events, entryId, SEVEN_DAYS_MS, nowMs);
37694
+ const w30 = countWindowedReceipts(events, entryId, THIRTY_DAYS_MS, nowMs);
37695
+ const rate7d = safeDivide(w7.violations, w7.totalReceipts);
37696
+ const rate30d = safeDivide(w30.violations, w30.totalReceipts);
37697
+ const hasData = w7.totalReceipts > 0 || w30.totalReceipts > 0;
37698
+ violationTrends.push({
37699
+ entryId,
37700
+ lesson,
37701
+ priority,
37702
+ violationRate7d: rate7d,
37703
+ violationRate30d: rate30d,
37704
+ trend: determineTrend(rate7d, rate30d, hasData)
37705
+ });
37706
+ }
37707
+ let totalViolations7d = 0;
37708
+ let totalReceipts7d = 0;
37709
+ let totalViolations30d = 0;
37710
+ let totalReceipts30d = 0;
37711
+ for (const e of events) {
37712
+ if (!isReceiptType(e))
37713
+ continue;
37714
+ const t = Date.parse(e.timestamp);
37715
+ if (Number.isNaN(t))
37716
+ continue;
37717
+ if (t >= nowMs - SEVEN_DAYS_MS) {
37718
+ totalReceipts7d++;
37719
+ if (e.type === "violated")
37720
+ totalViolations7d++;
37721
+ }
37722
+ if (t >= nowMs - THIRTY_DAYS_MS) {
37723
+ totalReceipts30d++;
37724
+ if (e.type === "violated")
37725
+ totalViolations30d++;
37726
+ }
37727
+ }
37728
+ const overallViolationRate = {
37729
+ window7d: safeDivide(totalViolations7d, totalReceipts7d),
37730
+ window30d: safeDivide(totalViolations30d, totalReceipts30d)
37731
+ };
37732
+ const priorityGroups = new Map;
37733
+ for (const entry of entries) {
37734
+ const priority = entry.directive_priority ?? "medium";
37735
+ const rollup = rollups.get(entry.id);
37736
+ if (!rollup)
37737
+ continue;
37738
+ let group = priorityGroups.get(priority);
37739
+ if (!group) {
37740
+ group = { applied: 0, total: 0 };
37741
+ priorityGroups.set(priority, group);
37742
+ }
37743
+ group.applied += rollup.applied_explicit_count;
37744
+ group.total += rollup.shown_count;
37745
+ }
37746
+ const applicationRateByPriority = {};
37747
+ for (const [priority, group] of priorityGroups) {
37748
+ applicationRateByPriority[priority] = {
37749
+ applied: group.applied,
37750
+ total: group.total,
37751
+ rate: safeDivide(group.applied, group.total)
37752
+ };
37753
+ }
37754
+ const timeToLatestApplication = [];
37755
+ for (const entry of entries) {
37756
+ const rollup = rollups.get(entry.id);
37757
+ let daysToApply = null;
37758
+ if (rollup?.last_applied_at && entry.created_at) {
37759
+ const appliedMs = Date.parse(rollup.last_applied_at);
37760
+ const createdMs = Date.parse(entry.created_at);
37761
+ if (!Number.isNaN(appliedMs) && !Number.isNaN(createdMs) && appliedMs >= createdMs) {
37762
+ daysToApply = (appliedMs - createdMs) / MS_PER_DAY;
37763
+ }
37764
+ }
37765
+ timeToLatestApplication.push({
37766
+ entryId: entry.id,
37767
+ lesson: entry.lesson,
37768
+ daysToApply
37769
+ });
37770
+ }
37771
+ const recentEscalations30d = await readRecentEscalations(directory, 30, now);
37772
+ const last7dCutoff = nowMs - SEVEN_DAYS_MS;
37773
+ const last7d = recentEscalations30d.filter((esc3) => {
37774
+ const t = Date.parse(esc3.at);
37775
+ return !Number.isNaN(t) && t >= last7dCutoff;
37776
+ }).length;
37777
+ let totalEscalations = 0;
37778
+ for (const e of events) {
37779
+ if (e.type === "escalation")
37780
+ totalEscalations++;
37781
+ }
37782
+ const escalationFrequency = {
37783
+ total: totalEscalations,
37784
+ last7d,
37785
+ last30d: recentEscalations30d.length
37786
+ };
37787
+ let unacknowledgedCriticalCount = 0;
37788
+ for (const entry of entries) {
37789
+ if (entry.directive_priority !== "critical")
37790
+ continue;
37791
+ const rollup = rollups.get(entry.id);
37792
+ if (!rollup)
37793
+ continue;
37794
+ if (rollup.shown_count > 0 && rollup.acknowledged_count === 0 && rollup.applied_explicit_count === 0) {
37795
+ unacknowledgedCriticalCount++;
37796
+ }
37797
+ }
37798
+ const entryROI = [];
37799
+ for (const entry of entries) {
37800
+ const rollup = rollups.get(entry.id);
37801
+ if (!rollup) {
37802
+ entryROI.push({
37803
+ entryId: entry.id,
37804
+ lesson: entry.lesson,
37805
+ appliedCount: 0,
37806
+ shownCount: 0,
37807
+ succeededCount: 0,
37808
+ failedCount: 0,
37809
+ roi: "unused"
37810
+ });
37811
+ continue;
37812
+ }
37813
+ entryROI.push({
37814
+ entryId: entry.id,
37815
+ lesson: entry.lesson,
37816
+ appliedCount: rollup.applied_explicit_count,
37817
+ shownCount: rollup.shown_count,
37818
+ succeededCount: rollup.succeeded_after_shown_count,
37819
+ failedCount: rollup.failed_after_shown_count,
37820
+ roi: classifyROI(rollup)
37821
+ });
37822
+ }
37823
+ const neverApplied = [];
37824
+ for (const entry of entries) {
37825
+ const rollup = rollups.get(entry.id);
37826
+ const applied = rollup?.applied_explicit_count ?? 0;
37827
+ const phasesAlive = entry.phases_alive ?? 0;
37828
+ if (applied === 0 && phasesAlive >= phasesThreshold) {
37829
+ neverApplied.push({
37830
+ entryId: entry.id,
37831
+ lesson: entry.lesson,
37832
+ phasesAlive
37833
+ });
37834
+ }
37835
+ }
37836
+ const learningSummary = buildLearningSummary(overallViolationRate, violationTrends, sessionCount);
37837
+ return {
37838
+ violationTrends,
37839
+ overallViolationRate,
37840
+ applicationRateByPriority,
37841
+ timeToLatestApplication,
37842
+ escalationFrequency,
37843
+ unacknowledgedCriticalCount,
37844
+ entryROI,
37845
+ neverApplied,
37846
+ learningSummary,
37847
+ sessionCount
37848
+ };
37849
+ }
37850
+ function emptyMetrics() {
37851
+ return {
37852
+ violationTrends: [],
37853
+ overallViolationRate: { window7d: 0, window30d: 0 },
37854
+ applicationRateByPriority: {},
37855
+ timeToLatestApplication: [],
37856
+ escalationFrequency: { total: 0, last7d: 0, last30d: 0 },
37857
+ unacknowledgedCriticalCount: 0,
37858
+ entryROI: [],
37859
+ neverApplied: [],
37860
+ learningSummary: "No learning data yet",
37861
+ sessionCount: 0
37862
+ };
37863
+ }
37864
+ function buildLearningSummary(overallRate, trends, sessionCount) {
37865
+ const overallTrend = overallRate.window7d < overallRate.window30d ? "improving" : overallRate.window7d > overallRate.window30d ? "worsening" : "stable";
37866
+ const rate7dPct = (overallRate.window7d * 100).toFixed(1);
37867
+ const rate30dPct = (overallRate.window30d * 100).toFixed(1);
37868
+ const line1 = `Learning trend: ${overallTrend} \u2014 ${rate7dPct}% violation rate (7d), ${rate30dPct}% (30d) across ${sessionCount} sessions`;
37869
+ let line2 = "No improvement data yet";
37870
+ let bestDrop = 0;
37871
+ let bestDropEntry;
37872
+ for (const t of trends) {
37873
+ if (t.trend !== "improving")
37874
+ continue;
37875
+ const drop = t.violationRate30d - t.violationRate7d;
37876
+ if (drop > bestDrop) {
37877
+ bestDrop = drop;
37878
+ bestDropEntry = t;
37879
+ }
37880
+ }
37881
+ if (bestDropEntry) {
37882
+ line2 = `Top improvement: ${truncateLesson(bestDropEntry.lesson)}`;
37883
+ }
37884
+ let line3 = "No concerns detected";
37885
+ let worstRise = 0;
37886
+ let worstEntry;
37887
+ for (const t of trends) {
37888
+ if (t.trend !== "worsening")
37889
+ continue;
37890
+ const rise = t.violationRate7d - t.violationRate30d;
37891
+ if (rise > worstRise) {
37892
+ worstRise = rise;
37893
+ worstEntry = t;
37894
+ }
37895
+ }
37896
+ if (worstEntry) {
37897
+ line3 = `Watch: ${truncateLesson(worstEntry.lesson)}`;
37898
+ }
37899
+ return `${line1}
37900
+ ${line2}
37901
+ ${line3}`;
37902
+ }
37903
+ function formatLearningMarkdown(metrics) {
37904
+ const lines = [];
37905
+ lines.push("## Learning Summary", "", metrics.learningSummary, "");
37906
+ lines.push("## Violation Trends", "");
37907
+ if (metrics.violationTrends.length === 0) {
37908
+ lines.push("No violation trends recorded.", "");
37909
+ } else {
37910
+ lines.push("| Entry | Priority | 7d Rate | 30d Rate | Trend |");
37911
+ lines.push("|-------|----------|---------|----------|-------|");
37912
+ for (const t of metrics.violationTrends) {
37913
+ lines.push(`| ${truncateLesson(t.lesson)} | ${t.priority} | ${(t.violationRate7d * 100).toFixed(1)}% | ${(t.violationRate30d * 100).toFixed(1)}% | ${t.trend} |`);
37914
+ }
37915
+ lines.push("");
37916
+ }
37917
+ lines.push("## Application Rates by Priority", "");
37918
+ const priorities = Object.keys(metrics.applicationRateByPriority);
37919
+ if (priorities.length === 0) {
37920
+ lines.push("No application data recorded.", "");
37921
+ } else {
37922
+ lines.push("| Priority | Applied | Total | Rate |");
37923
+ lines.push("|----------|---------|-------|------|");
37924
+ for (const p of priorities) {
37925
+ const g = metrics.applicationRateByPriority[p];
37926
+ lines.push(`| ${p} | ${g.applied} | ${g.total} | ${(g.rate * 100).toFixed(1)}% |`);
37927
+ }
37928
+ lines.push("");
37929
+ }
37930
+ lines.push("## Escalation Activity", "");
37931
+ lines.push(`- Total escalations: ${metrics.escalationFrequency.total}`);
37932
+ lines.push(`- Last 7 days: ${metrics.escalationFrequency.last7d}`);
37933
+ lines.push(`- Last 30 days: ${metrics.escalationFrequency.last30d}`);
37934
+ lines.push("");
37935
+ lines.push("## Entry ROI", "");
37936
+ const sortedROI = [...metrics.entryROI].sort((a, b) => b.shownCount - a.shownCount);
37937
+ const top10 = sortedROI.slice(0, 10);
37938
+ const unused = metrics.entryROI.filter((r) => r.roi === "unused");
37939
+ const roiEntries = [...top10];
37940
+ for (const u of unused) {
37941
+ if (!roiEntries.some((r) => r.entryId === u.entryId)) {
37942
+ roiEntries.push(u);
37943
+ }
37944
+ }
37945
+ if (roiEntries.length === 0) {
37946
+ lines.push("No ROI data recorded.", "");
37947
+ } else {
37948
+ lines.push("| Entry | Applied | Shown | Succeeded | Failed | ROI |");
37949
+ lines.push("|-------|---------|-------|-----------|--------|-----|");
37950
+ for (const r of roiEntries) {
37951
+ lines.push(`| ${truncateLesson(r.lesson)} | ${r.appliedCount} | ${r.shownCount} | ${r.succeededCount} | ${r.failedCount} | ${r.roi} |`);
37952
+ }
37953
+ lines.push("");
37954
+ }
37955
+ lines.push("## Never Applied", "");
37956
+ if (metrics.neverApplied.length === 0) {
37957
+ lines.push("All entries have been applied or are below the phase threshold.", "");
37958
+ } else {
37959
+ lines.push("| Entry | Phases Alive |");
37960
+ lines.push("|-------|-------------|");
37961
+ for (const n of metrics.neverApplied) {
37962
+ lines.push(`| ${truncateLesson(n.lesson)} | ${n.phasesAlive} |`);
37963
+ }
37964
+ lines.push("");
37965
+ }
37966
+ lines.push("## Time to Latest Application", "");
37967
+ const applied = metrics.timeToLatestApplication.filter((t) => t.daysToApply !== null).map((t) => t.daysToApply);
37968
+ if (applied.length === 0) {
37969
+ lines.push("No application timing data available.", "");
37970
+ } else {
37971
+ applied.sort((a, b) => a - b);
37972
+ const median = applied.length % 2 === 0 ? (applied[applied.length / 2 - 1] + applied[applied.length / 2]) / 2 : applied[Math.floor(applied.length / 2)];
37973
+ const min = applied[0];
37974
+ const max = applied[applied.length - 1];
37975
+ lines.push(`- Median: ${median.toFixed(1)} days`);
37976
+ lines.push(`- Min: ${min.toFixed(1)} days`);
37977
+ lines.push(`- Max: ${max.toFixed(1)} days`);
37978
+ lines.push(`- Entries with data: ${applied.length}`);
37979
+ lines.push("");
37980
+ }
37981
+ return lines.join(`
37982
+ `);
37983
+ }
37984
+ function formatLearningJSON(metrics) {
37985
+ return metrics;
37986
+ }
37987
+ var SEVEN_DAYS_MS, THIRTY_DAYS_MS, MS_PER_DAY, DEFAULT_PHASES_ALIVE_THRESHOLD = 3, MAX_LESSON_DISPLAY_CHARS = 60;
37988
+ var init_learning_metrics = __esm(() => {
37989
+ init_knowledge_escalator();
37990
+ init_knowledge_events();
37991
+ init_knowledge_store();
37992
+ SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
37993
+ THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
37994
+ MS_PER_DAY = 24 * 60 * 60 * 1000;
37343
37995
  });
37344
37996
 
37345
37997
  // src/hooks/knowledge-validator.ts
37346
- import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
37347
- import * as path14 from "path";
37998
+ import { appendFile as appendFile4, mkdir as mkdir4 } from "fs/promises";
37999
+ import * as path15 from "path";
37348
38000
  function normalizeText(text) {
37349
38001
  return text.normalize("NFKC").toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
37350
38002
  }
@@ -37497,7 +38149,7 @@ function validateSkillPath(p) {
37497
38149
  return false;
37498
38150
  if (p.includes("\x00"))
37499
38151
  return false;
37500
- if (path14.isAbsolute(p))
38152
+ if (path15.isAbsolute(p))
37501
38153
  return false;
37502
38154
  if (p.includes(".."))
37503
38155
  return false;
@@ -37599,15 +38251,15 @@ function validateActionability(entry) {
37599
38251
  return { actionable: false, reason };
37600
38252
  }
37601
38253
  function resolveUnactionablePath(directory) {
37602
- return path14.join(directory, ".swarm", "knowledge-unactionable.jsonl");
38254
+ return path15.join(directory, ".swarm", "knowledge-unactionable.jsonl");
37603
38255
  }
37604
38256
  async function appendUnactionable(directory, entry, reason) {
37605
38257
  const filePath = resolveUnactionablePath(directory);
37606
- const dirPath = path14.dirname(filePath);
37607
- await mkdir3(dirPath, { recursive: true });
38258
+ const dirPath = path15.dirname(filePath);
38259
+ await mkdir4(dirPath, { recursive: true });
37608
38260
  let release = null;
37609
38261
  try {
37610
- release = await import_proper_lockfile4.default.lock(dirPath, {
38262
+ release = await import_proper_lockfile5.default.lock(dirPath, {
37611
38263
  retries: { retries: 50, minTimeout: 10, maxTimeout: 100 },
37612
38264
  stale: 5000
37613
38265
  });
@@ -37617,7 +38269,7 @@ async function appendUnactionable(directory, entry, reason) {
37617
38269
  unactionable_reason: reason,
37618
38270
  quarantined_at: new Date().toISOString()
37619
38271
  };
37620
- await appendFile3(filePath, `${JSON.stringify(record3)}
38272
+ await appendFile4(filePath, `${JSON.stringify(record3)}
37621
38273
  `, "utf-8");
37622
38274
  const existing = await readKnowledge(filePath);
37623
38275
  if (existing.length > 200) {
@@ -37646,14 +38298,14 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
37646
38298
  return;
37647
38299
  }
37648
38300
  const sanitizedReason = reason.slice(0, 500).replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\x0d]/g, "");
37649
- const knowledgePath = path14.join(directory, ".swarm", "knowledge.jsonl");
37650
- const quarantinePath = path14.join(directory, ".swarm", "knowledge-quarantined.jsonl");
37651
- const rejectedPath = path14.join(directory, ".swarm", "knowledge-rejected.jsonl");
37652
- const swarmDir = path14.join(directory, ".swarm");
37653
- await mkdir3(swarmDir, { recursive: true });
38301
+ const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
38302
+ const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
38303
+ const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
38304
+ const swarmDir = path15.join(directory, ".swarm");
38305
+ await mkdir4(swarmDir, { recursive: true });
37654
38306
  let release;
37655
38307
  try {
37656
- release = await import_proper_lockfile4.default.lock(swarmDir, {
38308
+ release = await import_proper_lockfile5.default.lock(swarmDir, {
37657
38309
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37658
38310
  stale: 5000
37659
38311
  });
@@ -37675,7 +38327,7 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
37675
38327
  `)}
37676
38328
  ` : "";
37677
38329
  await atomicWriteFile(knowledgePath, jsonlContent);
37678
- await appendFile3(quarantinePath, `${JSON.stringify(quarantined)}
38330
+ await appendFile4(quarantinePath, `${JSON.stringify(quarantined)}
37679
38331
  `, "utf-8");
37680
38332
  const quarantinedEntries = await readKnowledge(quarantinePath);
37681
38333
  if (quarantinedEntries.length > 100) {
@@ -37692,7 +38344,7 @@ async function quarantineEntry(directory, entryId, reason, reportedBy) {
37692
38344
  rejected_at: new Date().toISOString(),
37693
38345
  rejection_layer: 3
37694
38346
  };
37695
- await appendFile3(rejectedPath, `${JSON.stringify(rejectedRecord)}
38347
+ await appendFile4(rejectedPath, `${JSON.stringify(rejectedRecord)}
37696
38348
  `, "utf-8");
37697
38349
  } finally {
37698
38350
  if (release) {
@@ -37710,14 +38362,14 @@ async function restoreEntry(directory, entryId) {
37710
38362
  warn("[knowledge-validator] restoreEntry: invalid entryId rejected");
37711
38363
  return;
37712
38364
  }
37713
- const knowledgePath = path14.join(directory, ".swarm", "knowledge.jsonl");
37714
- const quarantinePath = path14.join(directory, ".swarm", "knowledge-quarantined.jsonl");
37715
- const rejectedPath = path14.join(directory, ".swarm", "knowledge-rejected.jsonl");
37716
- const swarmDir = path14.join(directory, ".swarm");
37717
- await mkdir3(swarmDir, { recursive: true });
38365
+ const knowledgePath = path15.join(directory, ".swarm", "knowledge.jsonl");
38366
+ const quarantinePath = path15.join(directory, ".swarm", "knowledge-quarantined.jsonl");
38367
+ const rejectedPath = path15.join(directory, ".swarm", "knowledge-rejected.jsonl");
38368
+ const swarmDir = path15.join(directory, ".swarm");
38369
+ await mkdir4(swarmDir, { recursive: true });
37718
38370
  let release;
37719
38371
  try {
37720
- release = await import_proper_lockfile4.default.lock(swarmDir, {
38372
+ release = await import_proper_lockfile5.default.lock(swarmDir, {
37721
38373
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
37722
38374
  stale: 5000
37723
38375
  });
@@ -37749,7 +38401,7 @@ async function restoreEntry(directory, entryId) {
37749
38401
  `)}
37750
38402
  ` : "";
37751
38403
  await atomicWriteFile(quarantinePath, jsonlContent);
37752
- await appendFile3(knowledgePath, `${JSON.stringify(original)}
38404
+ await appendFile4(knowledgePath, `${JSON.stringify(original)}
37753
38405
  `, "utf-8");
37754
38406
  const rejectedEntries = await readKnowledge(rejectedPath);
37755
38407
  const filtered = rejectedEntries.filter((e) => e.id !== entryId);
@@ -37763,12 +38415,12 @@ async function restoreEntry(directory, entryId) {
37763
38415
  }
37764
38416
  }
37765
38417
  }
37766
- var import_proper_lockfile4, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, VALID_CATEGORIES, TECH_REFERENCE_WORDS, ACTION_VERB_WORDS, NEGATION_PAIRS, ACTIONABLE_STRING_MAX = 200, ACTIONABLE_LIST_MAX = 20, NAME_PATTERN, SOURCE_REF_FORBIDDEN, ALLOWED_SKILL_PATH_PREFIXES, VALID_DIRECTIVE_PRIORITIES;
38418
+ var import_proper_lockfile5, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, VALID_CATEGORIES, TECH_REFERENCE_WORDS, ACTION_VERB_WORDS, NEGATION_PAIRS, ACTIONABLE_STRING_MAX = 200, ACTIONABLE_LIST_MAX = 20, NAME_PATTERN, SOURCE_REF_FORBIDDEN, ALLOWED_SKILL_PATH_PREFIXES, VALID_DIRECTIVE_PRIORITIES;
37767
38419
  var init_knowledge_validator = __esm(() => {
37768
38420
  init_task_file();
37769
38421
  init_logger();
37770
38422
  init_knowledge_store();
37771
- import_proper_lockfile4 = __toESM(require_proper_lockfile(), 1);
38423
+ import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
37772
38424
  DANGEROUS_COMMAND_PATTERNS = [
37773
38425
  /\brm\s+-rf\b/,
37774
38426
  /\bsudo\s+rm\b/,
@@ -37885,10 +38537,15 @@ var init_knowledge_validator = __esm(() => {
37885
38537
  ]);
37886
38538
  });
37887
38539
 
38540
+ // src/services/skill-changelog.ts
38541
+ var init_skill_changelog = __esm(() => {
38542
+ init_logger();
38543
+ });
38544
+
37888
38545
  // src/services/skill-generator.ts
37889
- import { existsSync as existsSync10, unlinkSync as unlinkSync5 } from "fs";
37890
- import { mkdir as mkdir4, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
37891
- import * as path15 from "path";
38546
+ import { existsSync as existsSync11, unlinkSync as unlinkSync5 } from "fs";
38547
+ import { mkdir as mkdir5, readFile as readFile4, rename as rename3, writeFile as writeFile4 } from "fs/promises";
38548
+ import * as path16 from "path";
37892
38549
  function sanitizeSlug(input) {
37893
38550
  const lc = input.toLowerCase().trim();
37894
38551
  const mapped = lc.replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-");
@@ -37899,10 +38556,10 @@ function isValidSlug(slug) {
37899
38556
  return SLUG_PATTERN.test(slug);
37900
38557
  }
37901
38558
  function proposalPath(directory, slug) {
37902
- return path15.join(directory, ".swarm", "skills", "proposals", `${slug}.md`);
38559
+ return path16.join(directory, ".swarm", "skills", "proposals", `${slug}.md`);
37903
38560
  }
37904
38561
  function activePath(directory, slug) {
37905
- return path15.join(directory, ".opencode", "skills", "generated", slug, "SKILL.md");
38562
+ return path16.join(directory, ".opencode", "skills", "generated", slug, "SKILL.md");
37906
38563
  }
37907
38564
  function activeRepoRelativePath(slug) {
37908
38565
  return `.opencode/skills/generated/${slug}/SKILL.md`;
@@ -37910,7 +38567,7 @@ function activeRepoRelativePath(slug) {
37910
38567
  async function selectCandidateEntries(directory, opts) {
37911
38568
  const swarm = await readKnowledge(resolveSwarmKnowledgePath(directory));
37912
38569
  const hivePath = resolveHiveKnowledgePath();
37913
- const hive = existsSync10(hivePath) ? await readKnowledge(hivePath) : [];
38570
+ const hive = existsSync11(hivePath) ? await readKnowledge(hivePath) : [];
37914
38571
  const all = [...swarm, ...hive];
37915
38572
  return all.filter((e) => {
37916
38573
  if (e.status === "archived")
@@ -37993,10 +38650,12 @@ function clusterEntries(entries) {
37993
38650
  function uniqueStrings(arr) {
37994
38651
  return [...new Set(arr.filter((s) => typeof s === "string" && s.length > 0))];
37995
38652
  }
37996
- function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().toISOString()) {
38653
+ function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().toISOString(), overrides) {
37997
38654
  const description = cluster.title.length > 200 ? `${cluster.title.slice(0, 197)}\u2026` : cluster.title;
37998
38655
  const ids = cluster.entries.map((e) => ` - ${e.id}`).join(`
37999
38656
  `);
38657
+ const version3 = overrides?.version ?? 1;
38658
+ const skillOrigin = overrides?.skillOrigin ?? "generated";
38000
38659
  const lines = [];
38001
38660
  lines.push("---");
38002
38661
  lines.push(`name: ${cluster.slug}`);
@@ -38008,6 +38667,8 @@ function renderSkillMarkdown(cluster, mode = "active", generatedAt = new Date().
38008
38667
  lines.push(`generated_at: ${generatedAt}`);
38009
38668
  lines.push(`confidence: ${cluster.avgConfidence.toFixed(2)}`);
38010
38669
  lines.push(`status: ${mode === "active" ? "active" : "draft"}`);
38670
+ lines.push(`version: ${version3}`);
38671
+ lines.push(`skill_origin: ${skillOrigin}`);
38011
38672
  lines.push("---");
38012
38673
  lines.push("");
38013
38674
  lines.push("<!-- generated by opencode-swarm skill-generator. Do not edit by hand; edits will be preserved on regeneration only with controlled update mode. -->");
@@ -38080,9 +38741,9 @@ function escapeMarkdown(s) {
38080
38741
  return s.replace(/[\r\n]+/g, " ").slice(0, 280);
38081
38742
  }
38082
38743
  async function atomicWrite(p, content) {
38083
- await mkdir4(path15.dirname(p), { recursive: true });
38744
+ await mkdir5(path16.dirname(p), { recursive: true });
38084
38745
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
38085
- await writeFile3(tmp, content, "utf-8");
38746
+ await writeFile4(tmp, content, "utf-8");
38086
38747
  await rename3(tmp, p);
38087
38748
  }
38088
38749
  async function generateSkills(req) {
@@ -38097,7 +38758,7 @@ async function generateSkills(req) {
38097
38758
  const idSet = new Set(req.sourceKnowledgeIds);
38098
38759
  const swarm = await readKnowledge(resolveSwarmKnowledgePath(req.directory));
38099
38760
  const hivePath = resolveHiveKnowledgePath();
38100
- const hive = existsSync10(hivePath) ? await readKnowledge(hivePath) : [];
38761
+ const hive = existsSync11(hivePath) ? await readKnowledge(hivePath) : [];
38101
38762
  pool = [...swarm, ...hive].filter((e) => idSet.has(e.id) && e.status !== "archived");
38102
38763
  } else {
38103
38764
  pool = candidates;
@@ -38125,7 +38786,7 @@ async function generateSkills(req) {
38125
38786
  continue;
38126
38787
  }
38127
38788
  const targetPath = req.mode === "active" ? activePath(req.directory, cluster.slug) : proposalPath(req.directory, cluster.slug);
38128
- const repoRel = path15.relative(req.directory, targetPath).replace(/\\/g, "/");
38789
+ const repoRel = path16.relative(req.directory, targetPath).replace(/\\/g, "/");
38129
38790
  if (!validateSkillPath(repoRel)) {
38130
38791
  result.skipped.push({
38131
38792
  slug: cluster.slug,
@@ -38134,8 +38795,8 @@ async function generateSkills(req) {
38134
38795
  continue;
38135
38796
  }
38136
38797
  let preserved = false;
38137
- if (req.mode === "active" && existsSync10(targetPath) && !req.force) {
38138
- const existing = await readFile3(targetPath, "utf-8");
38798
+ if (req.mode === "active" && existsSync11(targetPath) && !req.force) {
38799
+ const existing = await readFile4(targetPath, "utf-8");
38139
38800
  if (!existing.includes("generated by opencode-swarm skill-generator")) {
38140
38801
  preserved = true;
38141
38802
  result.skipped.push({
@@ -38179,7 +38840,7 @@ async function stampSourceEntries(directory, slug, ids) {
38179
38840
  if (touched)
38180
38841
  await rewriteKnowledge(swarmPath, swarm);
38181
38842
  const hivePath = resolveHiveKnowledgePath();
38182
- if (!existsSync10(hivePath))
38843
+ if (!existsSync11(hivePath))
38183
38844
  return;
38184
38845
  const hive = await readKnowledge(hivePath);
38185
38846
  let touchedHive = false;
@@ -38239,6 +38900,16 @@ function parseDraftFrontmatter(content) {
38239
38900
  out.generatedAt = ga[1];
38240
38901
  continue;
38241
38902
  }
38903
+ const vm = line.match(/^version:\s*(\d+)\s*$/);
38904
+ if (vm) {
38905
+ out.version = parseInt(vm[1], 10);
38906
+ continue;
38907
+ }
38908
+ const so = line.match(/^skill_origin:\s*(\S+)\s*$/);
38909
+ if (so) {
38910
+ out.skillOrigin = so[1];
38911
+ continue;
38912
+ }
38242
38913
  if (/^generated_from_knowledge:\s*$/.test(line)) {
38243
38914
  inLegacyIdsList = true;
38244
38915
  continue;
@@ -38255,10 +38926,10 @@ async function listSkills(directory) {
38255
38926
  proposals: [],
38256
38927
  active: []
38257
38928
  };
38258
- const proposalsDir = path15.join(directory, ".swarm", "skills", "proposals");
38259
- const activeDir = path15.join(directory, ".opencode", "skills", "generated");
38929
+ const proposalsDir = path16.join(directory, ".swarm", "skills", "proposals");
38930
+ const activeDir = path16.join(directory, ".opencode", "skills", "generated");
38260
38931
  const fs9 = await import("fs/promises");
38261
- if (existsSync10(proposalsDir)) {
38932
+ if (existsSync11(proposalsDir)) {
38262
38933
  const entries = await fs9.readdir(proposalsDir);
38263
38934
  for (const f of entries) {
38264
38935
  if (!f.endsWith(".md"))
@@ -38266,20 +38937,20 @@ async function listSkills(directory) {
38266
38937
  const slug = f.replace(/\.md$/, "");
38267
38938
  result.proposals.push({
38268
38939
  slug,
38269
- path: path15.join(proposalsDir, f)
38940
+ path: path16.join(proposalsDir, f)
38270
38941
  });
38271
38942
  }
38272
38943
  }
38273
- if (existsSync10(activeDir)) {
38944
+ if (existsSync11(activeDir)) {
38274
38945
  const entries = await fs9.readdir(activeDir, { withFileTypes: true });
38275
38946
  for (const e of entries) {
38276
38947
  if (!e.isDirectory())
38277
38948
  continue;
38278
- const retiredMarker = path15.join(activeDir, e.name, "retired.marker");
38279
- if (existsSync10(retiredMarker))
38949
+ const retiredMarker = path16.join(activeDir, e.name, "retired.marker");
38950
+ if (existsSync11(retiredMarker))
38280
38951
  continue;
38281
- const skillPath = path15.join(activeDir, e.name, "SKILL.md");
38282
- if (existsSync10(skillPath)) {
38952
+ const skillPath = path16.join(activeDir, e.name, "SKILL.md");
38953
+ if (existsSync11(skillPath)) {
38283
38954
  result.active.push({
38284
38955
  slug: e.name,
38285
38956
  path: skillPath
@@ -38294,13 +38965,155 @@ var init_skill_generator = __esm(() => {
38294
38965
  init_knowledge_store();
38295
38966
  init_knowledge_validator();
38296
38967
  init_logger();
38968
+ init_skill_changelog();
38297
38969
  SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
38298
38970
  });
38299
38971
 
38972
+ // src/services/skill-improver-quota.ts
38973
+ import { existsSync as existsSync12 } from "fs";
38974
+ import { mkdir as mkdir6, readFile as readFile5, rename as rename4, writeFile as writeFile5 } from "fs/promises";
38975
+ import * as path17 from "path";
38976
+ async function acquireLock(dir) {
38977
+ const acquire = import_proper_lockfile6.default.lock(dir, LOCK_RETRY_OPTS);
38978
+ let timer;
38979
+ const timeout = new Promise((_, reject) => {
38980
+ timer = setTimeout(() => {
38981
+ reject(new Error(`SKILL_IMPROVER_QUOTA_LOCK_TIMEOUT: failed to acquire lock on ${dir} within ${LOCK_ACQUIRE_TIMEOUT_MS}ms`));
38982
+ }, LOCK_ACQUIRE_TIMEOUT_MS);
38983
+ });
38984
+ try {
38985
+ const release = await Promise.race([acquire, timeout]);
38986
+ return release;
38987
+ } finally {
38988
+ if (timer)
38989
+ clearTimeout(timer);
38990
+ }
38991
+ }
38992
+ function resolveQuotaPath(directory) {
38993
+ return path17.join(directory, ".swarm", "skill-improver-quota.json");
38994
+ }
38995
+ function todayKey(window, now = new Date) {
38996
+ if (window === "utc") {
38997
+ return now.toISOString().slice(0, 10);
38998
+ }
38999
+ const yr = now.getFullYear();
39000
+ const m = String(now.getMonth() + 1).padStart(2, "0");
39001
+ const d = String(now.getDate()).padStart(2, "0");
39002
+ return `${yr}-${m}-${d}`;
39003
+ }
39004
+ async function readState(filePath) {
39005
+ if (!existsSync12(filePath))
39006
+ return null;
39007
+ try {
39008
+ const raw = await readFile5(filePath, "utf-8");
39009
+ const parsed = JSON.parse(raw);
39010
+ if (typeof parsed.date !== "string" || typeof parsed.calls_used !== "number" || typeof parsed.max_calls !== "number" || parsed.window !== "utc" && parsed.window !== "local") {
39011
+ return null;
39012
+ }
39013
+ return parsed;
39014
+ } catch {
39015
+ return null;
39016
+ }
39017
+ }
39018
+ async function writeState(filePath, state) {
39019
+ await mkdir6(path17.dirname(filePath), { recursive: true });
39020
+ const tmp = `${filePath}.tmp-${process.pid}`;
39021
+ await writeFile5(tmp, JSON.stringify(state, null, 2), "utf-8");
39022
+ await rename4(tmp, filePath);
39023
+ }
39024
+ async function getQuotaState(directory, opts) {
39025
+ const filePath = resolveQuotaPath(directory);
39026
+ const today = todayKey(opts.window, opts.now);
39027
+ const existing = await readState(filePath);
39028
+ if (!existing || existing.date !== today || existing.window !== opts.window) {
39029
+ const fresh = {
39030
+ date: today,
39031
+ calls_used: 0,
39032
+ max_calls: opts.maxCalls,
39033
+ window: opts.window
39034
+ };
39035
+ await writeState(filePath, fresh);
39036
+ return fresh;
39037
+ }
39038
+ return { ...existing, max_calls: opts.maxCalls };
39039
+ }
39040
+ async function reserveQuota(directory, opts) {
39041
+ const filePath = resolveQuotaPath(directory);
39042
+ await mkdir6(path17.dirname(filePath), { recursive: true });
39043
+ let release = null;
39044
+ try {
39045
+ release = await acquireLock(path17.dirname(filePath));
39046
+ const state = await getQuotaState(directory, opts);
39047
+ if (state.calls_used + opts.nCalls > opts.maxCalls) {
39048
+ return {
39049
+ allowed: false,
39050
+ state,
39051
+ reason: `daily quota exhausted: used=${state.calls_used} requested=${opts.nCalls} max=${opts.maxCalls}`
39052
+ };
39053
+ }
39054
+ const next = {
39055
+ ...state,
39056
+ calls_used: state.calls_used + opts.nCalls,
39057
+ max_calls: opts.maxCalls,
39058
+ last_run_at: (opts.now ?? new Date).toISOString()
39059
+ };
39060
+ await writeState(filePath, next);
39061
+ return { allowed: true, state: next };
39062
+ } finally {
39063
+ if (release) {
39064
+ try {
39065
+ await release();
39066
+ } catch {}
39067
+ }
39068
+ }
39069
+ }
39070
+ async function releaseQuota(directory, opts) {
39071
+ const filePath = resolveQuotaPath(directory);
39072
+ await mkdir6(path17.dirname(filePath), { recursive: true });
39073
+ let release = null;
39074
+ try {
39075
+ release = await acquireLock(path17.dirname(filePath));
39076
+ const state = await getQuotaState(directory, opts);
39077
+ const next = {
39078
+ ...state,
39079
+ calls_used: Math.max(0, state.calls_used - opts.nCalls),
39080
+ max_calls: opts.maxCalls
39081
+ };
39082
+ await writeState(filePath, next);
39083
+ return next;
39084
+ } finally {
39085
+ if (release) {
39086
+ try {
39087
+ await release();
39088
+ } catch {}
39089
+ }
39090
+ }
39091
+ }
39092
+ var import_proper_lockfile6, LOCK_ACQUIRE_TIMEOUT_MS = 1e4, LOCK_RETRY_OPTS;
39093
+ var init_skill_improver_quota = __esm(() => {
39094
+ import_proper_lockfile6 = __toESM(require_proper_lockfile(), 1);
39095
+ LOCK_RETRY_OPTS = {
39096
+ retries: {
39097
+ retries: 30,
39098
+ minTimeout: 50,
39099
+ maxTimeout: 200,
39100
+ factor: 1.5
39101
+ },
39102
+ stale: 5000
39103
+ };
39104
+ });
39105
+
39106
+ // src/services/skill-reviser.ts
39107
+ var init_skill_reviser = __esm(() => {
39108
+ init_logger();
39109
+ init_skill_changelog();
39110
+ init_skill_improver_quota();
39111
+ });
39112
+
38300
39113
  // src/hooks/skill-usage-log.ts
38301
39114
  import * as crypto3 from "crypto";
38302
39115
  import * as fs9 from "fs";
38303
- import * as path16 from "path";
39116
+ import * as path18 from "path";
38304
39117
  function resolveLogPath(directory) {
38305
39118
  return validateSwarmPath(directory, "skill-usage.jsonl");
38306
39119
  }
@@ -38312,7 +39125,8 @@ function appendSkillUsageEntry(directory, entry) {
38312
39125
  timestamp,
38313
39126
  complianceVerdict,
38314
39127
  sessionID,
38315
- reviewerNotes
39128
+ reviewerNotes,
39129
+ skillVersion
38316
39130
  } = entry;
38317
39131
  if (!skillPath || typeof skillPath !== "string") {
38318
39132
  throw new Error("skillPath is required and must be a non-empty string");
@@ -38336,7 +39150,7 @@ function appendSkillUsageEntry(directory, entry) {
38336
39150
  throw new Error("sessionID is required and must be a non-empty string");
38337
39151
  }
38338
39152
  const resolved = validateSwarmPath(directory, "skill-usage.jsonl");
38339
- const dir = path16.dirname(resolved);
39153
+ const dir = path18.dirname(resolved);
38340
39154
  if (!_internals12.existsSync(dir)) {
38341
39155
  _internals12.mkdirSync(dir, { recursive: true });
38342
39156
  }
@@ -38348,7 +39162,8 @@ function appendSkillUsageEntry(directory, entry) {
38348
39162
  timestamp,
38349
39163
  complianceVerdict,
38350
39164
  sessionID,
38351
- ...reviewerNotes !== undefined && { reviewerNotes }
39165
+ ...reviewerNotes !== undefined && { reviewerNotes },
39166
+ ...skillVersion !== undefined && { skillVersion }
38352
39167
  };
38353
39168
  _internals12.appendFileSync(resolved, `${JSON.stringify(fullEntry)}
38354
39169
  `, "utf-8");
@@ -38445,6 +39260,34 @@ function readSkillUsageEntriesTail(directory, filters, maxBytes = TAIL_BYTES_DEF
38445
39260
  return [];
38446
39261
  }
38447
39262
  }
39263
+ function computeComplianceByVersion(entries, skillPath) {
39264
+ const map3 = new Map;
39265
+ const normalizedTarget = skillPath.replace(/^file:/, "").replace(/\\/g, "/");
39266
+ for (const e of entries) {
39267
+ let p = e.skillPath;
39268
+ if (p.startsWith("file:"))
39269
+ p = p.slice(5);
39270
+ const normalized = p.replace(/\\/g, "/");
39271
+ if (normalized !== normalizedTarget && !normalizedTarget.endsWith(`/${normalized}`) && !normalized.endsWith(`/${normalizedTarget}`)) {
39272
+ continue;
39273
+ }
39274
+ const version3 = e.skillVersion;
39275
+ let stats = map3.get(version3);
39276
+ if (!stats) {
39277
+ stats = { compliant: 0, violation: 0, total: 0, rate: 0 };
39278
+ map3.set(version3, stats);
39279
+ }
39280
+ stats.total += 1;
39281
+ if (e.complianceVerdict === "compliant")
39282
+ stats.compliant += 1;
39283
+ if (e.complianceVerdict === "violation")
39284
+ stats.violation += 1;
39285
+ }
39286
+ for (const stats of map3.values()) {
39287
+ stats.rate = stats.total === 0 ? 0 : stats.compliant / stats.total;
39288
+ }
39289
+ return map3;
39290
+ }
38448
39291
  function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
38449
39292
  const resolved = resolveLogPath(directory);
38450
39293
  if (!_internals12.existsSync(resolved)) {
@@ -38477,8 +39320,8 @@ function pruneSkillUsageLog(directory, maxEntriesPerSkill = 500) {
38477
39320
  if (pruned === 0) {
38478
39321
  return { pruned: 0, remaining: allEntries.length };
38479
39322
  }
38480
- const dir = path16.dirname(resolved);
38481
- const tmpPath = path16.join(dir, `skill-usage-${Date.now()}.tmp`);
39323
+ const dir = path18.dirname(resolved);
39324
+ const tmpPath = path18.join(dir, `skill-usage-${Date.now()}.tmp`);
38482
39325
  const content = surviving.map((e) => JSON.stringify(e)).join(`
38483
39326
  `).concat(`
38484
39327
  `);
@@ -38505,9 +39348,9 @@ async function resolveSourceKnowledgeIds(directory, skillPath) {
38505
39348
  if (/\.\.[/\\]/.test(cleanPath)) {
38506
39349
  return [];
38507
39350
  }
38508
- const absolute = path16.normalize(path16.isAbsolute(cleanPath) ? cleanPath : path16.resolve(directory, cleanPath));
38509
- const baseDir = path16.normalize(path16.resolve(directory));
38510
- const isContained = process.platform === "win32" ? absolute.toLowerCase().startsWith((baseDir + path16.sep).toLowerCase()) : absolute.startsWith(baseDir + path16.sep);
39351
+ const absolute = path18.normalize(path18.isAbsolute(cleanPath) ? cleanPath : path18.resolve(directory, cleanPath));
39352
+ const baseDir = path18.normalize(path18.resolve(directory));
39353
+ const isContained = process.platform === "win32" ? absolute.toLowerCase().startsWith((baseDir + path18.sep).toLowerCase()) : absolute.startsWith(baseDir + path18.sep);
38511
39354
  if (!isContained) {
38512
39355
  return [];
38513
39356
  }
@@ -38626,7 +39469,8 @@ var init_skill_usage_log = __esm(() => {
38626
39469
  pruneSkillUsageLog,
38627
39470
  resolveSourceKnowledgeIds,
38628
39471
  applySkillUsageFeedback,
38629
- parseGeneratedFromKnowledge
39472
+ parseGeneratedFromKnowledge,
39473
+ computeComplianceByVersion
38630
39474
  };
38631
39475
  TAIL_BYTES_DEFAULT = 64 * 1024;
38632
39476
  MAX_TAIL_BYTES = TAIL_BYTES_DEFAULT;
@@ -38638,7 +39482,9 @@ var init_curator = __esm(() => {
38638
39482
  init_event_bus();
38639
39483
  init_schema();
38640
39484
  init_manager();
39485
+ init_learning_metrics();
38641
39486
  init_skill_generator();
39487
+ init_skill_reviser();
38642
39488
  init_state();
38643
39489
  init_bun_compat();
38644
39490
  init_logger();
@@ -38649,7 +39495,7 @@ var init_curator = __esm(() => {
38649
39495
  });
38650
39496
 
38651
39497
  // src/hooks/hive-promoter.ts
38652
- import path17 from "path";
39498
+ import path19 from "path";
38653
39499
  function carryActionableFields(source) {
38654
39500
  const out = {};
38655
39501
  if (source.triggers?.length)
@@ -38845,7 +39691,7 @@ async function promoteToHive(directory, lesson, category) {
38845
39691
  schema_version: 1,
38846
39692
  created_at: new Date().toISOString(),
38847
39693
  updated_at: new Date().toISOString(),
38848
- source_project: path17.basename(directory) || "unknown",
39694
+ source_project: path19.basename(directory) || "unknown",
38849
39695
  encounter_score: 1
38850
39696
  };
38851
39697
  await appendKnowledge(resolveHiveKnowledgePath(), newHiveEntry);
@@ -38901,150 +39747,16 @@ var init_hive_promoter = __esm(() => {
38901
39747
  init_utils2();
38902
39748
  });
38903
39749
 
38904
- // src/services/skill-improver-quota.ts
38905
- import { existsSync as existsSync12 } from "fs";
38906
- import { mkdir as mkdir5, readFile as readFile4, rename as rename4, writeFile as writeFile4 } from "fs/promises";
38907
- import * as path18 from "path";
38908
- async function acquireLock(dir) {
38909
- const acquire = import_proper_lockfile5.default.lock(dir, LOCK_RETRY_OPTS);
38910
- let timer;
38911
- const timeout = new Promise((_, reject) => {
38912
- timer = setTimeout(() => {
38913
- reject(new Error(`SKILL_IMPROVER_QUOTA_LOCK_TIMEOUT: failed to acquire lock on ${dir} within ${LOCK_ACQUIRE_TIMEOUT_MS}ms`));
38914
- }, LOCK_ACQUIRE_TIMEOUT_MS);
38915
- });
38916
- try {
38917
- const release = await Promise.race([acquire, timeout]);
38918
- return release;
38919
- } finally {
38920
- if (timer)
38921
- clearTimeout(timer);
38922
- }
38923
- }
38924
- function resolveQuotaPath(directory) {
38925
- return path18.join(directory, ".swarm", "skill-improver-quota.json");
38926
- }
38927
- function todayKey(window, now = new Date) {
38928
- if (window === "utc") {
38929
- return now.toISOString().slice(0, 10);
38930
- }
38931
- const yr = now.getFullYear();
38932
- const m = String(now.getMonth() + 1).padStart(2, "0");
38933
- const d = String(now.getDate()).padStart(2, "0");
38934
- return `${yr}-${m}-${d}`;
38935
- }
38936
- async function readState(filePath) {
38937
- if (!existsSync12(filePath))
38938
- return null;
38939
- try {
38940
- const raw = await readFile4(filePath, "utf-8");
38941
- const parsed = JSON.parse(raw);
38942
- if (typeof parsed.date !== "string" || typeof parsed.calls_used !== "number" || typeof parsed.max_calls !== "number" || parsed.window !== "utc" && parsed.window !== "local") {
38943
- return null;
38944
- }
38945
- return parsed;
38946
- } catch {
38947
- return null;
38948
- }
38949
- }
38950
- async function writeState(filePath, state) {
38951
- await mkdir5(path18.dirname(filePath), { recursive: true });
38952
- const tmp = `${filePath}.tmp-${process.pid}`;
38953
- await writeFile4(tmp, JSON.stringify(state, null, 2), "utf-8");
38954
- await rename4(tmp, filePath);
38955
- }
38956
- async function getQuotaState(directory, opts) {
38957
- const filePath = resolveQuotaPath(directory);
38958
- const today = todayKey(opts.window, opts.now);
38959
- const existing = await readState(filePath);
38960
- if (!existing || existing.date !== today || existing.window !== opts.window) {
38961
- const fresh = {
38962
- date: today,
38963
- calls_used: 0,
38964
- max_calls: opts.maxCalls,
38965
- window: opts.window
38966
- };
38967
- await writeState(filePath, fresh);
38968
- return fresh;
38969
- }
38970
- return { ...existing, max_calls: opts.maxCalls };
38971
- }
38972
- async function reserveQuota(directory, opts) {
38973
- const filePath = resolveQuotaPath(directory);
38974
- await mkdir5(path18.dirname(filePath), { recursive: true });
38975
- let release = null;
38976
- try {
38977
- release = await acquireLock(path18.dirname(filePath));
38978
- const state = await getQuotaState(directory, opts);
38979
- if (state.calls_used + opts.nCalls > opts.maxCalls) {
38980
- return {
38981
- allowed: false,
38982
- state,
38983
- reason: `daily quota exhausted: used=${state.calls_used} requested=${opts.nCalls} max=${opts.maxCalls}`
38984
- };
38985
- }
38986
- const next = {
38987
- ...state,
38988
- calls_used: state.calls_used + opts.nCalls,
38989
- max_calls: opts.maxCalls,
38990
- last_run_at: (opts.now ?? new Date).toISOString()
38991
- };
38992
- await writeState(filePath, next);
38993
- return { allowed: true, state: next };
38994
- } finally {
38995
- if (release) {
38996
- try {
38997
- await release();
38998
- } catch {}
38999
- }
39000
- }
39001
- }
39002
- async function releaseQuota(directory, opts) {
39003
- const filePath = resolveQuotaPath(directory);
39004
- await mkdir5(path18.dirname(filePath), { recursive: true });
39005
- let release = null;
39006
- try {
39007
- release = await acquireLock(path18.dirname(filePath));
39008
- const state = await getQuotaState(directory, opts);
39009
- const next = {
39010
- ...state,
39011
- calls_used: Math.max(0, state.calls_used - opts.nCalls),
39012
- max_calls: opts.maxCalls
39013
- };
39014
- await writeState(filePath, next);
39015
- return next;
39016
- } finally {
39017
- if (release) {
39018
- try {
39019
- await release();
39020
- } catch {}
39021
- }
39022
- }
39023
- }
39024
- var import_proper_lockfile5, LOCK_ACQUIRE_TIMEOUT_MS = 1e4, LOCK_RETRY_OPTS;
39025
- var init_skill_improver_quota = __esm(() => {
39026
- import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
39027
- LOCK_RETRY_OPTS = {
39028
- retries: {
39029
- retries: 30,
39030
- minTimeout: 50,
39031
- maxTimeout: 200,
39032
- factor: 1.5
39033
- },
39034
- stale: 5000
39035
- };
39036
- });
39037
-
39038
39750
  // src/services/synonym-map.ts
39039
39751
  import {
39040
- mkdir as mkdir6,
39041
- readFile as readFile5,
39752
+ mkdir as mkdir7,
39753
+ readFile as readFile6,
39042
39754
  rename as rename5,
39043
39755
  stat as stat2,
39044
39756
  unlink as unlink2,
39045
- writeFile as writeFile5
39757
+ writeFile as writeFile6
39046
39758
  } from "fs/promises";
39047
- import * as path19 from "path";
39759
+ import * as path20 from "path";
39048
39760
  function emptySynonymMap() {
39049
39761
  return { version: 1, cursor: 0, pairs: {} };
39050
39762
  }
@@ -39186,17 +39898,17 @@ async function readSynonymMap(directory, maxPairs = DEFAULT_MAX_PAIRS) {
39186
39898
  const st = await stat2(filePath);
39187
39899
  if (st.size > ceiling)
39188
39900
  return emptySynonymMap();
39189
- const raw = await readFile5(filePath, "utf-8");
39901
+ const raw = await readFile6(filePath, "utf-8");
39190
39902
  return coerceSynonymMap(JSON.parse(raw), maxPairs);
39191
39903
  } catch {
39192
39904
  return emptySynonymMap();
39193
39905
  }
39194
39906
  }
39195
39907
  async function writeSynonymMapAtomic(filePath, map3) {
39196
- await mkdir6(path19.dirname(filePath), { recursive: true });
39908
+ await mkdir7(path20.dirname(filePath), { recursive: true });
39197
39909
  const tmp = `${filePath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
39198
39910
  try {
39199
- await writeFile5(tmp, JSON.stringify(map3, null, 2), "utf-8");
39911
+ await writeFile6(tmp, JSON.stringify(map3, null, 2), "utf-8");
39200
39912
  await rename5(tmp, filePath);
39201
39913
  } finally {
39202
39914
  try {
@@ -39206,11 +39918,11 @@ async function writeSynonymMapAtomic(filePath, map3) {
39206
39918
  }
39207
39919
  async function rebuildSynonymMap(directory, entries, maxPairs = DEFAULT_MAX_PAIRS) {
39208
39920
  const filePath = resolveSynonymMapPath(directory);
39209
- const dir = path19.dirname(filePath);
39210
- await mkdir6(dir, { recursive: true });
39921
+ const dir = path20.dirname(filePath);
39922
+ await mkdir7(dir, { recursive: true });
39211
39923
  let release = null;
39212
39924
  try {
39213
- release = await import_proper_lockfile6.default.lock(dir, {
39925
+ release = await import_proper_lockfile7.default.lock(dir, {
39214
39926
  retries: { retries: 5, minTimeout: 100, maxTimeout: 500 },
39215
39927
  stale: 5000
39216
39928
  });
@@ -39225,217 +39937,14 @@ async function rebuildSynonymMap(directory, entries, maxPairs = DEFAULT_MAX_PAIR
39225
39937
  }
39226
39938
  }
39227
39939
  }
39228
- var import_proper_lockfile6, SYNONYM_MAP_FILENAME = "synonym-map.json", MAX_TOKEN_LENGTH = 64, DEFAULT_MAX_PAIRS = 500, PAIR_SEP, APPROX_BYTES_PER_PAIR = 512, MIN_READ_CEILING_BYTES;
39940
+ var import_proper_lockfile7, SYNONYM_MAP_FILENAME = "synonym-map.json", MAX_TOKEN_LENGTH = 64, DEFAULT_MAX_PAIRS = 500, PAIR_SEP, APPROX_BYTES_PER_PAIR = 512, MIN_READ_CEILING_BYTES;
39229
39941
  var init_synonym_map = __esm(() => {
39230
39942
  init_utils2();
39231
- import_proper_lockfile6 = __toESM(require_proper_lockfile(), 1);
39943
+ import_proper_lockfile7 = __toESM(require_proper_lockfile(), 1);
39232
39944
  PAIR_SEP = String.fromCharCode(0);
39233
39945
  MIN_READ_CEILING_BYTES = 64 * 1024;
39234
39946
  });
39235
39947
 
39236
- // src/hooks/knowledge-events.ts
39237
- import { existsSync as existsSync13 } from "fs";
39238
- import { appendFile as appendFile4, mkdir as mkdir7, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
39239
- import * as path20 from "path";
39240
- function resolveKnowledgeEventsPath(directory) {
39241
- return path20.join(directory, ".swarm", "knowledge-events.jsonl");
39242
- }
39243
- function resolveLegacyApplicationLogPath(directory) {
39244
- return path20.join(directory, ".swarm", "knowledge-application.jsonl");
39245
- }
39246
- async function readKnowledgeEvents(directory) {
39247
- const filePath = resolveKnowledgeEventsPath(directory);
39248
- if (!existsSync13(filePath))
39249
- return [];
39250
- const content = await readFile6(filePath, "utf-8");
39251
- const out = [];
39252
- for (const line of content.split(`
39253
- `)) {
39254
- const trimmed = line.trim();
39255
- if (!trimmed)
39256
- continue;
39257
- try {
39258
- out.push(JSON.parse(trimmed));
39259
- } catch {
39260
- warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
39261
- }
39262
- }
39263
- return out;
39264
- }
39265
- async function readLegacyApplicationRecords(directory) {
39266
- const filePath = resolveLegacyApplicationLogPath(directory);
39267
- if (!existsSync13(filePath))
39268
- return [];
39269
- const content = await readFile6(filePath, "utf-8");
39270
- const out = [];
39271
- for (const line of content.split(`
39272
- `)) {
39273
- const trimmed = line.trim();
39274
- if (!trimmed)
39275
- continue;
39276
- try {
39277
- out.push(JSON.parse(trimmed));
39278
- } catch {
39279
- warn(`[knowledge-events] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
39280
- }
39281
- }
39282
- return out;
39283
- }
39284
- function emptyRollup() {
39285
- return {
39286
- shown_count: 0,
39287
- acknowledged_count: 0,
39288
- applied_explicit_count: 0,
39289
- ignored_count: 0,
39290
- violated_count: 0,
39291
- contradicted_count: 0,
39292
- n_a_count: 0,
39293
- succeeded_after_shown_count: 0,
39294
- failed_after_shown_count: 0,
39295
- partial_after_shown_count: 0,
39296
- violation_timestamps: []
39297
- };
39298
- }
39299
- function get(map3, id) {
39300
- let r = map3.get(id);
39301
- if (!r) {
39302
- r = emptyRollup();
39303
- map3.set(id, r);
39304
- }
39305
- return r;
39306
- }
39307
- function maxIso(current, candidate) {
39308
- if (!current)
39309
- return candidate;
39310
- return candidate > current ? candidate : current;
39311
- }
39312
- function recomputeCounters(events, legacyRecords = []) {
39313
- const map3 = new Map;
39314
- const retrievedIds = new Set;
39315
- for (const e of events) {
39316
- switch (e.type) {
39317
- case "retrieved": {
39318
- for (const id of e.result_ids) {
39319
- retrievedIds.add(id);
39320
- get(map3, id).shown_count += 1;
39321
- }
39322
- break;
39323
- }
39324
- case "acknowledged": {
39325
- const r = get(map3, e.knowledge_id);
39326
- r.acknowledged_count += 1;
39327
- r.last_acknowledged_at = maxIso(r.last_acknowledged_at, e.timestamp);
39328
- break;
39329
- }
39330
- case "applied": {
39331
- const r = get(map3, e.knowledge_id);
39332
- r.applied_explicit_count += 1;
39333
- r.last_applied_at = maxIso(r.last_applied_at, e.timestamp);
39334
- break;
39335
- }
39336
- case "ignored":
39337
- get(map3, e.knowledge_id).ignored_count += 1;
39338
- break;
39339
- case "violated": {
39340
- const r = get(map3, e.knowledge_id);
39341
- r.violated_count += 1;
39342
- r.violation_timestamps.push(e.timestamp);
39343
- break;
39344
- }
39345
- case "contradicted":
39346
- get(map3, e.knowledge_id).contradicted_count += 1;
39347
- break;
39348
- case "n_a":
39349
- get(map3, e.knowledge_id).n_a_count += 1;
39350
- break;
39351
- case "outcome": {
39352
- if (!e.knowledge_id)
39353
- break;
39354
- const r = get(map3, e.knowledge_id);
39355
- if (e.outcome === "success")
39356
- r.succeeded_after_shown_count += 1;
39357
- else if (e.outcome === "failure")
39358
- r.failed_after_shown_count += 1;
39359
- else if (e.outcome === "partial")
39360
- r.partial_after_shown_count += 1;
39361
- break;
39362
- }
39363
- }
39364
- }
39365
- for (const rec of legacyRecords) {
39366
- const r = get(map3, rec.knowledgeId);
39367
- switch (rec.result) {
39368
- case "shown":
39369
- if (!retrievedIds.has(rec.knowledgeId))
39370
- r.shown_count += 1;
39371
- break;
39372
- case "acknowledged":
39373
- r.acknowledged_count += 1;
39374
- r.last_acknowledged_at = maxIso(r.last_acknowledged_at, rec.timestamp);
39375
- break;
39376
- case "applied":
39377
- r.applied_explicit_count += 1;
39378
- r.last_applied_at = maxIso(r.last_applied_at, rec.timestamp);
39379
- break;
39380
- case "ignored":
39381
- r.ignored_count += 1;
39382
- break;
39383
- case "violated":
39384
- r.violated_count += 1;
39385
- r.violation_timestamps.push(rec.timestamp);
39386
- break;
39387
- }
39388
- }
39389
- for (const r of map3.values()) {
39390
- if (r.violation_timestamps.length > 1) {
39391
- r.violation_timestamps.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
39392
- }
39393
- if (r.violation_timestamps.length > MAX_VIOLATION_TIMESTAMPS) {
39394
- r.violation_timestamps = r.violation_timestamps.slice(0, MAX_VIOLATION_TIMESTAMPS);
39395
- }
39396
- }
39397
- return map3;
39398
- }
39399
- async function readKnowledgeCounterRollups(directory) {
39400
- try {
39401
- const [events, legacyRecords] = await Promise.all([
39402
- readKnowledgeEvents(directory),
39403
- readLegacyApplicationRecords(directory)
39404
- ]);
39405
- return recomputeCounters(events, legacyRecords);
39406
- } catch (err) {
39407
- warn(`[knowledge-events] readKnowledgeCounterRollups failed: ${err instanceof Error ? err.message : String(err)}`);
39408
- return new Map;
39409
- }
39410
- }
39411
- function effectiveRetrievalOutcomes(stored, rollup) {
39412
- const base = stored ?? {
39413
- applied_count: 0,
39414
- succeeded_after_count: 0,
39415
- failed_after_count: 0
39416
- };
39417
- if (!rollup)
39418
- return base;
39419
- return {
39420
- ...base,
39421
- ...rollup
39422
- };
39423
- }
39424
- var import_proper_lockfile7, RECEIPT_EVENT_TYPES, MAX_VIOLATION_TIMESTAMPS = 10;
39425
- var init_knowledge_events = __esm(() => {
39426
- init_logger();
39427
- import_proper_lockfile7 = __toESM(require_proper_lockfile(), 1);
39428
- RECEIPT_EVENT_TYPES = new Set([
39429
- "acknowledged",
39430
- "applied",
39431
- "ignored",
39432
- "contradicted",
39433
- "violated",
39434
- "n_a",
39435
- "override"
39436
- ]);
39437
- });
39438
-
39439
39948
  // src/hooks/skill-scoring.ts
39440
39949
  import * as fs10 from "fs";
39441
39950
  import * as path21 from "path";
@@ -44221,7 +44730,7 @@ async function computeKnowledgeDebug(directory) {
44221
44730
  try {
44222
44731
  const events = await readKnowledgeEvents(directory);
44223
44732
  eventCount = events.length;
44224
- const cutoff = Date.now() - SEVEN_DAYS_MS;
44733
+ const cutoff = Date.now() - SEVEN_DAYS_MS2;
44225
44734
  for (const ev of events) {
44226
44735
  eventsByType[ev.type] = (eventsByType[ev.type] ?? 0) + 1;
44227
44736
  if (ev.type !== "retrieved")
@@ -44331,7 +44840,7 @@ async function checkKnowledgeHealth(directory) {
44331
44840
  }
44332
44841
  return { name: "Knowledge health", status: "\u2705", detail: summary };
44333
44842
  }
44334
- var version3, SEVEN_DAYS_MS, UNACTIONABLE_BACKLOG_WARN = 100, INSIGHT_BACKLOG_WARN = 50;
44843
+ var version3, SEVEN_DAYS_MS2, UNACTIONABLE_BACKLOG_WARN = 100, INSIGHT_BACKLOG_WARN = 50;
44335
44844
  var init_knowledge_diagnostics = __esm(() => {
44336
44845
  init_package();
44337
44846
  init_knowledge_events();
@@ -44341,7 +44850,7 @@ var init_knowledge_diagnostics = __esm(() => {
44341
44850
  init_synonym_map();
44342
44851
  init_version_check();
44343
44852
  ({ version: version3 } = package_default);
44344
- SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
44853
+ SEVEN_DAYS_MS2 = 7 * 24 * 60 * 60 * 1000;
44345
44854
  });
44346
44855
 
44347
44856
  // src/services/diagnose-service.ts
@@ -47958,7 +48467,7 @@ function formatEvidenceEntry(index, entry) {
47958
48467
  type: entry.type,
47959
48468
  verdict: entry.verdict,
47960
48469
  verdictIcon: getVerdictEmoji(entry.verdict),
47961
- agent: entry.agent,
48470
+ agent: entry.agent ?? "",
47962
48471
  summary: entry.summary,
47963
48472
  timestamp: entry.timestamp,
47964
48473
  details
@@ -49439,6 +49948,7 @@ var KNOWLEDGE_SCHEMA_VERSION = 2;
49439
49948
  import { randomUUID as randomUUID3 } from "crypto";
49440
49949
  import { existsSync as existsSync25, readFileSync as readFileSync15 } from "fs";
49441
49950
  import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
49951
+ import * as os8 from "os";
49442
49952
  import * as path37 from "path";
49443
49953
  async function migrateKnowledgeToExternal(_directory, _config) {
49444
49954
  return {
@@ -49550,6 +50060,123 @@ async function migrateContextToKnowledge(directory, config3) {
49550
50060
  entriesTotal: rawEntries.length
49551
50061
  };
49552
50062
  }
50063
+ async function migrateHiveKnowledgeLegacy(config3) {
50064
+ const legacyHivePath = _internals23.resolveLegacyHiveKnowledgePath();
50065
+ const canonicalHivePath = resolveHiveKnowledgePath();
50066
+ const sentinelPath = path37.join(path37.dirname(canonicalHivePath), ".hive-knowledge-migrated");
50067
+ if (existsSync25(sentinelPath)) {
50068
+ return {
50069
+ migrated: false,
50070
+ entriesMigrated: 0,
50071
+ entriesDropped: 0,
50072
+ entriesTotal: 0,
50073
+ skippedReason: "sentinel-exists"
50074
+ };
50075
+ }
50076
+ if (!existsSync25(legacyHivePath)) {
50077
+ return {
50078
+ migrated: false,
50079
+ entriesMigrated: 0,
50080
+ entriesDropped: 0,
50081
+ entriesTotal: 0,
50082
+ skippedReason: "no-context-file"
50083
+ };
50084
+ }
50085
+ const legacyEntries = await readKnowledge(legacyHivePath);
50086
+ if (legacyEntries.length === 0) {
50087
+ await _internals23.writeSentinel(sentinelPath, 0, 0);
50088
+ return {
50089
+ migrated: true,
50090
+ entriesMigrated: 0,
50091
+ entriesDropped: 0,
50092
+ entriesTotal: 0
50093
+ };
50094
+ }
50095
+ const existingHiveEntries = await readKnowledge(canonicalHivePath);
50096
+ let migrated = 0;
50097
+ let dropped = 0;
50098
+ const entryErrors = [];
50099
+ for (const legacyEntry of legacyEntries) {
50100
+ try {
50101
+ const lesson = legacyEntry.lesson;
50102
+ if (!lesson || typeof lesson !== "string" || lesson.length < 15) {
50103
+ dropped++;
50104
+ continue;
50105
+ }
50106
+ const dup = findNearDuplicate(lesson, existingHiveEntries, config3.dedup_threshold ?? 0.6);
50107
+ if (dup) {
50108
+ dropped++;
50109
+ continue;
50110
+ }
50111
+ const category = legacyEntry.category || "process";
50112
+ const validationResult = validateLesson(lesson, existingHiveEntries.map((e) => e.lesson), {
50113
+ category,
50114
+ scope: "global",
50115
+ confidence: 0.3
50116
+ });
50117
+ if (!validationResult.valid) {
50118
+ const errorMsg = `Validation failed for legacy entry: ${validationResult.reason}`;
50119
+ entryErrors.push(errorMsg);
50120
+ warn(`[knowledge-migrator] ${errorMsg}`);
50121
+ dropped++;
50122
+ continue;
50123
+ }
50124
+ const confidence = legacyEntry.confidence ?? 0.8;
50125
+ const scopeTag = legacyEntry.scope_tag || "global";
50126
+ const legacyId = legacyEntry.id;
50127
+ const existingIds = new Set(existingHiveEntries.map((e) => e.id));
50128
+ const resolvedId = legacyId && existingIds.has(legacyId) ? randomUUID3() : legacyId || randomUUID3();
50129
+ if (legacyId && existingIds.has(legacyId)) {
50130
+ warn(`[knowledge-migrator] Legacy entry ID collision for "${legacyId}", generating new UUID`);
50131
+ }
50132
+ const newHiveEntry = {
50133
+ id: resolvedId,
50134
+ tier: "hive",
50135
+ lesson: _internals23.truncateLesson(lesson),
50136
+ category,
50137
+ tags: ["migration:legacy-hive"],
50138
+ scope: scopeTag,
50139
+ confidence: Math.min(Math.max(confidence, 0), 1),
50140
+ status: "established",
50141
+ confirmed_by: [],
50142
+ retrieval_outcomes: {
50143
+ applied_count: 0,
50144
+ succeeded_after_count: 0,
50145
+ failed_after_count: 0
50146
+ },
50147
+ schema_version: KNOWLEDGE_SCHEMA_VERSION,
50148
+ created_at: legacyEntry.created_at || new Date().toISOString(),
50149
+ updated_at: legacyEntry.updated_at || new Date().toISOString(),
50150
+ source_project: "legacy-promotion",
50151
+ encounter_score: 1
50152
+ };
50153
+ try {
50154
+ await _internals23.appendKnowledge(canonicalHivePath, newHiveEntry);
50155
+ existingHiveEntries.push(newHiveEntry);
50156
+ migrated++;
50157
+ } catch (appendError) {
50158
+ const errorMsg = `Failed to append entry: ${appendError instanceof Error ? appendError.message : String(appendError)}`;
50159
+ entryErrors.push(errorMsg);
50160
+ warn(`[knowledge-migrator] ${errorMsg}`);
50161
+ dropped++;
50162
+ }
50163
+ } catch (entryError) {
50164
+ const errorMsg = `Unexpected error processing legacy entry: ${entryError instanceof Error ? entryError.message : String(entryError)}`;
50165
+ entryErrors.push(errorMsg);
50166
+ warn(`[knowledge-migrator] ${errorMsg}`);
50167
+ dropped++;
50168
+ }
50169
+ }
50170
+ await _internals23.writeSentinel(sentinelPath, migrated, dropped);
50171
+ log(`[knowledge-migrator] Migrated ${migrated} legacy hive entries, dropped ${dropped}`);
50172
+ return {
50173
+ migrated: true,
50174
+ entriesMigrated: migrated,
50175
+ entriesDropped: dropped,
50176
+ entriesTotal: legacyEntries.length,
50177
+ ...entryErrors.length > 0 && { entryErrors }
50178
+ };
50179
+ }
49553
50180
  function parseContextMd(content) {
49554
50181
  const sections = _internals23.splitIntoSections(content);
49555
50182
  const entries = [];
@@ -49641,7 +50268,7 @@ function inferCategoryFromText(text) {
49641
50268
  return "process";
49642
50269
  return "other";
49643
50270
  }
49644
- function truncateLesson(text) {
50271
+ function truncateLesson2(text) {
49645
50272
  if (text.length <= 280)
49646
50273
  return text;
49647
50274
  return `${text.slice(0, 277)}...`;
@@ -49671,21 +50298,37 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
49671
50298
  await mkdir12(path37.dirname(sentinelPath), { recursive: true });
49672
50299
  await writeFile10(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
49673
50300
  }
50301
+ function resolveLegacyHiveKnowledgePath() {
50302
+ const platform = process.platform;
50303
+ const home = process.env.HOME || os8.homedir();
50304
+ let dataDir;
50305
+ if (platform === "win32") {
50306
+ dataDir = path37.join(process.env.LOCALAPPDATA || path37.join(home, "AppData", "Local"), "opencode-swarm", "Data");
50307
+ } else if (platform === "darwin") {
50308
+ dataDir = path37.join(home, "Library", "Application Support", "opencode-swarm");
50309
+ } else {
50310
+ dataDir = path37.join(process.env.XDG_DATA_HOME || path37.join(home, ".local", "share"), "opencode-swarm");
50311
+ }
50312
+ return path37.join(dataDir, "hive-knowledge.jsonl");
50313
+ }
49674
50314
  var _internals23;
49675
50315
  var init_knowledge_migrator = __esm(() => {
49676
50316
  init_logger();
49677
50317
  init_knowledge_store();
49678
50318
  init_knowledge_validator();
49679
50319
  _internals23 = {
50320
+ appendKnowledge,
49680
50321
  migrateContextToKnowledge,
49681
50322
  migrateKnowledgeToExternal,
50323
+ migrateHiveKnowledgeLegacy,
49682
50324
  parseContextMd,
49683
50325
  splitIntoSections,
49684
50326
  extractBullets,
49685
50327
  inferCategoryFromText,
49686
- truncateLesson,
50328
+ truncateLesson: truncateLesson2,
49687
50329
  inferProjectName,
49688
- writeSentinel
50330
+ writeSentinel,
50331
+ resolveLegacyHiveKnowledgePath
49689
50332
  };
49690
50333
  });
49691
50334
 
@@ -49757,24 +50400,43 @@ async function handleKnowledgeRestoreCommand(directory, args) {
49757
50400
  }
49758
50401
  async function handleKnowledgeMigrateCommand(directory, args) {
49759
50402
  const targetDir = args[0] || directory;
50403
+ const config3 = KnowledgeConfigSchema.parse({});
49760
50404
  try {
49761
- const result = await migrateContextToKnowledge(targetDir, KnowledgeConfigSchema.parse({}));
49762
- if (result.skippedReason) {
49763
- switch (result.skippedReason) {
50405
+ const contextResult = await migrateContextToKnowledge(targetDir, config3);
50406
+ const hiveResult = await migrateHiveKnowledgeLegacy(config3);
50407
+ const messages = [];
50408
+ if (contextResult.skippedReason) {
50409
+ switch (contextResult.skippedReason) {
49764
50410
  case "sentinel-exists":
49765
- return "\u23ED Migration already completed for this project. Delete .swarm/.knowledge-migrated to re-run.";
50411
+ messages.push("\u23ED Context migration already completed. Delete .swarm/.knowledge-migrated to re-run.");
50412
+ break;
49766
50413
  case "no-context-file":
49767
- return "\u2139\uFE0F No .swarm/context.md found \u2014 nothing to migrate.";
50414
+ messages.push("\u2139\uFE0F No .swarm/context.md found \u2014 nothing to migrate.");
50415
+ break;
49768
50416
  case "empty-context":
49769
- return "\u2139\uFE0F .swarm/context.md is empty \u2014 nothing to migrate.";
49770
- default:
49771
- return "\u26A0\uFE0F Migration skipped for an unknown reason.";
50417
+ messages.push("\u2139\uFE0F .swarm/context.md is empty \u2014 nothing to migrate.");
50418
+ break;
50419
+ }
50420
+ } else {
50421
+ messages.push(`\u2705 Context migration: ${contextResult.entriesMigrated} entries added, ${contextResult.entriesDropped} dropped`);
50422
+ }
50423
+ if (hiveResult.skippedReason) {
50424
+ switch (hiveResult.skippedReason) {
50425
+ case "sentinel-exists":
50426
+ messages.push("\u23ED Hive legacy migration already completed. Delete the sentinel in the hive data dir to re-run.");
50427
+ break;
50428
+ case "no-context-file":
50429
+ messages.push("\u2139\uFE0F No legacy hive-knowledge.jsonl found \u2014 nothing to migrate.");
50430
+ break;
49772
50431
  }
50432
+ } else if (hiveResult.migrated) {
50433
+ messages.push(`\u2705 Hive legacy migration: ${hiveResult.entriesMigrated} entries added, ${hiveResult.entriesDropped} dropped`);
49773
50434
  }
49774
- return `\u2705 Migration complete: ${result.entriesMigrated} entries added, ${result.entriesDropped} dropped (validation/dedup), ${result.entriesTotal} total processed.`;
50435
+ return messages.join(`
50436
+ `);
49775
50437
  } catch (error93) {
49776
- console.warn("[knowledge-command] migrateContextToKnowledge error:", error93 instanceof Error ? error93.message : String(error93));
49777
- return "\u274C Migration failed. Check .swarm/context.md is readable.";
50438
+ console.warn("[knowledge-command] migration error:", error93 instanceof Error ? error93.message : String(error93));
50439
+ return "\u274C Migration failed. Check that knowledge source files are readable.";
49778
50440
  }
49779
50441
  }
49780
50442
  async function handleKnowledgeListCommand(directory, _args) {
@@ -49811,6 +50473,34 @@ var init_knowledge = __esm(() => {
49811
50473
  init_knowledge_validator();
49812
50474
  });
49813
50475
 
50476
+ // src/commands/learning.ts
50477
+ async function handleLearningCommand(directory, args) {
50478
+ try {
50479
+ const jsonMode = args.includes("--json");
50480
+ let currentPhase;
50481
+ const phaseIdx = args.indexOf("--phase");
50482
+ if (phaseIdx !== -1 && phaseIdx + 1 < args.length) {
50483
+ const parsed = Number(args[phaseIdx + 1]);
50484
+ if (Number.isFinite(parsed)) {
50485
+ currentPhase = parsed;
50486
+ }
50487
+ }
50488
+ const metrics = await computeLearningMetrics(directory, { currentPhase });
50489
+ if (jsonMode) {
50490
+ return `[LEARNING_JSON]
50491
+ ${JSON.stringify(formatLearningJSON(metrics), null, 2)}
50492
+ [/LEARNING_JSON]`;
50493
+ }
50494
+ return formatLearningMarkdown(metrics);
50495
+ } catch (err) {
50496
+ const message = err instanceof Error ? err.message : String(err);
50497
+ return `Error computing learning metrics: ${message}. Run /swarm diagnose to check .swarm/ health.`;
50498
+ }
50499
+ }
50500
+ var init_learning = __esm(() => {
50501
+ init_learning_metrics();
50502
+ });
50503
+
49814
50504
  // src/memory/config.ts
49815
50505
  function resolveMemoryConfig(input) {
49816
50506
  return {
@@ -52299,7 +52989,7 @@ var init_gateway = __esm(() => {
52299
52989
 
52300
52990
  // src/memory/evaluation.ts
52301
52991
  import * as fs16 from "fs/promises";
52302
- import * as os8 from "os";
52992
+ import * as os9 from "os";
52303
52993
  import * as path41 from "path";
52304
52994
  async function evaluateMemoryRecallFixtures(options) {
52305
52995
  const fixtureDirectory = path41.resolve(options.fixtureDirectory);
@@ -52311,7 +53001,7 @@ async function evaluateMemoryRecallFixtures(options) {
52311
53001
  for (const fixture of fixtures) {
52312
53002
  const materialized = materializeFixture(fixture);
52313
53003
  for (const providerName of providers) {
52314
- const tempRoot = await fs16.realpath(await fs16.mkdtemp(path41.join(os8.tmpdir(), "swarm-memory-eval-")));
53004
+ const tempRoot = await fs16.realpath(await fs16.mkdtemp(path41.join(os9.tmpdir(), "swarm-memory-eval-")));
52315
53005
  const provider = createEvaluationProvider(providerName, tempRoot);
52316
53006
  try {
52317
53007
  await provider.initialize?.();
@@ -61127,38 +61817,6 @@ async function handleSpecifyCommand(_directory, args) {
61127
61817
  return "[MODE: SPECIFY] Please enter MODE: SPECIFY and generate a spec for this project.";
61128
61818
  }
61129
61819
 
61130
- // src/hooks/knowledge-escalator.ts
61131
- async function readRecentEscalations(directory, windowDays = ESCALATION_DISPLAY_WINDOW_DAYS, now = new Date) {
61132
- try {
61133
- const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;
61134
- const events = await readKnowledgeEvents(directory);
61135
- const out = [];
61136
- for (const e of events) {
61137
- if (e.type !== "escalation")
61138
- continue;
61139
- const t = Date.parse(e.timestamp);
61140
- if (Number.isNaN(t) || t < cutoff)
61141
- continue;
61142
- out.push({
61143
- entry_id: e.entry_id,
61144
- from: e.from,
61145
- to: e.to,
61146
- reason: e.reason,
61147
- at: e.timestamp
61148
- });
61149
- }
61150
- out.sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
61151
- return out;
61152
- } catch {
61153
- return [];
61154
- }
61155
- }
61156
- var ESCALATION_DISPLAY_WINDOW_DAYS = 7;
61157
- var init_knowledge_escalator = __esm(() => {
61158
- init_knowledge_events();
61159
- init_knowledge_store();
61160
- });
61161
-
61162
61820
  // src/turbo/lean/state.ts
61163
61821
  import * as fs34 from "fs";
61164
61822
  import * as path61 from "path";
@@ -62304,6 +62962,7 @@ __export(exports_commands, {
62304
62962
  handleMemoryImportCommand: () => handleMemoryImportCommand,
62305
62963
  handleMemoryExportCommand: () => handleMemoryExportCommand,
62306
62964
  handleMemoryCommand: () => handleMemoryCommand,
62965
+ handleLearningCommand: () => handleLearningCommand,
62307
62966
  handleKnowledgeRestoreCommand: () => handleKnowledgeRestoreCommand,
62308
62967
  handleKnowledgeQuarantineCommand: () => handleKnowledgeQuarantineCommand,
62309
62968
  handleKnowledgeMigrateCommand: () => handleKnowledgeMigrateCommand,
@@ -62603,6 +63262,7 @@ var init_commands = __esm(() => {
62603
63262
  init_handoff();
62604
63263
  init_history();
62605
63264
  init_knowledge();
63265
+ init_learning();
62606
63266
  init_memory2();
62607
63267
  init_plan();
62608
63268
  init_preflight();
@@ -62836,6 +63496,7 @@ var init_registry = __esm(() => {
62836
63496
  init_history();
62837
63497
  init_issue();
62838
63498
  init_knowledge();
63499
+ init_learning();
62839
63500
  init_memory2();
62840
63501
  init_plan();
62841
63502
  init_pr_feedback();
@@ -62956,6 +63617,13 @@ var init_registry = __esm(() => {
62956
63617
  args: "--cumulative, --ci-gate",
62957
63618
  category: "diagnostics"
62958
63619
  },
63620
+ learning: {
63621
+ handler: (ctx) => handleLearningCommand(ctx.directory, ctx.args),
63622
+ description: "Show learning metrics and violation trends",
63623
+ args: "--json, --phase <N>",
63624
+ details: "Computes aggregate learning metrics from knowledge events: violation-rate trends, directive application rates, escalation frequency, per-entry ROI, and never-applied entries. Surfaces a learning summary for the curator digest.",
63625
+ category: "diagnostics"
63626
+ },
62959
63627
  export: {
62960
63628
  handler: (ctx) => handleExportCommand(ctx.directory, ctx.args),
62961
63629
  description: "Export plan and context as JSON",
@@ -63465,7 +64133,7 @@ init_registry();
63465
64133
  init_cache_paths();
63466
64134
  init_constants();
63467
64135
  import * as fs36 from "fs";
63468
- import * as os9 from "os";
64136
+ import * as os10 from "os";
63469
64137
  import * as path64 from "path";
63470
64138
  var { version: version5 } = package_default;
63471
64139
  var CONFIG_DIR = getPluginConfigDir();
@@ -63476,7 +64144,7 @@ var OPENCODE_PLUGIN_CACHE_PATHS = getPluginCachePaths();
63476
64144
  var OPENCODE_PLUGIN_LOCK_FILE_PATHS = getPluginLockFilePaths();
63477
64145
  function isSafeCachePath(p) {
63478
64146
  const resolved = path64.resolve(p);
63479
- const home = path64.resolve(os9.homedir());
64147
+ const home = path64.resolve(os10.homedir());
63480
64148
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
63481
64149
  return false;
63482
64150
  }
@@ -63500,7 +64168,7 @@ function isSafeCachePath(p) {
63500
64168
  }
63501
64169
  function isSafeLockFilePath(p) {
63502
64170
  const resolved = path64.resolve(p);
63503
- const home = path64.resolve(os9.homedir());
64171
+ const home = path64.resolve(os10.homedir());
63504
64172
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
63505
64173
  return false;
63506
64174
  }