11import { execFileSync } from 'node:child_process' ;
22import fs from 'node:fs' ;
33import path from 'node:path' ;
4+ import type { CodegraphConfig } from '../types.js' ;
45import { debug , warn } from './logger.js' ;
56
6- export const CONFIG_FILES = [ '.codegraphrc.json' , '.codegraphrc' , 'codegraph.config.json' ] ;
7+ export type { CodegraphConfig } from '../types.js' ;
8+
9+ export const CONFIG_FILES : readonly string [ ] = [
10+ '.codegraphrc.json' ,
11+ '.codegraphrc' ,
12+ 'codegraph.config.json' ,
13+ ] ;
714
815export const DEFAULTS = {
9- include : [ ] ,
10- exclude : [ ] ,
11- ignoreDirs : [ ] ,
12- extensions : [ ] ,
13- aliases : { } ,
16+ include : [ ] as string [ ] ,
17+ exclude : [ ] as string [ ] ,
18+ ignoreDirs : [ ] as string [ ] ,
19+ extensions : [ ] as string [ ] ,
20+ aliases : { } as Record < string , string > ,
1421 build : {
1522 incremental : true ,
1623 dbPath : '.codegraph/graph.db' ,
@@ -21,29 +28,35 @@ export const DEFAULTS = {
2128 defaultLimit : 20 ,
2229 excludeTests : false ,
2330 } ,
24- embeddings : { model : 'nomic-v1.5' , llmProvider : null } ,
25- llm : { provider : null , model : null , baseUrl : null , apiKey : null , apiKeyCommand : null } ,
31+ embeddings : { model : 'nomic-v1.5' , llmProvider : null as string | null } ,
32+ llm : {
33+ provider : null as string | null ,
34+ model : null as string | null ,
35+ baseUrl : null as string | null ,
36+ apiKey : null as string | null ,
37+ apiKeyCommand : null as string | null ,
38+ } ,
2639 search : { defaultMinScore : 0.2 , rrfK : 60 , topK : 15 , similarityWarnThreshold : 0.85 } ,
27- ci : { failOnCycles : false , impactThreshold : null } ,
40+ ci : { failOnCycles : false , impactThreshold : null as number | null } ,
2841 manifesto : {
2942 rules : {
3043 cognitive : { warn : 15 } ,
3144 cyclomatic : { warn : 10 } ,
3245 maxNesting : { warn : 4 } ,
33- maintainabilityIndex : { warn : 20 , fail : null } ,
34- importCount : { warn : null , fail : null } ,
35- exportCount : { warn : null , fail : null } ,
36- lineCount : { warn : null , fail : null } ,
37- fanIn : { warn : null , fail : null } ,
38- fanOut : { warn : null , fail : null } ,
39- noCycles : { warn : null , fail : null } ,
40- boundaries : { warn : null , fail : null } ,
46+ maintainabilityIndex : { warn : 20 , fail : null as number | null } ,
47+ importCount : { warn : null as number | null , fail : null as number | null } ,
48+ exportCount : { warn : null as number | null , fail : null as number | null } ,
49+ lineCount : { warn : null as number | null , fail : null as number | null } ,
50+ fanIn : { warn : null as number | null , fail : null as number | null } ,
51+ fanOut : { warn : null as number | null , fail : null as number | null } ,
52+ noCycles : { warn : null as number | null , fail : null as number | null } ,
53+ boundaries : { warn : null as number | null , fail : null as number | null } ,
4154 } ,
42- boundaries : null ,
55+ boundaries : null as unknown ,
4356 } ,
4457 check : {
4558 cycles : true ,
46- blastRadius : null ,
59+ blastRadius : null as number | null ,
4760 signatures : true ,
4861 boundaries : true ,
4962 depth : 3 ,
@@ -94,7 +107,7 @@ export const DEFAULTS = {
94107 'dead-entry' : 0.3 ,
95108 'dead-ffi' : 0.05 ,
96109 'dead-unresolved' : 0.15 ,
97- } ,
110+ } as Record < string , number > ,
98111 defaultRoleWeight : 0.5 ,
99112 } ,
100113 display : {
@@ -129,19 +142,21 @@ export const DEFAULTS = {
129142 structure : 30 ,
130143 triage : 20 ,
131144 ast_query : 50 ,
145+ implementations : 50 ,
146+ interfaces : 50 ,
132147 } ,
133148 } ,
134- } ;
149+ } satisfies CodegraphConfig ;
135150
136151// Per-cwd config cache — avoids re-reading the config file on every query call.
137152// The config file rarely changes within a single process lifetime.
138- const _configCache = new Map ( ) ;
153+ const _configCache = new Map < string , CodegraphConfig > ( ) ;
139154
140155/**
141156 * Load project configuration from a .codegraphrc.json or similar file.
142157 * Returns merged config with defaults. Results are cached per cwd.
143158 */
144- export function loadConfig ( cwd ) {
159+ export function loadConfig ( cwd ?: string ) : CodegraphConfig {
145160 cwd = cwd || process . cwd ( ) ;
146161 const cached = _configCache . get ( cwd ) ;
147162 if ( cached ) return structuredClone ( cached ) ;
@@ -153,16 +168,18 @@ export function loadConfig(cwd) {
153168 const raw = fs . readFileSync ( filePath , 'utf-8' ) ;
154169 const config = JSON . parse ( raw ) ;
155170 debug ( `Loaded config from ${ filePath } ` ) ;
156- const merged = mergeConfig ( DEFAULTS , config ) ;
171+ const merged = mergeConfig ( DEFAULTS as unknown as Record < string , unknown > , config ) ;
157172 if ( 'excludeTests' in config && ! ( config . query && 'excludeTests' in config . query ) ) {
158- merged . query . excludeTests = Boolean ( config . excludeTests ) ;
173+ ( merged [ 'query' ] as Record < string , unknown > ) [ 'excludeTests' ] = Boolean (
174+ config . excludeTests ,
175+ ) ;
159176 }
160- delete merged . excludeTests ;
161- const result = resolveSecrets ( applyEnvOverrides ( merged ) ) ;
177+ delete merged [ ' excludeTests' ] ;
178+ const result = resolveSecrets ( applyEnvOverrides ( merged as unknown as CodegraphConfig ) ) ;
162179 _configCache . set ( cwd , structuredClone ( result ) ) ;
163180 return result ;
164- } catch ( err ) {
165- debug ( `Failed to parse config ${ filePath } : ${ err . message } ` ) ;
181+ } catch ( err : unknown ) {
182+ debug ( `Failed to parse config ${ filePath } : ${ ( err as Error ) . message } ` ) ;
166183 }
167184 }
168185 }
@@ -176,43 +193,44 @@ export function loadConfig(cwd) {
176193 * pick up on-disk config changes, and for test isolation when tests share
177194 * the same cwd.
178195 */
179- export function clearConfigCache ( ) {
196+ export function clearConfigCache ( ) : void {
180197 _configCache . clear ( ) ;
181198}
182199
183- const ENV_LLM_MAP = {
200+ const ENV_LLM_MAP : Record < string , string > = {
184201 CODEGRAPH_LLM_PROVIDER : 'provider' ,
185202 CODEGRAPH_LLM_API_KEY : 'apiKey' ,
186203 CODEGRAPH_LLM_MODEL : 'model' ,
187204} ;
188205
189- export function applyEnvOverrides ( config ) {
206+ export function applyEnvOverrides ( config : CodegraphConfig ) : CodegraphConfig {
190207 for ( const [ envKey , field ] of Object . entries ( ENV_LLM_MAP ) ) {
191- if ( process . env [ envKey ] !== undefined ) {
192- config . llm [ field ] = process . env [ envKey ] ;
208+ if ( process . env [ envKey as keyof NodeJS . ProcessEnv ] !== undefined ) {
209+ ( config . llm as Record < string , unknown > ) [ field ] =
210+ process . env [ envKey as keyof NodeJS . ProcessEnv ] ;
193211 }
194212 }
195213 return config ;
196214}
197215
198- export function resolveSecrets ( config ) {
216+ export function resolveSecrets ( config : CodegraphConfig ) : CodegraphConfig {
199217 const cmd = config . llm . apiKeyCommand ;
200218 if ( typeof cmd !== 'string' || cmd . trim ( ) === '' ) return config ;
201219
202220 const parts = cmd . trim ( ) . split ( / \s + / ) ;
203221 const [ executable , ...args ] = parts ;
204222 try {
205- const result = execFileSync ( executable , args , {
223+ const result = execFileSync ( executable ! , args , {
206224 encoding : 'utf-8' ,
207225 timeout : 10_000 ,
208226 maxBuffer : 64 * 1024 ,
209227 stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
210228 } ) . trim ( ) ;
211229 if ( result ) {
212- config . llm . apiKey = result ;
230+ ( config . llm as Record < string , unknown > ) [ ' apiKey' ] = result ;
213231 }
214- } catch ( err ) {
215- warn ( `apiKeyCommand failed: ${ err . message } ` ) ;
232+ } catch ( err : unknown ) {
233+ warn ( `apiKeyCommand failed: ${ ( err as Error ) . message } ` ) ;
216234 }
217235 return config ;
218236}
@@ -224,7 +242,7 @@ export function resolveSecrets(config) {
224242 * Supports trailing `/*` or `/**` patterns (e.g. "packages/*").
225243 * Does not depend on an external glob library — uses fs.readdirSync.
226244 */
227- function expandWorkspaceGlob ( pattern , rootDir ) {
245+ function expandWorkspaceGlob ( pattern : string , rootDir : string ) : string [ ] {
228246 // Strip trailing /*, /**, or just *
229247 const clean = pattern . replace ( / \/ ? \* \* ? $ / , '' ) ;
230248 const baseDir = path . resolve ( rootDir , clean ) ;
@@ -243,7 +261,7 @@ function expandWorkspaceGlob(pattern, rootDir) {
243261/**
244262 * Read a package.json and return its name field, or null.
245263 */
246- function readPackageName ( pkgDir ) {
264+ function readPackageName ( pkgDir : string ) : string | null {
247265 try {
248266 const raw = fs . readFileSync ( path . join ( pkgDir , 'package.json' ) , 'utf-8' ) ;
249267 const pkg = JSON . parse ( raw ) ;
@@ -253,11 +271,16 @@ function readPackageName(pkgDir) {
253271 }
254272}
255273
274+ interface WorkspaceEntry {
275+ dir : string ;
276+ entry : string | null ;
277+ }
278+
256279/**
257280 * Resolve the entry-point source file for a workspace package.
258281 * Checks exports → main → index file fallback.
259282 */
260- function resolveWorkspaceEntry ( pkgDir ) {
283+ function resolveWorkspaceEntry ( pkgDir : string ) : string | null {
261284 try {
262285 const raw = fs . readFileSync ( path . join ( pkgDir , 'package.json' ) , 'utf-8' ) ;
263286 const pkg = JSON . parse ( raw ) ;
@@ -300,14 +323,10 @@ function resolveWorkspaceEntry(pkgDir) {
300323 * 1. pnpm-workspace.yaml — `packages:` array
301324 * 2. package.json — `workspaces` field (npm/yarn)
302325 * 3. lerna.json — `packages` array
303- *
304- * @param {string } rootDir - Project root directory
305- * @returns {Map<string, { dir: string, entry: string|null }> }
306- * Map of package name → { absolute dir, resolved entry file }
307326 */
308- export function detectWorkspaces ( rootDir ) {
309- const workspaces = new Map ( ) ;
310- const patterns = [ ] ;
327+ export function detectWorkspaces ( rootDir : string ) : Map < string , WorkspaceEntry > {
328+ const workspaces = new Map < string , WorkspaceEntry > ( ) ;
329+ const patterns : string [ ] = [ ] ;
311330
312331 // 1. pnpm-workspace.yaml
313332 const pnpmPath = path . join ( rootDir , 'pnpm-workspace.yaml' ) ;
@@ -317,11 +336,11 @@ export function detectWorkspaces(rootDir) {
317336 // Simple YAML parse for `packages:` array — no dependency needed
318337 const packagesMatch = raw . match ( / ^ p a c k a g e s : \s * \n ( (?: \s + - \s + .+ \n ? ) * ) / m) ;
319338 if ( packagesMatch ) {
320- const lines = packagesMatch [ 1 ] . match ( / ^ \s + - \s + [ ' " ] ? ( [ ^ ' " # \n ] + ) [ ' " ] ? \s * $ / gm) ;
339+ const lines = packagesMatch [ 1 ] ! . match ( / ^ \s + - \s + [ ' " ] ? ( [ ^ ' " # \n ] + ) [ ' " ] ? \s * $ / gm) ;
321340 if ( lines ) {
322341 for ( const line of lines ) {
323342 const m = line . match ( / ^ \s + - \s + [ ' " ] ? ( [ ^ ' " # \n ] + ?) [ ' " ] ? \s * $ / ) ;
324- if ( m ) patterns . push ( m [ 1 ] . trim ( ) ) ;
343+ if ( m ) patterns . push ( m [ 1 ] ! . trim ( ) ) ;
325344 }
326345 }
327346 }
@@ -393,8 +412,11 @@ export function detectWorkspaces(rootDir) {
393412 return workspaces ;
394413}
395414
396- export function mergeConfig ( defaults , overrides ) {
397- const result = { ...defaults } ;
415+ export function mergeConfig (
416+ defaults : Record < string , unknown > ,
417+ overrides : Record < string , unknown > ,
418+ ) : Record < string , unknown > {
419+ const result : Record < string , unknown > = { ...defaults } ;
398420 for ( const [ key , value ] of Object . entries ( overrides ) ) {
399421 if (
400422 value &&
@@ -404,7 +426,10 @@ export function mergeConfig(defaults, overrides) {
404426 typeof defaults [ key ] === 'object' &&
405427 ! Array . isArray ( defaults [ key ] )
406428 ) {
407- result [ key ] = mergeConfig ( defaults [ key ] , value ) ;
429+ result [ key ] = mergeConfig (
430+ defaults [ key ] as Record < string , unknown > ,
431+ value as Record < string , unknown > ,
432+ ) ;
408433 } else {
409434 result [ key ] = value ;
410435 }
0 commit comments