Skip to content

Commit eec9cc7

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/review-558
2 parents 3d33583 + 2656b98 commit eec9cc7

11 files changed

Lines changed: 344 additions & 169 deletions

File tree

src/domain/analysis/context.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ function buildCallees(
6363
const summary = cLines ? extractSummary(cLines, c.line, displayOpts) : null;
6464
let calleeSource: string | null = null;
6565
if (depth >= 1) {
66-
calleeSource = readSourceRange(repoRoot, c.file, c.line, c.end_line, displayOpts);
66+
calleeSource = readSourceRange(
67+
repoRoot,
68+
c.file,
69+
c.line,
70+
c.end_line ?? undefined,
71+
displayOpts,
72+
);
6773
}
6874
return {
6975
name: c.name,
@@ -97,7 +103,13 @@ function buildCallees(
97103
line: c.line,
98104
endLine: c.end_line || null,
99105
summary: cLines ? extractSummary(cLines, c.line, displayOpts) : null,
100-
source: readSourceRange(repoRoot, c.file, c.line, c.end_line, displayOpts),
106+
source: readSourceRange(
107+
repoRoot,
108+
c.file,
109+
c.line,
110+
c.end_line ?? undefined,
111+
displayOpts,
112+
),
101113
});
102114
}
103115
}
Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { execFileSync } from 'node:child_process';
22
import fs from 'node:fs';
33
import path from 'node:path';
4+
import type { CodegraphConfig } from '../types.js';
45
import { 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

815
export 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(/^packages:\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

Comments
 (0)