@a13xu/lucid 1.1.0 → 1.4.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/build/index.js CHANGED
@@ -11,6 +11,9 @@ import { recallAll } from "./tools/recall-all.js";
11
11
  import { forget, ForgetSchema } from "./tools/forget.js";
12
12
  import { memoryStats } from "./tools/stats.js";
13
13
  import { handleValidateFile, ValidateFileSchema, handleCheckDrift, CheckDriftSchema, handleGetChecklist, } from "./tools/guardian.js";
14
+ import { handleGrepCode, GrepCodeSchema } from "./tools/grep.js";
15
+ import { handleInitProject, InitProjectSchema } from "./tools/init.js";
16
+ import { handleSyncFile, SyncFileSchema, handleSyncProject, SyncProjectSchema, } from "./tools/sync.js";
14
17
  // ---------------------------------------------------------------------------
15
18
  // Init DB
16
19
  // ---------------------------------------------------------------------------
@@ -90,6 +93,67 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
90
93
  description: "Get memory usage statistics.",
91
94
  inputSchema: { type: "object", properties: {} },
92
95
  },
96
+ // ── Init / Indexing ──────────────────────────────────────────────────────
97
+ {
98
+ name: "init_project",
99
+ description: "Scan and index a project directory into the knowledge graph. " +
100
+ "Reads CLAUDE.md (directives, conventions), package.json / pyproject.toml (dependencies, scripts), " +
101
+ "README.md (description), .mcp.json (MCP servers), logic-guardian.yaml (drift patterns), " +
102
+ "and source files (exported functions/classes). " +
103
+ "Call this once when starting work on a project to bootstrap memory with project context.",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ directory: {
108
+ type: "string",
109
+ description: "Absolute path to the project root. Defaults to current working directory.",
110
+ },
111
+ },
112
+ },
113
+ },
114
+ {
115
+ name: "sync_file",
116
+ description: "Index or re-index a single source file after it was written or modified. " +
117
+ "Extracts exports, description, and open TODOs, then updates the knowledge graph. " +
118
+ "IMPORTANT: call this automatically after every Write or Edit tool call.",
119
+ inputSchema: {
120
+ type: "object",
121
+ properties: {
122
+ path: { type: "string", description: "Absolute or relative path to the modified file." },
123
+ },
124
+ required: ["path"],
125
+ },
126
+ },
127
+ {
128
+ name: "sync_project",
129
+ description: "Re-index the entire project directory incrementally. " +
130
+ "Use this when multiple files have changed (e.g. after a refactor or git pull).",
131
+ inputSchema: {
132
+ type: "object",
133
+ properties: {
134
+ directory: {
135
+ type: "string",
136
+ description: "Project root directory. Defaults to current working directory.",
137
+ },
138
+ },
139
+ },
140
+ },
141
+ {
142
+ name: "grep_code",
143
+ description: "Search indexed source files using a regex pattern. " +
144
+ "Decompresses stored binary content and returns only matching lines with context. " +
145
+ "Token-efficient: returns ~20-50 tokens instead of full file contents. " +
146
+ "Useful for finding function calls, variable usages, import patterns.",
147
+ inputSchema: {
148
+ type: "object",
149
+ properties: {
150
+ pattern: { type: "string", description: "Regex pattern to search for." },
151
+ language: { type: "string", enum: ["python", "javascript", "typescript", "generic"], description: "Filter by language." },
152
+ context: { type: "number", description: "Lines of context before/after each match (0-10, default 2)." },
153
+ },
154
+ required: ["pattern"],
155
+ },
156
+ },
93
157
  // ── Logic Guardian ───────────────────────────────────────────────────────
