1+ import { spawnSync } from 'node:child_process' ;
12import { describe , it , expect } from 'vitest' ;
23import { promises as fs } from 'fs' ;
34import os from 'os' ;
@@ -19,6 +20,7 @@ const __filename = fileURLToPath(import.meta.url);
1920const __dirname = path . dirname ( __filename ) ;
2021const FIXTURE_ROOT = path . join ( __dirname , 'fixtures' , 'map-fixture' ) ;
2122const CURRENT_REPO_ROOT = path . resolve ( __dirname , '..' ) ;
23+ const ENTRYPOINT = path . join ( CURRENT_REPO_ROOT , 'src' , 'index.ts' ) ;
2224const 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