codemolt-mcp 0.4.1 → 0.5.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.
@@ -0,0 +1,2 @@
1
+ import type { ParsedSession, SessionAnalysis } from "./types.js";
2
+ export declare function analyzeSession(session: ParsedSession): SessionAnalysis;
@@ -0,0 +1,225 @@
1
+ // Analyze a parsed session and extract structured insights
2
+ export function analyzeSession(session) {
3
+ const allContent = session.turns.map((t) => t.content).join("\n");
4
+ const humanContent = session.turns
5
+ .filter((t) => t.role === "human")
6
+ .map((t) => t.content)
7
+ .join("\n");
8
+ const aiContent = session.turns
9
+ .filter((t) => t.role === "assistant")
10
+ .map((t) => t.content)
11
+ .join("\n");
12
+ return {
13
+ summary: generateSummary(session),
14
+ topics: extractTopics(allContent),
15
+ languages: detectLanguages(allContent),
16
+ keyInsights: extractInsights(session.turns),
17
+ codeSnippets: extractCodeSnippets(allContent),
18
+ problems: extractProblems(humanContent),
19
+ solutions: extractSolutions(aiContent),
20
+ suggestedTitle: suggestTitle(session),
21
+ suggestedTags: suggestTags(allContent),
22
+ };
23
+ }
24
+ function generateSummary(session) {
25
+ const humanMsgs = session.turns.filter((t) => t.role === "human");
26
+ const topics = humanMsgs
27
+ .slice(0, 5)
28
+ .map((m) => m.content.slice(0, 100))
29
+ .join("; ");
30
+ return (`${session.source} session in project "${session.project}" with ` +
31
+ `${session.humanMessages} user messages and ${session.aiMessages} AI responses. ` +
32
+ `Topics discussed: ${topics}`);
33
+ }
34
+ function extractTopics(content) {
35
+ const topics = new Set();
36
+ // Common programming topics
37
+ const topicPatterns = [
38
+ [/\b(react|vue|angular|svelte|nextjs|next\.js|nuxt)\b/i, "frontend"],
39
+ [/\b(express|fastify|koa|nest\.?js|django|flask|rails)\b/i, "backend"],
40
+ [/\b(typescript|javascript|python|rust|go|java|c\+\+|ruby|swift|kotlin)\b/i, "programming-language"],
41
+ [/\b(docker|kubernetes|k8s|ci\/cd|deploy|devops)\b/i, "devops"],
42
+ [/\b(sql|postgres|mysql|mongodb|redis|database|prisma|drizzle)\b/i, "database"],
43
+ [/\b(test|jest|vitest|pytest|testing|spec|unit test)\b/i, "testing"],
44
+ [/\b(api|rest|graphql|grpc|websocket)\b/i, "api"],
45
+ [/\b(auth|jwt|oauth|session|login|password)\b/i, "authentication"],
46
+ [/\b(css|tailwind|styled|sass|scss|styling)\b/i, "styling"],
47
+ [/\b(git|merge|rebase|branch|commit)\b/i, "git"],
48
+ [/\b(performance|optimize|cache|lazy|memo)\b/i, "performance"],
49
+ [/\b(debug|error|bug|fix|issue|crash)\b/i, "debugging"],
50
+ [/\b(refactor|clean|architecture|pattern|design)\b/i, "architecture"],
51
+ [/\b(security|vulnerability|xss|csrf|injection)\b/i, "security"],
52
+ [/\b(ai|ml|llm|gpt|claude|model|prompt)\b/i, "ai-ml"],
53
+ ];
54
+ for (const [pattern, topic] of topicPatterns) {
55
+ if (pattern.test(content)) {
56
+ topics.add(topic);
57
+ }
58
+ }
59
+ return Array.from(topics);
60
+ }
61
+ function detectLanguages(content) {
62
+ const langs = new Set();
63
+ const langPatterns = [
64
+ [/```(?:typescript|tsx?)\b/i, "TypeScript"],
65
+ [/```(?:javascript|jsx?)\b/i, "JavaScript"],
66
+ [/```python\b/i, "Python"],
67
+ [/```rust\b/i, "Rust"],
68
+ [/```go\b/i, "Go"],
69
+ [/```java\b/i, "Java"],
70
+ [/```(?:c\+\+|cpp)\b/i, "C++"],
71
+ [/```c\b/i, "C"],
72
+ [/```ruby\b/i, "Ruby"],
73
+ [/```swift\b/i, "Swift"],
74
+ [/```kotlin\b/i, "Kotlin"],
75
+ [/```(?:bash|sh|shell|zsh)\b/i, "Shell"],
76
+ [/```sql\b/i, "SQL"],
77
+ [/```html\b/i, "HTML"],
78
+ [/```css\b/i, "CSS"],
79
+ [/```yaml\b/i, "YAML"],
80
+ [/```json\b/i, "JSON"],
81
+ [/```(?:dockerfile|docker)\b/i, "Docker"],
82
+ ];
83
+ for (const [pattern, lang] of langPatterns) {
84
+ if (pattern.test(content)) {
85
+ langs.add(lang);
86
+ }
87
+ }
88
+ // Also detect from imports/keywords if no code blocks
89
+ if (langs.size === 0) {
90
+ if (/\bimport\s+.*\s+from\s+['"]/.test(content))
91
+ langs.add("JavaScript/TypeScript");
92
+ if (/\bdef\s+\w+\s*\(/.test(content))
93
+ langs.add("Python");
94
+ if (/\bfn\s+\w+\s*\(/.test(content))
95
+ langs.add("Rust");
96
+ if (/\bfunc\s+\w+\s*\(/.test(content))
97
+ langs.add("Go");
98
+ }
99
+ return Array.from(langs);
100
+ }
101
+ function extractInsights(turns) {
102
+ const insights = [];
103
+ for (let i = 0; i < turns.length; i++) {
104
+ const turn = turns[i];
105
+ if (turn.role !== "assistant")
106
+ continue;
107
+ const content = turn.content;
108
+ // Look for key insight patterns in AI responses
109
+ const patterns = [
110
+ /(?:the (?:issue|problem|bug|root cause) (?:is|was))\s+(.{20,150})/i,
111
+ /(?:the (?:solution|fix|answer) (?:is|was))\s+(.{20,150})/i,
112
+ /(?:you (?:should|need to|can))\s+(.{20,150})/i,
113
+ /(?:this (?:happens|occurs) because)\s+(.{20,150})/i,
114
+ /(?:(?:key|important) (?:thing|point|takeaway))\s+(.{20,150})/i,
115
+ /(?:TIL|Today I learned|Learned that)\s+(.{20,150})/i,
116
+ ];
117
+ for (const pattern of patterns) {
118
+ const match = content.match(pattern);
119
+ if (match && match[1]) {
120
+ insights.push(match[1].trim().replace(/\.$/, ""));
121
+ }
122
+ }
123
+ }
124
+ // Deduplicate and limit
125
+ return [...new Set(insights)].slice(0, 10);
126
+ }
127
+ function extractCodeSnippets(content) {
128
+ const snippets = [];
129
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
130
+ let match;
131
+ while ((match = codeBlockRegex.exec(content)) !== null) {
132
+ const language = match[1] || "unknown";
133
+ const code = match[2].trim();
134
+ if (code.length < 10 || code.length > 2000)
135
+ continue;
136
+ // Get surrounding context (text before the code block)
137
+ const beforeIdx = Math.max(0, match.index - 200);
138
+ const context = content.slice(beforeIdx, match.index).trim().split("\n").pop() || "";
139
+ snippets.push({ language, code, context });
140
+ }
141
+ return snippets.slice(0, 10);
142
+ }
143
+ function extractProblems(humanContent) {
144
+ const problems = [];
145
+ const lines = humanContent.split("\n");
146
+ for (const line of lines) {
147
+ const trimmed = line.trim();
148
+ if (trimmed.length < 15 || trimmed.length > 300)
149
+ continue;
150
+ // Look for problem indicators
151
+ if (/\b(error|bug|issue|problem|broken|doesn't work|not working|failing|crash|wrong)\b/i.test(trimmed) &&
152
+ !trimmed.startsWith("//") &&
153
+ !trimmed.startsWith("#")) {
154
+ problems.push(trimmed);
155
+ }
156
+ }
157
+ return [...new Set(problems)].slice(0, 5);
158
+ }
159
+ function extractSolutions(aiContent) {
160
+ const solutions = [];
161
+ const sentences = aiContent.split(/[.!]\s+/);
162
+ for (const sentence of sentences) {
163
+ const trimmed = sentence.trim();
164
+ if (trimmed.length < 20 || trimmed.length > 300)
165
+ continue;
166
+ if (/\b(fix|solve|solution|resolve|instead|should|try|change|update|replace|use)\b/i.test(trimmed) &&
167
+ !/\b(error|bug|issue|problem)\b/i.test(trimmed)) {
168
+ solutions.push(trimmed);
169
+ }
170
+ }
171
+ return [...new Set(solutions)].slice(0, 5);
172
+ }
173
+ function suggestTitle(session) {
174
+ const firstHuman = session.turns.find((t) => t.role === "human");
175
+ if (!firstHuman)
176
+ return `${session.source} coding session`;
177
+ const content = firstHuman.content.slice(0, 100);
178
+ // Clean up for title
179
+ return content
180
+ .replace(/\n/g, " ")
181
+ .replace(/\s+/g, " ")
182
+ .trim();
183
+ }
184
+ function suggestTags(content) {
185
+ const tags = new Set();
186
+ // Detect specific technologies
187
+ const techPatterns = [
188
+ [/\breact\b/i, "react"],
189
+ [/\bnext\.?js\b/i, "nextjs"],
190
+ [/\btypescript\b/i, "typescript"],
191
+ [/\bpython\b/i, "python"],
192
+ [/\brust\b/i, "rust"],
193
+ [/\bdocker\b/i, "docker"],
194
+ [/\bprisma\b/i, "prisma"],
195
+ [/\btailwind\b/i, "tailwindcss"],
196
+ [/\bnode\.?js\b/i, "nodejs"],
197
+ [/\bgit\b/i, "git"],
198
+ [/\bpostgres\b/i, "postgresql"],
199
+ [/\bmongodb\b/i, "mongodb"],
200
+ [/\bredis\b/i, "redis"],
201
+ [/\baws\b/i, "aws"],
202
+ [/\bvue\b/i, "vue"],
203
+ [/\bangular\b/i, "angular"],
204
+ [/\bsvelte\b/i, "svelte"],
205
+ [/\bgraphql\b/i, "graphql"],
206
+ [/\bwebsocket\b/i, "websocket"],
207
+ ];
208
+ for (const [pattern, tag] of techPatterns) {
209
+ if (pattern.test(content)) {
210
+ tags.add(tag);
211
+ }
212
+ }
213
+ // Detect activity type
214
+ if (/\b(bug|fix|error|debug)\b/i.test(content))
215
+ tags.add("bug-fix");
216
+ if (/\b(refactor|clean|restructure)\b/i.test(content))
217
+ tags.add("refactoring");
218
+ if (/\b(performance|optimize|speed|cache)\b/i.test(content))
219
+ tags.add("performance");
220
+ if (/\b(test|spec|coverage)\b/i.test(content))
221
+ tags.add("testing");
222
+ if (/\b(deploy|ci|cd|pipeline)\b/i.test(content))
223
+ tags.add("devops");
224
+ return Array.from(tags).slice(0, 8);
225
+ }
@@ -0,0 +1,9 @@
1
+ import * as fs from "fs";
2
+ export declare function safeReadFile(filePath: string): string | null;
3
+ export declare function safeReadJson<T = unknown>(filePath: string): T | null;
4
+ export declare function safeStats(filePath: string): fs.Stats | null;
5
+ export declare function listFiles(dir: string, extensions?: string[], recursive?: boolean): string[];
6
+ export declare function listDirs(dir: string): string[];
7
+ export declare function exists(p: string): boolean;
8
+ export declare function extractProjectDescription(projectPath: string): string | null;
9
+ export declare function readJsonl<T = unknown>(filePath: string): T[];
@@ -0,0 +1,147 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ // Safely read a file, return null on error
4
+ export function safeReadFile(filePath) {
5
+ try {
6
+ return fs.readFileSync(filePath, "utf-8");
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ // Safely read JSON file
13
+ export function safeReadJson(filePath) {
14
+ const content = safeReadFile(filePath);
15
+ if (!content)
16
+ return null;
17
+ try {
18
+ return JSON.parse(content);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ // Get file stats safely
25
+ export function safeStats(filePath) {
26
+ try {
27
+ return fs.statSync(filePath);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ // List files in directory with extension filter
34
+ export function listFiles(dir, extensions, recursive = false) {
35
+ if (!fs.existsSync(dir))
36
+ return [];
37
+ const results = [];
38
+ try {
39
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const fullPath = path.join(dir, entry.name);
42
+ if (entry.isFile()) {
43
+ if (!extensions || extensions.some((ext) => entry.name.endsWith(ext))) {
44
+ results.push(fullPath);
45
+ }
46
+ }
47
+ else if (entry.isDirectory() && recursive) {
48
+ results.push(...listFiles(fullPath, extensions, true));
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ // Permission denied or other errors
54
+ }
55
+ return results;
56
+ }
57
+ // List subdirectories
58
+ export function listDirs(dir) {
59
+ if (!fs.existsSync(dir))
60
+ return [];
61
+ try {
62
+ return fs
63
+ .readdirSync(dir, { withFileTypes: true })
64
+ .filter((e) => e.isDirectory())
65
+ .map((e) => path.join(dir, e.name));
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ // Check if path exists
72
+ export function exists(p) {
73
+ try {
74
+ return fs.existsSync(p);
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ // Extract project description from a project directory
81
+ // Reads README.md (first paragraph) and package.json (description field)
82
+ export function extractProjectDescription(projectPath) {
83
+ if (!projectPath || !fs.existsSync(projectPath))
84
+ return null;
85
+ // Try package.json first (most concise)
86
+ const pkgPath = path.join(projectPath, "package.json");
87
+ const pkg = safeReadJson(pkgPath);
88
+ if (pkg?.description) {
89
+ return pkg.description.slice(0, 200);
90
+ }
91
+ // Try README.md — extract first non-heading, non-empty paragraph
92
+ for (const readmeName of ["README.md", "readme.md", "Readme.md", "README.rst"]) {
93
+ const readmePath = path.join(projectPath, readmeName);
94
+ const content = safeReadFile(readmePath);
95
+ if (!content)
96
+ continue;
97
+ const lines = content.split("\n");
98
+ let desc = "";
99
+ for (const line of lines) {
100
+ const trimmed = line.trim();
101
+ if (!trimmed) {
102
+ if (desc)
103
+ break;
104
+ continue;
105
+ }
106
+ if (trimmed.startsWith("#") || trimmed.startsWith("=") || trimmed.startsWith("-")) {
107
+ if (desc)
108
+ break;
109
+ continue;
110
+ }
111
+ if (trimmed.startsWith("![") || trimmed.startsWith("<img"))
112
+ continue;
113
+ desc += (desc ? " " : "") + trimmed;
114
+ if (desc.length > 200)
115
+ break;
116
+ }
117
+ if (desc.length > 10)
118
+ return desc.slice(0, 300);
119
+ }
120
+ // Try Cargo.toml, pyproject.toml etc.
121
+ const cargoPath = path.join(projectPath, "Cargo.toml");
122
+ const cargo = safeReadFile(cargoPath);
123
+ if (cargo) {
124
+ const match = cargo.match(/description\s*=\s*"([^"]+)"/);
125
+ if (match)
126
+ return match[1].slice(0, 200);
127
+ }
128
+ return null;
129
+ }
130
+ // Read JSONL file (one JSON object per line)
131
+ export function readJsonl(filePath) {
132
+ const content = safeReadFile(filePath);
133
+ if (!content)
134
+ return [];
135
+ return content
136
+ .split("\n")
137
+ .filter(Boolean)
138
+ .map((line) => {
139
+ try {
140
+ return JSON.parse(line);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ })
146
+ .filter((x) => x !== null);
147
+ }
@@ -0,0 +1,6 @@
1
+ export type Platform = "macos" | "windows" | "linux";
2
+ export declare function getPlatform(): Platform;
3
+ export declare function getHome(): string;
4
+ export declare function getAppDataDir(): string;
5
+ export declare function getLocalAppDataDir(): string;
6
+ export declare function resolvePaths(candidates: string[]): string[];
@@ -0,0 +1,50 @@
1
+ import * as os from "os";
2
+ import * as path from "path";
3
+ export function getPlatform() {
4
+ switch (os.platform()) {
5
+ case "win32":
6
+ return "windows";
7
+ case "darwin":
8
+ return "macos";
9
+ default:
10
+ return "linux";
11
+ }
12
+ }
13
+ export function getHome() {
14
+ return os.homedir();
15
+ }
16
+ // Windows: %APPDATA%, %LOCALAPPDATA%, %USERPROFILE%
17
+ // macOS: ~/Library/Application Support, ~/
18
+ // Linux: ~/.config, ~/.local/share, ~/
19
+ export function getAppDataDir() {
20
+ const platform = getPlatform();
21
+ if (platform === "windows") {
22
+ return process.env.APPDATA || path.join(getHome(), "AppData", "Roaming");
23
+ }
24
+ if (platform === "macos") {
25
+ return path.join(getHome(), "Library", "Application Support");
26
+ }
27
+ return process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
28
+ }
29
+ export function getLocalAppDataDir() {
30
+ const platform = getPlatform();
31
+ if (platform === "windows") {
32
+ return process.env.LOCALAPPDATA || path.join(getHome(), "AppData", "Local");
33
+ }
34
+ if (platform === "macos") {
35
+ return path.join(getHome(), "Library", "Application Support");
36
+ }
37
+ return process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
38
+ }
39
+ // Resolve a list of candidate paths, return all that exist
40
+ export function resolvePaths(candidates) {
41
+ const fs = require("fs");
42
+ return candidates.filter((p) => {
43
+ try {
44
+ return fs.existsSync(p);
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,13 @@
1
+ import type { Scanner, Session, ParsedSession } from "./types.js";
2
+ export declare function registerScanner(scanner: Scanner): void;
3
+ export declare function getScanners(): Scanner[];
4
+ export declare function getScannerBySource(source: string): Scanner | undefined;
5
+ export declare function scanAll(limit?: number): Session[];
6
+ export declare function parseSession(filePath: string, source: string, maxTurns?: number): ParsedSession | null;
7
+ export declare function listScannerStatus(): Array<{
8
+ name: string;
9
+ source: string;
10
+ description: string;
11
+ available: boolean;
12
+ dirs: string[];
13
+ }>;
@@ -0,0 +1,48 @@
1
+ // Scanner registry — all IDE scanners register here
2
+ const scanners = [];
3
+ export function registerScanner(scanner) {
4
+ scanners.push(scanner);
5
+ }
6
+ export function getScanners() {
7
+ return [...scanners];
8
+ }
9
+ export function getScannerBySource(source) {
10
+ return scanners.find((s) => s.sourceType === source);
11
+ }
12
+ // Scan all registered IDEs, merge and sort results
13
+ export function scanAll(limit = 20) {
14
+ const allSessions = [];
15
+ for (const scanner of scanners) {
16
+ try {
17
+ const sessions = scanner.scan(limit);
18
+ allSessions.push(...sessions);
19
+ }
20
+ catch (err) {
21
+ // Silently skip failing scanners
22
+ console.error(`Scanner ${scanner.name} failed:`, err);
23
+ }
24
+ }
25
+ // Sort by modification time (newest first)
26
+ allSessions.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
27
+ return allSessions.slice(0, limit);
28
+ }
29
+ // Parse a session file using the appropriate scanner
30
+ export function parseSession(filePath, source, maxTurns) {
31
+ const scanner = getScannerBySource(source);
32
+ if (!scanner)
33
+ return null;
34
+ return scanner.parse(filePath, maxTurns);
35
+ }
36
+ // List available scanners with their status
37
+ export function listScannerStatus() {
38
+ return scanners.map((s) => {
39
+ const dirs = s.getSessionDirs();
40
+ return {
41
+ name: s.name,
42
+ source: s.sourceType,
43
+ description: s.description,
44
+ available: dirs.length > 0,
45
+ dirs,
46
+ };
47
+ });
48
+ }
@@ -0,0 +1,47 @@
1
+ export interface Session {
2
+ id: string;
3
+ source: SourceType;
4
+ project: string;
5
+ projectPath?: string;
6
+ projectDescription?: string;
7
+ title: string;
8
+ messageCount: number;
9
+ humanMessages: number;
10
+ aiMessages: number;
11
+ preview: string;
12
+ filePath: string;
13
+ modifiedAt: Date;
14
+ sizeBytes: number;
15
+ }
16
+ export interface ConversationTurn {
17
+ role: "human" | "assistant" | "system" | "tool";
18
+ content: string;
19
+ timestamp?: Date;
20
+ }
21
+ export interface ParsedSession extends Session {
22
+ turns: ConversationTurn[];
23
+ }
24
+ export interface SessionAnalysis {
25
+ summary: string;
26
+ topics: string[];
27
+ languages: string[];
28
+ keyInsights: string[];
29
+ codeSnippets: Array<{
30
+ language: string;
31
+ code: string;
32
+ context: string;
33
+ }>;
34
+ problems: string[];
35
+ solutions: string[];
36
+ suggestedTitle: string;
37
+ suggestedTags: string[];
38
+ }
39
+ export type SourceType = "claude-code" | "cursor" | "windsurf" | "codex" | "warp" | "vscode-copilot" | "aider" | "continue" | "zed" | "unknown";
40
+ export interface Scanner {
41
+ name: string;
42
+ sourceType: SourceType;
43
+ description: string;
44
+ getSessionDirs(): string[];
45
+ scan(limit: number): Session[];
46
+ parse(filePath: string, maxTurns?: number): ParsedSession | null;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Scanner } from "../lib/types.js";
2
+ export declare const aiderScanner: Scanner;