94
158
  {
95
159
  name: "validate_file",
@@ -156,6 +220,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
156
220
  case "memory_stats":
157
221
  text = memoryStats(db, stmts);
158
222
  break;
223
+ // Init & Sync
224
+ case "init_project":
225
+ text = handleInitProject(stmts, InitProjectSchema.parse(args));
226
+ break;
227
+ case "sync_file":
228
+ text = handleSyncFile(stmts, SyncFileSchema.parse(args));
229
+ break;
230
+ case "sync_project":
231
+ text = handleSyncProject(stmts, SyncProjectSchema.parse(args));
232
+ break;
233
+ // Grep
234
+ case "grep_code":
235
+ text = handleGrepCode(stmts, GrepCodeSchema.parse(args));
236
+ break;
159
237
  // Logic Guardian
160
238
  case "validate_file":
161
239
  text = handleValidateFile(ValidateFileSchema.parse(args));
@@ -0,0 +1,15 @@
1
+ import type { Statements } from "../database.js";
2
+ export interface FileIndex {
3
+ module: string;
4
+ exports: string[];
5
+ description: string;
6
+ todos: string[];
7
+ language: string;
8
+ }
9
+ export declare function indexFile(filepath: string): FileIndex | null;
10
+ export interface UpsertResult {
11
+ observations: string[];
12
+ stored: boolean;
13
+ savedBytes: number;
14
+ }
15
+ export declare function upsertFileIndex(index: FileIndex, source: string, stmts: Statements): UpsertResult;
@@ -0,0 +1,100 @@
1
+ import { readFileSync } from "fs";
2
+ import { extname } from "path";
3
+ import { compress, sha256 } from "../store/content.js";
4
+ function extractTS(source) {
5
+ const exports = [];
6
+ const todos = [];
7
+ // Exported symbols
8
+ for (const m of source.matchAll(/export\s+(?:async\s+)?(?:function|class|const|type|interface|enum)\s+(\w+)/g)) {
9
+ exports.push(m[1]);
10
+ }
11
+ // First JSDoc / block comment as description
12
+ const docMatch = source.match(/^\/\*\*([\s\S]*?)\*\//m) ?? source.match(/^\/\/(.*)/m);
13
+ const description = docMatch
14
+ ? docMatch[1].replace(/\s*\*\s*/g, " ").trim().slice(0, 200)
15
+ : "";
16
+ // TODOs
17
+ for (const m of source.matchAll(/\/\/\s*(TODO|FIXME|HACK)[:\s]+(.+)/gi)) {
18
+ todos.push(`${m[1]}: ${m[2].trim()}`);
19
+ }
20
+ return { exports, description, todos };
21
+ }
22
+ function extractPython(source) {
23
+ const exports = [];
24
+ const todos = [];
25
+ // Public functions and classes
26
+ for (const m of source.matchAll(/^(?:def|class|async def)\s+(\w+)/gm)) {
27
+ if (!m[1].startsWith("_"))
28
+ exports.push(m[1]);
29
+ }
30
+ // Module docstring
31
+ const docMatch = source.match(/^["']{3}([\s\S]*?)["']{3}/m);
32
+ const description = docMatch ? docMatch[1].trim().slice(0, 200) : "";
33
+ // TODOs
34
+ for (const m of source.matchAll(/#\s*(TODO|FIXME|HACK)[:\s]+(.+)/gi)) {
35
+ todos.push(`${m[1]}: ${m[2].trim()}`);
36
+ }
37
+ return { exports, description, todos };
38
+ }
39
+ function extractGeneric(source) {
40
+ const todos = [];
41
+ for (const m of source.matchAll(/(?:\/\/|#)\s*(TODO|FIXME|HACK)[:\s]+(.+)/gi)) {
42
+ todos.push(`${m[1]}: ${m[2].trim()}`);
43
+ }
44
+ return { exports: [], description: "", todos };
45
+ }
46
+ export function indexFile(filepath) {
47
+ let source;
48
+ try {
49
+ source = readFileSync(filepath, { encoding: "utf-8" });
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ const ext = extname(filepath).toLowerCase();
55
+ const module = filepath.replace(/\\/g, "/");
56
+ let extracted;
57
+ let language;
58
+ if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
59
+ extracted = extractTS(source);
60
+ language = ext.includes("ts") ? "typescript" : "javascript";
61
+ }
62
+ else if (ext === ".py") {
63
+ extracted = extractPython(source);
64
+ language = "python";
65
+ }
66
+ else {
67
+ extracted = extractGeneric(source);
68
+ language = "generic";
69
+ }
70
+ return { module, language, ...extracted };
71
+ }
72
+ export function upsertFileIndex(index, source, stmts) {
73
+ const fileHash = sha256(source);
74
+ // Change detection — skip everything se hash-ul e identic
75
+ const existing = stmts.getFileByPath.get(index.module);
76
+ if (existing?.content_hash === fileHash) {
77
+ return { observations: [], stored: false, savedBytes: 0 };
78
+ }
79
+ // Comprimă și stochează conținutul binar
80
+ const blob = compress(source);
81
+ stmts.upsertFile.run(index.module, blob, fileHash, Buffer.byteLength(source, "utf-8"), blob.byteLength, index.language);
82
+ // Index structural în entities (compact, pentru recall)
83
+ const observations = [];
84
+ if (index.description)
85
+ observations.push(`description: ${index.description}`);
86
+ if (index.exports.length > 0)
87
+ observations.push(`exports: ${index.exports.join(", ")}`);
88
+ if (index.todos.length > 0)
89
+ observations.push(`TODOs: ${index.todos.join(" | ")}`);
90
+ observations.push(`language: ${index.language}`);
91
+ const entityRow = stmts.getEntityByName.get(index.module);
92
+ if (entityRow) {
93
+ stmts.updateEntity.run(JSON.stringify(observations), entityRow.id);
94
+ }
95
+ else {
96
+ stmts.insertEntity.run(index.module, "pattern", JSON.stringify(observations));
97
+ }
98
+ const savedBytes = Buffer.byteLength(source, "utf-8") - blob.byteLength;
99
+ return { observations, stored: true, savedBytes };
100
+ }
@@ -0,0 +1,8 @@
1
+ import type { Statements } from "../database.js";
2
+ export interface IndexResult {
3
+ entity: string;
4
+ type: string;
5
+ observations: number;
6
+ source: string;
7
+ }
8
+ export declare function indexProject(directory: string, stmts: Statements): IndexResult[];
@@ -0,0 +1,312 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { join, extname, basename } from "path";
3
+ // ---------------------------------------------------------------------------
4
+ // Helpers
5
+ // ---------------------------------------------------------------------------
6
+ function readFile(path) {
7
+ try {
8
+ return readFileSync(path, { encoding: "utf-8" });
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ function upsert(stmts, name, type, observations) {
15
+ const existing = stmts.getEntityByName.get(name);
16
+ if (existing) {
17
+ const current = JSON.parse(existing.observations);
18
+ const merged = [...current];
19
+ for (const obs of observations) {
20
+ if (!merged.includes(obs))
21
+ merged.push(obs);
22
+ }
23
+ stmts.updateEntity.run(JSON.stringify(merged), existing.id);
24
+ }
25
+ else {
26
+ stmts.insertEntity.run(name, type, JSON.stringify(observations));
27
+ }
28
+ }
29
+ function relate(stmts, from, to, type) {
30
+ const fromRow = stmts.getEntityByName.get(from);
31
+ const toRow = stmts.getEntityByName.get(to);
32
+ if (fromRow && toRow) {
33
+ stmts.insertRelation.run(fromRow.id, toRow.id, type);
34
+ }
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // Parsers per file type
38
+ // ---------------------------------------------------------------------------
39
+ function indexClaudeMd(path, stmts, results) {
40
+ const content = readFile(path);
41
+ if (!content)
42
+ return;
43
+ // Indexează fiecare secțiune H2 ca observație separată pe entitatea proiectului
44
+ const sections = content.split(/\n##\s+/).filter(Boolean);
45
+ const observations = [];
46
+ for (const section of sections) {
47
+ const lines = section.trim().split("\n");
48
+ const title = lines[0]?.trim() ?? "directive";
49
+ const body = lines.slice(1).join("\n").trim();
50
+ if (body.length > 0) {
51
+ observations.push(`[${title}] ${body.slice(0, 300)}`);
52
+ }
53
+ }
54
+ if (observations.length > 0) {
55
+ upsert(stmts, "CLAUDE.md directives", "convention", observations);
56
+ results.push({ entity: "CLAUDE.md directives", type: "convention", observations: observations.length, source: "CLAUDE.md" });
57
+ }
58
+ }
59
+ function indexPackageJson(path, stmts, results) {
60
+ const content = readFile(path);
61
+ if (!content)
62
+ return;
63
+ let pkg;
64
+ try {
65
+ pkg = JSON.parse(content);
66
+ }
67
+ catch {
68
+ return;
69
+ }
70
+ const name = pkg["name"] ?? "project";
71
+ const version = pkg["version"] ?? "unknown";
72
+ const description = pkg["description"] ?? "";
73
+ const projectName = name.replace(/^@[\w-]+\//, ""); // strip scope
74
+ const obs = [`version: ${version}`];
75
+ if (description)
76
+ obs.push(`description: ${description}`);
77
+ // Scripts
78
+ const scripts = pkg["scripts"];
79
+ if (scripts) {
80
+ for (const [k, v] of Object.entries(scripts).slice(0, 6)) {
81
+ obs.push(`script ${k}: ${v}`);
82
+ }
83
+ }
84
+ upsert(stmts, projectName, "project", obs);
85
+ results.push({ entity: projectName, type: "project", observations: obs.length, source: "package.json" });
86
+ // Dependențe principale
87
+ const deps = {
88
+ ...(pkg["dependencies"] ?? {}),
89
+ ...(pkg["devDependencies"] ?? {}),
90
+ };
91
+ for (const [dep, ver] of Object.entries(deps).slice(0, 20)) {
92
+ upsert(stmts, dep, "tool", [`version: ${ver}`, `used in: ${projectName}`]);
93
+ relate(stmts, projectName, dep, "depends_on");
94
+ }
95
+ if (Object.keys(deps).length > 0) {
96
+ results.push({ entity: `${Object.keys(deps).length} dependencies`, type: "tool", observations: 1, source: "package.json" });
97
+ }
98
+ }
99
+ function indexPyprojectToml(path, stmts, results) {
100
+ const content = readFile(path);
101
+ if (!content)
102
+ return;
103
+ const nameMatch = content.match(/^name\s*=\s*["']([^"']+)["']/m);
104
+ const versionMatch = content.match(/^version\s*=\s*["']([^"']+)["']/m);
105
+ const descMatch = content.match(/^description\s*=\s*["']([^"']+)["']/m);
106
+ const name = nameMatch?.[1] ?? "project";
107
+ const obs = [];
108
+ if (versionMatch?.[1])
109
+ obs.push(`version: ${versionMatch[1]}`);
110
+ if (descMatch?.[1])
111
+ obs.push(`description: ${descMatch[1]}`);
112
+ // Dependencies
113
+ const depsSection = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?=\[|$)/)?.[1]
114
+ ?? content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/)?.[1]
115
+ ?? "";
116
+ const deps = [...depsSection.matchAll(/["']?([\w-]+)["']?\s*[=:]/g)].map((m) => m[1]).filter(Boolean);
117
+ for (const dep of deps.slice(0, 20)) {
118
+ upsert(stmts, dep, "tool", [`used in: ${name}`]);
119
+ relate(stmts, name, dep, "depends_on");
120
+ }
121
+ upsert(stmts, name, "project", obs);
122
+ results.push({ entity: name, type: "project", observations: obs.length, source: "pyproject.toml" });
123
+ }
124
+ function indexReadme(path, projectName, stmts, results) {
125
+ const content = readFile(path);
126
+ if (!content)
127
+ return;
128
+ // Prima secțiune (descriere)
129
+ const firstParagraph = content.replace(/^#[^\n]*\n/, "").trim().split("\n\n")[0] ?? "";
130
+ if (firstParagraph.length < 10)
131
+ return;
132
+ upsert(stmts, projectName, "project", [
133
+ `README: ${firstParagraph.slice(0, 400)}`,
134
+ ]);
135
+ results.push({ entity: projectName, type: "project", observations: 1, source: "README.md" });
136
+ }
137
+ function indexMcpJson(path, stmts, results) {
138
+ const content = readFile(path);
139
+ if (!content)
140
+ return;
141
+ let cfg;
142
+ try {
143
+ cfg = JSON.parse(content);
144
+ }
145
+ catch {
146
+ return;
147
+ }
148
+ const servers = cfg.mcpServers ?? {};
149
+ for (const [serverName, config] of Object.entries(servers)) {
150
+ const c = config;
151
+ const obs = [`MCP server configured in project`];
152
+ if (c.command)
153
+ obs.push(`command: ${c.command} ${(c.args ?? []).join(" ")}`);
154
+ upsert(stmts, serverName, "tool", obs);
155
+ results.push({ entity: serverName, type: "tool", observations: obs.length, source: ".mcp.json" });
156
+ }
157
+ }
158
+ function indexLogicGuardianYaml(path, stmts, results) {
159
+ const content = readFile(path);
160
+ if (!content)
161
+ return;
162
+ // Extrage known_drift_patterns
163
+ const patternMatches = [...content.matchAll(/id:\s*["']?(DRIFT-\d+)["']?\s*\n\s*name:\s*["']?([^\n"']+)["']?\s*\n\s*description:\s*["']?([^\n"']+)/g)];
164
+ for (const m of patternMatches) {
165
+ const [, id, name, desc] = m;
166
+ upsert(stmts, `${id}: ${name.trim()}`, "pattern", [
167
+ `drift pattern: ${desc.trim()}`,
168
+ "source: logic-guardian.yaml",
169
+ ]);
170
+ }
171
+ // Invarianți de proiect
172
+ const invariantsMatch = content.match(/project_invariants:([\s\S]*?)(?=\n\w|\n#|$)/);
173
+ if (invariantsMatch) {
174
+ const invariants = [...invariantsMatch[1].matchAll(/-\s+"([^"]+)"/g)].map((m) => m[1]);
175
+ if (invariants.length > 0) {
176
+ upsert(stmts, "project invariants", "convention", invariants);
177
+ results.push({ entity: "project invariants", type: "convention", observations: invariants.length, source: "logic-guardian.yaml" });
178
+ }
179
+ }
180
+ if (patternMatches.length > 0) {
181
+ results.push({ entity: `${patternMatches.length} drift patterns`, type: "pattern", observations: patternMatches.length, source: "logic-guardian.yaml" });
182
+ }
183
+ }
184
+ // Source file indexing — extrage exporturi, clase, funcții principale
185
+ const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
186
+ const SKIP_DIRS = new Set(["node_modules", ".git", "build", "dist", "__pycache__", ".next", "venv", ".venv", "target"]);
187
+ const MAX_SOURCE_FILES = 30;
188
+ function indexSourceFile(filepath, projectName, stmts) {
189
+ const content = readFile(filepath);
190
+ if (!content)
191
+ return [];
192
+ const exports = [];
193
+ const lang = extname(filepath);
194
+ // TypeScript / JavaScript
195
+ if ([".ts", ".tsx", ".js", ".jsx"].includes(lang)) {
196
+ for (const m of content.matchAll(/export\s+(?:async\s+)?(?:function|class|const|type|interface)\s+(\w+)/g)) {
197
+ exports.push(m[1]);
198
+ }
199
+ }
200
+ // Python
201
+ if (lang === ".py") {
202
+ for (const m of content.matchAll(/^(?:def|class|async def)\s+(\w+)/gm)) {
203
+ if (!m[1].startsWith("_"))
204
+ exports.push(m[1]);
205
+ }
206
+ }
207
+ if (exports.length === 0)
208
+ return [];
209
+ const relPath = filepath.replace(/\\/g, "/").split("/src/")[1] ?? basename(filepath);
210
+ const obs = [`exports from ${relPath}: ${exports.slice(0, 10).join(", ")}`];
211
+ upsert(stmts, projectName, "project", obs);
212
+ return exports;
213
+ }
214
+ function scanSources(dir, projectName, stmts, results) {
215
+ const srcDir = join(dir, "src");
216
+ const scanDir = existsSync(srcDir) ? srcDir : dir;
217
+ let fileCount = 0;
218
+ const exportedSymbols = [];
219
+ function walk(d, depth) {
220
+ if (depth > 3 || fileCount >= MAX_SOURCE_FILES)
221
+ return;
222
+ let entries;
223
+ try {
224
+ entries = readdirSync(d);
225
+ }
226
+ catch {
227
+ return;
228
+ }
229
+ for (const entry of entries) {
230
+ if (SKIP_DIRS.has(entry))
231
+ continue;
232
+ const full = join(d, entry);
233
+ const stat = statSync(full);
234
+ if (stat.isDirectory()) {
235
+ walk(full, depth + 1);
236
+ }
237
+ else if (SOURCE_EXTS.has(extname(entry))) {
238
+ const syms = indexSourceFile(full, projectName, stmts);
239
+ exportedSymbols.push(...syms);
240
+ fileCount++;
241
+ }
242
+ }
243
+ }
244
+ walk(scanDir, 0);
245
+ if (fileCount > 0) {
246
+ results.push({
247
+ entity: projectName,
248
+ type: "project",
249
+ observations: fileCount,
250
+ source: `${fileCount} source files (${exportedSymbols.length} exports)`,
251
+ });
252
+ }
253
+ }
254
+ // ---------------------------------------------------------------------------
255
+ // Main entry point
256
+ // ---------------------------------------------------------------------------
257
+ export function indexProject(directory, stmts) {
258
+ const results = [];
259
+ const dir = directory.replace(/\\/g, "/");
260
+ // Detectează numele proiectului din package.json sau pyproject.toml
261
+ let projectName = basename(dir);
262
+ const pkgPath = join(dir, "package.json");
263
+ if (existsSync(pkgPath)) {
264
+ const raw = readFile(pkgPath);
265
+ if (raw) {
266
+ try {
267
+ const pkg = JSON.parse(raw);
268
+ if (pkg.name)
269
+ projectName = pkg.name.replace(/^@[\w-]+\//, "");
270
+ }
271
+ catch { /* ignore */ }
272
+ }
273
+ }
274
+ // 1. CLAUDE.md — cel mai important
275
+ const claudeMdPaths = ["CLAUDE.md", ".claude/CLAUDE.md", "claude.md"];
276
+ for (const p of claudeMdPaths) {
277
+ const full = join(dir, p);
278
+ if (existsSync(full)) {
279
+ indexClaudeMd(full, stmts, results);
280
+ break;
281
+ }
282
+ }
283
+ // 2. package.json
284
+ if (existsSync(join(dir, "package.json"))) {
285
+ indexPackageJson(join(dir, "package.json"), stmts, results);
286
+ }
287
+ // 3. pyproject.toml
288
+ if (existsSync(join(dir, "pyproject.toml"))) {
289
+ indexPyprojectToml(join(dir, "pyproject.toml"), stmts, results);
290
+ }
291
+ // 4. README
292
+ for (const p of ["README.md", "readme.md", "Readme.md"]) {
293
+ if (existsSync(join(dir, p))) {
294
+ indexReadme(join(dir, p), projectName, stmts, results);
295
+ break;
296
+ }
297
+ }
298
+ // 5. MCP config
299
+ for (const p of [".mcp.json", "mcp.json"]) {
300
+ if (existsSync(join(dir, p))) {
301
+ indexMcpJson(join(dir, p), stmts, results);
302
+ break;
303
+ }
304
+ }
305
+ // 6. Logic Guardian config
306
+ if (existsSync(join(dir, "logic-guardian.yaml"))) {
307
+ indexLogicGuardianYaml(join(dir, "logic-guardian.yaml"), stmts, results);
308
+ }
309
+ // 7. Surse
310
+ scanSources(dir, projectName, stmts, results);
311
+ return results;
312
+ }
@@ -0,0 +1,3 @@
1
+ export declare function compress(source: string): Buffer;
2
+ export declare function decompress(blob: Buffer): string;
3
+ export declare function sha256(source: string): string;
@@ -0,0 +1,11 @@
1
+ import { deflateSync, inflateSync } from "zlib";
2
+ import { createHash } from "crypto";
3
+ export function compress(source) {
4
+ return deflateSync(Buffer.from(source, "utf-8"), { level: 9 });
5
+ }
6
+ export function decompress(blob) {
7
+ return inflateSync(blob).toString("utf-8");
8
+ }
9
+ export function sha256(source) {
10
+ return createHash("sha256").update(source).digest("hex");
11
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const GrepCodeSchema: z.ZodObject<{
4
+ pattern: z.ZodString;
5
+ language: z.ZodOptional<z.ZodEnum<["python", "javascript", "typescript", "generic"]>>;
6
+ context: z.ZodDefault<z.ZodNumber>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ pattern: string;
9
+ context: number;
10
+ language?: "python" | "javascript" | "typescript" | "generic" | undefined;
11
+ }, {
12
+ pattern: string;
13
+ language?: "python" | "javascript" | "typescript" | "generic" | undefined;
14
+ context?: number | undefined;
15
+ }>;
16
+ export type GrepCodeInput = z.infer<typeof GrepCodeSchema>;
17
+ export declare function handleGrepCode(stmts: Statements, input: GrepCodeInput): string;
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { decompress } from "../store/content.js";
3
+ export const GrepCodeSchema = z.object({
4
+ pattern: z.string().min(1),
5
+ language: z.enum(["python", "javascript", "typescript", "generic"]).optional(),
6
+ context: z.number().int().min(0).max(10).default(2),
7
+ });
8
+ export function handleGrepCode(stmts, input) {
9
+ let regex;
10
+ try {
11
+ regex = new RegExp(input.pattern, "i");
12
+ }
13
+ catch {
14
+ return `Invalid regex pattern: ${input.pattern}`;
15
+ }
16
+ const files = stmts.getAllFiles.all();
17
+ const matches = [];
18
+ for (const file of files) {
19
+ if (input.language && file.language !== input.language)
20
+ continue;
21
+ let source;
22
+ try {
23
+ source = decompress(file.content);
24
+ }
25
+ catch {
26
+ continue; // skip fișiere corupte
27
+ }
28
+ const lines = source.split("\n");
29
+ for (let i = 0; i < lines.length; i++) {
30
+ if (!regex.test(lines[i]))
31
+ continue;
32
+ matches.push({
33
+ filepath: file.filepath,
34
+ line: i + 1,
35
+ text: lines[i],
36
+ contextBefore: lines.slice(Math.max(0, i - input.context), i),
37
+ contextAfter: lines.slice(i + 1, i + 1 + input.context),
38
+ });
39
+ if (matches.length >= 30)
40
+ break; // cap la 30 match-uri
41
+ }
42
+ if (matches.length >= 30)
43
+ break;
44
+ }
45
+ if (matches.length === 0) {
46
+ return `No matches for /${input.pattern}/ in ${files.length} indexed file(s).`;
47
+ }
48
+ const lines = [
49
+ `Found ${matches.length} match(es) for /${input.pattern}/ across ${files.length} file(s):\n`,
50
+ ];
51
+ let lastFile = "";
52
+ for (const m of matches) {
53
+ if (m.filepath !== lastFile) {
54
+ lines.push(`── ${m.filepath}`);
55
+ lastFile = m.filepath;
56
+ }
57
+ for (const l of m.contextBefore)
58
+ lines.push(` ${m.line - m.contextBefore.length + m.contextBefore.indexOf(l)}│ ${l}`);
59
+ lines.push(`▶ ${m.line}│ ${m.text}`);
60
+ for (const l of m.contextAfter)
61
+ lines.push(` ${m.line + 1 + m.contextAfter.indexOf(l)}│ ${l}`);
62
+ lines.push("");
63
+ }
64
+ return lines.join("\n");
65
+ }
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ import type { Statements } from "../database.js";
3
+ export declare const InitProjectSchema: z.ZodObject<{
4
+ directory: z.ZodOptional<z.ZodString>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ directory?: string | undefined;
7
+ }, {
8
+ directory?: string | undefined;
9
+ }>;
10
+ export type InitProjectInput = z.infer<typeof InitProjectSchema>;
11
+ export declare function handleInitProject(stmts: Statements, input: InitProjectInput): string;