@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
@@ -1,224 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import type { ParsedMessage, ParsedSession, ToolCall } from "../parser/types.js";
3
- import type { ExtractedSkill } from "./types.js";
4
-
5
- /**
6
- * A task-completion sequence: user request -> assistant actions -> completion.
7
- */
8
- type TaskSequence = {
9
- userMessage: ParsedMessage;
10
- assistantMessages: ParsedMessage[];
11
- allToolCalls: ToolCall[];
12
- filesInvolved: string[];
13
- };
14
-
15
- const FILE_PATH_TOOLS = new Set(["Edit", "Write", "Read", "NotebookEdit", "Glob", "Grep"]);
16
-
17
- function extractFilePaths(toolCalls: ToolCall[]): string[] {
18
- const files = new Set<string>();
19
- for (const tc of toolCalls) {
20
- if (FILE_PATH_TOOLS.has(tc.name)) {
21
- const filePath = tc.input["file_path"] ?? tc.input["path"];
22
- if (typeof filePath === "string") {
23
- files.add(filePath);
24
- }
25
- }
26
- // Bash commands that reference files
27
- if (tc.name === "Bash") {
28
- const cmd = tc.input["command"];
29
- if (typeof cmd === "string") {
30
- const fileRefs = cmd.match(/[\w./~-]+\.\w{1,10}/g);
31
- if (fileRefs) {
32
- for (const ref of fileRefs) {
33
- if (ref.includes("/") || ref.includes(".ts") || ref.includes(".js") || ref.includes(".py")) {
34
- files.add(ref);
35
- }
36
- }
37
- }
38
- }
39
- }
40
- }
41
- return Array.from(files);
42
- }
43
-
44
- function extractActionName(userContent: string): string {
45
- const firstLine = userContent.split("\n")[0].trim();
46
- // Truncate to something reasonable for a skill name
47
- const cleaned = firstLine
48
- .replace(/^(please|can you|could you|i want to|i need to|let's|lets)\s+/i, "")
49
- .replace(/[.!?]+$/, "")
50
- .trim();
51
-
52
- if (cleaned.length > 60) {
53
- return cleaned.slice(0, 60).replace(/\s+\S*$/, "").trim();
54
- }
55
- return cleaned || "unnamed task";
56
- }
57
-
58
- function describeToolCall(tc: ToolCall): string {
59
- switch (tc.name) {
60
- case "Bash": {
61
- const cmd = tc.input["command"];
62
- return typeof cmd === "string"
63
- ? `Run \`Bash\`: \`${cmd.length > 80 ? cmd.slice(0, 80) + "..." : cmd}\``
64
- : `Run \`Bash\``;
65
- }
66
- case "Edit": {
67
- const fp = tc.input["file_path"];
68
- return typeof fp === "string" ? `\`Edit\`: modify \`${fp}\`` : `\`Edit\`: modify file`;
69
- }
70
- case "Write": {
71
- const fp = tc.input["file_path"];
72
- return typeof fp === "string" ? `\`Write\`: create/overwrite \`${fp}\`` : `\`Write\`: create file`;
73
- }
74
- case "Read": {
75
- const fp = tc.input["file_path"];
76
- return typeof fp === "string" ? `\`Read\`: read \`${fp}\`` : `\`Read\`: read file`;
77
- }
78
- case "Grep": {
79
- const pat = tc.input["pattern"];
80
- return typeof pat === "string" ? `\`Grep\`: search for \`${pat}\`` : `\`Grep\`: search`;
81
- }
82
- case "Glob": {
83
- const pat = tc.input["pattern"];
84
- return typeof pat === "string" ? `\`Glob\`: find files matching \`${pat}\`` : `\`Glob\`: find files`;
85
- }
86
- default:
87
- return `\`${tc.name}\``;
88
- }
89
- }
90
-
91
- function generalizeTrigger(userContent: string): string {
92
- const firstLine = userContent.split("\n")[0].trim();
93
- // Strip specifics to make it more generic
94
- let trigger = firstLine
95
- .replace(/^(please|can you|could you|i want to|i need to)\s+/i, "When you need to ")
96
- .replace(/[.!?]+$/, "");
97
-
98
- if (!trigger.toLowerCase().startsWith("when")) {
99
- trigger = "When asked to " + trigger.charAt(0).toLowerCase() + trigger.slice(1);
100
- }
101
- return trigger;
102
- }
103
-
104
- function computeConfidence(seq: TaskSequence): number {
105
- const toolCount = seq.allToolCalls.length;
106
- if (toolCount === 0) return 0;
107
-
108
- // Base confidence from tool usage
109
- let confidence = Math.min(0.3 + toolCount * 0.1, 0.95);
110
-
111
- // Boost for variety of tools
112
- const uniqueTools = new Set(seq.allToolCalls.map((tc) => tc.name));
113
- if (uniqueTools.size >= 3) confidence = Math.min(confidence + 0.1, 0.95);
114
-
115
- // Boost for files involved
116
- if (seq.filesInvolved.length > 0) confidence = Math.min(confidence + 0.05, 0.95);
117
-
118
- return Math.round(confidence * 100) / 100;
119
- }
120
-
121
- function findTaskSequences(session: ParsedSession): TaskSequence[] {
122
- const sequences: TaskSequence[] = [];
123
- const messages = session.messages;
124
-
125
- let i = 0;
126
- while (i < messages.length) {
127
- const msg = messages[i];
128
- if (msg.role !== "user") {
129
- i++;
130
- continue;
131
- }
132
-
133
- // Collect subsequent assistant messages until next user message
134
- const assistantMessages: ParsedMessage[] = [];
135
- let j = i + 1;
136
- while (j < messages.length && messages[j].role === "assistant") {
137
- assistantMessages.push(messages[j]);
138
- j++;
139
- }
140
-
141
- // Only keep sequences where the assistant actually did something with tools
142
- const allToolCalls: ToolCall[] = [];
143
- for (const am of assistantMessages) {
144
- if (am.toolCalls) {
145
- allToolCalls.push(...am.toolCalls);
146
- }
147
- }
148
-
149
- if (allToolCalls.length > 0) {
150
- const filesInvolved = extractFilePaths(allToolCalls);
151
- sequences.push({
152
- userMessage: msg,
153
- assistantMessages,
154
- allToolCalls,
155
- filesInvolved,
156
- });
157
- }
158
-
159
- i = j;
160
- }
161
-
162
- return sequences;
163
- }
164
-
165
- export function extractSkills(session: ParsedSession): ExtractedSkill[] {
166
- const sequences = findTaskSequences(session);
167
- const skills: ExtractedSkill[] = [];
168
-
169
- for (const seq of sequences) {
170
- const name = extractActionName(seq.userMessage.content);
171
- const toolsUsed = Array.from(new Set(seq.allToolCalls.map((tc) => tc.name))).sort();
172
- const steps = seq.allToolCalls.map((tc) => describeToolCall(tc));
173
- const trigger = generalizeTrigger(seq.userMessage.content);
174
- const confidence = computeConfidence(seq);
175
- const description = `Extracted from session: ${name}. Uses ${toolsUsed.join(", ")}.`;
176
-
177
- // Generalize file paths to glob patterns
178
- const filesInvolved = generalizeFilePaths(seq.filesInvolved);
179
-
180
- skills.push({
181
- id: randomUUID(),
182
- name,
183
- description,
184
- trigger,
185
- steps,
186
- toolsUsed,
187
- filesInvolved,
188
- sourceSession: session.sessionId,
189
- extractedAt: new Date().toISOString(),
190
- confidence,
191
- });
192
- }
193
-
194
- return skills;
195
- }
196
-
197
- function generalizeFilePaths(paths: string[]): string[] {
198
- if (paths.length === 0) return [];
199
-
200
- // Group by directory, produce glob patterns where multiple files share a dir
201
- const dirMap = new Map<string, Set<string>>();
202
- for (const p of paths) {
203
- const lastSlash = p.lastIndexOf("/");
204
- const dir = lastSlash >= 0 ? p.slice(0, lastSlash) : ".";
205
- const ext = p.includes(".") ? p.slice(p.lastIndexOf(".")) : "";
206
- const key = `${dir}|${ext}`;
207
- if (!dirMap.has(key)) dirMap.set(key, new Set());
208
- dirMap.get(key)!.add(p);
209
- }
210
-
211
- const result: string[] = [];
212
- for (const [key, fileset] of dirMap) {
213
- if (fileset.size >= 3) {
214
- const [dir, ext] = key.split("|");
215
- result.push(`${dir}/**/*${ext}`);
216
- } else {
217
- for (const f of fileset) {
218
- result.push(f);
219
- }
220
- }
221
- }
222
-
223
- return result.sort();
224
- }