Files
proffesor-for-testing__agen…/tests/integration/code-intelligence/knowledge-graph-real.test.ts
Dragan Spiridonov cc5220c5a0 fix(code-intelligence): pin KG to a fixed namespace + evict stale file cache (#511)
Resolves the two remaining gaps from the audit.

Namespace divergence (CLI↔MCP parity):
KnowledgeGraphService persisted nodes/edges under the backend's DEFAULT
namespace — 'qe-kernel' for the CLI kernel, 'mcp-tools' for the MCP shared
backend — so a graph indexed by one tool was invisible to the other, and the
init existence check (`SELECT ... FROM kv_store WHERE namespace =
'code-intelligence:kg'`) always returned 0 because the data was elsewhere.

Now every KG kv op passes an explicit `{ namespace: 'code-intelligence:kg' }`
(via a single `nsOpts` getter so reads and writes can't drift apart — issue
#491 Bug 2). The two external readers that searched the default namespace by key
prefix (impact-analyzer test discovery, test-generator KG-context) now read that
namespace too. Verified: both `aqe code index` and the MCP `code_index` tool
land KG rows in `code-intelligence:kg`; deps/search still work; the init
`hasCodeIntelligenceIndex` check now finds the index.

FileReader stale content on incremental re-index:
indexFile() now evicts the file from the FileReader content cache before reading
it. The reader caches content for 5 min, so a re-index in a long-lived process
(the MCP server) could otherwise extract from pre-edit text. The two reads
within indexFile (entities + imports) still share one cache fill, so they stay
consistent. The incremental regression test now re-indexes on the SAME instance,
exercising both removeFileFromGraph and the cache eviction.

Adds a regression test asserting KG data is stored under 'code-intelligence:kg'
regardless of the backend's default namespace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:59:47 +00:00

740 lines
27 KiB
TypeScript

/**
* Agentic QE v3 - Knowledge Graph Real I/O Integration Tests
* Tests that perform actual file system operations on the v3 codebase
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import path from 'path';
import fs from 'fs/promises';
import { KnowledgeGraphService } from '../../../src/domains/code-intelligence/services/knowledge-graph';
import type { MemoryBackend, VectorSearchResult } from '../../../src/kernel/interfaces';
// ============================================================================
// Mock Memory Backend with Persistence Simulation
// ============================================================================
function createMockMemoryBackend(): MemoryBackend {
const storage = new Map<string, unknown>();
const vectors = new Map<string, { embedding: number[]; metadata: unknown }>();
return {
initialize: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
set: vi.fn(async (key: string, value: unknown) => {
storage.set(key, value);
}),
get: vi.fn(async <T>(key: string): Promise<T | undefined> => {
return storage.get(key) as T | undefined;
}),
delete: vi.fn(async (key: string): Promise<boolean> => {
return storage.delete(key);
}),
has: vi.fn(async (key: string): Promise<boolean> => {
return storage.has(key);
}),
search: vi.fn(async (pattern: string, limit?: number): Promise<string[]> => {
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
const matches: string[] = [];
for (const key of storage.keys()) {
if (regex.test(key)) {
matches.push(key);
if (limit && matches.length >= limit) break;
}
}
return matches;
}),
vectorSearch: vi.fn(async (_embedding: number[], k: number): Promise<VectorSearchResult[]> => {
const results: VectorSearchResult[] = [];
let count = 0;
for (const [key, data] of vectors.entries()) {
if (count >= k) break;
results.push({
key,
score: 0.9 - count * 0.1,
metadata: data.metadata,
});
count++;
}
return results;
}),
storeVector: vi.fn(async (key: string, embedding: number[], metadata?: unknown) => {
vectors.set(key, { embedding, metadata });
}),
};
}
// ============================================================================
// File System Helpers
// ============================================================================
/**
* Recursively get all TypeScript files in a directory
*/
async function getTypeScriptFiles(dir: string): Promise<string[]> {
const files: string[] = [];
async function walk(currentDir: string) {
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
await walk(fullPath);
} else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
files.push(fullPath);
}
}
} catch {
// Directory doesn't exist or can't be read
}
}
await walk(dir);
return files;
}
/**
* Extract entities from actual TypeScript file content
*/
async function extractEntitiesFromFile(filePath: string): Promise<ExtractedEntity[]> {
const content = await fs.readFile(filePath, 'utf-8');
const entities: ExtractedEntity[] = [];
// Extract class declarations - check for export keyword properly
const classMatches = content.matchAll(/(export\s+)?class\s+(\w+)/g);
for (const match of classMatches) {
entities.push({
type: 'class',
name: match[2],
file: filePath,
exported: match[1] !== undefined,
});
}
// Extract interface declarations
const interfaceMatches = content.matchAll(/(export\s+)?interface\s+(\w+)/g);
for (const match of interfaceMatches) {
entities.push({
type: 'interface',
name: match[2],
file: filePath,
exported: match[1] !== undefined,
});
}
// Extract function declarations
const functionMatches = content.matchAll(/(export\s+)?(?:async\s+)?function\s+(\w+)/g);
for (const match of functionMatches) {
entities.push({
type: 'function',
name: match[2],
file: filePath,
exported: match[1] !== undefined,
});
}
// Extract type declarations
const typeMatches = content.matchAll(/(export\s+)?type\s+(\w+)/g);
for (const match of typeMatches) {
entities.push({
type: 'type',
name: match[2],
file: filePath,
exported: match[1] !== undefined,
});
}
return entities;
}
interface ExtractedEntity {
type: 'class' | 'interface' | 'function' | 'type';
name: string;
file: string;
exported: boolean;
}
// ============================================================================
// Integration Tests
// ============================================================================
describe('Knowledge Graph Real I/O', () => {
let service: KnowledgeGraphService;
let memory: MemoryBackend;
const v3SrcPath = path.join(__dirname, '../../../src');
beforeEach(() => {
memory = createMockMemoryBackend();
service = new KnowledgeGraphService(memory, {
maxNodes: 10000,
enableVectorEmbeddings: false, // Disable for faster tests
});
});
afterEach(async () => {
await service.clear();
});
describe('indexing actual v3 source files', () => {
it('should index kernel directory and create nodes', async () => {
const kernelPath = path.join(v3SrcPath, 'kernel');
const files = await getTypeScriptFiles(kernelPath);
expect(files.length).toBeGreaterThan(0);
const result = await service.index({
paths: files,
incremental: false,
includeTests: false,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.filesIndexed).toBeGreaterThan(0);
expect(result.value.nodesCreated).toBeGreaterThan(0);
expect(result.value.duration).toBeGreaterThan(0);
}
});
it('should index domains directory with multiple files', async () => {
const domainsPath = path.join(v3SrcPath, 'domains');
const files = await getTypeScriptFiles(domainsPath);
expect(files.length).toBeGreaterThan(10); // Should have many domain files
const result = await service.index({
paths: files,
incremental: false,
includeTests: false,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.filesIndexed).toBeGreaterThan(10);
expect(result.value.nodesCreated).toBeGreaterThan(10);
}
});
it('should index coordination files', async () => {
const coordPath = path.join(v3SrcPath, 'coordination');
const files = await getTypeScriptFiles(coordPath);
const result = await service.index({
paths: files,
incremental: false,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.filesIndexed).toBeGreaterThan(0);
}
});
it('should perform incremental indexing correctly', async () => {
const kernelPath = path.join(v3SrcPath, 'kernel');
const kernelFiles = await getTypeScriptFiles(kernelPath);
// First indexing
const firstResult = await service.index({
paths: kernelFiles.slice(0, 3),
incremental: false,
});
expect(firstResult.success).toBe(true);
// Incremental indexing
const secondResult = await service.index({
paths: kernelFiles.slice(3),
incremental: true,
});
expect(secondResult.success).toBe(true);
if (secondResult.success && firstResult.success) {
// Both should have created nodes
expect(firstResult.value.nodesCreated).toBeGreaterThan(0);
expect(secondResult.value.nodesCreated).toBeGreaterThan(0);
}
});
it('should handle non-existent files gracefully', async () => {
const result = await service.index({
paths: ['/non/existent/file.ts', '/another/missing/file.ts'],
incremental: false,
});
expect(result.success).toBe(true);
if (result.success) {
// Should record errors for missing files
expect(result.value.errors.length).toBeGreaterThanOrEqual(0);
}
});
});
describe('extracting real entities from domain services', () => {
it('should extract entities from test-generator.ts', async () => {
const filePath = path.join(v3SrcPath, 'domains/test-generation/services/test-generator.ts');
const entities = await extractEntitiesFromFile(filePath);
expect(entities.length).toBeGreaterThan(0);
// Should have TestGeneratorService class
const serviceClass = entities.find(e => e.type === 'class' && e.name === 'TestGeneratorService');
expect(serviceClass).toBeDefined();
// Should have ITestGenerationService interface
const serviceInterface = entities.find(e => e.type === 'interface' && e.name === 'ITestGenerationService');
expect(serviceInterface).toBeDefined();
});
it('should extract entities from security-scanner.ts', async () => {
// Note: SecurityScannerService was refactored into scanners/scanner-orchestrator.ts
// The original security-scanner.ts is now a facade that re-exports
const filePath = path.join(v3SrcPath, 'domains/security-compliance/services/scanners/scanner-orchestrator.ts');
const entities = await extractEntitiesFromFile(filePath);
expect(entities.length).toBeGreaterThan(0);
const serviceClass = entities.find(e => e.type === 'class' && e.name === 'SecurityScannerService');
expect(serviceClass).toBeDefined();
expect(serviceClass!.exported).toBe(true);
});
it('should extract entities from knowledge-graph.ts', async () => {
const filePath = path.join(v3SrcPath, 'domains/code-intelligence/services/knowledge-graph.ts');
const entities = await extractEntitiesFromFile(filePath);
expect(entities.length).toBeGreaterThan(0);
const serviceClass = entities.find(e => e.type === 'class' && e.name === 'KnowledgeGraphService');
expect(serviceClass).toBeDefined();
const serviceInterface = entities.find(e => e.type === 'interface' && e.name === 'IKnowledgeGraphService');
expect(serviceInterface).toBeDefined();
});
it('should extract all exported types from interfaces.ts', async () => {
const filePath = path.join(v3SrcPath, 'kernel/interfaces.ts');
const entities = await extractEntitiesFromFile(filePath);
// Should have many interfaces
const interfaces = entities.filter(e => e.type === 'interface');
expect(interfaces.length).toBeGreaterThan(5);
// Key interfaces should be present
const interfaceNames = interfaces.map(i => i.name);
expect(interfaceNames).toContain('DomainPlugin');
expect(interfaceNames).toContain('EventBus');
expect(interfaceNames).toContain('MemoryBackend');
});
});
describe('querying the knowledge graph', () => {
beforeEach(async () => {
// Index some real files first
const kernelPath = path.join(v3SrcPath, 'kernel');
const files = await getTypeScriptFiles(kernelPath);
await service.index({
paths: files.slice(0, 5),
incremental: false,
});
});
it('should query for File nodes using cypher-style query', async () => {
const result = await service.query({
query: 'MATCH (n:File) RETURN n',
type: 'cypher',
limit: 10,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.metadata.type).toBe('cypher');
}
});
it('should query using natural language', async () => {
const result = await service.query({
query: 'find event bus',
type: 'natural-language',
limit: 5,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.metadata).toBeDefined();
}
});
});
describe('dependency mapping', () => {
beforeEach(async () => {
const coordPath = path.join(v3SrcPath, 'coordination');
const files = await getTypeScriptFiles(coordPath);
await service.index({
paths: files,
incremental: false,
});
});
it('should map dependencies for coordination files', async () => {
const result = await service.mapDependencies({
files: ['src/coordination/cross-domain-router.ts'],
direction: 'outgoing',
depth: 2,
});
expect(result.success).toBe(true);
if (result.success) {
expect(result.value.nodes).toBeDefined();
expect(result.value.edges).toBeDefined();
expect(result.value.metrics).toBeDefined();
expect(result.value.metrics.totalNodes).toBeGreaterThanOrEqual(0);
}
});
it('should calculate dependency metrics', async () => {
const result = await service.mapDependencies({
files: ['src/coordination/protocol-executor.ts'],
direction: 'both',
depth: 3,
});
expect(result.success).toBe(true);
if (result.success) {
const metrics = result.value.metrics;
expect(metrics).toHaveProperty('totalNodes');
expect(metrics).toHaveProperty('totalEdges');
expect(metrics).toHaveProperty('avgDegree');
expect(metrics).toHaveProperty('maxDepth');
expect(metrics).toHaveProperty('cyclomaticComplexity');
expect(metrics.cyclomaticComplexity).toBeGreaterThanOrEqual(1);
}
});
});
describe('file statistics', () => {
it('should count TypeScript files in v3 src directory', async () => {
const files = await getTypeScriptFiles(v3SrcPath);
// v3 should have a substantial number of source files
expect(files.length).toBeGreaterThan(50);
// All files should be .ts or .tsx
for (const file of files) {
expect(file).toMatch(/\.tsx?$/);
}
});
it('should have kernel files', async () => {
const kernelFiles = await getTypeScriptFiles(path.join(v3SrcPath, 'kernel'));
expect(kernelFiles.length).toBeGreaterThan(5);
expect(kernelFiles.some(f => f.includes('event-bus'))).toBe(true);
expect(kernelFiles.some(f => f.includes('interfaces'))).toBe(true);
});
it('should have domain files organized by domain', async () => {
const domainsPath = path.join(v3SrcPath, 'domains');
const domainFiles = await getTypeScriptFiles(domainsPath);
// Should have files from multiple domains
expect(domainFiles.some(f => f.includes('test-generation'))).toBe(true);
expect(domainFiles.some(f => f.includes('coverage-analysis'))).toBe(true);
expect(domainFiles.some(f => f.includes('security-compliance'))).toBe(true);
expect(domainFiles.some(f => f.includes('code-intelligence'))).toBe(true);
});
it('should have coordination files', async () => {
const coordFiles = await getTypeScriptFiles(path.join(v3SrcPath, 'coordination'));
expect(coordFiles.length).toBeGreaterThan(5);
expect(coordFiles.some(f => f.includes('cross-domain-router'))).toBe(true);
expect(coordFiles.some(f => f.includes('protocol-executor'))).toBe(true);
});
});
describe('entity extraction statistics', () => {
it('should extract significant number of entities from kernel', async () => {
const kernelFiles = await getTypeScriptFiles(path.join(v3SrcPath, 'kernel'));
let totalEntities = 0;
for (const file of kernelFiles) {
try {
const entities = await extractEntitiesFromFile(file);
totalEntities += entities.length;
} catch {
// Skip files that can't be read
}
}
expect(totalEntities).toBeGreaterThan(20);
});
it('should find exported classes across domains', async () => {
const domainFiles = await getTypeScriptFiles(path.join(v3SrcPath, 'domains'));
const exportedClasses: string[] = [];
for (const file of domainFiles) {
try {
const entities = await extractEntitiesFromFile(file);
const classes = entities.filter(e => e.type === 'class' && e.exported);
exportedClasses.push(...classes.map(c => c.name));
} catch {
// Skip files that can't be read
}
}
expect(exportedClasses.length).toBeGreaterThan(10);
// Should include key domain services
expect(exportedClasses).toContain('TestGeneratorService');
expect(exportedClasses).toContain('SecurityScannerService');
expect(exportedClasses).toContain('KnowledgeGraphService');
});
});
describe('codebase structure validation', () => {
it('should verify v3 follows expected directory structure', async () => {
const srcDir = v3SrcPath;
// Check expected directories exist
const expectedDirs = ['kernel', 'domains', 'coordination', 'shared'];
for (const dir of expectedDirs) {
const dirPath = path.join(srcDir, dir);
try {
const stat = await fs.stat(dirPath);
expect(stat.isDirectory()).toBe(true);
} catch {
throw new Error(`Expected directory ${dir} not found in v3/src`);
}
}
});
it('should verify domain directory structure', async () => {
const domainsDir = path.join(v3SrcPath, 'domains');
const expectedDomains = [
'test-generation',
'test-execution',
'coverage-analysis',
'quality-assessment',
'defect-intelligence',
'code-intelligence',
'security-compliance',
'chaos-resilience',
'contract-testing',
'learning-optimization',
'visual-accessibility',
'requirements-validation',
];
for (const domain of expectedDomains) {
const domainPath = path.join(domainsDir, domain);
try {
const stat = await fs.stat(domainPath);
expect(stat.isDirectory()).toBe(true);
} catch {
throw new Error(`Expected domain directory ${domain} not found`);
}
}
});
});
// ==========================================================================
// Regression: #511 — TS/JS dependency edges survive across processes
//
// `aqe code index` and `aqe code deps` run as SEPARATE CLI processes, so the
// graph must be persisted: an index by one service instance must be visible
// to mapDependencies() on a fresh instance sharing the same backend. Before
// the fix, nodes/edges were cache-only and `deps` returned 0 edges on TS
// repos even though `index` reported edges created.
// ==========================================================================
describe('#511 cross-instance TS dependency edges', () => {
let tmpDir: string;
let sharedMemory: MemoryBackend;
beforeEach(async () => {
sharedMemory = createMockMemoryBackend();
// Fixture must live under the project root: FileReader (SEC-004) rejects
// absolute paths outside process.cwd(), so os.tmpdir() would be unreadable.
tmpDir = await fs.mkdtemp(path.join(__dirname, 'tmp-kg-511-'));
await fs.writeFile(
path.join(tmpDir, 'math.ts'),
'export function add(a: number, b: number): number { return a + b; }\n'
);
await fs.writeFile(
path.join(tmpDir, 'app.ts'),
"import { add } from './math';\nexport const total = add(1, 2);\n"
);
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
it('should expose import edges to a fresh service instance after indexing', async () => {
// Arrange: index with one service ("the index process")
const appPath = path.join(tmpDir, 'app.ts');
const mathPath = path.join(tmpDir, 'math.ts');
const indexer = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
const indexResult = await indexer.index({ paths: [appPath, mathPath] });
expect(indexResult.success).toBe(true);
// Act: map dependencies with a SEPARATE instance sharing only the backend
const reader = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
const depsResult = await reader.mapDependencies({
files: [appPath, mathPath],
direction: 'both',
depth: 3,
});
// Assert: the import edge survived persistence and connects the files
expect(depsResult.success).toBe(true);
const deps = depsResult.value!;
expect(deps.metrics.totalEdges).toBeGreaterThan(0);
const appNodeId = ':' + appPath.replace(/^[/\\]/, '').replace(/[/\\]/g, ':').replace(/\./g, '_');
const mathNodeId = ':' + mathPath.replace(/^[/\\]/, '').replace(/[/\\]/g, ':').replace(/\./g, '_');
const importEdge = deps.edges.find(
(e) => e.source === appNodeId && e.target === mathNodeId && e.type === 'import'
);
expect(importEdge).toBeDefined();
// math.ts is depended upon (inDegree > 0), confirming the graph connects
const mathNode = deps.nodes.find((n) => n.path === mathPath);
expect(mathNode?.inDegree).toBeGreaterThan(0);
});
it('should not report phantom cycles for a simple acyclic import chain', async () => {
const appPath = path.join(tmpDir, 'app.ts');
const mathPath = path.join(tmpDir, 'math.ts');
const indexer = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
await indexer.index({ paths: [appPath, mathPath] });
const reader = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
const depsResult = await reader.mapDependencies({
files: [appPath, mathPath],
direction: 'both',
depth: 3,
});
expect(depsResult.success).toBe(true);
// app → math is acyclic; 'both' traversal must not fabricate A→B→A
expect(depsResult.value!.cycles).toHaveLength(0);
});
it('should warn when a file has no dependency extractor (#511 req #2)', async () => {
// Arrange: a Python file alongside the TS files — no TS-Compiler extractor
const pyPath = path.join(tmpDir, 'util.py');
await fs.writeFile(pyPath, 'def hello():\n return 1\n');
const indexer = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
// Act
const result = await indexer.index({
paths: [path.join(tmpDir, 'app.ts'), pyPath],
});
// Assert: a loud warning naming the unsupported extension is surfaced
expect(result.success).toBe(true);
const warnings = result.value!.warnings ?? [];
expect(warnings.some((w) => w.includes(".py"))).toBe(true);
});
it('should drop stale outgoing edges on incremental re-index (#511)', async () => {
const appPath = path.join(tmpDir, 'app.ts');
const mathPath = path.join(tmpDir, 'math.ts');
const indexer = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
await indexer.index({ paths: [appPath, mathPath] });
// app.ts no longer imports ./math. The incremental re-index runs on the
// SAME long-lived instance — exercising both the persistence-layer cleanup
// (removeFileFromGraph) and the FileReader cache eviction that guarantees
// the new file content is read (#511).
await fs.writeFile(appPath, 'export const total = 42;\n');
await indexer.index({ paths: [appPath, mathPath], incremental: true });
// The stale app → math import edge must be gone
const reader = new KnowledgeGraphService(sharedMemory, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
const depsResult = await reader.mapDependencies({
files: [appPath, mathPath],
direction: 'both',
depth: 3,
});
expect(depsResult.success).toBe(true);
const mathNode = depsResult.value!.nodes.find((n) => n.path === mathPath);
expect(mathNode?.inDegree).toBe(0);
});
it('should persist under the code-intelligence:kg namespace regardless of backend default (#511)', async () => {
// A namespace-honoring backend whose DEFAULT namespace is something else
// entirely (mirrors CLI 'qe-kernel' / MCP 'mcp-tools'). The KG must still
// write/read under 'code-intelligence:kg' so init checks + CLI↔MCP agree.
const storage = new Map<string, unknown>();
const composite = (key: string, ns?: string) => `${ns ?? 'backend-default'}::${key}`;
const honoring = {
initialize: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn().mockResolvedValue(undefined),
set: vi.fn(async (k: string, v: unknown, o?: { namespace?: string }) => {
storage.set(composite(k, o?.namespace), v);
}),
get: vi.fn(async (k: string, o?: { namespace?: string }) => storage.get(composite(k, o?.namespace))),
delete: vi.fn(async (k: string, o?: { namespace?: string }) => storage.delete(composite(k, o?.namespace))),
has: vi.fn(async (k: string, o?: { namespace?: string }) => storage.has(composite(k, o?.namespace))),
search: vi.fn(async (pattern: string, limit?: number, o?: { namespace?: string }) => {
const prefix = `${o?.namespace ?? 'backend-default'}::`;
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
const out: string[] = [];
for (const ck of storage.keys()) {
if (!ck.startsWith(prefix)) continue;
const key = ck.slice(prefix.length);
if (regex.test(key)) {
out.push(key);
if (limit && out.length >= limit) break;
}
}
return out;
}),
vectorSearch: vi.fn(async () => []),
storeVector: vi.fn(async () => {}),
} as unknown as MemoryBackend;
const appPath = path.join(tmpDir, 'app.ts');
const indexer = new KnowledgeGraphService(honoring, {
enableVectorEmbeddings: false,
enableLLMExtraction: false,
});
await indexer.index({ paths: [appPath, path.join(tmpDir, 'math.ts')] });
// Nodes are found under 'code-intelligence:kg', NOT the backend default —
// this is exactly what `hasCodeIntelligenceIndex` / cross-tool reads need.
const inKgNs = await honoring.search('code-intelligence:kg:node:*', 100, {
namespace: 'code-intelligence:kg',
});
const inDefaultNs = await honoring.search('code-intelligence:kg:node:*', 100);
expect(inKgNs.length).toBeGreaterThan(0);
expect(inDefaultNs.length).toBe(0);
});
});
});