Skip to content

Commit 4a80425

Browse files
committed
test(cli): cover direct full-map and status entrypoints
1 parent 907228d commit 4a80425

2 files changed

Lines changed: 157 additions & 2 deletions

File tree

tests/cli-entrypoint-runtime.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ type MapJson = {
1212
};
1313

1414
describe('CLI entrypoint runtime', () => {
15-
it('dispatches map without loading MCP server runtime on the CLI path', () => {
16-
const result = spawnSync(process.execPath, ['--import', 'tsx', entrypoint, 'map', '--json'], {
15+
function runCli(args: string[]) {
16+
return spawnSync(process.execPath, ['--import', 'tsx', entrypoint, ...args], {
1717
cwd: root,
1818
env: {
1919
...process.env,
@@ -22,6 +22,10 @@ describe('CLI entrypoint runtime', () => {
2222
encoding: 'utf8',
2323
timeout: 120_000
2424
});
25+
}
26+
27+
it('dispatches map without loading MCP server runtime on the CLI path', () => {
28+
const result = runCli(['map', '--json']);
2529

2630
expect(result.status).toBe(0);
2731
expect(result.stderr).not.toContain('ERR_MODULE_NOT_FOUND');
@@ -32,4 +36,22 @@ describe('CLI entrypoint runtime', () => {
3236
expect(parsed.architecture).toBeTruthy();
3337
expect(Array.isArray(parsed.activePatterns)).toBe(true);
3438
}, 120_000);
39+
40+
it('dispatches status without loading MCP server runtime on the CLI path', () => {
41+
const result = runCli(['status', '--json']);
42+
43+
expect(result.status).toBe(0);
44+
expect(result.stderr).not.toContain('ERR_MODULE_NOT_FOUND');
45+
expect(result.stderr).not.toContain('@modelcontextprotocol/sdk/server/stdio.js');
46+
47+
const parsed = JSON.parse(result.stdout) as {
48+
status?: string;
49+
rootPath?: string;
50+
hint?: string;
51+
};
52+
53+
expect(typeof parsed.status).toBe('string');
54+
expect(parsed.rootPath).toBe(root);
55+
expect(parsed.hint).toContain('refresh_index');
56+
}, 120_000);
3557
});

tests/codebase-map.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { spawnSync } from 'node:child_process';
12
import { describe, it, expect } from 'vitest';
23
import { promises as fs } from 'fs';
34
import os from 'os';
@@ -19,6 +20,7 @@ const __filename = fileURLToPath(import.meta.url);
1920
const __dirname = path.dirname(__filename);
2021
const FIXTURE_ROOT = path.join(__dirname, 'fixtures', 'map-fixture');
2122
const CURRENT_REPO_ROOT = path.resolve(__dirname, '..');
23+
const ENTRYPOINT = path.join(CURRENT_REPO_ROOT, 'src', 'index.ts');
2224
const BOUNDED_LIMITS = {
2325
entrypoints: 8,
2426
hubFiles: 5,
@@ -81,6 +83,18 @@ async function removeTempMapProject(rootPath: string): Promise<void> {
8183
await fs.rm(path.dirname(rootPath), { recursive: true, force: true });
8284
}
8385

86+
function runMapCli(args: string[], rootPath: string) {
87+
return spawnSync(process.execPath, ['--import', 'tsx', ENTRYPOINT, 'map', ...args], {
88+
cwd: CURRENT_REPO_ROOT,
89+
env: {
90+
...process.env,
91+
CODEBASE_ROOT: rootPath
92+
},
93+
encoding: 'utf8',
94+
timeout: 120_000
95+
});
96+
}
97+
8498
// ---------------------------------------------------------------------------
8599
// buildCodebaseMap
86100
// ---------------------------------------------------------------------------
@@ -672,3 +686,122 @@ describe('generateCodebaseIntelligence (eval guard)', () => {
672686
expect(result.length).toBeGreaterThan(0);
673687
});
674688
});
689+
690+
describe('map CLI contract', () => {
691+
it('supports --full on the real CLI entrypoint', async () => {
692+
const rootPath = await createTempMapProject({
693+
projectName: 'codebase-context',
694+
patterns: {
695+
default: {
696+
primary: { name: 'Factory', frequency: '80%', trend: 'Stable' }
697+
}
698+
},
699+
goldenFiles: [
700+
{ file: 'repos/external-lib/src/index.ts', score: 0.98 },
701+
{ file: 'src/core/map.ts', score: 0.95 }
702+
],
703+
graph: {
704+
imports: {
705+
'src/index.ts': ['src/core/map.ts'],
706+
'repos/external-lib/src/index.ts': ['src/core/map.ts']
707+
},
708+
importedBy: {
709+
'src/core/map.ts': ['src/index.ts', 'repos/external-lib/src/index.ts']
710+
},
711+
exports: {
712+
'src/index.ts': [{ name: 'serve', type: 'function' }],
713+
'repos/external-lib/src/index.ts': [{ name: 'mountExternalRepo', type: 'function' }]
714+
},
715+
stats: { files: 2, edges: 2, avgDependencies: 1 }
716+
},
717+
chunks: [
718+
{
719+
relativePath: 'src/core/map.ts',
720+
content: 'export class MapBuilder { build() {} }',
721+
metadata: {
722+
symbolAware: true,
723+
symbolKind: 'class',
724+
symbolName: 'MapBuilder'
725+
}
726+
} as CodeChunk
727+
]
728+
});
729+
730+
try {
731+
const result = runMapCli(['--full', '--json'], rootPath);
732+
733+
expect(result.status).toBe(0);
734+
expect(result.stderr).toBe('');
735+
736+
const parsed = JSON.parse(result.stdout) as {
737+
architecture?: { apiSurface?: Array<{ file: string }> };
738+
bestExamples?: Array<{ file: string }>;
739+
};
740+
741+
expect(parsed.architecture?.apiSurface?.map((surface) => surface.file)).toContain(
742+
'repos/external-lib/src/index.ts'
743+
);
744+
expect(parsed.bestExamples?.map((example) => example.file)).toContain(
745+
'repos/external-lib/src/index.ts'
746+
);
747+
} finally {
748+
await removeTempMapProject(rootPath);
749+
}
750+
}, 120_000);
751+
752+
it('writes the exhaustive markdown map when --export and --full are combined', async () => {
753+
const rootPath = await createTempMapProject({
754+
projectName: 'codebase-context',
755+
patterns: {
756+
default: {
757+
primary: { name: 'Factory', frequency: '80%', trend: 'Stable' }
758+
}
759+
},
760+
goldenFiles: [
761+
{ file: 'repos/external-lib/src/index.ts', score: 0.98 },
762+
{ file: 'src/core/map.ts', score: 0.95 }
763+
],
764+
graph: {
765+
imports: {
766+
'src/index.ts': ['src/core/map.ts'],
767+
'repos/external-lib/src/index.ts': ['src/core/map.ts']
768+
},
769+
importedBy: {
770+
'src/core/map.ts': ['src/index.ts', 'repos/external-lib/src/index.ts']
771+
},
772+
exports: {
773+
'src/index.ts': [{ name: 'serve', type: 'function' }],
774+
'repos/external-lib/src/index.ts': [{ name: 'mountExternalRepo', type: 'function' }]
775+
},
776+
stats: { files: 2, edges: 2, avgDependencies: 1 }
777+
},
778+
chunks: [
779+
{
780+
relativePath: 'src/core/map.ts',
781+
content: 'export class MapBuilder { build() {} }',
782+
metadata: {
783+
symbolAware: true,
784+
symbolKind: 'class',
785+
symbolName: 'MapBuilder'
786+
}
787+
} as CodeChunk
788+
]
789+
});
790+
791+
try {
792+
const result = runMapCli(['--export', '--full'], rootPath);
793+
794+
expect(result.status).toBe(0);
795+
expect(result.stderr).toBe('');
796+
797+
const exportPath = path.join(rootPath, 'CODEBASE_MAP.md');
798+
const exportedMarkdown = await fs.readFile(exportPath, 'utf-8');
799+
800+
expect(result.stdout).toContain(`Wrote ${exportPath}`);
801+
expect(exportedMarkdown).toContain('repos/external-lib/src/index.ts');
802+
expect(exportedMarkdown).toContain('mountExternalRepo');
803+
} finally {
804+
await removeTempMapProject(rootPath);
805+
}
806+
}, 120_000);
807+
});

0 commit comments

Comments
 (0)