@hawon/nexus 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cli/index.js +44 -71
  2. package/dist/index.js +15 -24
  3. package/dist/mcp/server.js +7 -6
  4. package/package.json +1 -1
  5. package/scripts/benchmark.ts +13 -35
  6. package/src/cli/index.ts +45 -84
  7. package/src/index.ts +17 -27
  8. package/src/mcp/server.ts +8 -7
  9. package/src/memory-engine/index.ts +4 -6
  10. package/src/memory-engine/semantic.ts +38 -11
  11. package/src/promptguard/advanced-rules.ts +122 -5
  12. package/src/promptguard/entropy.ts +21 -2
  13. package/src/promptguard/rules.ts +87 -2
  14. package/src/promptguard/scanner.test.ts +1 -4
  15. package/src/promptguard/scanner.ts +1 -1
  16. package/src/promptguard/semantic.ts +19 -4
  17. package/src/promptguard/token-analysis.ts +17 -5
  18. package/src/review/analyzer.ts +106 -23
  19. package/src/skills/index.ts +11 -27
  20. package/src/testing/health-check.ts +19 -2
  21. package/src/memory-engine/compressor.ts +0 -97
  22. package/src/memory-engine/context-window.ts +0 -113
  23. package/src/memory-engine/store.ts +0 -371
  24. package/src/memory-engine/types.ts +0 -32
  25. package/src/skills/context-engine.ts +0 -863
  26. package/src/skills/extractor.ts +0 -224
  27. package/src/skills/global-context.ts +0 -731
  28. package/src/skills/library.ts +0 -189
  29. package/src/skills/pattern-engine.ts +0 -715
  30. package/src/skills/render-evolved.ts +0 -160
  31. package/src/skills/skill-reconciler.ts +0 -699
  32. package/src/skills/smart-extractor.ts +0 -849
  33. package/src/skills/types.ts +0 -18
  34. package/src/skills/wisdom-extractor.ts +0 -737
  35. package/src/superdev-evolution/index.ts +0 -3
  36. package/src/superdev-evolution/skill-manager.ts +0 -266
  37. package/src/superdev-evolution/types.ts +0 -20
package/dist/cli/index.js CHANGED
@@ -6,16 +6,14 @@ import { discoverSessions } from "../parser/discover.js";
6
6
  import { discoverAllSessions } from "../parser/unified.js";
7
7
  import { parseSession } from "../parser/parse.js";
8
8
  import { exportSession } from "../obsidian/exporter.js";
9
- import { extractSkills } from "../skills/extractor.js";
10
9
  import { reviewCode } from "../review/analyzer.js";
11
10
  import { mapCodebase } from "../codebase/mapper.js";
12
11
  import { generateOnboardingGuide } from "../codebase/onboard.js";
13
12
  import { checkTestHealth } from "../testing/health-check.js";
14
13
  import { suggestFixes } from "../testing/test-fixer.js";
15
14
  import { validateConfig } from "../config/validator.js";
16
- import { createMemoryStore } from "../memory-engine/store.js";
15
+ import { createNexusMemory } from "../memory-engine/nexus-memory.js";
17
16
  import { scan as scanPrompt } from "../promptguard/scanner.js";
18
- import { addSkills, exportToObsidian, loadSkillLibrary, saveSkillLibrary, searchSkills, } from "../skills/library.js";
19
17
  import { extractMemorySkills, renderKnowledgeBase } from "../skills/memory-skill-engine.js";
20
18
  import { readdirSync, rmSync } from "node:fs";
21
19
  // ── ANSI Colors ──────────────────────────────────────────────────
@@ -54,7 +52,7 @@ function resolveConfig(flags) {
54
52
  const vaultPath = flags["--vault"] ??
55
53
  process.env["NEXUS_VAULT_PATH"] ??
56
54
  join(homedir(), "ObsidianVault", "Claude");
57
- const dataDir = join(vaultPath, ".nexus");
55
+ const dataDir = process.env["NEXUS_DATA"] ?? join(homedir(), ".nexus");
58
56
  return { vaultPath, dataDir };
59
57
  }
