@hongmaple0820/scale-engine 0.43.0 → 0.45.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.en.md +2 -2
- package/README.md +3 -3
- package/dist/api/cli.js +24 -2
- package/dist/api/cli.js.map +1 -1
- package/dist/api/mcp.js +86 -0
- package/dist/api/mcp.js.map +1 -1
- package/dist/codegraph/CodeIntelligence.d.ts +67 -0
- package/dist/codegraph/CodeIntelligence.js +457 -5
- package/dist/codegraph/CodeIntelligence.js.map +1 -1
- package/dist/cortex/SessionInjector.d.ts +1 -0
- package/dist/cortex/SessionInjector.js +33 -0
- package/dist/cortex/SessionInjector.js.map +1 -1
- package/dist/dashboard/DashboardServer.d.ts +33 -13
- package/dist/dashboard/DashboardServer.js +314 -182
- package/dist/dashboard/DashboardServer.js.map +1 -1
- package/dist/dashboard/index.d.ts +2 -2
- package/dist/dashboard/index.js +1 -1
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/server.d.ts +8 -22
- package/dist/dashboard/server.js +2 -83
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/MemoryBrain.d.ts +22 -0
- package/dist/memory/MemoryBrain.js +183 -4
- package/dist/memory/MemoryBrain.js.map +1 -1
- package/dist/memory/MemoryProviders.d.ts +6 -1
- package/dist/memory/MemoryProviders.js +190 -6
- package/dist/memory/MemoryProviders.js.map +1 -1
- package/dist/setup/SetupWizard.js +21 -7
- package/dist/setup/SetupWizard.js.map +1 -1
- package/dist/skills/SkillRepository.js +64 -1
- package/dist/skills/SkillRepository.js.map +1 -1
- package/dist/topology/DomainMapper.d.ts +23 -0
- package/dist/topology/DomainMapper.js +179 -0
- package/dist/topology/DomainMapper.js.map +1 -0
- package/dist/topology/LayerClassifier.d.ts +8 -0
- package/dist/topology/LayerClassifier.js +109 -0
- package/dist/topology/LayerClassifier.js.map +1 -0
- package/dist/topology/TourGenerator.d.ts +18 -0
- package/dist/topology/TourGenerator.js +120 -0
- package/dist/topology/TourGenerator.js.map +1 -0
- package/dist/topology/index.d.ts +3 -0
- package/dist/topology/index.js +4 -0
- package/dist/topology/index.js.map +1 -0
- package/docs/README.md +3 -0
- package/docs/architecture/README.md +248 -0
- package/docs/migration/v0.38-to-v0.44.md +232 -0
- package/docs/reference/cli.md +234 -0
- package/package.json +6 -5
- package/docs/EXTERNAL_REFERENCES.md +0 -66
- package/docs/SKILL-REPOSITORY.md +0 -57
- package/docs/SKILL_RADAR.md +0 -135
- package/docs/THIRD_PARTY_SKILLS.md +0 -114
|
@@ -24,6 +24,10 @@ const CODEGRAPH_PROJECT_INIT_HINT = 'codegraph init -i';
|
|
|
24
24
|
const CODEGRAPH_SERVE_COMMAND = 'codegraph serve --mcp';
|
|
25
25
|
const GRAPHIFY_SOURCE = 'https://github.com/safishamsi/graphify';
|
|
26
26
|
const GRAPHIFY_INSTALL_HINT = 'uv tool install graphify && graphify install --platform codex';
|
|
27
|
+
const CRG_SOURCE = 'https://github.com/tirth8205/code-review-graph';
|
|
28
|
+
const CRG_INSTALL_HINT = 'pip install code-review-graph';
|
|
29
|
+
const CRG_PROJECT_INIT_HINT = 'code-review-graph build';
|
|
30
|
+
const CRG_SERVE_COMMAND = 'code-review-graph serve';
|
|
27
31
|
const DEFAULT_CODEGRAPH_PROVIDER = {
|
|
28
32
|
id: 'codegraph',
|
|
29
33
|
type: 'external-cli',
|
|
@@ -44,6 +48,17 @@ const DEFAULT_GRAPHIFY_PROVIDER = {
|
|
|
44
48
|
source: GRAPHIFY_SOURCE,
|
|
45
49
|
installHint: GRAPHIFY_INSTALL_HINT,
|
|
46
50
|
};
|
|
51
|
+
const DEFAULT_CRG_PROVIDER = {
|
|
52
|
+
id: 'code-review-graph',
|
|
53
|
+
type: 'external-cli',
|
|
54
|
+
enabled: true,
|
|
55
|
+
command: 'code-review-graph',
|
|
56
|
+
capabilities: ['symbols', 'callers', 'callees', 'impact', 'context', 'summary', 'module-map'],
|
|
57
|
+
source: CRG_SOURCE,
|
|
58
|
+
installHint: CRG_INSTALL_HINT,
|
|
59
|
+
projectInitHint: CRG_PROJECT_INIT_HINT,
|
|
60
|
+
serveCommand: CRG_SERVE_COMMAND,
|
|
61
|
+
};
|
|
47
62
|
let execFileSyncImpl = childProcess.execFileSync;
|
|
48
63
|
export function setCodeIntelligenceExecFileSyncForTesting(impl) {
|
|
49
64
|
execFileSyncImpl = impl ?? childProcess.execFileSync;
|
|
@@ -54,6 +69,7 @@ export function defaultCodeIntelligenceConfig() {
|
|
|
54
69
|
providers: [
|
|
55
70
|
{ ...DEFAULT_CODEGRAPH_PROVIDER },
|
|
56
71
|
{ ...DEFAULT_GRAPHIFY_PROVIDER },
|
|
72
|
+
{ ...DEFAULT_CRG_PROVIDER },
|
|
57
73
|
],
|
|
58
74
|
fallback: {
|
|
59
75
|
enabled: true,
|
|
@@ -225,6 +241,314 @@ export function createCodeGraphRoiReport(options) {
|
|
|
225
241
|
evidenceLevel: report.fallbackUsed ? 'estimated' : 'measured',
|
|
226
242
|
};
|
|
227
243
|
}
|
|
244
|
+
export function dumpCodeGraphData(options) {
|
|
245
|
+
const projectDir = resolve(options.projectDir ?? process.cwd());
|
|
246
|
+
const status = inspectCodeIntelligence({ projectDir, scaleDir: options.scaleDir });
|
|
247
|
+
const warnings = [];
|
|
248
|
+
// Priority 1: artifact manifest (graph.json)
|
|
249
|
+
const artifactProvider = status.providers.find(p => p.available && p.type === 'artifact');
|
|
250
|
+
if (artifactProvider) {
|
|
251
|
+
const config = loadCodeIntelligenceConfig(projectDir, options.scaleDir).config;
|
|
252
|
+
const providerConfig = config.providers.find(p => p.id === artifactProvider.id);
|
|
253
|
+
if (providerConfig?.manifest) {
|
|
254
|
+
const result = dumpArtifactManifest(projectDir, providerConfig);
|
|
255
|
+
if (result.nodes.length > 0)
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Priority 2: codegraph CLI (full index)
|
|
260
|
+
const externalProvider = status.providers.find(p => p.available && p.type === 'external-cli' && p.id === 'codegraph');
|
|
261
|
+
if (externalProvider && status.projectIndexExists) {
|
|
262
|
+
const result = dumpExternalCodeGraph(projectDir);
|
|
263
|
+
if (result.nodes.length > 0)
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
// Priority 3: fallback file walk
|
|
267
|
+
return dumpFallbackTopology(projectDir);
|
|
268
|
+
}
|
|
269
|
+
function dumpArtifactManifest(projectDir, provider) {
|
|
270
|
+
if (!provider.manifest)
|
|
271
|
+
return emptyTopology(projectDir, provider.id);
|
|
272
|
+
const manifestPath = resolveManifestWithFallback(projectDir, provider.manifest);
|
|
273
|
+
if (!manifestPath)
|
|
274
|
+
return emptyTopology(projectDir, provider.id);
|
|
275
|
+
try {
|
|
276
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
277
|
+
const parsed = JSON.parse(content);
|
|
278
|
+
// Graphify format: nodes[] + links[]
|
|
279
|
+
if (Array.isArray(parsed.nodes) && (Array.isArray(parsed.links) || Array.isArray(parsed.edges))) {
|
|
280
|
+
return dumpGraphifyManifest(projectDir, provider.id, parsed);
|
|
281
|
+
}
|
|
282
|
+
// Traditional format: symbols[] + files[]
|
|
283
|
+
const symbols = Array.isArray(parsed.symbols) ? parsed.symbols : [];
|
|
284
|
+
const files = Array.isArray(parsed.files) ? parsed.files : [];
|
|
285
|
+
return dumpTraditionalManifest(projectDir, provider.id, symbols, files);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return emptyTopology(projectDir, provider.id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function resolveManifestWithFallback(projectDir, manifest) {
|
|
292
|
+
const primary = resolveProjectPath(projectDir, manifest);
|
|
293
|
+
if (existsSync(primary))
|
|
294
|
+
return primary;
|
|
295
|
+
// Fallback: try graph.json in the same directory
|
|
296
|
+
const dir = dirname(primary);
|
|
297
|
+
const fallback = join(dir, 'graph.json');
|
|
298
|
+
if (existsSync(fallback))
|
|
299
|
+
return fallback;
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
function dumpGraphifyManifest(projectDir, provider, manifest) {
|
|
303
|
+
const nodes = [];
|
|
304
|
+
const edges = [];
|
|
305
|
+
for (const node of manifest.nodes) {
|
|
306
|
+
const filePath = normalizeManifestPath(node.source_file ?? '');
|
|
307
|
+
nodes.push({
|
|
308
|
+
id: node.id,
|
|
309
|
+
kind: mapGraphifyKind(node.metadata?.kind),
|
|
310
|
+
name: node.label ?? node.id,
|
|
311
|
+
filePath,
|
|
312
|
+
line: parseLineNumber(node.source_location),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const linkSource = manifest.links ?? manifest.edges ?? [];
|
|
316
|
+
for (const link of linkSource) {
|
|
317
|
+
edges.push({
|
|
318
|
+
source: link.source,
|
|
319
|
+
target: link.target,
|
|
320
|
+
kind: mapGraphifyRelation(link.relation),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
nodes: dedupeTopologyNodes(nodes),
|
|
325
|
+
edges: dedupeTopologyEdges(edges),
|
|
326
|
+
generatedAt: new Date().toISOString(),
|
|
327
|
+
provider,
|
|
328
|
+
projectDir,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function mapGraphifyKind(kind) {
|
|
332
|
+
switch (kind) {
|
|
333
|
+
case 'file': return 'file';
|
|
334
|
+
case 'bash_function':
|
|
335
|
+
case 'bash_entrypoint': return 'function';
|
|
336
|
+
case 'code': return 'function';
|
|
337
|
+
default: return 'file';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function mapGraphifyRelation(relation) {
|
|
341
|
+
switch (relation) {
|
|
342
|
+
case 'calls': return 'calls';
|
|
343
|
+
case 'imports':
|
|
344
|
+
case 'imports_from': return 'imports';
|
|
345
|
+
case 'extends': return 'extends';
|
|
346
|
+
case 'implements': return 'implements';
|
|
347
|
+
case 'contains':
|
|
348
|
+
case 'defines':
|
|
349
|
+
case 'method': return 'depends-on';
|
|
350
|
+
default: return 'depends-on';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function parseLineNumber(loc) {
|
|
354
|
+
if (!loc)
|
|
355
|
+
return undefined;
|
|
356
|
+
const match = loc.match(/L(\d+)/);
|
|
357
|
+
return match ? parseInt(match[1], 10) : undefined;
|
|
358
|
+
}
|
|
359
|
+
function dumpTraditionalManifest(projectDir, provider, symbols, files) {
|
|
360
|
+
const nodes = [];
|
|
361
|
+
const edges = [];
|
|
362
|
+
const nodeIds = new Set();
|
|
363
|
+
// Symbol nodes
|
|
364
|
+
for (const sym of symbols) {
|
|
365
|
+
const name = String(sym.name ?? '');
|
|
366
|
+
const file = normalizeManifestPath(sym.file ?? sym.path);
|
|
367
|
+
if (!name)
|
|
368
|
+
continue;
|
|
369
|
+
const id = file ? `${file}::${name}` : name;
|
|
370
|
+
nodeIds.add(id);
|
|
371
|
+
nodes.push({
|
|
372
|
+
id,
|
|
373
|
+
kind: guessSymbolKind(name),
|
|
374
|
+
name,
|
|
375
|
+
filePath: file || '',
|
|
376
|
+
});
|
|
377
|
+
// Edges from callers/callees/dependencies
|
|
378
|
+
for (const callee of stringifyArray(sym.callees)) {
|
|
379
|
+
const target = resolveEdgeTarget(callee, symbols);
|
|
380
|
+
if (target)
|
|
381
|
+
edges.push({ source: id, target, kind: 'calls' });
|
|
382
|
+
}
|
|
383
|
+
for (const dep of stringifyArray(sym.dependencies)) {
|
|
384
|
+
const target = resolveEdgeTarget(dep, symbols);
|
|
385
|
+
if (target)
|
|
386
|
+
edges.push({ source: id, target, kind: 'depends-on' });
|
|
387
|
+
}
|
|
388
|
+
for (const caller of stringifyArray(sym.callers)) {
|
|
389
|
+
const target = resolveEdgeTarget(caller, symbols);
|
|
390
|
+
if (target)
|
|
391
|
+
edges.push({ source: target, target: id, kind: 'calls' });
|
|
392
|
+
}
|
|
393
|
+
for (const dependent of stringifyArray(sym.dependents)) {
|
|
394
|
+
const target = resolveEdgeTarget(dependent, symbols);
|
|
395
|
+
if (target)
|
|
396
|
+
edges.push({ source: target, target: id, kind: 'depends-on' });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// File nodes (for files not yet represented by symbols)
|
|
400
|
+
for (const file of files) {
|
|
401
|
+
const path = normalizeManifestPath(file.path ?? file.file);
|
|
402
|
+
if (!path)
|
|
403
|
+
continue;
|
|
404
|
+
const fileId = `file:${path}`;
|
|
405
|
+
if (!nodeIds.has(fileId)) {
|
|
406
|
+
nodeIds.add(fileId);
|
|
407
|
+
nodes.push({ id: fileId, kind: 'file', name: path.split('/').pop() ?? path, filePath: path });
|
|
408
|
+
}
|
|
409
|
+
// Import edges
|
|
410
|
+
for (const imp of stringifyArray(file.imports)) {
|
|
411
|
+
const targetFile = normalizeManifestPath(imp);
|
|
412
|
+
if (targetFile) {
|
|
413
|
+
const targetId = `file:${targetFile}`;
|
|
414
|
+
edges.push({ source: fileId, target: targetId, kind: 'imports' });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// dependsOn edges
|
|
418
|
+
for (const dep of stringifyArray(file.dependsOn)) {
|
|
419
|
+
const targetFile = normalizeManifestPath(dep);
|
|
420
|
+
if (targetFile) {
|
|
421
|
+
const targetId = `file:${targetFile}`;
|
|
422
|
+
edges.push({ source: fileId, target: targetId, kind: 'depends-on' });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
nodes: dedupeTopologyNodes(nodes),
|
|
428
|
+
edges: dedupeTopologyEdges(edges),
|
|
429
|
+
generatedAt: new Date().toISOString(),
|
|
430
|
+
provider,
|
|
431
|
+
projectDir,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function dumpExternalCodeGraph(projectDir) {
|
|
435
|
+
try {
|
|
436
|
+
// Use codegraph CLI to get a broad dump via empty query
|
|
437
|
+
const output = runExternalCommandSync('codegraph', ['query', '', '-p', projectDir, '--json'], {
|
|
438
|
+
encoding: 'utf8',
|
|
439
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
440
|
+
}, {
|
|
441
|
+
execFileSync: execFileSyncImpl,
|
|
442
|
+
spawnSync: childProcess.spawnSync,
|
|
443
|
+
});
|
|
444
|
+
const parsed = JSON.parse(String(output));
|
|
445
|
+
const nodes = [];
|
|
446
|
+
const seen = new Set();
|
|
447
|
+
for (const entry of parsed) {
|
|
448
|
+
const file = normalizeManifestPath(entry.node?.filePath);
|
|
449
|
+
const name = String(entry.node?.qualifiedName ?? entry.node?.name ?? '').trim();
|
|
450
|
+
if (!file || !name)
|
|
451
|
+
continue;
|
|
452
|
+
const id = `${file}::${name}`;
|
|
453
|
+
if (seen.has(id))
|
|
454
|
+
continue;
|
|
455
|
+
seen.add(id);
|
|
456
|
+
nodes.push({
|
|
457
|
+
id,
|
|
458
|
+
kind: mapCodeGraphKind(entry.node?.kind),
|
|
459
|
+
name,
|
|
460
|
+
filePath: file,
|
|
461
|
+
line: entry.node?.startLine,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
nodes,
|
|
466
|
+
edges: [], // CLI doesn't return edge data in query mode
|
|
467
|
+
generatedAt: new Date().toISOString(),
|
|
468
|
+
provider: 'codegraph',
|
|
469
|
+
projectDir,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return emptyTopology(projectDir, 'codegraph');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function dumpFallbackTopology(projectDir) {
|
|
477
|
+
const files = sourceFiles(projectDir);
|
|
478
|
+
const nodes = files.map(file => {
|
|
479
|
+
const rel = normalizePath(relative(projectDir, file));
|
|
480
|
+
return {
|
|
481
|
+
id: `file:${rel}`,
|
|
482
|
+
kind: 'file',
|
|
483
|
+
name: rel.split('/').pop() ?? rel,
|
|
484
|
+
filePath: rel,
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
return {
|
|
488
|
+
nodes,
|
|
489
|
+
edges: [],
|
|
490
|
+
generatedAt: new Date().toISOString(),
|
|
491
|
+
provider: 'fallback-file-walk',
|
|
492
|
+
projectDir,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function resolveEdgeTarget(ref, symbols) {
|
|
496
|
+
const asPath = normalizeManifestPath(ref);
|
|
497
|
+
if (asPath && hasFileExtension(asPath)) {
|
|
498
|
+
// Reference is a file path — find a symbol in that file
|
|
499
|
+
const sym = symbols.find(s => normalizeManifestPath(s.file ?? s.path) === asPath);
|
|
500
|
+
return sym ? `${asPath}::${sym.name}` : `file:${asPath}`;
|
|
501
|
+
}
|
|
502
|
+
// Reference is a symbol name
|
|
503
|
+
const sym = symbols.find(s => String(s.name) === ref);
|
|
504
|
+
if (sym) {
|
|
505
|
+
const file = normalizeManifestPath(sym.file ?? sym.path);
|
|
506
|
+
return file ? `${file}::${ref}` : ref;
|
|
507
|
+
}
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
function guessSymbolKind(name) {
|
|
511
|
+
if (/^[A-Z]/.test(name))
|
|
512
|
+
return 'class';
|
|
513
|
+
if (/^[A-Z_][A-Z_0-9]*$/.test(name))
|
|
514
|
+
return 'constant';
|
|
515
|
+
return 'function';
|
|
516
|
+
}
|
|
517
|
+
function mapCodeGraphKind(kind) {
|
|
518
|
+
switch (kind?.toLowerCase()) {
|
|
519
|
+
case 'class':
|
|
520
|
+
case 'struct':
|
|
521
|
+
case 'interface':
|
|
522
|
+
case 'type': return 'class';
|
|
523
|
+
case 'function':
|
|
524
|
+
case 'method':
|
|
525
|
+
case 'constructor': return 'function';
|
|
526
|
+
case 'constant':
|
|
527
|
+
case 'enum': return 'constant';
|
|
528
|
+
case 'module':
|
|
529
|
+
case 'namespace':
|
|
530
|
+
case 'package': return 'module';
|
|
531
|
+
default: return 'function';
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function dedupeTopologyNodes(nodes) {
|
|
535
|
+
const seen = new Set();
|
|
536
|
+
return nodes.filter(n => { if (seen.has(n.id))
|
|
537
|
+
return false; seen.add(n.id); return true; });
|
|
538
|
+
}
|
|
539
|
+
function dedupeTopologyEdges(edges) {
|
|
540
|
+
const seen = new Set();
|
|
541
|
+
return edges.filter(e => {
|
|
542
|
+
const key = `${e.source}->${e.target}:${e.kind}`;
|
|
543
|
+
if (seen.has(key))
|
|
544
|
+
return false;
|
|
545
|
+
seen.add(key);
|
|
546
|
+
return true;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
function emptyTopology(projectDir, provider) {
|
|
550
|
+
return { nodes: [], edges: [], generatedAt: new Date().toISOString(), provider, projectDir };
|
|
551
|
+
}
|
|
228
552
|
function runCodeGraphQuery(options) {
|
|
229
553
|
const projectDir = resolve(options.projectDir ?? process.cwd());
|
|
230
554
|
const query = options.query.trim();
|
|
@@ -255,6 +579,16 @@ function runCodeGraphQuery(options) {
|
|
|
255
579
|
warnings.push(`Provider ${externalAvailable.id} returned no hits for "${query}".`);
|
|
256
580
|
}
|
|
257
581
|
}
|
|
582
|
+
else if (externalAvailable?.id === 'code-review-graph' && options.mode === 'query') {
|
|
583
|
+
const crgQuery = queryExternalCRG({ projectDir, query });
|
|
584
|
+
if (crgQuery) {
|
|
585
|
+
hits = crgQuery.hits;
|
|
586
|
+
provider = externalAvailable.id;
|
|
587
|
+
warnings.push(...crgQuery.warnings);
|
|
588
|
+
if (hits.length === 0)
|
|
589
|
+
warnings.push(`Provider code-review-graph returned no hits for "${query}".`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
258
592
|
else if (externalAvailable) {
|
|
259
593
|
if (externalAvailable.id === 'codegraph' && !status.projectIndexExists) {
|
|
260
594
|
warnings.push('CodeGraph CLI is installed, but this project has no .codegraph/ index yet; run codegraph init -i to enable upstream graph queries.');
|
|
@@ -310,15 +644,20 @@ function providerStatus(projectDir) {
|
|
|
310
644
|
};
|
|
311
645
|
}
|
|
312
646
|
if (provider.type === 'artifact') {
|
|
313
|
-
const
|
|
314
|
-
const available = Boolean(
|
|
647
|
+
const resolved = provider.manifest ? resolveManifestWithFallback(projectDir, provider.manifest) : undefined;
|
|
648
|
+
const available = Boolean(resolved);
|
|
649
|
+
const reason = available
|
|
650
|
+
? resolved === resolveProjectPath(projectDir, provider.manifest ?? '')
|
|
651
|
+
? `manifest found at ${provider.manifest}`
|
|
652
|
+
: `manifest found via fallback at graph.json (configured: ${provider.manifest})`
|
|
653
|
+
: `manifest not found: ${provider.manifest ?? '(missing)'}`;
|
|
315
654
|
return {
|
|
316
655
|
id: provider.id,
|
|
317
656
|
type: provider.type,
|
|
318
657
|
enabled,
|
|
319
658
|
available,
|
|
320
659
|
capabilities,
|
|
321
|
-
reason
|
|
660
|
+
reason,
|
|
322
661
|
source: provider.source,
|
|
323
662
|
installHint: provider.installHint,
|
|
324
663
|
projectInitHint: provider.projectInitHint,
|
|
@@ -331,7 +670,9 @@ function providerStatus(projectDir) {
|
|
|
331
670
|
? projectIndexExists
|
|
332
671
|
? '; project index found at .codegraph/'
|
|
333
672
|
: '; project index missing (.codegraph/)'
|
|
334
|
-
: ''
|
|
673
|
+
: provider.id === 'code-review-graph'
|
|
674
|
+
? '; run code-review-graph build to index the project'
|
|
675
|
+
: '';
|
|
335
676
|
return {
|
|
336
677
|
id: provider.id,
|
|
337
678
|
type: provider.type,
|
|
@@ -529,6 +870,7 @@ function confidence(hitCount, fallbackUsed) {
|
|
|
529
870
|
function recommendations(configExists, providers, fallbackAvailable, projectIndexExists) {
|
|
530
871
|
const output = [];
|
|
531
872
|
const codegraph = providers.find(provider => provider.id === 'codegraph');
|
|
873
|
+
const crg = providers.find(provider => provider.id === 'code-review-graph');
|
|
532
874
|
if (!configExists)
|
|
533
875
|
output.push('Run scale codegraph init to create .scale/code-intelligence.json.');
|
|
534
876
|
if (codegraph && !codegraph.available && codegraph.installHint) {
|
|
@@ -537,6 +879,9 @@ function recommendations(configExists, providers, fallbackAvailable, projectInde
|
|
|
537
879
|
if (codegraph?.available && !projectIndexExists) {
|
|
538
880
|
output.push('Run codegraph init -i in the project root to build the local .codegraph/ index.');
|
|
539
881
|
}
|
|
882
|
+
if (crg && !crg.available && crg.installHint) {
|
|
883
|
+
output.push(`Install code-review-graph from ${crg.source ?? CRG_SOURCE}: ${crg.installHint}.`);
|
|
884
|
+
}
|
|
540
885
|
if (providers.every(provider => !provider.available))
|
|
541
886
|
output.push('No graph provider is available; exploration will use explicit fallback.');
|
|
542
887
|
if (!fallbackAvailable)
|
|
@@ -584,7 +929,9 @@ function hydrateProviderConfig(provider) {
|
|
|
584
929
|
? DEFAULT_CODEGRAPH_PROVIDER
|
|
585
930
|
: provider.id === 'graphify'
|
|
586
931
|
? DEFAULT_GRAPHIFY_PROVIDER
|
|
587
|
-
:
|
|
932
|
+
: provider.id === 'code-review-graph'
|
|
933
|
+
? DEFAULT_CRG_PROVIDER
|
|
934
|
+
: undefined;
|
|
588
935
|
return {
|
|
589
936
|
...(defaults ?? {}),
|
|
590
937
|
...provider,
|
|
@@ -678,4 +1025,109 @@ function queryExternalCodeGraphContext(options) {
|
|
|
678
1025
|
return null;
|
|
679
1026
|
}
|
|
680
1027
|
}
|
|
1028
|
+
function crgCommandExists() {
|
|
1029
|
+
return commandExists('code-review-graph');
|
|
1030
|
+
}
|
|
1031
|
+
function runCRGCommand(args, projectDir) {
|
|
1032
|
+
try {
|
|
1033
|
+
const result = runExternalCommandSync('code-review-graph', args, {
|
|
1034
|
+
encoding: 'utf8',
|
|
1035
|
+
cwd: projectDir,
|
|
1036
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1037
|
+
}, {
|
|
1038
|
+
execFileSync: execFileSyncImpl,
|
|
1039
|
+
spawnSync: childProcess.spawnSync,
|
|
1040
|
+
});
|
|
1041
|
+
return typeof result === 'string' ? result : String(result);
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
export function queryExternalCRG(options) {
|
|
1048
|
+
// code-review-graph exposes search via MCP tools, not CLI query.
|
|
1049
|
+
// Use detect-changes as a proxy for graph availability, and fall through
|
|
1050
|
+
// to the artifact provider (SQLite graph) for actual search.
|
|
1051
|
+
if (!crgCommandExists())
|
|
1052
|
+
return null;
|
|
1053
|
+
// Verify graph exists
|
|
1054
|
+
const output = runCRGCommand(['status'], options.projectDir);
|
|
1055
|
+
if (!output || !output.includes('Nodes:'))
|
|
1056
|
+
return { hits: [], warnings: ['code-review-graph graph not built; run code-review-graph build'] };
|
|
1057
|
+
// CRG search is MCP-only; return empty hits so the caller falls through to artifact or fallback
|
|
1058
|
+
return { hits: [], warnings: ['code-review-graph search is available via MCP tools only; use code-review-graph serve or code-review-graph mcp'] };
|
|
1059
|
+
}
|
|
1060
|
+
export function detectChangesCRG(options) {
|
|
1061
|
+
if (!crgCommandExists())
|
|
1062
|
+
return null;
|
|
1063
|
+
const args = ['detect-changes'];
|
|
1064
|
+
if (options.baseRef)
|
|
1065
|
+
args.push('--base', options.baseRef);
|
|
1066
|
+
const output = runCRGCommand(args, options.projectDir);
|
|
1067
|
+
if (!output)
|
|
1068
|
+
return null;
|
|
1069
|
+
try {
|
|
1070
|
+
const parsed = JSON.parse(output);
|
|
1071
|
+
const changedFiles = [];
|
|
1072
|
+
const affectedSymbols = [];
|
|
1073
|
+
const affectedTests = [];
|
|
1074
|
+
for (const fn of parsed.changed_functions ?? []) {
|
|
1075
|
+
const file = normalizeManifestPath(fn.file);
|
|
1076
|
+
if (file && !changedFiles.includes(file))
|
|
1077
|
+
changedFiles.push(file);
|
|
1078
|
+
if (fn.name && file)
|
|
1079
|
+
affectedSymbols.push({ symbol: fn.name, file, reason: 'changed function' });
|
|
1080
|
+
}
|
|
1081
|
+
for (const gap of parsed.test_gaps ?? []) {
|
|
1082
|
+
const file = normalizeManifestPath(gap.file);
|
|
1083
|
+
if (file)
|
|
1084
|
+
affectedTests.push(file);
|
|
1085
|
+
}
|
|
1086
|
+
for (const priority of parsed.review_priorities ?? []) {
|
|
1087
|
+
const file = normalizeManifestPath(priority.file);
|
|
1088
|
+
if (file && !changedFiles.includes(file))
|
|
1089
|
+
changedFiles.push(file);
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
changedFiles,
|
|
1093
|
+
affectedSymbols,
|
|
1094
|
+
affectedTests,
|
|
1095
|
+
blastRadiusFiles: changedFiles,
|
|
1096
|
+
provider: 'code-review-graph',
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
export function reviewContextCRG(options) {
|
|
1104
|
+
if (!crgCommandExists())
|
|
1105
|
+
return null;
|
|
1106
|
+
// Use detect-changes for token savings overview
|
|
1107
|
+
const output = runCRGCommand(['detect-changes'], options.projectDir);
|
|
1108
|
+
if (!output)
|
|
1109
|
+
return null;
|
|
1110
|
+
try {
|
|
1111
|
+
const parsed = JSON.parse(output);
|
|
1112
|
+
const files = (parsed.changed_functions ?? [])
|
|
1113
|
+
.map(fn => normalizeManifestPath(fn.file))
|
|
1114
|
+
.filter(Boolean);
|
|
1115
|
+
const savedTokens = parsed.context_savings?.saved_tokens ?? 0;
|
|
1116
|
+
const savedPercent = parsed.context_savings?.saved_percent ?? 0;
|
|
1117
|
+
const naiveCorpus = savedTokens > 0 && savedPercent > 0 && savedPercent < 100
|
|
1118
|
+
? Math.round(savedTokens / (1 - savedPercent / 100))
|
|
1119
|
+
: savedTokens > 0 ? savedTokens + 70 : 0;
|
|
1120
|
+
const graphQuery = naiveCorpus > savedTokens ? naiveCorpus - savedTokens : 70;
|
|
1121
|
+
const reduction = naiveCorpus > 0 ? Math.round(naiveCorpus / graphQuery) : 1;
|
|
1122
|
+
return {
|
|
1123
|
+
files,
|
|
1124
|
+
blastRadius: [],
|
|
1125
|
+
tokenSavings: { naiveCorpus, graphQuery, reduction },
|
|
1126
|
+
provider: 'code-review-graph',
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
catch {
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
681
1133
|
//# sourceMappingURL=CodeIntelligence.js.map
|