crg-dev-kit 1.0.0 → 2.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/README.md +51 -90
- package/bin/cli.js +14 -214
- package/lib/actions.js +127 -0
- package/lib/analytics.js +14 -11
- package/lib/paths.js +13 -0
- package/lib/roi.js +1 -9
- package/package.json +1 -1
- package/server.js +521 -240
- package/bin/tutorial.js +0 -198
package/README.md
CHANGED
|
@@ -1,111 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# code-review-graph — Dev Kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AI knowledge graph for your codebase. Gives your AI assistant (Claude Code, Cursor, etc.) structural understanding — callers, dependencies, test coverage, blast radius — without reading every file.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Check installation status
|
|
15
|
-
npx crg-dev-kit status
|
|
7
|
+
### Windows (PowerShell)
|
|
8
|
+
```powershell
|
|
9
|
+
cd your-project
|
|
10
|
+
.\setup-crg.ps1 # basic install
|
|
11
|
+
.\setup-crg.ps1 -WithCommunities # + architecture view
|
|
12
|
+
.\setup-crg.ps1 -WithAll # + semantic search (2GB download)
|
|
16
13
|
```
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| `npx crg-dev-kit start --no-open` | Dashboard without auto-opening browser |
|
|
26
|
-
| `npx crg-dev-kit status` | Check if CRG is installed in current project |
|
|
27
|
-
| `npx crg-dev-kit uninstall` | Remove CRG files from current project |
|
|
28
|
-
| `npx crg-dev-kit help` | Show all commands |
|
|
29
|
-
|
|
30
|
-
## What You Get
|
|
31
|
-
|
|
32
|
-
### Setup Scripts
|
|
33
|
-
- **Linux/macOS/WSL**: `bash setup-crg.sh --with-communities`
|
|
34
|
-
- **Windows**: `.\setup-crg.ps1 -WithCommunities`
|
|
35
|
-
- **Health check**: `bash check-crg.sh`
|
|
36
|
-
- **AI config**: `CLAUDE.md` (drop in project root)
|
|
37
|
-
- **Reference**: `crg-cheatsheet.pdf`
|
|
38
|
-
|
|
39
|
-
### Token Analytics Dashboard
|
|
40
|
-
|
|
41
|
-
The dashboard at `http://localhost:8742` shows:
|
|
42
|
-
- **Token savings %** — average reduction vs reading full source files
|
|
43
|
-
- **Cost estimates** — approximate $ savings at current LLM pricing
|
|
44
|
-
- **Per-project breakdown** — sessions, files reviewed, savings by project
|
|
45
|
-
- **Tool usage** — which CRG tools your team uses most
|
|
46
|
-
|
|
47
|
-
Data stored locally in `~/.crg-analytics/` — zero telemetry.
|
|
48
|
-
|
|
49
|
-
### REST API
|
|
50
|
-
|
|
51
|
-
The dashboard exposes endpoints for programmatic access:
|
|
15
|
+
### Linux / macOS / WSL
|
|
16
|
+
```bash
|
|
17
|
+
cd your-project
|
|
18
|
+
bash setup-crg.sh # basic install
|
|
19
|
+
bash setup-crg.sh --with-communities # + architecture view
|
|
20
|
+
bash setup-crg.sh --with-all # + semantic search (2GB download)
|
|
21
|
+
```
|
|
52
22
|
|
|
23
|
+
### Health Check
|
|
53
24
|
```bash
|
|
54
|
-
#
|
|
55
|
-
curl http://localhost:8742/api/analytics
|
|
56
|
-
|
|
57
|
-
# Download analytics report (Markdown)
|
|
58
|
-
curl http://localhost:8742/api/report
|
|
59
|
-
|
|
60
|
-
# Start a new session
|
|
61
|
-
curl -X POST http://localhost:8742/api/session \
|
|
62
|
-
-H 'Content-Type: application/json' \
|
|
63
|
-
-d '{"project": "/path/to/repo", "operation": "code_review"}'
|
|
64
|
-
|
|
65
|
-
# Log tool usage
|
|
66
|
-
curl -X POST http://localhost:8742/api/session/{sessionId} \
|
|
67
|
-
-H 'Content-Type: application/json' \
|
|
68
|
-
-d '{"tool": "detect_changes", "count": 1}'
|
|
69
|
-
|
|
70
|
-
# End session
|
|
71
|
-
curl -X POST http://localhost:8742/api/session/{sessionId} \
|
|
72
|
-
-H 'Content-Type: application/json' \
|
|
73
|
-
-d '{"filesReviewed": 12, "avgLinesPerFile": 150}'
|
|
25
|
+
bash check-crg.sh # run anytime to verify
|
|
74
26
|
```
|
|
75
27
|
|
|
76
|
-
##
|
|
28
|
+
## What's in this kit
|
|
77
29
|
|
|
78
|
-
|
|
30
|
+
| File | Purpose |
|
|
31
|
+
|------|---------|
|
|
32
|
+
| `setup-crg.sh` | Setup script for Linux/macOS/WSL |
|
|
33
|
+
| `setup-crg.ps1` | Setup script for Windows |
|
|
34
|
+
| `check-crg.sh` | Health check (run anytime) |
|
|
35
|
+
| `CLAUDE.md` | Drop into project root — tells AI which tools to use |
|
|
36
|
+
| `crg-cheatsheet.pdf` | One-page visual reference |
|
|
37
|
+
| `.github/workflows/test-crg.yml` | CI workflow to validate on Windows + Ubuntu |
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
# Everyone runs this in their project
|
|
82
|
-
npx crg-dev-kit install
|
|
83
|
-
bash setup-crg.sh --with-communities
|
|
84
|
-
```
|
|
39
|
+
## What the setup script does
|
|
85
40
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
41
|
+
1. Detects OS and Python version
|
|
42
|
+
2. Installs `code-review-graph` (pip on Windows, pipx on Linux)
|
|
43
|
+
3. Configures MCP server for your AI tool
|
|
44
|
+
4. Builds the knowledge graph
|
|
45
|
+
5. Optionally installs extras (communities, embeddings)
|
|
46
|
+
6. Runs community detection workaround (v2.1.0 bug)
|
|
47
|
+
7. Adds `.code-review-graph/` to `.gitignore`
|
|
48
|
+
8. Runs health check
|
|
90
49
|
|
|
91
|
-
##
|
|
50
|
+
## What works after setup
|
|
92
51
|
|
|
93
|
-
|
|
52
|
+
**13 tools work immediately** — code review, search, impact analysis, refactoring.
|
|
94
53
|
|
|
95
|
-
|
|
96
|
-
|----------|:---:|:---:|:---:|
|
|
97
|
-
| 200-file project review | ~150k tokens | ~25k tokens | 6x |
|
|
98
|
-
| Incremental review | ~150k tokens | ~8k tokens | 19x |
|
|
99
|
-
| PR review | ~100k tokens | ~15k tokens | 6.7x |
|
|
54
|
+
**5 tools need `--with-communities`** — architecture overview, module clusters.
|
|
100
55
|
|
|
101
|
-
|
|
56
|
+
**3 tools are broken in v2.1.0** — flow analysis (trace_flows TypeError). Skip these.
|
|
102
57
|
|
|
103
|
-
##
|
|
58
|
+
## Known Issues (v2.1.0)
|
|
104
59
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
60
|
+
| Issue | Impact | Workaround |
|
|
61
|
+
|-------|--------|------------|
|
|
62
|
+
| `architecture_overview` returns empty | Misleading — no error | Install communities extra |
|
|
63
|
+
| `build` doesn't trigger Leiden | Communities stay 0 | Setup script handles this automatically |
|
|
64
|
+
| `trace_flows` TypeError | Flows completely broken | No fix — wait for v2.2+ |
|
|
65
|
+
| Over-clustering (400+ for 500 files) | Token overflow | Use `min_size=20` param |
|
|
66
|
+
| `crg` is wrong package name | Installs different package | Always use `code-review-graph` |
|
|
67
|
+
| PEP 668 on Linux | pip blocked | Setup script auto-detects, uses pipx |
|
|
68
|
+
| MCP server caches old env | Extras don't activate | Restart AI tool after installing extras |
|
|
108
69
|
|
|
109
|
-
##
|
|
70
|
+
## For CI
|
|
110
71
|
|
|
111
|
-
|
|
72
|
+
Copy `.github/workflows/test-crg.yml` into your repo. Tests install + build + communities on both Windows and Ubuntu. Trigger manually from Actions tab.
|
package/bin/cli.js
CHANGED
|
@@ -1,168 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { spawn, execFile } = require('child_process');
|
|
3
2
|
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
3
|
const pkg = require('../package.json');
|
|
8
|
-
|
|
4
|
+
|
|
9
5
|
const args = process.argv.slice(2);
|
|
10
6
|
const cmd = args[0];
|
|
11
|
-
const roi = require('../lib/roi');
|
|
12
|
-
|
|
13
|
-
const NO_ROI = args.includes('--no-roi');
|
|
14
7
|
|
|
15
8
|
function printHelp() {
|
|
16
9
|
console.log(`
|
|
17
|
-
\x1b[36mCRG Dev Kit\x1b[0m —
|
|
10
|
+
\x1b[36mCRG Dev Kit\x1b[0m v${pkg.version} — AI-powered code review setup
|
|
18
11
|
|
|
19
12
|
\x1b[1mUsage:\x1b[0m
|
|
20
|
-
npx crg-dev-kit [
|
|
21
|
-
|
|
22
|
-
\x1b[1mCommands:\x1b[0m
|
|
23
|
-
install, setup Copy setup scripts to current project
|
|
24
|
-
start, serve Launch the dashboard (default)
|
|
25
|
-
uninstall Remove CRG files from current project
|
|
26
|
-
status Check if CRG is installed in current project
|
|
27
|
-
roi Show ROI report (token savings)
|
|
28
|
-
tutorial Launch interactive tutorial
|
|
29
|
-
help Show this help message
|
|
30
|
-
version Show version
|
|
13
|
+
npx crg-dev-kit [options]
|
|
31
14
|
|
|
32
15
|
\x1b[1mOptions:\x1b[0m
|
|
33
16
|
--port <number> Dashboard port (default: 8742)
|
|
34
17
|
--no-open Don't auto-open browser
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
help Show this help message
|
|
19
|
+
version Show version
|
|
37
20
|
|
|
38
|
-
\x1b[
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
npx crg-dev-kit status # Check installation
|
|
21
|
+
\x1b[1mHow it works:\x1b[0m
|
|
22
|
+
Run \x1b[36mnpx crg-dev-kit\x1b[0m to open the dashboard.
|
|
23
|
+
Everything happens in the browser — install, status,
|
|
24
|
+
analytics, downloads, and documentation.
|
|
43
25
|
`);
|
|
44
26
|
}
|
|
45
27
|
|
|
46
|
-
|
|
28
|
+
if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
29
|
+
printHelp();
|
|
30
|
+
} else if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
47
31
|
console.log(`crg-dev-kit v${pkg.version}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function copyFiles() {
|
|
51
|
-
const cwd = process.cwd();
|
|
52
|
-
const files = [
|
|
53
|
-
{ name: 'setup-crg.sh', executable: true },
|
|
54
|
-
{ name: 'setup-crg.ps1', executable: false },
|
|
55
|
-
{ name: 'check-crg.sh', executable: true },
|
|
56
|
-
{ name: 'CLAUDE.md', executable: false },
|
|
57
|
-
{ name: 'crg-cheatsheet.pdf', executable: false },
|
|
58
|
-
{ name: 'README.md', executable: false },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
let copied = 0;
|
|
62
|
-
files.forEach(f => {
|
|
63
|
-
const src = path.join(ASSETS, f.name);
|
|
64
|
-
const dst = path.join(cwd, f.name);
|
|
65
|
-
if (fs.existsSync(src)) {
|
|
66
|
-
if (fs.existsSync(dst)) {
|
|
67
|
-
console.log(` \x1b[33m~\x1b[0m ${f.name} \x1b[2m(already exists, skipping)\x1b[0m`);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
fs.copyFileSync(src, dst);
|
|
71
|
-
if (f.executable) {
|
|
72
|
-
try { fs.chmodSync(dst, '755'); } catch {}
|
|
73
|
-
}
|
|
74
|
-
console.log(` \x1b[32m✓\x1b[0m ${f.name}`);
|
|
75
|
-
copied++;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Track install date (only if --no-roi not set)
|
|
80
|
-
if (!NO_ROI) {
|
|
81
|
-
roi.setInstallDate(cwd);
|
|
82
|
-
console.log('\n \x1b[36m✓ Install date recorded for ROI tracking\x1b[0m');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (copied === 0) {
|
|
86
|
-
console.log('\n \x1b[33mFiles already exist, install date updated.\x1b[0m');
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const isWin = process.platform === 'win32';
|
|
91
|
-
console.log(`\n \x1b[32m${copied} file(s) copied to ${cwd}\x1b[0m\n`);
|
|
92
|
-
console.log(' \x1b[1mNext steps:\x1b[0m');
|
|
93
|
-
if (isWin) {
|
|
94
|
-
console.log(' .\\setup-crg.ps1 -WithCommunities');
|
|
95
|
-
console.log(' code-review-graph status');
|
|
96
|
-
} else {
|
|
97
|
-
console.log(' bash setup-crg.sh --with-communities');
|
|
98
|
-
console.log(' bash check-crg.sh');
|
|
99
|
-
}
|
|
100
|
-
console.log('');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function checkStatus() {
|
|
104
|
-
const cwd = process.cwd();
|
|
105
|
-
const checks = [
|
|
106
|
-
{ file: 'setup-crg.sh', label: 'Linux/macOS setup script' },
|
|
107
|
-
{ file: 'setup-crg.ps1', label: 'Windows setup script' },
|
|
108
|
-
{ file: 'check-crg.sh', label: 'Health check script' },
|
|
109
|
-
{ file: 'CLAUDE.md', label: 'AI configuration' },
|
|
110
|
-
];
|
|
111
|
-
|
|
112
|
-
console.log('\n \x1b[36mCRG Installation Status\x1b[0m\n');
|
|
113
|
-
let found = 0;
|
|
114
|
-
checks.forEach(c => {
|
|
115
|
-
const exists = fs.existsSync(path.join(cwd, c.file));
|
|
116
|
-
if (exists) found++;
|
|
117
|
-
console.log(` ${exists ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m'} ${c.label}`);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Check if code-review-graph is installed
|
|
121
|
-
const checkCRG = () => {
|
|
122
|
-
return new Promise(resolve => {
|
|
123
|
-
execFile('code-review-graph', ['--version'], (err) => {
|
|
124
|
-
resolve(!err);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
checkCRG().then(installed => {
|
|
130
|
-
console.log(` ${installed ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m'} code-review-graph CLI`);
|
|
131
|
-
const total = checks.length + 1;
|
|
132
|
-
const score = found + (installed ? 1 : 0);
|
|
133
|
-
console.log(`\n ${score}/${total} components present`);
|
|
134
|
-
if (score === total) {
|
|
135
|
-
console.log(' \x1b[32m✓ Fully set up!\x1b[0m\n');
|
|
136
|
-
} else if (score >= 3) {
|
|
137
|
-
console.log(' \x1b[33m~ Partially set up. Run install command.\x1b[0m\n');
|
|
138
|
-
} else {
|
|
139
|
-
console.log(' \x1b[31m✗ Not set up. Run: npx crg-dev-kit install\x1b[0m\n');
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function uninstallFiles() {
|
|
145
|
-
const cwd = process.cwd();
|
|
146
|
-
const files = ['setup-crg.sh', 'setup-crg.ps1', 'check-crg.sh', 'CLAUDE.md', 'crg-cheatsheet.pdf', 'README.md'];
|
|
147
|
-
let removed = 0;
|
|
148
|
-
|
|
149
|
-
files.forEach(f => {
|
|
150
|
-
const dst = path.join(cwd, f);
|
|
151
|
-
if (fs.existsSync(dst)) {
|
|
152
|
-
fs.unlinkSync(dst);
|
|
153
|
-
console.log(` \x1b[31m✗\x1b[0m ${f}`);
|
|
154
|
-
removed++;
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
if (removed === 0) {
|
|
159
|
-
console.log('\n \x1b[33mNo CRG files found to remove.\x1b[0m\n');
|
|
160
|
-
} else {
|
|
161
|
-
console.log(`\n \x1b[31m${removed} file(s) removed from ${cwd}\x1b[0m\n`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function startServer() {
|
|
32
|
+
} else {
|
|
166
33
|
let port = 8742;
|
|
167
34
|
let noOpen = false;
|
|
168
35
|
|
|
@@ -178,73 +45,6 @@ function startServer() {
|
|
|
178
45
|
if (args[i] === '--no-open') noOpen = true;
|
|
179
46
|
}
|
|
180
47
|
|
|
181
|
-
const
|
|
182
|
-
const server = require(serverPath);
|
|
48
|
+
const server = require('../server');
|
|
183
49
|
server.start(port, noOpen);
|
|
184
50
|
}
|
|
185
|
-
|
|
186
|
-
function showROI() {
|
|
187
|
-
const cwd = process.cwd();
|
|
188
|
-
const report = roi.generateROIReport(cwd);
|
|
189
|
-
console.log('\n' + report);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function runTutorial() {
|
|
193
|
-
const tutorialPath = path.join(__dirname, 'tutorial.js');
|
|
194
|
-
const tutorial = require(tutorialPath);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Command routing
|
|
198
|
-
switch (cmd) {
|
|
199
|
-
case 'install':
|
|
200
|
-
case 'setup':
|
|
201
|
-
copyFiles();
|
|
202
|
-
break;
|
|
203
|
-
|
|
204
|
-
case 'uninstall':
|
|
205
|
-
case 'remove':
|
|
206
|
-
uninstallFiles();
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case 'status':
|
|
210
|
-
case 'check':
|
|
211
|
-
checkStatus();
|
|
212
|
-
break;
|
|
213
|
-
|
|
214
|
-
case 'start':
|
|
215
|
-
case 'serve':
|
|
216
|
-
case 'dashboard':
|
|
217
|
-
startServer();
|
|
218
|
-
break;
|
|
219
|
-
|
|
220
|
-
case 'help':
|
|
221
|
-
case '--help':
|
|
222
|
-
case '-h':
|
|
223
|
-
printHelp();
|
|
224
|
-
break;
|
|
225
|
-
|
|
226
|
-
case 'version':
|
|
227
|
-
case '--version':
|
|
228
|
-
case '-v':
|
|
229
|
-
printVersion();
|
|
230
|
-
break;
|
|
231
|
-
|
|
232
|
-
case 'roi':
|
|
233
|
-
showROI();
|
|
234
|
-
break;
|
|
235
|
-
|
|
236
|
-
case 'tutorial':
|
|
237
|
-
runTutorial();
|
|
238
|
-
break;
|
|
239
|
-
|
|
240
|
-
default:
|
|
241
|
-
// No command or unknown command = start server
|
|
242
|
-
if (!cmd || cmd.startsWith('-')) {
|
|
243
|
-
startServer();
|
|
244
|
-
} else {
|
|
245
|
-
console.log(`\x1b[31mUnknown command: ${cmd}\x1b[0m`);
|
|
246
|
-
console.log('Run \x1b[1mnpx crg-dev-kit help\x1b[0m for usage.\n');
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
}
|
package/lib/actions.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFile } = require('child_process');
|
|
4
|
+
const { ANALYTICS_DIR } = require('./paths');
|
|
5
|
+
const roi = require('./roi');
|
|
6
|
+
|
|
7
|
+
const ASSETS = path.join(__dirname, '..', 'assets');
|
|
8
|
+
|
|
9
|
+
const INSTALL_FILES = [
|
|
10
|
+
{ name: 'setup-crg.sh', executable: true, label: 'Linux/macOS setup script' },
|
|
11
|
+
{ name: 'setup-crg.ps1', executable: false, label: 'Windows setup script' },
|
|
12
|
+
{ name: 'check-crg.sh', executable: true, label: 'Health check script' },
|
|
13
|
+
{ name: 'CLAUDE.md', executable: false, label: 'AI configuration' },
|
|
14
|
+
{ name: 'crg-cheatsheet.pdf', executable: false, label: 'Cheatsheet PDF' },
|
|
15
|
+
{ name: 'README.md', executable: false, label: 'Documentation' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function install(targetDir, trackROI = true) {
|
|
19
|
+
const results = [];
|
|
20
|
+
let copied = 0;
|
|
21
|
+
|
|
22
|
+
INSTALL_FILES.forEach(f => {
|
|
23
|
+
const src = path.join(ASSETS, f.name);
|
|
24
|
+
const dst = path.join(targetDir, f.name);
|
|
25
|
+
if (!fs.existsSync(src)) {
|
|
26
|
+
results.push({ file: f.name, status: 'missing_source' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (fs.existsSync(dst)) {
|
|
30
|
+
results.push({ file: f.name, status: 'skipped', reason: 'already exists' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fs.copyFileSync(src, dst);
|
|
34
|
+
if (f.executable) {
|
|
35
|
+
try { fs.chmodSync(dst, '755'); } catch {}
|
|
36
|
+
}
|
|
37
|
+
results.push({ file: f.name, status: 'copied' });
|
|
38
|
+
copied++;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (trackROI) {
|
|
42
|
+
roi.setInstallDate(targetDir);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { copied, total: INSTALL_FILES.length, results, targetDir };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function status(targetDir) {
|
|
49
|
+
const checks = INSTALL_FILES.slice(0, 4).map(f => ({
|
|
50
|
+
file: f.name,
|
|
51
|
+
label: f.label,
|
|
52
|
+
exists: fs.existsSync(path.join(targetDir, f.name))
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
return new Promise(resolve => {
|
|
56
|
+
execFile('code-review-graph', ['--version'], (err, stdout) => {
|
|
57
|
+
const crgInstalled = !err;
|
|
58
|
+
const crgVersion = crgInstalled ? (stdout || '').trim() : null;
|
|
59
|
+
const found = checks.filter(c => c.exists).length + (crgInstalled ? 1 : 0);
|
|
60
|
+
const total = checks.length + 1;
|
|
61
|
+
|
|
62
|
+
let verdict = 'not_setup';
|
|
63
|
+
if (found === total) verdict = 'ready';
|
|
64
|
+
else if (found >= 3) verdict = 'partial';
|
|
65
|
+
|
|
66
|
+
resolve({ checks, crgInstalled, crgVersion, found, total, verdict, targetDir });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function uninstall(targetDir) {
|
|
72
|
+
const files = INSTALL_FILES.map(f => f.name);
|
|
73
|
+
const existing = files.filter(f => fs.existsSync(path.join(targetDir, f)));
|
|
74
|
+
const removed = [];
|
|
75
|
+
|
|
76
|
+
existing.forEach(f => {
|
|
77
|
+
fs.unlinkSync(path.join(targetDir, f));
|
|
78
|
+
removed.push(f);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return { removed, targetDir };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function healthCheck(targetDir) {
|
|
85
|
+
const results = [];
|
|
86
|
+
|
|
87
|
+
// Check graph DB
|
|
88
|
+
const dbPath = path.join(targetDir, '.code-review-graph', 'graph.db');
|
|
89
|
+
results.push({ check: 'Graph DB exists', pass: fs.existsSync(dbPath) });
|
|
90
|
+
|
|
91
|
+
// Check setup files
|
|
92
|
+
results.push({ check: 'setup-crg.sh present', pass: fs.existsSync(path.join(targetDir, 'setup-crg.sh')) });
|
|
93
|
+
results.push({ check: 'CLAUDE.md present', pass: fs.existsSync(path.join(targetDir, 'CLAUDE.md')) });
|
|
94
|
+
|
|
95
|
+
return new Promise(resolve => {
|
|
96
|
+
execFile('code-review-graph', ['--version'], (err, stdout) => {
|
|
97
|
+
results.push({ check: 'code-review-graph CLI', pass: !err, version: err ? null : (stdout || '').trim() });
|
|
98
|
+
|
|
99
|
+
// Try getting graph stats
|
|
100
|
+
if (fs.existsSync(dbPath)) {
|
|
101
|
+
try {
|
|
102
|
+
const sqlite3 = require('child_process');
|
|
103
|
+
// Use python to read SQLite since we can't require sqlite3
|
|
104
|
+
const cmd = `python3 -c "import sqlite3,json;db=sqlite3.connect('${dbPath}');r=dict(nodes=db.execute('SELECT COUNT(*) FROM nodes').fetchone()[0],edges=db.execute('SELECT COUNT(*) FROM edges').fetchone()[0]);print(json.dumps(r))"`;
|
|
105
|
+
execFile('bash', ['-c', cmd], (err2, stdout2) => {
|
|
106
|
+
if (!err2 && stdout2) {
|
|
107
|
+
try {
|
|
108
|
+
const stats = JSON.parse(stdout2.trim());
|
|
109
|
+
results.push({ check: `Graph: ${stats.nodes} nodes, ${stats.edges} edges`, pass: stats.nodes > 0 });
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
const pass = results.filter(r => r.pass).length;
|
|
113
|
+
resolve({ results, pass, total: results.length, targetDir });
|
|
114
|
+
});
|
|
115
|
+
} catch {
|
|
116
|
+
const pass = results.filter(r => r.pass).length;
|
|
117
|
+
resolve({ results, pass, total: results.length, targetDir });
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
const pass = results.filter(r => r.pass).length;
|
|
121
|
+
resolve({ results, pass, total: results.length, targetDir });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { install, status, uninstall, healthCheck, INSTALL_FILES, ASSETS };
|
package/lib/analytics.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
const ANALYTICS_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.crg-analytics');
|
|
6
|
-
const SESSIONS_FILE = path.join(ANALYTICS_DIR, 'sessions.json');
|
|
7
|
-
const TOKEN_BASELINES = path.join(ANALYTICS_DIR, 'baselines.json');
|
|
4
|
+
const { ANALYTICS_DIR, SESSIONS_FILE, ensureDir } = require('./paths');
|
|
8
5
|
|
|
9
6
|
const TOKEN_ESTIMATES = {
|
|
10
7
|
read_file_per_100_lines: 75,
|
|
@@ -23,12 +20,6 @@ const TOKEN_ESTIMATES = {
|
|
|
23
20
|
get_review_context_tool: 400,
|
|
24
21
|
};
|
|
25
22
|
|
|
26
|
-
function ensureDir() {
|
|
27
|
-
if (!fs.existsSync(ANALYTICS_DIR)) {
|
|
28
|
-
fs.mkdirSync(ANALYTICS_DIR, { recursive: true });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
23
|
function readJSON(file, fallback) {
|
|
33
24
|
try {
|
|
34
25
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -60,10 +51,14 @@ function estimateTokensWithCRG(toolsUsed) {
|
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
function createSession(projectPath, operation) {
|
|
54
|
+
if (typeof projectPath !== 'string' || projectPath.length === 0 || projectPath.length > 500) {
|
|
55
|
+
throw new Error('Invalid project path');
|
|
56
|
+
}
|
|
57
|
+
projectPath = path.resolve(projectPath);
|
|
63
58
|
ensureDir();
|
|
64
59
|
const sessions = readJSON(SESSIONS_FILE, []);
|
|
65
60
|
const session = {
|
|
66
|
-
id: crypto.randomUUID(),
|
|
61
|
+
id: crypto.randomUUID ? crypto.randomUUID() : require('crypto').randomBytes(16).toString('hex'),
|
|
67
62
|
project: projectPath,
|
|
68
63
|
operation: operation || 'code_review',
|
|
69
64
|
startTime: new Date().toISOString(),
|
|
@@ -174,6 +169,13 @@ function getAllStats() {
|
|
|
174
169
|
const totalTokensWithCRG = projectStats.reduce((sum, p) => sum + p.totalTokensWithCRG, 0);
|
|
175
170
|
const avgSavings = projectStats.reduce((sum, p) => sum + p.avgSavingsPercent, 0) / projectStats.length;
|
|
176
171
|
|
|
172
|
+
const toolUsage = {};
|
|
173
|
+
projectStats.forEach(p => {
|
|
174
|
+
Object.entries(p.toolUsage).forEach(([tool, count]) => {
|
|
175
|
+
toolUsage[tool] = (toolUsage[tool] || 0) + count;
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
177
179
|
return {
|
|
178
180
|
totalProjects: projectPaths.length,
|
|
179
181
|
totalSessions: completedSessions.length,
|
|
@@ -182,6 +184,7 @@ function getAllStats() {
|
|
|
182
184
|
totalTokensSaved: totalTokensWithoutCRG - totalTokensWithCRG,
|
|
183
185
|
avgSavingsPercent: Math.round(avgSavings),
|
|
184
186
|
estimatedCostSavings: Math.round((totalTokensWithoutCRG - totalTokensWithCRG) / 1000 * 0.01 * 100) / 100,
|
|
187
|
+
toolUsage,
|
|
185
188
|
projects: projectStats
|
|
186
189
|
};
|
|
187
190
|
}
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const ANALYTICS_DIR = path.join(os.homedir(), '.crg-analytics');
|
|
6
|
+
const SESSIONS_FILE = path.join(ANALYTICS_DIR, 'sessions.json');
|
|
7
|
+
const HISTORY_FILE = path.join(os.homedir(), '.claude', 'history.jsonl');
|
|
8
|
+
|
|
9
|
+
function ensureDir() {
|
|
10
|
+
fs.mkdirSync(ANALYTICS_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { ANALYTICS_DIR, SESSIONS_FILE, HISTORY_FILE, ensureDir };
|
package/lib/roi.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const ANALYTICS_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.crg-analytics');
|
|
5
|
-
const HISTORY_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', 'history.jsonl');
|
|
6
|
-
|
|
7
|
-
function ensureDir() {
|
|
8
|
-
if (!fs.existsSync(ANALYTICS_DIR)) {
|
|
9
|
-
fs.mkdirSync(ANALYTICS_DIR, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
}
|
|
3
|
+
const { ANALYTICS_DIR, HISTORY_FILE, ensureDir } = require('./paths');
|
|
12
4
|
|
|
13
5
|
function getInstallDate(projectPath) {
|
|
14
6
|
const installFile = path.join(ANALYTICS_DIR, 'install.json');
|