@hawon/nexus 0.1.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 (52) hide show
  1. package/README.md +60 -38
  2. package/dist/cli/index.js +76 -145
  3. package/dist/index.js +15 -26
  4. package/dist/mcp/server.js +61 -32
  5. package/package.json +2 -1
  6. package/scripts/auto-skill.sh +54 -0
  7. package/scripts/auto-sync.sh +11 -0
  8. package/scripts/benchmark.ts +444 -0
  9. package/scripts/scan-tool-result.sh +46 -0
  10. package/src/cli/index.ts +79 -172
  11. package/src/index.ts +17 -29
  12. package/src/mcp/server.ts +67 -41
  13. package/src/memory-engine/index.ts +4 -6
  14. package/src/memory-engine/nexus-memory.test.ts +437 -0
  15. package/src/memory-engine/nexus-memory.ts +631 -0
  16. package/src/memory-engine/semantic.ts +380 -0
  17. package/src/parser/parse.ts +1 -21
  18. package/src/promptguard/advanced-rules.ts +129 -12
  19. package/src/promptguard/entropy.ts +21 -2
  20. package/src/promptguard/evolution/auto-update.ts +16 -6
  21. package/src/promptguard/multilingual-rules.ts +68 -0
  22. package/src/promptguard/rules.ts +87 -2
  23. package/src/promptguard/scanner.test.ts +262 -0
  24. package/src/promptguard/scanner.ts +1 -1
  25. package/src/promptguard/semantic.ts +19 -4
  26. package/src/promptguard/token-analysis.ts +17 -5
  27. package/src/review/analyzer.test.ts +279 -0
  28. package/src/review/analyzer.ts +112 -28
  29. package/src/shared/stop-words.ts +21 -0
  30. package/src/skills/index.ts +11 -27
  31. package/src/skills/memory-skill-engine.ts +1044 -0
  32. package/src/testing/health-check.ts +19 -2
  33. package/src/cost/index.ts +0 -3
  34. package/src/cost/tracker.ts +0 -290
  35. package/src/cost/types.ts +0 -34
  36. package/src/memory-engine/compressor.ts +0 -97
  37. package/src/memory-engine/context-window.ts +0 -113
  38. package/src/memory-engine/store.ts +0 -371
  39. package/src/memory-engine/types.ts +0 -32
  40. package/src/skills/context-engine.ts +0 -863
  41. package/src/skills/extractor.ts +0 -224
  42. package/src/skills/global-context.ts +0 -726
  43. package/src/skills/library.ts +0 -189
  44. package/src/skills/pattern-engine.ts +0 -712
  45. package/src/skills/render-evolved.ts +0 -160
  46. package/src/skills/skill-reconciler.ts +0 -703
  47. package/src/skills/smart-extractor.ts +0 -843
  48. package/src/skills/types.ts +0 -18
  49. package/src/skills/wisdom-extractor.ts +0 -737
  50. package/src/superdev-evolution/index.ts +0 -3
  51. package/src/superdev-evolution/skill-manager.ts +0 -266
  52. package/src/superdev-evolution/types.ts +0 -20
