Goal: Eliminate magic numbers scattered across the codebase by routing all tunable parameters through the existing
.codegraphrc.jsonconfig system (DEFAULTSinsrc/infrastructure/config.js).
The config system already exists and handles deep-merge + env overrides, but ~50+ behavioral constants are hardcoded in individual modules and never read from config. Users cannot tune thresholds, depths, weights, or limits without editing source code.
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| A1 | maxDepth = 5 |
domain/analysis/impact.js |
111 | fn-impact transitive caller depth |
| A2 | maxDepth = 3 |
domain/analysis/impact.js |
31, 144 | BFS default depth for impact/diff-impact |
| A3 | maxDepth = 3 |
features/audit.js |
102 | Audit blast-radius depth |
| A4 | maxDepth = 3 |
features/check.js |
220 | CI check blast-radius depth |
| A5 | maxDepth = 10 |
features/sequence.js |
91 | Sequence diagram traversal depth |
| A6 | FALSE_POSITIVE_CALLER_THRESHOLD = 20 |
domain/analysis/module-map.js |
37 | Generic function false-positive filter |
| A7 | resolution = 1.0 |
graph/algorithms/louvain.js |
17 | Louvain community detection granularity |
| A8 | driftThreshold = 0.3 |
features/structure.js |
581 | Structure cohesion drift warning |
| A9 | maxCallers >= 10 |
domain/analysis/brief.js |
40 | brief high-risk tier threshold |
| A10 | maxCallers >= 3 |
domain/analysis/brief.js |
41 | brief medium-risk tier threshold |
| A11 | maxDepth = 5 |
domain/analysis/brief.js |
50 | brief transitive caller BFS depth |
| A12 | maxDepth = 5 |
domain/analysis/brief.js |
76 | brief transitive importer BFS depth |
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| B1 | fanIn: 0.25, complexity: 0.3, churn: 0.2, role: 0.15, mi: 0.1 |
graph/classifiers/risk.js |
10-14 | Risk score weighting |
| B2 | core: 1.0, utility: 0.9, entry: 0.8, adapter: 0.5, leaf: 0.2, dead: 0.1 |
graph/classifiers/risk.js |
21-27 | Role importance weights |
| B3 | DEFAULT_ROLE_WEIGHT = 0.5 |
graph/classifiers/risk.js |
30 | Fallback role weight |
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| C1 | limit = 15 |
domain/search/search/hybrid.js |
12 | Hybrid search default limit |
| C2 | rrfK = 60 |
domain/search/search/hybrid.js |
13 | RRF fusion constant |
| C3 | limit = 15 |
domain/search/search/semantic.js |
12 | Semantic search default limit |
| C4 | minScore = 0.2 |
domain/search/search/semantic.js |
13, 52 | Minimum similarity threshold |
| C5 | SIMILARITY_WARN_THRESHOLD = 0.85 |
domain/search/search/semantic.js |
71 | Duplicate query warning |
| C6 | Batch sizes per model | domain/search/models.js |
66-75 | Embedding batch sizes |
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| D1 | MAX_COL_WIDTH = 40 |
presentation/result-formatter.js |
82 | Table column width |
| D2 | 50 lines |
shared/file-utils.js |
23 | Source context excerpt length |
| D3 | 100 chars |
shared/file-utils.js |
48, 63 | Summary/docstring truncation |
| D4 | 10 / 20 lines |
shared/file-utils.js |
36, 54 | JSDoc scan depth |
| D5 | 5 lines |
shared/file-utils.js |
76 | Multi-line signature gather |
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| E1 | MCP_DEFAULTS (22 entries) |
shared/paginate.js |
9-34 | Per-tool default page sizes |
| E2 | MCP_MAX_LIMIT = 1000 |
shared/paginate.js |
37 | Hard abuse-prevention cap |
| # | Value | File | Line | Controls |
|---|---|---|---|---|
| F1 | CACHE_TTL_MS = 86400000 |
infrastructure/update-check.js |
10 | Version check cache (24h) |
| F2 | FETCH_TIMEOUT_MS = 3000 |
infrastructure/update-check.js |
11 | Version check HTTP timeout |
| F3 | debounce = 300 |
domain/graph/watcher.js |
80 | File watcher debounce (ms) |
| F4 | maxBuffer = 10MB |
features/check.js |
260 | Git diff buffer |
| F5 | volume / 3000 |
features/complexity.js |
85 | Halstead bugs formula (standard) |
| F6 | timeout = 10_000 |
infrastructure/config.js |
110 | apiKeyCommand timeout |
export const DEFAULTS = {
// ... existing fields ...
analysis: {
defaultDepth: 3, // A2: BFS depth for impact/diff-impact
fnImpactDepth: 5, // A1: fn-impact transitive depth
auditDepth: 3, // A3: audit blast-radius depth
sequenceDepth: 10, // A5: sequence diagram depth
falsePositiveCallers: 20, // A6: generic function filter threshold
briefBfsDepth: 5, // A11/A12: brief command BFS depth (callers + importers)
briefHighRiskCallers: 10, // A9: brief high-risk tier threshold
briefMediumRiskCallers: 3, // A10: brief medium-risk tier threshold
},
community: {
resolution: 1.0, // A7: Louvain resolution
driftThreshold: 0.2, // existing (build.driftThreshold → move here)
structureDriftThreshold: 0.3, // A8: structure cohesion drift
},
risk: {
weights: { // B1
fanIn: 0.25,
complexity: 0.3,
churn: 0.2,
role: 0.15,
mi: 0.1,
},
roleWeights: { // B2
core: 1.0,
utility: 0.9,
entry: 0.8,
adapter: 0.5,
leaf: 0.2,
dead: 0.1,
},
defaultRoleWeight: 0.5, // B3
},
display: {
maxColWidth: 40, // D1
excerptLines: 50, // D2
summaryMaxChars: 100, // D3
jsdocScanLines: 10, // D4
signatureGatherLines: 5, // D5
},
mcp: {
defaults: { /* E1: current MCP_DEFAULTS object */ },
maxLimit: 1000, // E2
},
};- Halstead
volume / 3000— industry-standard formula, not a tuning knob - Git
maxBuffer— platform concern, not analysis behavior apiKeyCommandtimeout — security boundary, not user-facing- Update check TTL/timeout — implementation detail
- Watcher debounce — could be configurable later but low priority
Files: src/infrastructure/config.js, tests/unit/config.test.js
- Add
analysis,community,risk,display,mcpsections toDEFAULTS - Move
build.driftThreshold→community.driftThreshold(keepbuild.driftThresholdas deprecated alias) - Update
mergeConfigto handle the new nested sections (already works for 1-level deep objects; verify 2-levelrisk.weightsmerges correctly — may need recursive merge) - Add tests: loading config with overrides for each new section
Files to change:
src/domain/analysis/impact.js→ readconfig.analysis.defaultDepth/config.analysis.fnImpactDepthsrc/features/audit.js→ readconfig.analysis.auditDepthsrc/features/check.js→ readconfig.check.depth(already exists) andconfig.analysis.defaultDepthsrc/features/sequence.js→ readconfig.analysis.sequenceDepthsrc/domain/analysis/module-map.js→ readconfig.analysis.falsePositiveCallerssrc/domain/analysis/brief.js→ readconfig.analysis.briefBfsDepth,config.analysis.briefHighRiskCallers,config.analysis.briefMediumRiskCallers(PR #480)
Pattern: Each module calls loadConfig() (or receives config as a parameter). Replace the hardcoded value with config.analysis.X ?? FALLBACK. The fallback ensures backward compatibility if config is missing.
Tests: Update integration tests to verify custom config values flow through.
Files to change:
src/graph/classifiers/risk.js→ readconfig.risk.weights,config.risk.roleWeights,config.risk.defaultRoleWeightsrc/graph/algorithms/louvain.js→ acceptresolutionparameter, default from configsrc/features/structure.js→ readconfig.community.structureDriftThreshold
Pattern: These modules don't currently receive config. Options:
- Preferred: Accept an
optionsparameter that callers populate from config - Alternative: Import
loadConfigdirectly (adds coupling but simpler)
Tests: Unit tests for risk scoring with custom weights. Integration test for Louvain with custom resolution.
Files to change:
src/domain/search/search/hybrid.js→ readconfig.search.rrfK,config.search.topKsrc/domain/search/search/semantic.js→ readconfig.search.defaultMinScoresrc/domain/search/models.js→ batch sizes could be config-overridable per model
Note: config.search already exists with defaultMinScore, rrfK, topK. The modules just don't read from it — they duplicate the values. This phase wires the existing config keys.
Files to change:
src/presentation/result-formatter.js→ readconfig.display.maxColWidthsrc/shared/file-utils.js→ readconfig.display.excerptLines, etc.src/shared/paginate.js→ readconfig.mcp.defaults,config.mcp.maxLimit
Consideration: file-utils.js and paginate.js are low-level shared utilities. They shouldn't call loadConfig() directly. Instead, pass display/mcp settings down from callers, or use a module-level config cache set at startup.
- Update
README.mdconfiguration section with the full schema - Add a
docs/configuration.mdreference with all keys, types, defaults, and descriptions - Document the deprecated
build.driftThresholdalias - Add a JSON Schema file (
.codegraphrc.schema.json) for IDE autocomplete
Add a Configuration section to CLAUDE.md that documents:
- The
.codegraphrc.jsonconfig file and its location - The full list of configurable sections (
analysis,community,risk,display,mcp,search,check,coChange,manifesto) - Key tunable parameters and their defaults (depth limits, risk weights, thresholds)
- How
mergeConfigworks (partial overrides deep-merge with defaults) - Env var overrides (
CODEGRAPH_LLM_*) - Guidance: when adding new behavioral constants, always add them to
DEFAULTSinconfig.jsand wire them through — never introduce new hardcoded magic numbers
- All new config keys have defaults matching current hardcoded values → zero breaking changes
- Existing
.codegraphrc.jsonfiles continue to work unchanged mergeConfigdeep-merges, so users only need to specify the keys they want to override- The
build.driftThreshold→community.driftThresholdmove uses a deprecated alias
{
"analysis": {
"fnImpactDepth": 8,
"falsePositiveCallers": 30
},
"risk": {
"weights": {
"complexity": 0.4,
"churn": 0.1
}
},
"community": {
"resolution": 1.5
},
"display": {
"maxColWidth": 60
}
}| Phase | Files changed | New tests | Risk |
|---|---|---|---|
| 1 — Schema | 2 | 3-4 | Low |
| 2 — Analysis wiring | 6 | 4-5 | Low |
| 3 — Risk/community | 3 | 2-3 | Medium (parameter threading) |
| 4 — Search wiring | 3 | 2 | Low (config keys already exist) |
| 5 — Display/MCP | 3 | 2 | Medium (shared utility coupling) |
| 6 — Docs + CLAUDE.md | 4 | 0 | None |
Total: ~20 files changed, 6 PRs, one concern per PR.