magector 1.0.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/LICENSE +21 -0
- package/README.md +627 -0
- package/config/mcp-config.json +13 -0
- package/package.json +53 -0
- package/src/binary.js +66 -0
- package/src/cli.js +203 -0
- package/src/init.js +293 -0
- package/src/magento-patterns.js +563 -0
- package/src/mcp-server.js +915 -0
- package/src/model.js +127 -0
- package/src/templates/claude-md.js +47 -0
- package/src/templates/cursorrules.js +45 -0
- package/src/validation/accuracy-calculator.js +397 -0
- package/src/validation/benchmark.js +355 -0
- package/src/validation/test-data-generator.js +672 -0
- package/src/validation/test-queries.js +326 -0
- package/src/validation/validator.js +302 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "magector",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Semantic code search for Magento 2 — index, search, MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/mcp-server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"magector": "./src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"config/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"index": "node src/cli.js index",
|
|
16
|
+
"search": "node src/cli.js search",
|
|
17
|
+
"mcp": "node src/mcp-server.js",
|
|
18
|
+
"start": "node src/mcp-server.js",
|
|
19
|
+
"validate": "node src/cli.js validate",
|
|
20
|
+
"validate:verbose": "node src/cli.js validate --verbose",
|
|
21
|
+
"validate:keep": "node src/cli.js validate --verbose --keep",
|
|
22
|
+
"benchmark": "node src/cli.js benchmark",
|
|
23
|
+
"test": "node tests/mcp-server.test.js",
|
|
24
|
+
"test:no-index": "node tests/mcp-server.test.js --no-index"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"glob": "^10.3.10",
|
|
30
|
+
"ora": "^8.0.1",
|
|
31
|
+
"ruvector": "^0.1.96"
|
|
32
|
+
},
|
|
33
|
+
"optionalDependencies": {
|
|
34
|
+
"@magector/cli-darwin-arm64": "1.0.0",
|
|
35
|
+
"@magector/cli-linux-x64": "1.0.0",
|
|
36
|
+
"@magector/cli-linux-arm64": "1.0.0",
|
|
37
|
+
"@magector/cli-win32-x64": "1.0.0"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"magento",
|
|
41
|
+
"indexer",
|
|
42
|
+
"vector-search",
|
|
43
|
+
"mcp",
|
|
44
|
+
"claude-code",
|
|
45
|
+
"semantic-search",
|
|
46
|
+
"cursor"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/krejcif/magector.git"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|
package/src/binary.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the platform-specific Rust binary (magector-core).
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. MAGECTOR_BIN env var
|
|
6
|
+
* 2. @magector/cli-{os}-{arch} optionalDependency
|
|
7
|
+
* 3. rust-core/target/release/magector-core (dev fallback)
|
|
8
|
+
* 4. magector-core in PATH
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { execFileSync } from 'child_process';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
const BINARY_NAME = process.platform === 'win32' ? 'magector-core.exe' : 'magector-core';
|
|
20
|
+
|
|
21
|
+
export function resolveBinary() {
|
|
22
|
+
// 1. Explicit env var
|
|
23
|
+
if (process.env.MAGECTOR_BIN) {
|
|
24
|
+
if (existsSync(process.env.MAGECTOR_BIN)) {
|
|
25
|
+
return process.env.MAGECTOR_BIN;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`MAGECTOR_BIN set to ${process.env.MAGECTOR_BIN} but file not found`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 2. Platform-specific npm package
|
|
31
|
+
const platformPkg = `@magector/cli-${process.platform}-${process.arch}`;
|
|
32
|
+
try {
|
|
33
|
+
const pkgDir = path.dirname(require.resolve(`${platformPkg}/package.json`));
|
|
34
|
+
const binPath = path.join(pkgDir, 'bin', BINARY_NAME);
|
|
35
|
+
if (existsSync(binPath)) {
|
|
36
|
+
return binPath;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Package not installed — continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. Dev fallback: local Rust build
|
|
43
|
+
const devPath = path.join(__dirname, '..', 'rust-core', 'target', 'release', BINARY_NAME);
|
|
44
|
+
if (existsSync(devPath)) {
|
|
45
|
+
return devPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 4. Global PATH
|
|
49
|
+
try {
|
|
50
|
+
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
51
|
+
const result = execFileSync(which, ['magector-core'], {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
54
|
+
}).trim();
|
|
55
|
+
if (result) return result.split('\n')[0];
|
|
56
|
+
} catch {
|
|
57
|
+
// Not in PATH
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Could not find magector-core binary.\n` +
|
|
62
|
+
`Install the platform package: npm install ${platformPkg}\n` +
|
|
63
|
+
`Or build from source: cd rust-core && cargo build --release\n` +
|
|
64
|
+
`Or set MAGECTOR_BIN environment variable.`
|
|
65
|
+
);
|
|
66
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Magector CLI — npx magector <command>
|
|
4
|
+
*
|
|
5
|
+
* All search/index/stats commands delegate to the Rust binary (magector-core).
|
|
6
|
+
* The CLI resolves the binary and model paths, then shells out.
|
|
7
|
+
*/
|
|
8
|
+
import { execFileSync, spawn } from 'child_process';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { resolveBinary } from './binary.js';
|
|
11
|
+
import { ensureModels, resolveModels } from './model.js';
|
|
12
|
+
import { init, setup } from './init.js';
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const command = args[0];
|
|
16
|
+
|
|
17
|
+
function showHelp() {
|
|
18
|
+
console.log(`
|
|
19
|
+
Magector — Semantic code search for Magento 2
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
npx magector init [path] Full setup: index + IDE config
|
|
23
|
+
npx magector index [path] Index (or re-index) Magento codebase
|
|
24
|
+
npx magector search <query> Search indexed code
|
|
25
|
+
npx magector mcp Start MCP server (for Claude Code / Cursor)
|
|
26
|
+
npx magector stats Show index statistics
|
|
27
|
+
npx magector setup [path] IDE setup only (no indexing)
|
|
28
|
+
npx magector help Show this help
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
-l, --limit <n> Number of search results (default: 10)
|
|
32
|
+
-f, --format <fmt> Output format: text, json (default: text)
|
|
33
|
+
|
|
34
|
+
Environment Variables:
|
|
35
|
+
MAGENTO_ROOT Path to Magento installation (default: cwd)
|
|
36
|
+
MAGECTOR_DB Path to index database (default: ./magector.db)
|
|
37
|
+
MAGECTOR_BIN Path to magector-core binary
|
|
38
|
+
MAGECTOR_MODELS Path to ONNX model directory
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
npx magector init /var/www/magento
|
|
42
|
+
npx magector search "product price calculation"
|
|
43
|
+
npx magector search "checkout controller" -l 20
|
|
44
|
+
npx magector index
|
|
45
|
+
npx magector mcp
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getConfig() {
|
|
50
|
+
return {
|
|
51
|
+
dbPath: process.env.MAGECTOR_DB || './magector.db',
|
|
52
|
+
magentoRoot: process.env.MAGENTO_ROOT || process.cwd()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseArgs(argv) {
|
|
57
|
+
const opts = {};
|
|
58
|
+
for (let i = 0; i < argv.length; i++) {
|
|
59
|
+
if (argv[i] === '-l' || argv[i] === '--limit') {
|
|
60
|
+
opts.limit = argv[++i];
|
|
61
|
+
} else if (argv[i] === '-f' || argv[i] === '--format') {
|
|
62
|
+
opts.format = argv[++i];
|
|
63
|
+
} else if (argv[i] === '-v' || argv[i] === '--verbose') {
|
|
64
|
+
opts.verbose = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return opts;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runIndex(targetPath) {
|
|
71
|
+
const config = getConfig();
|
|
72
|
+
const root = targetPath || config.magentoRoot;
|
|
73
|
+
const binary = resolveBinary();
|
|
74
|
+
const modelPath = await ensureModels();
|
|
75
|
+
|
|
76
|
+
console.log(`\nIndexing: ${path.resolve(root)}`);
|
|
77
|
+
console.log(`Database: ${path.resolve(config.dbPath)}\n`);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const output = execFileSync(binary, [
|
|
81
|
+
'index',
|
|
82
|
+
'-m', path.resolve(root),
|
|
83
|
+
'-d', path.resolve(config.dbPath),
|
|
84
|
+
'-c', modelPath
|
|
85
|
+
], { encoding: 'utf-8', timeout: 600000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
86
|
+
if (output.trim()) console.log(output.trim());
|
|
87
|
+
console.log('\nIndexing complete.');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
const output = err.stderr || err.stdout || err.message;
|
|
90
|
+
console.error(`Indexing error: ${output}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function runSearch(query, opts = {}) {
|
|
96
|
+
const config = getConfig();
|
|
97
|
+
const binary = resolveBinary();
|
|
98
|
+
const modelPath = resolveModels();
|
|
99
|
+
|
|
100
|
+
if (!modelPath) {
|
|
101
|
+
console.error('ONNX model not found. Run `npx magector init` or `npx magector index` first.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const searchArgs = [
|
|
106
|
+
'search', query,
|
|
107
|
+
'-d', path.resolve(config.dbPath),
|
|
108
|
+
'-c', modelPath,
|
|
109
|
+
'-l', String(opts.limit || 10),
|
|
110
|
+
'-f', opts.format || 'text'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const output = execFileSync(binary, searchArgs, {
|
|
115
|
+
encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe']
|
|
116
|
+
});
|
|
117
|
+
console.log(output);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const output = err.stderr || err.stdout || err.message;
|
|
120
|
+
console.error(`Search error: ${output}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function runStats() {
|
|
126
|
+
const config = getConfig();
|
|
127
|
+
const binary = resolveBinary();
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const output = execFileSync(binary, [
|
|
131
|
+
'stats', '-d', path.resolve(config.dbPath)
|
|
132
|
+
], { encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
133
|
+
console.log(output);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
const output = err.stderr || err.stdout || err.message;
|
|
136
|
+
console.error(`Stats error: ${output}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function main() {
|
|
142
|
+
switch (command) {
|
|
143
|
+
case 'init':
|
|
144
|
+
await init(args[1]);
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'index':
|
|
148
|
+
await runIndex(args[1]);
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'search': {
|
|
152
|
+
const query = args.slice(1).filter(a => !a.startsWith('-')).join(' ');
|
|
153
|
+
if (!query) {
|
|
154
|
+
console.error('Usage: npx magector search <query>');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const opts = parseArgs(args.slice(1));
|
|
158
|
+
runSearch(query, opts);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'mcp':
|
|
163
|
+
await import('./mcp-server.js');
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case 'stats':
|
|
167
|
+
runStats();
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 'setup':
|
|
171
|
+
await setup(args[1]);
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case 'validate': {
|
|
175
|
+
const { runFullValidation } = await import('./validation/validator.js');
|
|
176
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
177
|
+
const keepData = args.includes('--keep');
|
|
178
|
+
await runFullValidation({ verbose, keepTestData: keepData });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'benchmark':
|
|
183
|
+
await import('./validation/benchmark.js');
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case 'help':
|
|
187
|
+
case '--help':
|
|
188
|
+
case '-h':
|
|
189
|
+
case undefined:
|
|
190
|
+
showHelp();
|
|
191
|
+
break;
|
|
192
|
+
|
|
193
|
+
default:
|
|
194
|
+
console.error(`Unknown command: ${command}`);
|
|
195
|
+
showHelp();
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main().catch(err => {
|
|
201
|
+
console.error('Error:', err.message);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
package/src/init.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full init command: verify Magento project, index, detect IDE, write configs.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'fs';
|
|
5
|
+
import { execFileSync } from 'child_process';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { resolveBinary } from './binary.js';
|
|
8
|
+
import { ensureModels } from './model.js';
|
|
9
|
+
import { CURSORRULES } from './templates/cursorrules.js';
|
|
10
|
+
import { CLAUDE_MD } from './templates/claude-md.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect if the given path is a Magento 2 project root.
|
|
14
|
+
*/
|
|
15
|
+
function isMagentoProject(projectPath) {
|
|
16
|
+
// Check app/etc/env.php
|
|
17
|
+
if (existsSync(path.join(projectPath, 'app', 'etc', 'env.php'))) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Check composer.json for magento packages
|
|
21
|
+
const composerPath = path.join(projectPath, 'composer.json');
|
|
22
|
+
if (existsSync(composerPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const content = readFileSync(composerPath, 'utf-8');
|
|
25
|
+
if (content.includes('magento/') || content.includes('"magento-')) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// ignore read errors
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detect which IDEs are present.
|
|
37
|
+
* Returns { cursor: boolean, claude: boolean }
|
|
38
|
+
*/
|
|
39
|
+
function detectIDEs(projectPath) {
|
|
40
|
+
const cursor =
|
|
41
|
+
existsSync(path.join(projectPath, '.cursor')) ||
|
|
42
|
+
existsSync(path.join(projectPath, '.cursorrules'));
|
|
43
|
+
const claude =
|
|
44
|
+
existsSync(path.join(projectPath, '.claude')) ||
|
|
45
|
+
existsSync(path.join(projectPath, 'CLAUDE.md')) ||
|
|
46
|
+
existsSync(path.join(projectPath, '.mcp.json'));
|
|
47
|
+
return { cursor, claude };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Write MCP server configuration for the given IDE(s).
|
|
52
|
+
*/
|
|
53
|
+
function writeMcpConfig(projectPath, ides, dbPath) {
|
|
54
|
+
const mcpConfig = {
|
|
55
|
+
mcpServers: {
|
|
56
|
+
magector: {
|
|
57
|
+
command: 'npx',
|
|
58
|
+
args: ['-y', 'magector', 'mcp'],
|
|
59
|
+
env: {
|
|
60
|
+
MAGENTO_ROOT: projectPath,
|
|
61
|
+
MAGECTOR_DB: dbPath
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const configJson = JSON.stringify(mcpConfig, null, 2);
|
|
68
|
+
const written = [];
|
|
69
|
+
|
|
70
|
+
if (ides.cursor) {
|
|
71
|
+
const cursorDir = path.join(projectPath, '.cursor');
|
|
72
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
73
|
+
writeFileSync(path.join(cursorDir, 'mcp.json'), configJson);
|
|
74
|
+
written.push('.cursor/mcp.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ides.claude) {
|
|
78
|
+
writeFileSync(path.join(projectPath, '.mcp.json'), configJson);
|
|
79
|
+
written.push('.mcp.json');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If neither detected, set up both
|
|
83
|
+
if (!ides.cursor && !ides.claude) {
|
|
84
|
+
writeFileSync(path.join(projectPath, '.mcp.json'), configJson);
|
|
85
|
+
written.push('.mcp.json');
|
|
86
|
+
const cursorDir = path.join(projectPath, '.cursor');
|
|
87
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
88
|
+
writeFileSync(path.join(cursorDir, 'mcp.json'), configJson);
|
|
89
|
+
written.push('.cursor/mcp.json');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return written;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Write IDE rules files.
|
|
97
|
+
*/
|
|
98
|
+
function writeRules(projectPath, ides) {
|
|
99
|
+
const written = [];
|
|
100
|
+
|
|
101
|
+
const writeCursor = ides.cursor || (!ides.cursor && !ides.claude);
|
|
102
|
+
const writeClaude = ides.claude || (!ides.cursor && !ides.claude);
|
|
103
|
+
|
|
104
|
+
if (writeCursor) {
|
|
105
|
+
const rulesPath = path.join(projectPath, '.cursorrules');
|
|
106
|
+
if (!existsSync(rulesPath)) {
|
|
107
|
+
writeFileSync(rulesPath, CURSORRULES);
|
|
108
|
+
written.push('.cursorrules (created)');
|
|
109
|
+
} else {
|
|
110
|
+
const existing = readFileSync(rulesPath, 'utf-8');
|
|
111
|
+
if (!existing.includes('Magector')) {
|
|
112
|
+
appendFileSync(rulesPath, '\n\n' + CURSORRULES);
|
|
113
|
+
written.push('.cursorrules (appended)');
|
|
114
|
+
} else {
|
|
115
|
+
written.push('.cursorrules (already configured)');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (writeClaude) {
|
|
121
|
+
const claudePath = path.join(projectPath, 'CLAUDE.md');
|
|
122
|
+
if (!existsSync(claudePath)) {
|
|
123
|
+
writeFileSync(claudePath, CLAUDE_MD);
|
|
124
|
+
written.push('CLAUDE.md (created)');
|
|
125
|
+
} else {
|
|
126
|
+
const existing = readFileSync(claudePath, 'utf-8');
|
|
127
|
+
if (!existing.includes('Magector')) {
|
|
128
|
+
appendFileSync(claudePath, '\n\n' + CLAUDE_MD);
|
|
129
|
+
written.push('CLAUDE.md (appended)');
|
|
130
|
+
} else {
|
|
131
|
+
written.push('CLAUDE.md (already configured)');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return written;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Add magector.db to .gitignore if not already present.
|
|
141
|
+
*/
|
|
142
|
+
function updateGitignore(projectPath) {
|
|
143
|
+
const giPath = path.join(projectPath, '.gitignore');
|
|
144
|
+
if (existsSync(giPath)) {
|
|
145
|
+
const content = readFileSync(giPath, 'utf-8');
|
|
146
|
+
if (!content.includes('magector.db')) {
|
|
147
|
+
appendFileSync(giPath, '\n# Magector index\nmagector.db\n');
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
writeFileSync(giPath, '# Magector index\nmagector.db\n');
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Main init function.
|
|
158
|
+
*/
|
|
159
|
+
export async function init(projectPath) {
|
|
160
|
+
projectPath = path.resolve(projectPath || process.cwd());
|
|
161
|
+
const dbPath = path.join(projectPath, 'magector.db');
|
|
162
|
+
|
|
163
|
+
console.log('\nMagector Init\n');
|
|
164
|
+
|
|
165
|
+
// 1. Verify Magento project
|
|
166
|
+
console.log('Checking Magento project...');
|
|
167
|
+
if (!isMagentoProject(projectPath)) {
|
|
168
|
+
console.error(
|
|
169
|
+
`Error: ${projectPath} does not appear to be a Magento 2 project.\n` +
|
|
170
|
+
`Expected app/etc/env.php or composer.json with "magento/" dependencies.`
|
|
171
|
+
);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
console.log(` Magento project: ${projectPath}`);
|
|
175
|
+
|
|
176
|
+
// 2. Resolve binary
|
|
177
|
+
console.log('\nResolving binary...');
|
|
178
|
+
let binary;
|
|
179
|
+
try {
|
|
180
|
+
binary = resolveBinary();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
console.error(`Error: ${err.message}`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
console.log(` Binary: ${binary}`);
|
|
186
|
+
|
|
187
|
+
// 3. Ensure ONNX model
|
|
188
|
+
console.log('\nChecking ONNX model...');
|
|
189
|
+
let modelPath;
|
|
190
|
+
try {
|
|
191
|
+
modelPath = await ensureModels();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error(`Error downloading model: ${err.message}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
console.log(` Models: ${modelPath}`);
|
|
197
|
+
|
|
198
|
+
// 4. Run indexing
|
|
199
|
+
console.log('\nIndexing codebase...');
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
try {
|
|
202
|
+
const output = execFileSync(binary, [
|
|
203
|
+
'index',
|
|
204
|
+
'-m', projectPath,
|
|
205
|
+
'-d', dbPath,
|
|
206
|
+
'-c', modelPath
|
|
207
|
+
], { encoding: 'utf-8', timeout: 600000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
208
|
+
if (output.trim()) {
|
|
209
|
+
console.log(output.trim().split('\n').map(l => ` ${l}`).join('\n'));
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
const output = err.stderr || err.stdout || err.message;
|
|
213
|
+
console.error(`Indexing error: ${output}`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
217
|
+
|
|
218
|
+
// 5. Detect IDE
|
|
219
|
+
console.log('\nDetecting IDE...');
|
|
220
|
+
const ides = detectIDEs(projectPath);
|
|
221
|
+
const ideNames = [];
|
|
222
|
+
if (ides.cursor) ideNames.push('Cursor');
|
|
223
|
+
if (ides.claude) ideNames.push('Claude Code');
|
|
224
|
+
if (ideNames.length === 0) ideNames.push('Cursor', 'Claude Code');
|
|
225
|
+
console.log(` Detected: ${ideNames.join(' + ') || 'none (configuring both)'}`);
|
|
226
|
+
|
|
227
|
+
// 6. Write MCP config
|
|
228
|
+
console.log('\nWriting MCP config...');
|
|
229
|
+
const mcpFiles = writeMcpConfig(projectPath, ides, dbPath);
|
|
230
|
+
mcpFiles.forEach(f => console.log(` ${f}`));
|
|
231
|
+
|
|
232
|
+
// 7. Write rules
|
|
233
|
+
console.log('\nWriting IDE rules...');
|
|
234
|
+
const rulesFiles = writeRules(projectPath, ides);
|
|
235
|
+
rulesFiles.forEach(f => console.log(` ${f}`));
|
|
236
|
+
|
|
237
|
+
// 8. Update .gitignore
|
|
238
|
+
const giUpdated = updateGitignore(projectPath);
|
|
239
|
+
if (giUpdated) {
|
|
240
|
+
console.log('\nUpdated .gitignore with magector.db');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 9. Get stats and print summary
|
|
244
|
+
let vectorCount = '?';
|
|
245
|
+
try {
|
|
246
|
+
const statsOutput = execFileSync(binary, ['stats', '-d', dbPath], {
|
|
247
|
+
encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe']
|
|
248
|
+
});
|
|
249
|
+
const match = statsOutput.match(/Total vectors:\s*(\d+)/);
|
|
250
|
+
if (match) vectorCount = match[1];
|
|
251
|
+
} catch {
|
|
252
|
+
// ignore
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log(`\n${'='.repeat(50)}`);
|
|
256
|
+
console.log(`Setup complete!`);
|
|
257
|
+
console.log(` Indexed ${vectorCount} vectors in ${elapsed}s`);
|
|
258
|
+
console.log(` Configured for: ${ideNames.join(' + ')}`);
|
|
259
|
+
console.log(` Database: ${dbPath}`);
|
|
260
|
+
console.log(`\nTest it:`);
|
|
261
|
+
console.log(` npx magector search "product price calculation"`);
|
|
262
|
+
console.log(`${'='.repeat(50)}\n`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* IDE setup only (no indexing). For projects already indexed.
|
|
267
|
+
*/
|
|
268
|
+
export async function setup(projectPath) {
|
|
269
|
+
projectPath = path.resolve(projectPath || process.cwd());
|
|
270
|
+
const dbPath = path.join(projectPath, 'magector.db');
|
|
271
|
+
|
|
272
|
+
console.log('\nMagector IDE Setup\n');
|
|
273
|
+
|
|
274
|
+
const ides = detectIDEs(projectPath);
|
|
275
|
+
const ideNames = [];
|
|
276
|
+
if (ides.cursor) ideNames.push('Cursor');
|
|
277
|
+
if (ides.claude) ideNames.push('Claude Code');
|
|
278
|
+
if (ideNames.length === 0) ideNames.push('Cursor', 'Claude Code');
|
|
279
|
+
|
|
280
|
+
console.log(`Detected: ${ideNames.join(' + ')}`);
|
|
281
|
+
|
|
282
|
+
const mcpFiles = writeMcpConfig(projectPath, ides, dbPath);
|
|
283
|
+
console.log('\nMCP config:');
|
|
284
|
+
mcpFiles.forEach(f => console.log(` ${f}`));
|
|
285
|
+
|
|
286
|
+
const rulesFiles = writeRules(projectPath, ides);
|
|
287
|
+
console.log('\nIDE rules:');
|
|
288
|
+
rulesFiles.forEach(f => console.log(` ${f}`));
|
|
289
|
+
|
|
290
|
+
updateGitignore(projectPath);
|
|
291
|
+
|
|
292
|
+
console.log(`\nDone. Configured for: ${ideNames.join(' + ')}\n`);
|
|
293
|
+
}
|