@@ -0,0 +1,279 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { reviewCode } from "./analyzer.js";
4
+
5
+ // ═══════════════════════════════════════════════════════════════════
6
+ // Security detections
7
+ // ═══════════════════════════════════════════════════════════════════
8
+
9
+ describe("detects hardcoded secrets", () => {
10
+ it("detects API key", () => {
11
+ const code = `const API_KEY = "sk-abc123def456ghi789jkl012mno345pqr678";`;
12
+ const result = reviewCode(code, "test.ts");
13
+ assert.ok(
14
+ result.findings.some((f) => f.category === "security" && /secret|key|token/i.test(f.message)),
15
+ "Expected security finding for API key",
16
+ );
17
+ });
18
+
19
+ it("detects hardcoded password", () => {
20
+ const code = `const password = "SuperS3cretP@ss!";`;
21
+ const result = reviewCode(code, "config.ts");
22
+ assert.ok(
23
+ result.findings.some((f) => f.category === "security" && /password/i.test(f.message)),
24
+ "Expected security finding for hardcoded password",
25
+ );
26
+ });
27
+
28
+ it("detects AWS access key", () => {
29
+ const code = `const awsKey = "AKIAIOSFODNN7EXAMPLE";`;
30
+ const result = reviewCode(code, "aws.ts");
31
+ assert.ok(
32
+ result.findings.some((f) => f.category === "security" && /aws/i.test(f.message)),
33
+ "Expected security finding for AWS key",
34
+ );
35
+ });
36
+
37
+ it("detects GitHub PAT", () => {
38
+ const code = `const token = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij";`;
39
+ const result = reviewCode(code, "gh.ts");
40
+ assert.ok(
41
+ result.findings.some((f) => f.category === "security"),
42
+ "Expected security finding for GitHub PAT",
43
+ );
44
+ });
45
+ });
46
+
47
+ describe("detects SQL injection", () => {
48
+ it("template literal in exec", () => {
49
+ const code = 'db.exec(`DELETE FROM sessions WHERE user = ${userName}`);';
50
+ const result = reviewCode(code, "db.ts");
51
+ assert.ok(
52
+ result.findings.some((f) => f.category === "security" && /sql/i.test(f.message)),
53
+ "Expected SQL injection finding for exec template literal",
54
+ );
55
+ });
56
+
57
+ it("template literal in query", () => {
58
+ const code = 'db.execute(`SELECT * FROM users WHERE name = ${userName}`);';
59
+ const result = reviewCode(code, "db.ts");
60
+ assert.ok(
61
+ result.findings.some((f) => f.category === "security" && /sql/i.test(f.message)),
62
+ "Expected SQL injection finding for template literal",
63
+ );
64
+ });
65
+ });
66
+
67
+ describe("detects eval", () => {
68
+ it("eval() usage", () => {
69
+ const code = `const result = eval(userInput);`;
70
+ const result = reviewCode(code, "exec.ts");
71
+ assert.ok(
72
+ result.findings.some((f) => f.category === "security" && /eval|code injection/i.test(f.message)),
73
+ "Expected eval finding",
74
+ );
75
+ });
76
+
77
+ it("Function() constructor", () => {
78
+ const code = `const fn = new Function("return " + expr);`;
79
+ const result = reviewCode(code, "exec.ts");
80
+ assert.ok(
81
+ result.findings.some((f) => f.category === "security" && /eval|Function|code injection/i.test(f.message)),
82
+ "Expected Function constructor finding",
83
+ );
84
+ });
85
+ });
86
+
87
+ // ═══════════════════════════════════════════════════════════════════
88
+ // Bug detections
89
+ // ═══════════════════════════════════════════════════════════════════
90
+
91
+ describe("detects empty catch", () => {
92
+ it("catch(e) {} is flagged", () => {
93
+ const code = `
94
+ try {
95
+ doSomething();
96
+ } catch(e) {}
97
+ `;
98
+ const result = reviewCode(code, "handler.ts");
99
+ assert.ok(
100
+ result.findings.some((f) => f.category === "error_handling" && /empty catch/i.test(f.message)),
101
+ "Expected empty catch finding",
102
+ );
103
+ });
104
+ });
105
+
106
+ describe("detects TODO comments", () => {
107
+ it("TODO is flagged", () => {
108
+ const code = `
109
+ function process() {
110
+ // TODO fix this before release
111
+ return null;
112
+ }
113
+ `;
114
+ const result = reviewCode(code, "process.ts");
115
+ assert.ok(
116
+ result.findings.some((f) => /TODO/i.test(f.message)),
117
+ "Expected TODO finding",
118
+ );
119
+ });
120
+
121
+ it("FIXME is flagged", () => {
122
+ const code = `
123
+ // FIXME: memory leak here
124
+ setInterval(() => {}, 1000);
125
+ `;
126
+ const result = reviewCode(code, "timer.ts");
127
+ assert.ok(
128
+ result.findings.some((f) => /FIXME/i.test(f.message)),
129
+ "Expected FIXME finding",
130
+ );
131
+ });
132
+ });
133
+
134
+ // ═══════════════════════════════════════════════════════════════════
135
+ // Clean code passes
136
+ // ═══════════════════════════════════════════════════════════════════
137
+
138
+ describe("clean code", () => {
139
+ it("well-written code gets high score", () => {
140
+ const code = `
141
+ import { readFile } from "node:fs/promises";
142
+
143
+ interface Config {
144
+ port: number;
145
+ host: string;
146
+ }
147
+
148
+ async function loadConfig(path: string): Promise<Config> {
149
+ const raw = await readFile(path, "utf-8");
150
+ return JSON.parse(raw) as Config;
151
+ }
152
+
153
+ export { loadConfig };
154
+ `;
155
+ const result = reviewCode(code, "config.ts");
156
+ assert.ok(result.score >= 80, `Expected score >= 80 for clean code, got ${result.score}`);
157
+ });
158
+
159
+ it("returns summary string", () => {
160
+ const result = reviewCode("const x = 1;", "simple.ts");
161
+ assert.ok(typeof result.summary === "string");
162
+ assert.ok(result.summary.length > 0);
163
+ });
164
+ });
165
+
166
+ // ═══════════════════════════════════════════════════════════════════
167
+ // Scoring
168
+ // ═══════════════════════════════════════════════════════════════════
169
+
170
+ describe("scoring", () => {
171
+ it("score is between 0 and 100", () => {
172
+ const badCode = `
173
+ const api_key = "sk-abcdefghijklmnopqrstuvwxyz123456";
174
+ eval(userInput);
175
+ db.query("SELECT * FROM users WHERE id = " + id);
176
+ try { x(); } catch(e) {}
177
+ // TODO fix everything
178
+ console.log("debug");
179
+ `;
180
+ const result = reviewCode(badCode, "bad.ts");
181
+ assert.ok(result.score >= 0, `Score should be >= 0, got ${result.score}`);
182
+ assert.ok(result.score <= 100, `Score should be <= 100, got ${result.score}`);
183
+ });
184
+
185
+ it("more issues = lower score (diminishing returns)", () => {
186
+ const oneIssue = `const password = "hunter2abc1234";`;
187
+ const manyIssues = `
188
+ const password = "hunter2abc1234";
189
+ const api_key = "sk-abcdefghijklmnopqrstuvwxyz123456";
190
+ eval(userInput);
191
+ db.query("SELECT * FROM users WHERE id = " + id);
192
+ try { x(); } catch(e) {}
193
+ `;
194
+ const r1 = reviewCode(oneIssue, "a.ts");
195
+ const r2 = reviewCode(manyIssues, "b.ts");
196
+ assert.ok(r1.score > r2.score, `One issue (${r1.score}) should score higher than many (${r2.score})`);
197
+ });
198
+
199
+ it("durationMs is reported", () => {
200
+ const result = reviewCode("const x = 1;", "test.ts");
201
+ assert.ok(typeof result.durationMs === "number");
202
+ assert.ok(result.durationMs >= 0);
203
+ });
204
+ });
205
+
206
+ // ═══════════════════════════════════════════════════════════════════
207
+ // Filtering options
208
+ // ═══════════════════════════════════════════════════════════════════
209
+
210
+ describe("review options", () => {
211
+ const messyCode = `
212
+ const api_key = "sk-abcdefghijklmnopqrstuvwxyz123456";
213
+ eval(userInput);
214
+ // TODO fix this
215
+ console.log("debug");
216
+ try { x(); } catch(e) {}
217
+ `;
218
+
219
+ it("minSeverity filters lower severity findings", () => {
220
+ const all = reviewCode(messyCode, "test.ts");
221
+ const criticalOnly = reviewCode(messyCode, "test.ts", { minSeverity: "critical" });
222
+ assert.ok(criticalOnly.findings.length <= all.findings.length);
223
+ assert.ok(criticalOnly.findings.every((f) => f.severity === "critical"));
224
+ });
225
+
226
+ it("categories filter limits to specific categories", () => {
227
+ const secOnly = reviewCode(messyCode, "test.ts", { categories: ["security"] });
228
+ assert.ok(secOnly.findings.every((f) => f.category === "security"));
229
+ });
230
+
231
+ it("maxFindings limits result count", () => {
232
+ const result = reviewCode(messyCode, "test.ts", { maxFindings: 2 });
233
+ assert.ok(result.findings.length <= 2);
234
+ });
235
+ });
236
+
237
+ // ═══════════════════════════════════════════════════════════════════
238
+ // AI slop detection
239
+ // ═══════════════════════════════════════════════════════════════════
240
+
241
+ describe("AI slop detection", () => {
242
+ it("detects obvious comments", () => {
243
+ const code = `
244
+ // increment counter by 1
245
+ counter++;
246
+ // set name to empty string
247
+ let name = "";
248
+ `;
249
+ const result = reviewCode(code, "slop.ts");
250
+ assert.ok(
251
+ result.findings.some((f) => f.category === "ai_slop" && /comment/i.test(f.message)),
252
+ "Expected AI slop comment detection",
253
+ );
254
+ });
255
+
256
+ it("detects unnecessary type assertions", () => {
257
+ const code = `const data = response as any;`;
258
+ const result = reviewCode(code, "cast.ts");
259
+ assert.ok(
260
+ result.findings.some((f) => f.category === "ai_slop" && /type assertion/i.test(f.message)),
261
+ "Expected type assertion finding",
262
+ );
263
+ });
264
+ });
265
+
266
+ // ═══════════════════════════════════════════════════════════════════
267
+ // Performance detection
268
+ // ═══════════════════════════════════════════════════════════════════
269
+
270
+ describe("performance detection", () => {
271
+ it("detects innerHTML assignment", () => {
272
+ const code = `element.innerHTML = userInput;`;
273
+ const result = reviewCode(code, "dom.ts");
274
+ assert.ok(
275
+ result.findings.some((f) => f.category === "security" && /innerHTML|XSS/i.test(f.message)),
276
+ "Expected innerHTML/XSS finding",
277
+ );
278
+ });
279
+ });
@@ -20,12 +20,13 @@ const SEVERITY_PENALTY: Record<ReviewSeverity, number> = {
20
20
  info: 1,
21
21
  };
