Skip to content

chore(config): retire legacy ProjectConfigLoader static API (#52)#52

Merged
aksOps merged 6 commits into
mainfrom
cleanup/retire-legacy-project-config-loader
Apr 23, 2026
Merged

chore(config): retire legacy ProjectConfigLoader static API (#52)#52
aksOps merged 6 commits into
mainfrom
cleanup/retire-legacy-project-config-loader

Conversation

@aksOps

@aksOps aksOps commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Post-Phase-B cleanup. Eliminates the last setter-mutation path on CodeIqConfig by retiring the deprecated static API on ProjectConfigLoader and migrating all runtime callers to read directly from the injected CodeIqUnifiedConfig bean. Closes follow-up task #52.

Phase 1 — schema gap fix

The investigation revealed the unified tree was missing three fields that .osscodeiq.yml legitimately exposed: detectors.categories, detectors.include, indexing.parsers. Without these, retiring the legacy statics would have silently dropped user-visible filtering behavior. Extended the schema (records, loader, env overlay, merger, validator, adapter, legacy translator) as a prerequisite. Also fixed IndexingConfig.parallelism: String → Integer (null = auto-detect sentinel).

Phase 2 — migrate + delete

  • Analyzer (3 call sites) — inject CodeIqUnifiedConfig, replace ProjectConfigLoader.loadProjectConfig(root) with direct reads of unified.indexing() + unified.detectors().
  • CliOutput.configureFromOptions — drop the redundant loadIfPresent reload. Values already populated on the injected bean at startup. Also dropped the now-unused root parameter (updated AnalyzeCommand, IndexCommand).
  • ProjectConfigLoaderApplyOverridesTest — ported to the unified pipeline (ConfigResolver → UnifiedConfigAdapter). Dropped 5 legacy-only tests (hyphenated filename variants, loadIfPresent return-false semantics).
  • ConfigDrivenPipelineTest deleted (superseded by unified-pipeline coverage).
  • ProjectConfigLoader static API deleted (loadIfPresent, loadProjectConfig, public parseProjectConfig); legacy io.github.randomcodespace.iq.config.ProjectConfig POJO deleted (orphaned).

Post-change ProjectConfigLoader surface

  • loadFrom(Path) → LoadResult — the only public loader method.

Commits

  • 478d551 feat(config): extend unified tree with detectors.categories, detectors.include, indexing.parsers
  • 2d5d4ae refactor(analyzer): drop ProjectConfigLoader reload, read filters from CodeIqUnifiedConfig
  • 6d7e434 refactor(cli): drop redundant ProjectConfigLoader reload in CliOutput
  • c161312 refactor(test): port ProjectConfigLoaderApplyOverridesTest to unified config pipeline
  • 9d2b415 refactor(test): delete ConfigDrivenPipelineTest (superseded by unified-pipeline tests)
  • 0cbc924 refactor(config): delete legacy static ProjectConfigLoader API + orphaned ProjectConfig POJO

Tests

  • Before: 3275 pass / 0 fail / 31 skipped
  • After: 3272 pass / 0 fail / 31 skipped
  • Net delta: +16 new unified-config tests, −19 legacy-only tests deleted. Zero regressions.

Notable behavior changes

  • .code-iq.yml / .code-iq.yaml filename variants no longer recognized. Undocumented aliases; repo conventions always used codeiq.yml. Flag here if any deployment relied on them.
  • Strict parsing for codeiq.yml — malformed max_depth throws ConfigLoadException with field path. Legacy .osscodeiq.yml path retains lenient coercion (silent null) to minimize migration friction during the one-release deprecation window.

Follow-ups (tracked)

Test plan

  • CI green
  • mvn -B test on merge commit reports 3272 pass
  • Smoke: a .osscodeiq.yml with legacy flat detector_categories: [java] still filters correctly (via the shim translator)
  • Smoke: a codeiq.yml with `indexing: { parsers: [javaparser, antlr] }` scopes parser selection correctly
  • Regression: existing codeiq.yml users see no behavior change

🤖 Generated with Claude Code

aksOps and others added 6 commits April 23, 2026 12:10
…s.include, indexing.parsers

Prep for Phase-2 retirement of the legacy ProjectConfigLoader static API.
The Analyzer pipeline currently reads language/detector/parser/parallelism
filters from the legacy ProjectConfig POJO via ProjectConfigLoader.loadProjectConfig;
moving those call sites onto the injected CodeIqUnifiedConfig bean requires
the unified tree to carry the same fields. This commit adds:

- DetectorsConfig: categories, include (List<String>). empty() updated.
- IndexingConfig: parsers (List<String>); parallelism changed from String to
  Integer with null = auto-detect. empty() updated.
- UnifiedConfigLoader: reads detectors.categories/include (snake_case) with
  camelCase aliases detectorCategories/detectorInclude (deprecated, per-load
  WARN via existing pick() helper); reads indexing.parsers; parses parallelism
  as Integer via requireIntOrNull.
- EnvVarOverlay: CODEIQ_DETECTORS_CATEGORIES, CODEIQ_DETECTORS_INCLUDE,
  CODEIQ_INDEXING_PARSERS (CSV); CODEIQ_INDEXING_PARALLELISM now parses to
  Integer and throws ConfigLoadException on malformed input.
- ConfigMerger: merges the three new leaves with full provenance tracking.
- ConfigValidator: indexing.parallelism must be > 0 if set (null = auto).
- ConfigDefaults.builtIn(): parallelism = null (auto), parsers = [],
  detectors.categories = [], detectors.include = []. No behavior change on
  the default path.
- ProjectConfigLoader.translateLegacyToUnified: maps the nested legacy shape
  (detectors.categories, detectors.include, parsers map, pipeline.parallelism)
  plus the flat top-level aliases detector_categories / detector_include into
  the new unified fields. parsers map is flattened to its values (Analyzer
  never consumed the per-language map at runtime).
- docs/codeiq.yml.example: documents parsers, detectors.categories, and
  detectors.include; notes parallelism: null = auto.

Test coverage (+16 cases):
- UnifiedConfigLoaderTest: snake_case load, camelCase alias WARN, parallelism
  as Integer, malformed parallelism throws, missing detectors section =
  empty lists.
- EnvVarOverlayTest: CSV parsing for the three new vars, Integer parallelism,
  malformed parallelism throws.
- ConfigMergerTest: layer-replacement for categories/include/parsers,
  parallelism merge, provenance attribution.
- ConfigValidatorTest: parallelism > 0 rejection + null-is-valid (auto).
- ConfigDefaultsTest: baseline assertions for new defaults.

Full suite: 3275 -> 3291 tests, 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m CodeIqUnifiedConfig

Before: runWithCache/runBatchedWithCache/runAnalyzeBatched each called
ProjectConfigLoader.loadProjectConfig(root) and re-parsed .osscodeiq.yml
from disk to pull detector category/include filters, language filter,
exclude patterns, and pipeline.parallelism. The unified config bean has
already resolved these at startup (UnifiedConfigBeans.codeIqUnifiedConfig)
so the per-call file I/O is pure waste.

After: inject CodeIqUnifiedConfig into Analyzer and project the relevant
leaves through a small private PipelineFilters record (categories, include,
languages, exclude, parallelism). All three call sites now read filters
from the injected tree; no file I/O, no legacy POJO, no static loader.

Back-compat constructor (6-arg, used by 5 existing tests) preserved —
defaults the unified overlay to CodeIqUnifiedConfig.empty(), matching the
pre-Phase-B "no .osscodeiq.yml present" path (no filters, auto parallelism).

SmartIndexTest.setUp updated to the new 9-arg primary constructor with
CodeIqUnifiedConfig.empty() (no filter semantics — the test doesn't
exercise filters).

Full suite: 3291 tests, 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CliOutput.configureFromOptions called ProjectConfigLoader.loadIfPresent
to re-apply cache_dir / max_depth / max_radius from .osscodeiq.yml onto
the injected CodeIqConfig bean. Those fields are already resolved at
Spring startup by UnifiedConfigBeans.codeIqConfig (via ConfigResolver +
UnifiedConfigAdapter), so the re-read was pure duplication.

Removed the reload call. The `root` parameter became unused and was
dropped from the method signature; two callers (AnalyzeCommand,
IndexCommand) updated accordingly.

Full suite: 3291 tests, 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… config pipeline

The legacy static API (ProjectConfigLoader.loadIfPresent /
loadProjectConfig / parseProjectConfig) is being removed in the next
commit. This test file previously exercised those methods directly;
all cases are rewritten to drive the same behaviour through the
Phase-B canonical shim ProjectConfigLoader.loadFrom(Path), asserting
on the returned CodeIqUnifiedConfig (and the projected CodeIqConfig
via UnifiedConfigAdapter for legacy-POJO-shaped assertions).

Ported cases (11):
- legacy cache_dir/max_depth/max_radius flow through to CodeIqConfig.
- legacy languages/detectors.categories/detectors.include/exclude/parsers/
  pipeline sections populate CodeIqUnifiedConfig.
- missing .osscodeiq.yml returns CodeIqUnifiedConfig.empty() with no
  deprecation warning emitted.
- SafeConstructor rejects unsafe YAML tags without executing arbitrary
  code (either returns a safe representation or throws
  ConfigLoadException; no code execution is the invariant).

Dropped cases (3):
- nestedAnalysisSectionDoesNotCrash / nestedOutputSectionDoesNotCrash:
  exercised dead branches inside the legacy applyOverrides that are
  being deleted entirely.
- invalidMaxDepthFallsBackToDefault: the legacy applyOverrides
  silently coerced non-numeric max_depth back to the default via its
  toInt helper. The unified loader is deliberately strict — malformed
  scalars throw ConfigLoadException with the field path. That behaviour
  change is already covered by UnifiedConfigLoaderTest; preserving the
  old lenient semantics would mask real misconfiguration.

Full suite: 3291 -> 3288 tests (-3 net), 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d-pipeline tests)

ConfigDrivenPipelineTest exclusively exercised ProjectConfigLoader.parseProjectConfig(Map)
and .loadProjectConfig(Path) — the deprecated static legacy-POJO parsers being
deleted in the next commit. Every unique assertion has equivalent coverage on
the unified pipeline:

- languages/detectors.categories/detectors.include/exclude/parsers/pipeline map
  parsing: covered by UnifiedConfigLoaderTest (loadsDetectorsCategoriesAndInclude,
  loadsIndexingParsersList, loadsIndexingParallelismAsInteger, fullFileRoundTripsEveryField).
- legacy .osscodeiq.yml end-to-end with flat keys: covered by
  ProjectConfigLoaderApplyOverridesTest (ported in the prior commit).
- empty/missing-file semantics: covered by
  UnifiedConfigLoaderTest.missingFileProducesEmptyOverlay and
  ProjectConfigLoaderApplyOverridesTest.missingConfigFileReturnsEmptyOverlay.
- ProjectConfig.empty() "no filters" assertion: equivalent to
  CodeIqUnifiedConfig.empty() behavior pinned in ConfigDefaultsTest.

No unique coverage lost. Removing the file (not porting) keeps the test tree
clean — parseProjectConfig is a private implementation detail of the legacy
shim, not an API worth separate test coverage after migration.

Full suite: 3288 -> 3277 tests (-11), 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aned ProjectConfig POJO

With Analyzer and CliOutput migrated off the legacy static API (prior two
commits) and the legacy-only tests rewritten against loadFrom(Path) (prior
two commits), the deprecated surface has no remaining callers.

Removed from ProjectConfigLoader:
- public static boolean loadIfPresent(Path, CodeIqConfig)
- public static ProjectConfig loadProjectConfig(Path)
- package-private static ProjectConfig parseProjectConfig(Map)
- private static applyOverrides / toInt helpers (only used by loadIfPresent)
- LEGACY_CONFIG_FILE_NAMES array (only used by the deleted statics; the
  new surface only honors codeiq.yml and .osscodeiq.yml).

Inlined parseProjectConfig's logic directly into translateLegacyToUnified
so the legacy ProjectConfig POJO has no remaining reference. The
translator now reads languages/detectors/exclude/parsers/pipeline.* in
place from the raw YAML map, without the intermediate POJO.

Deleted:
- src/main/java/io/github/randomcodespace/iq/config/ProjectConfig.java
  (orphaned after inlining)

Kept:
- ProjectConfigLoader#loadFrom(Path) — canonical Phase-B surface.
- ProjectConfigLoader.LoadResult — returned record.
- translateLegacyToUnified + readAndTranslateLegacy + countLegacyKeys —
  internal helpers for the .osscodeiq.yml deprecation shim.

ProjectConfigLoaderTest trimmed: removed 5 tests that exclusively
exercised the deleted legacy-file-name (.code-iq.yml/.yaml) paths plus
the removed return-false-and-mutate-CodeIqConfig semantics. Ported 2
meaningful tests (empty-file handling, invalid-YAML resilience) onto
loadFrom(Path); those pin safety behaviour of the deprecation shim.

Full suite: 3277 -> 3272 tests (-5), 0 failures, 31 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
72.6% Coverage on New Code (required ≥ 80%)
5.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@aksOps aksOps merged commit 65aac67 into main Apr 23, 2026
8 of 9 checks passed
@aksOps aksOps deleted the cleanup/retire-legacy-project-config-loader branch April 26, 2026 05:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant