@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.
- package/README.md +60 -38
- package/dist/cli/index.js +76 -145
- package/dist/index.js +15 -26
- package/dist/mcp/server.js +61 -32
- package/package.json +2 -1
- package/scripts/auto-skill.sh +54 -0
- package/scripts/auto-sync.sh +11 -0
- package/scripts/benchmark.ts +444 -0
- package/scripts/scan-tool-result.sh +46 -0
- package/src/cli/index.ts +79 -172
- package/src/index.ts +17 -29
- package/src/mcp/server.ts +67 -41
- package/src/memory-engine/index.ts +4 -6
- package/src/memory-engine/nexus-memory.test.ts +437 -0
- package/src/memory-engine/nexus-memory.ts +631 -0
- package/src/memory-engine/semantic.ts +380 -0
- package/src/parser/parse.ts +1 -21
- package/src/promptguard/advanced-rules.ts +129 -12
- package/src/promptguard/entropy.ts +21 -2
- package/src/promptguard/evolution/auto-update.ts +16 -6
- package/src/promptguard/multilingual-rules.ts +68 -0
- package/src/promptguard/rules.ts +87 -2
- package/src/promptguard/scanner.test.ts +262 -0
- package/src/promptguard/scanner.ts +1 -1
- package/src/promptguard/semantic.ts +19 -4
- package/src/promptguard/token-analysis.ts +17 -5
- package/src/review/analyzer.test.ts +279 -0
- package/src/review/analyzer.ts +112 -28
- package/src/shared/stop-words.ts +21 -0
- package/src/skills/index.ts +11 -27
- package/src/skills/memory-skill-engine.ts +1044 -0
- package/src/testing/health-check.ts +19 -2
- package/src/cost/index.ts +0 -3
- package/src/cost/tracker.ts +0 -290
- package/src/cost/types.ts +0 -34
- package/src/memory-engine/compressor.ts +0 -97
- package/src/memory-engine/context-window.ts +0 -113
- package/src/memory-engine/store.ts +0 -371
- package/src/memory-engine/types.ts +0 -32
- package/src/skills/context-engine.ts +0 -863
- package/src/skills/extractor.ts +0 -224
- package/src/skills/global-context.ts +0 -726
- package/src/skills/library.ts +0 -189
- package/src/skills/pattern-engine.ts +0 -712
- package/src/skills/render-evolved.ts +0 -160
- package/src/skills/skill-reconciler.ts +0 -703
- package/src/skills/smart-extractor.ts +0 -843
- package/src/skills/types.ts +0 -18
- package/src/skills/wisdom-extractor.ts +0 -737
- package/src/superdev-evolution/index.ts +0 -3
- package/src/superdev-evolution/skill-manager.ts +0 -266
- 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
|
+
});
|
package/src/review/analyzer.ts
CHANGED
|
@@ -20,12 +20,13 @@ const SEVERITY_PENALTY: Record<ReviewSeverity, number> = {
|
|
|
20
20
|
info: 1,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
466
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
638
|
-
const
|
|
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
|
|
642
|
-
|
|
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
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
749
|
+
imp.startLine + 1,
|
|
666
750
|
"info",
|
|
667
751
|
"dead_code",
|
|
668
|
-
`
|
|
669
|
-
lines[
|
|
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
|
-
|
|
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
|
+
]);
|
package/src/skills/index.ts
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
export { extractSkills } from "./extractor.js";
|
|
2
1
|
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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";
|