22
22
 
23
- let findingCounter = 0;
24
-
25
- function nextId(): string {
26
- return `R${String(++findingCounter).padStart(4, "0")}`;
23
+ function createIdGenerator(): () => string {
24
+ let counter = 0;
25
+ return () => `R${String(++counter).padStart(4, "0")}`;
27
26
  }
28
27
 
28
+ let nextId = createIdGenerator();
29
+
29
30
  function finding(
30
31
  file: string,
31
32
  line: number,
@@ -61,14 +62,16 @@ function detectConsoleStatements(
61
62
  const re = /\bconsole\.(log|error|warn|debug|info|trace)\s*\(/;
62
63
  for (let i = 0; i < lines.length; i++) {
63
64
  const line = lines[i];
64
- if (re.test(line) && !line.trimStart().startsWith("//")) {
65
+ const match = line.match(re);
66
+ if (match && !line.trimStart().startsWith("//")) {
67
+ const method = match[1];
65
68
  results.push(
66
69
  finding(
67
70
  file,
68
71
  i + 1,
69
72
  "warning",
70
73
  "bug",
71
- "Console statement left in code",
74
+ `console.${method} statement left in code`,
72
75
  line.trim(),
73
76
  "Remove or replace with a proper logger",
74
77
  ),
@@ -462,10 +465,31 @@ function detectSqlInjection(
462
465
  file: string,
463
466
  results: ReviewFinding[],
464
467
  ): void {
465
- const re =
466
- /(?:query|execute|exec|raw)\s*\(\s*(?:["'`].*?\b(?:SELECT|INSERT|UPDATE|DELETE|DROP)\b.*?\$\{|["'`]\s*\+\s*\w+)/i;
468
+ const sqlKeyword = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|DROP)\b/i;
469
+
470
+ // String concatenation: line has a string literal + variable (handles mixed quotes)
471
+ const stringConcat = /["'`]\s*\+|\+\s*["'`]/;
472
+
473
+ // Template literal interpolation with SQL keyword
474
+ const templateRe =
475
+ /`[^`]*\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|DROP)\b[^`]*\$\{/i;
476
+
477
+ // Parameterized query (safe — do not flag)
478
+ const parameterized = /\?\s*[,)\]]|\$\d+/;
479
+
467
480
  for (let i = 0; i < lines.length; i++) {
468
- if (re.test(lines[i])) {
481
+ const line = lines[i];
482
+ if (line.trimStart().startsWith("//")) continue;
483
+
484
+ const hasSqlKeyword = sqlKeyword.test(line);
485
+ const hasConcatenation = stringConcat.test(line);
486
+ const hasTemplateInterp = templateRe.test(line);
487
+ const isSafe = parameterized.test(line);
488
+
489
+ if (
490
+ !isSafe &&
491
+ (hasTemplateInterp || (hasSqlKeyword && hasConcatenation))
492
+ ) {
469
493
  results.push(
470
494
  finding(
471
495
  file,
@@ -473,7 +497,7 @@ function detectSqlInjection(
473
497
  "critical",
474
498
  "security",
475
499
  "SQL string concatenation / template literal — potential SQL injection",
476
- lines[i].trim().substring(0, 120),
500
+ line.trim().substring(0, 120),
477
501
  "Use parameterized queries or prepared statements",
478
502
  ),
479
503
  );
@@ -634,39 +658,99 @@ function detectUnusedImports(
634
658
  file: string,
635
659
  results: ReviewFinding[],
636
660
  ): void {
637
- const importRe = /import\s+(?:type\s+)?(?:\{([^}]+)\}|(\w+))/;
638
- const bodyText = lines.join("\n");
661
+ // Collect all import statements, handling multi-line imports.
662
+ const imports: {
663
+ startLine: number;
664
+ endLine: number;
665
+ names: string[];
666
+ }[] = [];
639
667
 
640
668
  for (let i = 0; i < lines.length; i++) {
641
- const match = lines[i].match(importRe);
642
- if (!match) continue;
669
+ const line = lines[i];
670
+
671
+ // Check if this line starts an import statement
672
+ if (!/^\s*import\s+/.test(line)) continue;
673
+
674
+ // Accumulate the full import statement (may span multiple lines)
675
+ let fullStatement = line;
676
+ let endLine = i;
677
+ while (
678
+ endLine < lines.length - 1 &&
679
+ !/\bfrom\s+["'`]/.test(fullStatement)
680
+ ) {
681
+ endLine++;
682
+ fullStatement += "\n" + lines[endLine];
683
+ }
684
+
685
+ // Skip `import type { ... } from` and `import type X from` (compile-time only)
686
+ if (/^\s*import\s+type\s+[\w{]/.test(fullStatement)) {
687
+ i = endLine;
688
+ continue;
689
+ }
690
+ // Skip bare side-effect imports: `import "module"`
691
+ if (/^\s*import\s+["'`]/.test(fullStatement)) {
692
+ i = endLine;
693
+ continue;
694
+ }
643
695
 
644
696
  const names: string[] = [];
645
- if (match[1]) {
646
- // Named imports: { A, B as C }
647
- for (const part of match[1].split(",")) {
648
- const token = part.trim().split(/\s+as\s+/);
697
+
698
+ // Extract default import: `import foo from` or `import foo, { ... } from`
699
+ const defaultMatch = fullStatement.match(
700
+ /import\s+(\w+)\s*(?:,|\s+from\b)/,
701
+ );
702
+ if (defaultMatch && defaultMatch[1] !== "type") {
703
+ names.push(defaultMatch[1]);
704
+ }
705
+
706
+ // Extract named imports: `{ a, b as c, d }`
707
+ const namedMatch = fullStatement.match(/\{([^}]+)\}/);
708
+ if (namedMatch) {
709
+ for (const part of namedMatch[1].split(",")) {
710
+ const trimmed = part.trim();
711
+ if (!trimmed) continue;
712
+ // Skip inline `type Foo` inside `import { type Foo, bar } from "..."`
713
+ if (/^type\s+/.test(trimmed)) continue;
714
+ const token = trimmed.split(/\s+as\s+/);
649
715
  const local = (token[1] ?? token[0]).trim();
650
716
  if (local) names.push(local);
651
717
  }
652
- } else if (match[2]) {
653
- names.push(match[2]);
654
718
  }
655
719
 
656
- for (const name of names) {
657
- if (!name || name === "type") continue;
658
- // Check occurrences in the rest of the file (excluding the import line itself)
659
- const rest = lines.filter((_, idx) => idx !== i).join("\n");
720
+ // Extract namespace import: `import * as ns from "..."`
721
+ const nsMatch = fullStatement.match(/import\s+\*\s+as\s+(\w+)/);
722
+ if (nsMatch) {
723
+ names.push(nsMatch[1]);
724
+ }
725
+
726
+ if (names.length > 0) {
727
+ imports.push({ startLine: i, endLine, names });
728
+ }
729
+
730
+ // Advance past multi-line import
731
+ i = endLine;
732
+ }
733
+
734
+ // Check each imported name for usage in the rest of the file
735
+ for (const imp of imports) {
736
+ const importLineSet = new Set<number>();
737
+ for (let l = imp.startLine; l <= imp.endLine; l++) {
738
+ importLineSet.add(l);
739
+ }
740
+ const rest = lines.filter((_, idx) => !importLineSet.has(idx)).join("\n");
741
+
742
+ for (const name of imp.names) {
743
+ if (!name) continue;
660
744
  const usage = new RegExp(`\\b${escapeRegex(name)}\\b`);
661
745
  if (!usage.test(rest)) {
662
746
  results.push(
663
747
  finding(
664
748
  file,
665
- i + 1,
749
+ imp.startLine + 1,
666
750
  "info",
667
751
  "dead_code",
668
- `Import '${name}' appears unused`,
669
- lines[i].trim(),
752
+ `Unused import '${name}'`,
753
+ lines[imp.startLine].trim(),
670
754
  "Remove the unused import",
671
755
  ),
672
756
  );
@@ -817,7 +901,7 @@ export function reviewCode(
817
901
  options: ReviewOptions = {},
818
902
  ): ReviewResult {
819
903
  const start = performance.now();
820
- findingCounter = 0;
904
+ nextId = createIdGenerator();
821
905
 
822
906
  const lines = code.split("\n");
823
907
  const findings: ReviewFinding[] = [];
@@ -0,0 +1,21 @@
1
+ export const STOP_WORDS = new Set([
2
+ // English
3
+ "the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
4
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
5
+ "should", "may", "might", "shall", "can", "need", "must",
6
+ "i", "me", "my", "we", "our", "you", "your", "he", "she", "it",
7
+ "they", "them", "his", "her", "its", "this", "that", "these", "those",
8
+ "what", "which", "who", "whom", "how", "when", "where", "why",
9
+ "and", "but", "or", "nor", "not", "no", "so", "if", "then", "else",
10
+ "for", "of", "to", "in", "on", "at", "by", "with", "from", "as",
11
+ "into", "about", "up", "out", "off", "over", "under", "again",
12
+ "there", "here", "all", "each", "every", "both", "few", "more",
13
+ "most", "some", "any", "other", "just", "also", "than", "too",
14
+ "very", "only", "even", "still", "already", "now", "well",
15
+ "get", "got", "make", "made", "go", "going", "come", "take",
16
+ "use", "used", "using", "like", "want", "know", "see", "look",
17
+ "think", "said", "say", "tell", "let", "put", "set", "try",
18
+ "please", "thanks", "thank", "yes", "no", "ok", "okay", "sure",
19
+ // Korean
20
+ "이", "그", "저", "을", "를", "에", "가", "는", "은", "의", "로", "으로",
21
+ ]);
@@ -1,29 +1,13 @@
1
- export { extractSkills } from "./extractor.js";
2
1
  export {
3
- addSkills,
4
- exportToObsidian,
5
- loadSkillLibrary,
6
- saveSkillLibrary,
7
- searchSkills,
8
- } from "./library.js";
9
- export type { ExtractedSkill, SkillLibrary } from "./types.js";
10
-
11
- // Pattern Evolution Engine
12
- export {
13
- analyzePatternEvolution,
14
- extractContextualPatterns,
15
- fingerprintPattern,
16
- findSimilarPatterns,
17
- analyzeDrift,
18
- buildEvolvedSkill,
19
- } from "./pattern-engine.js";
2
+ extractMemorySkills,
3
+ renderKnowledgeBase,
4
+ renderMemorySkillMarkdown,
5
+ } from "./memory-skill-engine.js";
20
6
  export type {
21
- ContextWindow,
22
- TaskSequence,
23
- PatternFingerprint,
24
- DriftAnalysis,
25
- DriftReason,
26
- EvolvedSkill,
27
- EvolutionEntry,
28
- } from "./pattern-engine.js";
29
- export { exportEvolvedSkills, renderEvolvedSkillMarkdown } from "./render-evolved.js";
7
+ MemorySkill,
8
+ Tip,
9
+ Fact,
10
+ KnowledgeTier,
11
+ LearnedKnowledge,
12
+ SkillExtractionResult,
13
+ } from "./memory-skill-engine.js";