60
58
  function loadStatus(dataDir) {
@@ -189,30 +187,20 @@ function cmdSync(flags) {
189
187
  }
190
188
  const parsedSessions = [];
191
189
  const exportedFiles = [];
192
- let skillsExtracted = 0;
193
- const library = loadSkillLibrary(config.dataDir);
194
190
  for (let i = 0; i < allSessionPaths.length; i++) {
195
191
  const { path: sessionPath } = allSessionPaths[i];
196
192
  logProgress(i + 1, allSessionPaths.length, `Syncing session ${i + 1}/${allSessionPaths.length}...`);
197
193
  try {
198
194
  const session = parseSession(sessionPath);
199
195
  parsedSessions.push(session);
200
- // 3. Export to Obsidian
201
196
  const filePath = exportSessionToObsidian(session, config.vaultPath);
202
197
  exportedFiles.push(filePath);
203
- // 4. Extract skills
204
- const skills = extractSkills(session);
205
- const added = addSkills(library, skills);
206
- skillsExtracted += added;
207
198
  }
208
199
  catch (err) {
209
200
  const msg = err instanceof Error ? err.message : String(err);
210
201
  log(`\n${c.yellow}Warning:${c.reset} Failed to parse ${sessionPath}: ${msg}`);
211
202
  }
212
203
  }
213
- // 5. Save skill library & export to Obsidian
214
- saveSkillLibrary(library, config.dataDir);
215
- const skillFiles = exportToObsidian(library, config.vaultPath);
216
204
  // 6. Update MOC and daily notes
217
205
  logInfo("Updating MOC and daily notes...");
218
206
  updateMOC(parsedSessions, config.vaultPath);
@@ -221,18 +209,15 @@ function cmdSync(flags) {
221
209
  const status = {
222
210
  lastSync: new Date().toISOString(),
223
211
  totalSessions: parsedSessions.length,
224
- totalSkills: library.skills.length,
212
+ totalSkills: 0,
225
213
  sessionsExported: parsedSessions.map((s) => s.sessionId),
226
214
  };
227
215
  saveStatus(status, config.dataDir);
228
- // Summary
229
216
  log("");
230
217
  log(`${c.bold}${c.green}Sync complete!${c.reset}`);
231
218
  log(` ${c.cyan}Sessions exported:${c.reset} ${exportedFiles.length}`);
232
- log(` ${c.cyan}New skills found:${c.reset} ${skillsExtracted}`);
233
- log(` ${c.cyan}Total skills:${c.reset} ${library.skills.length}`);
234
- log(` ${c.cyan}Skill files:${c.reset} ${skillFiles.length}`);
235
219
  log(` ${c.cyan}Vault path:${c.reset} ${config.vaultPath}`);
220
+ log(` ${c.dim}Run \`nexus reorganize\` to extract knowledge.${c.reset}`);
236
221
  }
237
222
  function cmdSessions() {
238
223
  const discovery = discoverSessions();
@@ -289,30 +274,36 @@ function cmdExport(sessionId, flags) {
289
274
  const session = parseSession(matchedPath);
290
275
  const filePath = exportSessionToObsidian(session, config.vaultPath);
291
276
  logSuccess(`Exported to ${filePath}`);
292
- // Extract skills too
293
- const library = loadSkillLibrary(config.dataDir);
294
- const skills = extractSkills(session);
295
- const added = addSkills(library, skills);
296
- if (added > 0) {
297
- saveSkillLibrary(library, config.dataDir);
298
- logSuccess(`Extracted ${added} new skill(s)`);
299
- }
300
277
  }
301
278
  function cmdSkills(flags) {
302
279
  const config = resolveConfig(flags);
303
- const library = loadSkillLibrary(config.dataDir);
304
- if (library.skills.length === 0) {
305
- logInfo("No skills extracted yet. Run `nexus sync` first.");
280
+ const kbPath = join(config.dataDir, "knowledge.json");
281
+ if (!existsSync(kbPath)) {
282
+ logInfo("No knowledge extracted yet. Run `nexus reorganize` first.");
306
283
  return;
307
284
  }
308
- log(`\n${c.bold}Extracted Skills${c.reset} (${library.skills.length} total)\n`);
309
- log(`${"Name".padEnd(45)} ${"Confidence".padEnd(12)} Tools`);
310
- log(`${"-".repeat(45)} ${"-".repeat(12)} ${"-".repeat(30)}`);
311
- for (const skill of library.skills) {
312
- const name = skill.name.slice(0, 43).padEnd(45);
313
- const conf = `${(skill.confidence * 100).toFixed(0)}%`.padEnd(12);
314
- const tools = skill.toolsUsed.join(", ");
315
- log(`${c.cyan}${name}${c.reset} ${c.yellow}${conf}${c.reset} ${tools}`);
285
+ const kb = JSON.parse(readFileSync(kbPath, "utf-8"));
286
+ const skills = kb.skills ?? [];
287
+ const tips = kb.tips ?? [];
288
+ const facts = kb.facts ?? [];
289
+ log(`\n${c.bold}Knowledge Base${c.reset} (${skills.length} skills | ${tips.length} tips | ${facts.length} facts)\n`);
290
+ if (skills.length > 0) {
291
+ log(`${c.bold}Skills:${c.reset}`);
292
+ for (const s of skills) {
293
+ log(` ${c.cyan}${s.name.slice(0, 50)}${c.reset} — ${(s.confidence * 100).toFixed(0)}% | ${s.evidenceCount} evidence`);
294
+ }
295
+ }
296
+ if (tips.length > 0) {
297
+ log(`\n${c.bold}Tips:${c.reset}`);
298
+ for (const t of tips) {
299
+ log(` 💡 ${t.advice?.slice(0, 60) ?? t.name}`);
300
+ }
301
+ }
302
+ if (facts.length > 0) {
303
+ log(`\n${c.bold}Facts:${c.reset}`);
304
+ for (const f of facts) {
305
+ log(` 📌 ${f.statement?.slice(0, 60) ?? f.name}`);
306
+ }
316
307
  }
317
308
  log("");
318
309
  }
@@ -321,32 +312,16 @@ function cmdSkillsSearch(query, flags) {
321
312
  logError("Usage: nexus skills search <query>");
322
313
  process.exit(1);
323
314
  }
324
- const config = resolveConfig(flags);
325
- const library = loadSkillLibrary(config.dataDir);
326
- const results = searchSkills(library, query);
327
- if (results.length === 0) {
328
- logInfo(`No skills matching "${query}".`);
329
- return;
330
- }
331
- log(`\n${c.bold}Skills matching "${query}"${c.reset} (${results.length} results)\n`);
332
- for (const skill of results) {
333
- log(`${c.cyan}${c.bold}${skill.name}${c.reset} ${c.dim}(${(skill.confidence * 100).toFixed(0)}% confidence)${c.reset}`);
334
- log(` ${c.dim}Trigger:${c.reset} ${skill.trigger}`);
335
- log(` ${c.dim}Tools:${c.reset} ${skill.toolsUsed.join(", ")}`);
336
- log(` ${c.dim}Steps:${c.reset} ${skill.steps.length}`);
337
- log("");
338
- }
315
+ logInfo(`Search not available yet. Use \`nexus skills\` to view all knowledge.`);
339
316
  }
340
317
  function cmdStatus(flags) {
341
318
  const config = resolveConfig(flags);
342
319
  const status = loadStatus(config.dataDir);
343
- const library = loadSkillLibrary(config.dataDir);
344
- log(`\n${c.bold}Claude Vault Status${c.reset}\n`);
320
+ log(`\n${c.bold}Nexus Status${c.reset}\n`);
345
321
  log(` ${c.cyan}Vault path:${c.reset} ${config.vaultPath}`);
346
322
  log(` ${c.cyan}Last sync:${c.reset} ${status.lastSync || "never"}`);
347
- log(` ${c.cyan}Total sessions:${c.reset} ${status.totalSessions}`);
348
- log(` ${c.cyan}Total skills:${c.reset} ${library.skills.length}`);
349
- log(` ${c.cyan}Library ver:${c.reset} ${library.version}`);
323
+ log(` ${c.cyan}Sessions:${c.reset} ${status.sessionsExported ?? 0}`);
324
+ log(` ${c.cyan}Knowledge:${c.reset} ${status.totalKnowledge ?? "run reorganize"}`);
350
325
  log("");
351
326
  }
352
327
  function cmdReorganize(flags) {
@@ -604,13 +579,13 @@ async function cmdConfig(dir, flags) {
604
579
  }
605
580
  function cmdMemory(subcommand, query, flags) {
606
581
  const config = resolveConfig(flags);
607
- const store = createMemoryStore(config.dataDir);
582
+ const store = createNexusMemory(config.dataDir);
608
583
  if (subcommand === "search") {
609
584
  if (!query) {
610
585
  logError("Usage: nexus memory search <query>");
611
586
  process.exit(1);
612
587
  }
613
- const results = store.search({ query });
588
+ const results = store.search(query);
614
589
  if ("--json" in flags) {
615
590
  log(JSON.stringify(results, null, 2));
616
591
  return;
@@ -620,10 +595,9 @@ function cmdMemory(subcommand, query, flags) {
620
595
  return;
621
596
  }
622
597
  log(`\n${c.bold}Memory Search: "${query}"${c.reset} (${results.length} results)\n`);
623
- for (const entry of results) {
624
- log(` ${c.cyan}${c.bold}${entry.id}${c.reset} ${c.dim}[${entry.tier}]${c.reset}`);
625
- log(` ${c.dim}Tags:${c.reset} ${entry.tags.join(", ")}`);
626
- log(` ${entry.content.slice(0, 120)}${entry.content.length > 120 ? "..." : ""}`);
598
+ for (const r of results) {
599
+ log(` ${c.cyan}[${r.retrievalLevel}]${c.reset} score=${r.score.toFixed(2)}`);
600
+ log(` ${r.observation.content.slice(0, 120)}`);
627
601
  log("");
628
602
  }
629
603
  }
@@ -634,13 +608,12 @@ function cmdMemory(subcommand, query, flags) {
634
608
  return;
635
609
  }
636
610
  log(`\n${c.bold}Memory Stats${c.reset}\n`);
637
- log(` ${c.cyan}Total entries:${c.reset} ${stats.totalEntries}`);
638
- log(` ${c.cyan}Total size:${c.reset} ${(stats.totalSizeBytes / 1024).toFixed(1)} KB`);
639
- log(` ${c.cyan}Compression ratio:${c.reset} ${(stats.compressionRatio * 100).toFixed(1)}%`);
640
- log(`\n${c.bold}By Tier:${c.reset}`);
641
- for (const [tier, count] of Object.entries(stats.byTier)) {
642
- log(` ${c.yellow}${tier}${c.reset}: ${count}`);
643
- }
611
+ log(` ${c.cyan}Observations:${c.reset} ${stats.totalObservations}`);
612
+ log(` ${c.cyan}Valid:${c.reset} ${stats.validObservations}`);
613
+ log(` ${c.cyan}Graph nodes:${c.reset} ${stats.graphNodes}`);
614
+ log(` ${c.cyan}Graph edges:${c.reset} ${stats.graphEdges}`);
615
+ log(` ${c.cyan}Tunnels:${c.reset} ${stats.tunnels}`);
616
+ log(` ${c.cyan}Domains:${c.reset} ${stats.domains.join(", ")}`);
644
617
  log("");
645
618
  }
646
619
  else {
package/dist/index.js CHANGED
@@ -7,34 +7,25 @@ export { discoverOpenClawSessions, parseOpenClawSession } from "./parser/opencla
7
7
  export { exportSession } from "./obsidian/exporter.js";
8
8
  export { updateMOC } from "./obsidian/moc.js";
9
9
  export { appendToDailyNote } from "./obsidian/daily-note.js";
10
- // Skills — Wisdom Pipeline
11
- export { extractWisdom, renderWisdomMarkdown } from "./skills/wisdom-extractor.js";
12
- export { reconcileWisdom, loadRefinedLibrary, saveRefinedLibrary, exportRefinedSkills } from "./skills/skill-reconciler.js";
13
- // Skills Context Engine
14
- export { createContextEngine } from "./skills/context-engine.js";
15
- export { createGlobalContext } from "./skills/global-context.js";
16
- export { analyzePatternEvolution } from "./skills/pattern-engine.js";
17
- export { smartExtract } from "./skills/smart-extractor.js";
18
- // Code Review (from superdev)
10
+ // Skills — Memory-based Knowledge Extraction
11
+ export { extractMemorySkills, renderKnowledgeBase } from "./skills/memory-skill-engine.js";
12
+ // Prompt Injection Detection
13
+ export { scan, isInjected, guard, scanBatch, PromptInjectionError } from "./promptguard/scanner.js";
14
+ export { BUILTIN_RULES } from "./promptguard/rules.js";
15
+ export { ADVANCED_RULES } from "./promptguard/advanced-rules.js";
16
+ export { MULTILINGUAL_RULES } from "./promptguard/multilingual-rules.js";
17
+ export { normalizeText } from "./promptguard/normalize.js";
18
+ // Memory Engine
19
+ export { createNexusMemory } from "./memory-engine/nexus-memory.js";
20
+ export { semanticSimilarity, expandQuery, getSynonyms } from "./memory-engine/semantic.js";
21
+ // Code Review
19
22
  export { reviewCode } from "./review/analyzer.js";
20
23
  export { reviewDiff } from "./review/diff-reviewer.js";
21
- // Codebase Mapping (from superdev)
24
+ // Codebase Mapping
22
25
  export { mapCodebase } from "./codebase/mapper.js";
23
26
  export { generateOnboardingGuide } from "./codebase/onboard.js";
24
- // Test Health (from superdev)
27
+ // Test Health
25
28
  export { checkTestHealth } from "./testing/health-check.js";
26
29
  export { suggestFixes } from "./testing/test-fixer.js";
27
- // Config Validator (from superdev)
30
+ // Config Validator
28
31
  export { validateConfig } from "./config/validator.js";
29
- // Infinite Memory (from superdev)
30
- export { createMemoryStore } from "./memory-engine/store.js";
31
- export { createContextWindow } from "./memory-engine/context-window.js";
32
- // Prompt Injection Detection (from promptguard)
33
- export { scan, isInjected, guard, scanBatch, PromptInjectionError } from "./promptguard/scanner.js";
34
- export { BUILTIN_RULES } from "./promptguard/rules.js";
35
- export { ADVANCED_RULES } from "./promptguard/advanced-rules.js";
36
- export { MULTILINGUAL_RULES } from "./promptguard/multilingual-rules.js";
37
- export { normalizeText } from "./promptguard/normalize.js";
38
- export { analyzeEntropy } from "./promptguard/entropy.js";
39
- export { classifyIntent } from "./promptguard/semantic.js";
40
- export { analyzeTokens } from "./promptguard/token-analysis.js";
@@ -46,17 +46,17 @@ import { suggestFixes } from "../testing/test-fixer.js";
46
46
  // Config
47
47
  import { validateConfig } from "../config/validator.js";
48
48
  // Memory
49
- import { createMemoryStore } from "../memory-engine/store.js";
49
+ import { createNexusMemory } from "../memory-engine/nexus-memory.js";
50
50
  // Prompt injection
51
51
  import { scan, isInjected } from "../promptguard/scanner.js";
52
52
  // Knowledge — uses cached data from last `nexus reorganize`
53
53
  const server = new McpServer({ name: "nexus", version: "0.1.0" });
54
- const dataDir = resolve(process.env.NEXUS_DATA ?? ".nexus");
54
+ const dataDir = resolve(process.env.NEXUS_DATA ?? join(process.env.HOME ?? "/home", ".nexus"));
55
55
  // Singleton memory store (avoid re-reading from disk on every MCP call)
56
56
  let _memoryStore = null;
57
57
  function getMemoryStore() {
58
58
  if (!_memoryStore)
59
- _memoryStore = createMemoryStore(dataDir);
59
+ _memoryStore = createNexusMemory(dataDir);
60
60
  return _memoryStore;
61
61
  }
62
62
  // ─── Session Intelligence ────────────────────────────────────────
@@ -149,7 +149,7 @@ server.tool("nexus_memory_search", "Search persistent memory for relevant past i
149
149
  limit: z.number().optional().describe("Max results (default: 10)"),
150
150
  }, async ({ query, limit }) => {
151
151
  const store = getMemoryStore();
152
- const results = store.search({ query, limit: limit ?? 10 });
152
+ const results = store.search(query, limit ?? 10);
153
153
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
154
154
  });
155
155
  server.tool("nexus_memory_save", "Save information to persistent memory for future reference.", {
@@ -157,8 +157,9 @@ server.tool("nexus_memory_save", "Save information to persistent memory for futu
157
157
  tags: z.array(z.string()).optional().describe("Tags for categorization"),
158
158
  }, async ({ content, tags }) => {
159
159
  const store = getMemoryStore();
160
- const entry = store.add({ content, tags: tags ?? [], tier: "working" });
161
- return { content: [{ type: "text", text: JSON.stringify({ saved: true, id: entry.id }) }] };
160
+ const count = store.ingest(content, "manual");
161
+ store.save();
162
+ return { content: [{ type: "text", text: JSON.stringify({ saved: true, observations: count }) }] };
162
163
  });
163
164
  // ─── Knowledge (Skills + Tips + Facts) ───────────────────────────
164
165
  server.tool("nexus_skills", "List all knowledge: skills (complex patterns), tips (quick advice), and facts (reference info).", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hawon/nexus",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The all-in-one AI developer framework — session intelligence, code review, prompt injection defense, infinite memory, self-evolving skills",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -4,7 +4,7 @@
4
4
  * Tests all 5 performance areas:
5
5
  * 1. Prompt Injection Detection — accuracy, false positive rate
6
6
  * 2. Memory Search — BM25+semantic vs TF-IDF precision
7
- * 3. Skill Extractionquality gate effectiveness
7
+ * 3. Session Parserparse rate and coverage
8
8
  * 4. Code Review — detection accuracy
9
9
  * 5. Speed — throughput across all modules
10
10
  */
@@ -15,8 +15,6 @@ import { semanticSimilarity } from "../src/memory-engine/semantic.js";
15
15
  import { reviewCode } from "../src/review/analyzer.js";
16
16
  import { discoverSessions } from "../src/parser/discover.js";
17
17
  import { parseSession } from "../src/parser/parse.js";
18
- import { extractWisdom } from "../src/skills/wisdom-extractor.js";
19
- import { reconcileWisdom } from "../src/skills/skill-reconciler.js";
20
18
 
21
19
  const BOLD = "\x1b[1m";
22
20
  const GREEN = "\x1b[32m";
@@ -234,17 +232,15 @@ function benchmarkMemory(): void {
234
232
  // 3. SKILL EXTRACTION BENCHMARK
235
233
  // ═══════════════════════════════════════════════════════════════════
236
234
 
237
- function benchmarkSkills(): void {
238
- header("3. Skill Extraction Pipeline");
235
+ function benchmarkSessionParser(): void {
236
+ header("3. Session Parser");
239
237
 
240
238
  const discovery = discoverSessions();
241
239
  let totalSessions = 0;
242
240
  let parsedSessions = 0;
243
- let totalWisdoms = 0;
244
241
  let totalMessages = 0;
245
- const allWisdoms: ReturnType<typeof extractWisdom> = [];
242
+ let failedSessions = 0;
246
243
 
247
- // Parse all sessions
248
244
  for (const proj of discovery.projects) {
249
245
  for (const sp of proj.sessions) {
250
246
  totalSessions++;
@@ -252,38 +248,20 @@ function benchmarkSkills(): void {
252
248
  const session = parseSession(sp);
253
249
  parsedSessions++;
254
250
  totalMessages += session.messages.length;
255
- const wisdoms = extractWisdom(session.messages, session.sessionId);
256
- totalWisdoms += wisdoms.length;
257
- allWisdoms.push(...wisdoms);
258
- } catch { /* skip */ }
251
+ } catch {
252
+ failedSessions++;
253
+ }
259
254
  }
260
255
  }
261
256
 
262
- // Run reconciler
263
- const library = { skills: [], version: 1, updatedAt: new Date().toISOString() };
264
- const result = reconcileWisdom(allWisdoms, library);
257
+ const parseRate = totalSessions > 0 ? (parsedSessions / totalSessions * 100).toFixed(0) : "0";
258
+ const avgMessages = parsedSessions > 0 ? (totalMessages / parsedSessions).toFixed(1) : "0";
265
259
 
266
260
  metric("Sessions discovered", String(totalSessions));
267
- metric("Sessions parsed", `${parsedSessions} (${(parsedSessions / totalSessions * 100).toFixed(0)}% success)`);
261
+ metric("Sessions parsed", `${parsedSessions}/${totalSessions} (${parseRate}%)`);
262
+ metric("Parse failures", String(failedSessions));
268
263
  metric("Total messages", String(totalMessages));
269
- metric("Raw wisdoms extracted", String(totalWisdoms));
270
- metric("Quality gate rejected", `${result.rejected.length} (${(result.rejected.length / totalWisdoms * 100).toFixed(0)}%)`);
271
- metric("Refined skills created", String(result.created.length));
272
- metric("Skills updated", String(result.updated.length));
273
- metric("Preferences learned", String(result.preferencesLearned.length));
274
- metric("Extraction rate", `${(totalWisdoms / parsedSessions).toFixed(1)} wisdoms/session`);
275
- metric("Quality rate", `${((result.created.length + result.updated.length) / Math.max(totalWisdoms, 1) * 100).toFixed(1)}% pass quality gate`);
276
-
277
- // Rejection breakdown
278
- const reasons: Record<string, number> = {};
279
- for (const r of result.rejected) {
280
- reasons[r.reason] = (reasons[r.reason] ?? 0) + 1;
281
- }
282
- log("");
283
- log(" Quality Gate Rejection Breakdown:");
284
- for (const [reason, count] of Object.entries(reasons).sort(([, a], [, b]) => b - a)) {
285
- log(` ${count}x — ${reason.slice(0, 60)}`);
286
- }
264
+ metric("Avg messages/session", avgMessages);
287
265
  }
288
266
 
289
267
  // ═══════════════════════════════════════════════════════════════════
@@ -459,7 +437,7 @@ log(`${BOLD}╚═════════════════════
459
437
 
460
438
  benchmarkInjection();
461
439
  benchmarkMemory();
462
- benchmarkSkills();
440
+ benchmarkSessionParser();
463
441
  benchmarkReview();
464
442
  benchmarkSpeed();
465
443