From b141d20e8bbbf4f6fa3189c40d578ac9617f16c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 14:38:05 +0000 Subject: [PATCH 1/9] Extract core library module srcmorph (migration step 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the six non-mojo packages (config, document, indexer, prompt, provider, support) out of llamacpp-ai-index-maven-plugin into a new sibling reactor module net.ladenthin:srcmorph — a framework-free library with no Maven plugin API dependency. The plugin module now depends on srcmorph and keeps only the Mojo entry points. - New srcmorph/pom.xml (jar packaging), module-info.java (module net.ladenthin.srcmorph), and spotbugs-exclude.xml carrying the suppressions scoped to the moved classes (plus two new OPM_OVERLY_PERMISSIVE_METHOD suppressions on ProjectIndexer's constructor and SourceFileIndexer.classify, which only surfaced once their sole caller moved to a different SpotBugs analysis unit). net.ladenthin:llama and the gpu-cuda/gpu-vulkan classifier profiles moved to srcmorph, since only the provider package touches the JNI binding. - New CoreArchitectureTest (srcmorph) carries the internal layered architecture (Mojo layer removed), noPackageCycles, loggersArePrivateStaticFinal, jniConfinedToProvider, and a new coreIsMavenFree rule. PluginArchitectureTest is slimmed to mojo-relevant rules only. - PIT's 100%-mutation-parity targetClasses list (all config/document/ indexer/prompt/provider/support classes) moved to srcmorph's pom; the plugin pom drops its now-empty PIT execution with a comment. - The plugin's ai-index-selftest profile keeps invoking this plugin's own mojo goals (unchanged location) but its config/ provider self-index subtrees now point across the reactor into ../srcmorph. - REUSE.toml's GGUF resource annotation path updated to srcmorph/src/test/resources/. Verified: reactor-wide `mvn clean verify` is green (spotless, spotbugs 0 findings, 428 tests across both modules), and `mvn test-compile org.pitest:pitest-maven:mutationCoverage -pl srcmorph -am` reproduces the pre-extraction 100% mutation-kill parity (565/565 mutations killed across the 38 gated classes). The generated plugin.xml's 5 goals and every parameter name/required/ editable flag are unchanged from the pre-extraction baseline; only the @see javadoc FQNs now point at net.ladenthin.srcmorph.*. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- REUSE.toml | 2 +- llamacpp-ai-index-maven-plugin/pom.xml | 197 ++---- .../spotbugs-exclude.xml | 320 +-------- .../src/main/java/module-info.java | 24 +- .../aiindex/mojo/AbstractAiIndexMojo.java | 36 +- .../aiindex/mojo/AggregatePackagesMojo.java | 10 +- .../aiindex/mojo/AggregateProjectMojo.java | 14 +- .../llamacpp/aiindex/mojo/CalibrateMojo.java | 18 +- .../llamacpp/aiindex/mojo/GenerateMojo.java | 32 +- .../aiindex/PluginArchitectureTest.java | 106 +-- .../jcstress/AiGenerationKindRace.java | 2 +- pom.xml | 11 +- srcmorph/pom.xml | 628 ++++++++++++++++++ srcmorph/spotbugs-exclude.xml | 401 +++++++++++ srcmorph/src/main/java/module-info.java | 54 ++ .../srcmorph}/config/AiCalibration.java | 2 +- .../srcmorph}/config/AiCondition.java | 2 +- .../config/AiConditionEvaluator.java | 4 +- .../srcmorph}/config/AiConditionGroup.java | 2 +- .../srcmorph}/config/AiFactCounter.java | 2 +- .../srcmorph}/config/AiFactDefinition.java | 2 +- .../config/AiFactDefinitionSupport.java | 11 +- .../srcmorph}/config/AiFactExtractor.java | 2 +- .../config/AiFieldGenerationConfig.java | 4 +- .../config/AiFieldGenerationSelector.java | 2 +- .../srcmorph}/config/AiFileContext.java | 2 +- .../srcmorph}/config/AiGenerationConfig.java | 2 +- .../srcmorph}/config/AiGenerationKind.java | 2 +- .../srcmorph}/config/AiModelDefinition.java | 2 +- .../config/AiModelDefinitionSupport.java | 12 +- .../srcmorph}/config/AiOversizeStrategy.java | 2 +- .../srcmorph}/config/AiRangeCondition.java | 2 +- .../srcmorph}/config/package-info.java | 2 +- .../document/AiGenerationRequest.java | 8 +- .../document/AiGenerationResult.java | 8 +- .../document/AiMdChildEntryLineFormatter.java | 6 +- .../srcmorph}/document/AiMdDocument.java | 6 +- .../srcmorph}/document/AiMdDocumentCodec.java | 4 +- .../srcmorph}/document/AiMdHeader.java | 6 +- .../srcmorph}/document/AiMdHeaderCodec.java | 4 +- .../srcmorph}/document/AiMdHeaderSupport.java | 4 +- .../srcmorph}/document/AiMdLeadExtractor.java | 4 +- .../srcmorph}/document/package-info.java | 2 +- .../indexer/AiCalibrationMeasurement.java | 4 +- .../indexer/AiCalibrationRunner.java | 14 +- .../indexer/AiFieldGenerationSupport.java | 60 +- .../srcmorph}/indexer/AiIndexPlan.java | 8 +- .../indexer/AiInputWindowCalculator.java | 6 +- .../srcmorph}/indexer/PackageIndexer.java | 48 +- .../srcmorph}/indexer/ProjectIndexer.java | 32 +- .../srcmorph}/indexer/SourceFileIndexer.java | 46 +- .../srcmorph}/indexer/package-info.java | 2 +- .../srcmorph}/prompt/AiPreparedPrompt.java | 6 +- .../srcmorph}/prompt/AiPromptDefinition.java | 2 +- .../prompt/AiPromptPreparationSupport.java | 6 +- .../srcmorph}/prompt/AiPromptSupport.java | 14 +- .../srcmorph}/prompt/package-info.java | 2 +- .../provider/AiCompletionParser.java | 2 +- .../provider/AiGenerationProvider.java | 6 +- .../provider/AiGenerationProviderFactory.java | 6 +- .../provider/AiGenerationTimings.java | 4 +- .../LlamaCppJniAiGenerationProvider.java | 8 +- .../srcmorph}/provider/LlamaCppJniConfig.java | 6 +- .../provider/MockAiGenerationProvider.java | 4 +- .../srcmorph}/provider/package-info.java | 2 +- .../srcmorph}/support/AiChecksumSupport.java | 2 +- .../support/AiDeterministicSummary.java | 2 +- .../support/AiGenerationTimeEstimator.java | 2 +- .../srcmorph}/support/AiPathSupport.java | 2 +- .../srcmorph}/support/AiProgressBar.java | 2 +- .../srcmorph}/support/AiSourceChunker.java | 2 +- .../support/AiSourceExcludeFilter.java | 2 +- .../srcmorph}/support/AiTimeSupport.java | 2 +- .../srcmorph}/support/ConvertToRecord.java | 2 +- .../support/Java8CompatibilityHelper.java | 2 +- .../srcmorph}/support/package-info.java | 2 +- .../srcmorph}/CommonTestFixtures.java | 12 +- .../srcmorph/CoreArchitectureTest.java | 238 +++++++ .../srcmorph}/config/AiCalibrationTest.java | 2 +- .../config/AiConditionEvaluatorTest.java | 2 +- .../config/AiConditionGroupTest.java | 2 +- .../srcmorph}/config/AiConditionTest.java | 2 +- .../srcmorph}/config/AiFactCounterTest.java | 2 +- .../config/AiFactDefinitionSupportTest.java | 2 +- .../config/AiFactDefinitionTest.java | 2 +- .../srcmorph}/config/AiFactExtractorTest.java | 2 +- .../config/AiFieldGenerationConfigTest.java | 2 +- .../config/AiFieldGenerationSelectorTest.java | 2 +- .../srcmorph}/config/AiFileContextTest.java | 2 +- .../config/AiGenerationConfigTest.java | 2 +- .../config/AiGenerationKindLincheckTest.java | 2 +- .../config/AiModelDefinitionSupportTest.java | 2 +- .../config/AiModelDefinitionTest.java | 2 +- .../config/AiOversizeStrategyTest.java | 2 +- .../config/AiRangeConditionTest.java | 2 +- .../config/JavaStructureFactPatternsTest.java | 2 +- .../AiMdChildEntryLineFormatterTest.java | 2 +- .../document/AiMdDocumentCodecTest.java | 2 +- .../document/AiMdHeaderCodecProperties.java | 2 +- .../document/AiMdHeaderCodecTest.java | 2 +- .../document/AiMdHeaderSupportTest.java | 2 +- .../srcmorph}/document/AiMdHeaderTest.java | 2 +- .../document/AiMdLeadExtractorTest.java | 2 +- .../indexer/AiCalibrationMeasurementTest.java | 2 +- .../indexer/AiCalibrationRunnerTest.java | 18 +- ...AiFieldGenerationSupportRealModelTest.java | 28 +- .../indexer/AiFieldGenerationSupportTest.java | 28 +- .../srcmorph}/indexer/AiIndexPlanTest.java | 4 +- .../indexer/AiInputWindowCalculatorTest.java | 6 +- .../srcmorph}/indexer/PackageIndexerTest.java | 20 +- .../srcmorph}/indexer/ProjectIndexerTest.java | 20 +- .../indexer/SourceFileIndexerTest.java | 18 +- .../prompt/AiPreparedPromptTest.java | 2 +- .../AiPromptPreparationSupportTest.java | 8 +- .../srcmorph}/prompt/AiPromptSupportTest.java | 2 +- .../provider/AiCompletionParserTest.java | 2 +- ...iGenerationProviderDefaultTimingsTest.java | 6 +- .../AiGenerationProviderFactoryTest.java | 4 +- .../provider/AiGenerationTimingsTest.java | 2 +- .../LlamaCppJniAiGenerationProviderTest.java | 14 +- .../MockAiGenerationProviderTest.java | 6 +- .../support/AiChecksumSupportTest.java | 2 +- .../support/AiDeterministicSummaryTest.java | 2 +- .../AiGenerationTimeEstimatorTest.java | 2 +- .../srcmorph}/support/AiPathSupportTest.java | 2 +- .../srcmorph}/support/AiProgressBarTest.java | 2 +- .../support/AiSourceChunkerTest.java | 2 +- .../support/AiSourceExcludeFilterTest.java | 2 +- .../srcmorph}/support/AiTimeSupportTest.java | 2 +- .../support/Java8CompatibilityHelperTest.java | 2 +- .../SmolLM2-135M-Instruct-Q3_K_M.gguf | Bin 131 files changed, 1831 insertions(+), 953 deletions(-) create mode 100644 srcmorph/pom.xml create mode 100644 srcmorph/spotbugs-exclude.xml create mode 100644 srcmorph/src/main/java/module-info.java rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiCalibration.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiCondition.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiConditionEvaluator.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiConditionGroup.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFactCounter.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFactDefinition.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFactDefinitionSupport.java (91%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFactExtractor.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFieldGenerationConfig.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFieldGenerationSelector.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiFileContext.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiGenerationConfig.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiGenerationKind.java (89%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiModelDefinition.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiModelDefinitionSupport.java (93%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiOversizeStrategy.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/AiRangeCondition.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/config/package-info.java (78%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiGenerationRequest.java (85%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiGenerationResult.java (80%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdChildEntryLineFormatter.java (88%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdDocument.java (83%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdDocumentCodec.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdHeader.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdHeaderCodec.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdHeaderSupport.java (94%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/AiMdLeadExtractor.java (94%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/document/package-info.java (76%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/AiCalibrationMeasurement.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/AiCalibrationRunner.java (95%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/AiFieldGenerationSupport.java (93%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/AiIndexPlan.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/AiInputWindowCalculator.java (95%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/PackageIndexer.java (92%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/ProjectIndexer.java (95%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/SourceFileIndexer.java (92%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/indexer/package-info.java (79%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/prompt/AiPreparedPrompt.java (92%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/prompt/AiPromptDefinition.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/prompt/AiPromptPreparationSupport.java (95%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/prompt/AiPromptSupport.java (92%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/prompt/package-info.java (76%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/AiCompletionParser.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/AiGenerationProvider.java (89%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/AiGenerationProviderFactory.java (89%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/AiGenerationTimings.java (95%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/LlamaCppJniAiGenerationProvider.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/LlamaCppJniConfig.java (97%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/MockAiGenerationProvider.java (93%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/provider/package-info.java (80%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiChecksumSupport.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiDeterministicSummary.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiGenerationTimeEstimator.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiPathSupport.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiProgressBar.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiSourceChunker.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiSourceExcludeFilter.java (98%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/AiTimeSupport.java (96%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/ConvertToRecord.java (85%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/Java8CompatibilityHelper.java (99%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/main/java/net/ladenthin/srcmorph}/support/package-info.java (79%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/CommonTestFixtures.java (93%) create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiCalibrationTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiConditionEvaluatorTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiConditionGroupTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiConditionTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFactCounterTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFactDefinitionSupportTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFactDefinitionTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFactExtractorTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFieldGenerationConfigTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFieldGenerationSelectorTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiFileContextTest.java (93%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiGenerationConfigTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiGenerationKindLincheckTest.java (95%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiModelDefinitionSupportTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiModelDefinitionTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiOversizeStrategyTest.java (97%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/AiRangeConditionTest.java (97%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/config/JavaStructureFactPatternsTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdChildEntryLineFormatterTest.java (94%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdDocumentCodecTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdHeaderCodecProperties.java (94%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdHeaderCodecTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdHeaderSupportTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdHeaderTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/document/AiMdLeadExtractorTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiCalibrationMeasurementTest.java (93%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiCalibrationRunnerTest.java (81%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiFieldGenerationSupportRealModelTest.java (85%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiFieldGenerationSupportTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiIndexPlanTest.java (97%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/AiInputWindowCalculatorTest.java (94%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/PackageIndexerTest.java (94%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/ProjectIndexerTest.java (95%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/indexer/SourceFileIndexerTest.java (93%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/prompt/AiPreparedPromptTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/prompt/AiPromptPreparationSupportTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/prompt/AiPromptSupportTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/AiCompletionParserTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/AiGenerationProviderDefaultTimingsTest.java (87%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/AiGenerationProviderFactoryTest.java (95%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/AiGenerationTimingsTest.java (92%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/LlamaCppJniAiGenerationProviderTest.java (92%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/provider/MockAiGenerationProviderTest.java (92%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiChecksumSupportTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiDeterministicSummaryTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiGenerationTimeEstimatorTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiPathSupportTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiProgressBarTest.java (97%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiSourceChunkerTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiSourceExcludeFilterTest.java (99%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/AiTimeSupportTest.java (96%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph/src/test/java/net/ladenthin/srcmorph}/support/Java8CompatibilityHelperTest.java (98%) rename {llamacpp-ai-index-maven-plugin => srcmorph}/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf (100%) diff --git a/REUSE.toml b/REUSE.toml index 55888a5..1a2dcb1 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -18,7 +18,7 @@ SPDX-FileCopyrightText = "2026 Bernard Ladenthin " SPDX-License-Identifier = "Apache-2.0" [[annotations]] -path = "llamacpp-ai-index-maven-plugin/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf" +path = "srcmorph/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf" precedence = "aggregate" SPDX-FileCopyrightText = "HuggingFaceTB" SPDX-License-Identifier = "Apache-2.0" diff --git a/llamacpp-ai-index-maven-plugin/pom.xml b/llamacpp-ai-index-maven-plugin/pom.xml index 09bb6b6..7800049 100644 --- a/llamacpp-ai-index-maven-plugin/pom.xml +++ b/llamacpp-ai-index-maven-plugin/pom.xml @@ -30,16 +30,10 @@ SPDX-License-Identifier: Apache-2.0 UTF-8 3.15.2 3.9.16 - - 5.0.6 - - + - 1.9.3 + 1.4.2 4.10.2.0 1.18.46 @@ -127,18 +115,23 @@ SPDX-License-Identifier: Apache-2.0 checker-qual ${checker.version} + net.ladenthin - llama - ${llama.version} - ${llama.classifier} + srcmorph + ${project.version} org.slf4j @@ -172,12 +165,14 @@ SPDX-License-Identifier: Apache-2.0 ${hamcrest.version} test - - net.jqwik - jqwik - ${jqwik.version} - test - + com.tngtech.archunit archunit-junit5 @@ -202,12 +197,6 @@ SPDX-License-Identifier: Apache-2.0 ${jcstress.version} test - - org.jetbrains.lincheck - lincheck - ${lincheck.version} - test - - - ch.qos.logback - logback-classic - test - @@ -678,102 +656,27 @@ SPDX-License-Identifier: Apache-2.0 test - - - org.pitest - pitest-maven - - - org.pitest - pitest-junit5-plugin - 1.2.3 - - - - - - net.ladenthin.maven.llamacpp.aiindex.config.AiCalibration - net.ladenthin.maven.llamacpp.aiindex.config.AiCondition - net.ladenthin.maven.llamacpp.aiindex.config.AiConditionEvaluator - net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig - net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationSelector - net.ladenthin.maven.llamacpp.aiindex.config.AiFileContext - net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig - net.ladenthin.maven.llamacpp.aiindex.config.AiRangeCondition - net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition - net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport - net.ladenthin.maven.llamacpp.aiindex.indexer.AiCalibrationMeasurement - net.ladenthin.maven.llamacpp.aiindex.indexer.AiInputWindowCalculator - net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest - net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult - net.ladenthin.maven.llamacpp.aiindex.document.AiMdChildEntryLineFormatter - net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument - net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader - net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderSupport - net.ladenthin.maven.llamacpp.aiindex.document.AiMdLeadExtractor - net.ladenthin.maven.llamacpp.aiindex.prompt.AiPreparedPrompt - net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition - net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport - net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport - net.ladenthin.maven.llamacpp.aiindex.provider.AiCompletionParser - net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationTimings - net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider - net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory - net.ladenthin.maven.llamacpp.aiindex.provider.MockAiGenerationProvider - net.ladenthin.maven.llamacpp.aiindex.config.AiOversizeStrategy - net.ladenthin.maven.llamacpp.aiindex.config.AiFactCounter - net.ladenthin.maven.llamacpp.aiindex.config.AiFactDefinition - net.ladenthin.maven.llamacpp.aiindex.config.AiFactDefinitionSupport - net.ladenthin.maven.llamacpp.aiindex.config.AiFactExtractor - net.ladenthin.maven.llamacpp.aiindex.support.AiChecksumSupport - net.ladenthin.maven.llamacpp.aiindex.support.AiDeterministicSummary - net.ladenthin.maven.llamacpp.aiindex.support.AiGenerationTimeEstimator - net.ladenthin.maven.llamacpp.aiindex.support.AiPathSupport - net.ladenthin.maven.llamacpp.aiindex.support.AiProgressBar - net.ladenthin.maven.llamacpp.aiindex.support.AiSourceExcludeFilter - net.ladenthin.maven.llamacpp.aiindex.support.AiTimeSupport - net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper - - - net.ladenthin.maven.llamacpp.aiindex.* - - 100 - 30000 - - + - - - gpu-cuda - - cuda13-windows-x86-64 - - - - gpu-vulkan - - vulkan-windows-x86-64 - - - + single aiindex subtree for a full self-index. + UPDATED (srcmorph core-module extraction, step 4): config and + provider now live in the sibling srcmorph module, not under this + module's own basedir, so the subtrees below reach across the + reactor via a relative ".." path (AbstractAiIndexMojo resolves + subtrees with basePath.resolve(subtree).normalize(), which permits + ".." segments). This profile still runs `mvn ai-index:generate -P + ai-index-selftest` from THIS module (it invokes this plugin's own + goals), it just indexes source that now lives one directory up. --> - src/main/java/net/ladenthin/maven/llamacpp/aiindex/config - src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider + ../srcmorph/src/main/java/net/ladenthin/srcmorph/config + ../srcmorph/src/main/java/net/ladenthin/srcmorph/provider - - - - - - - - - - - - - - - - - - - - @@ -223,31 +156,6 @@ SPDX-License-Identifier: Apache-2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -511,46 +261,4 @@ SPDX-License-Identifier: Apache-2.0 - - - - - - - - - - - - - - - - - - diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java b/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java index 130d25c..a18381c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java @@ -5,8 +5,10 @@ /** * JPMS module descriptor for the llamacpp-ai-index Maven plugin. * - *

The module exports the single public package {@code net.ladenthin.maven.llamacpp.aiindex} - * that holds the configuration POJOs and Mojo entry points. The auto-generated + *

The module exports the single public package {@code net.ladenthin.maven.llamacpp.aiindex.mojo} + * that holds the Mojo entry points. The configuration POJOs, document codec, indexers, prompt + * support, and AI generation provider abstraction now live in the {@code net.ladenthin.srcmorph} + * module (the extracted core library), which this module {@code requires}. The auto-generated * {@code net.ladenthin.llamacpp_ai_index_maven_plugin.HelpMojo} package is deliberately * not exported: Maven loads the plugin via its own classpath classloader and * never consults the module descriptor for Mojo discovery, so the {@code HelpMojo} class @@ -25,11 +27,10 @@ * descriptor; JSpecify annotations carry {@code RetentionPolicy.CLASS} so module-path * consumers never need jspecify on their runtime path. Annotations from * {@code maven-plugin-annotations} and Checker Framework qualifiers are likewise - * compile-time only. The classes imported from {@code maven-plugin-api} and - * {@code net.ladenthin:llama} are referenced from Mojo source files only; javac in the - * separate {@code module-info-compile} execution compiles {@code module-info.java} in - * isolation and therefore does not need their module names. Maven, in turn, loads the - * plugin classpath-only and ignores the descriptor at runtime.

+ * compile-time only. The classes imported from {@code maven-plugin-api} are referenced from + * Mojo source files only; javac in the separate {@code module-info-compile} execution compiles + * {@code module-info.java} in isolation and therefore does not need their module names. Maven, + * in turn, loads the plugin classpath-only and ignores the descriptor at runtime.

* *

This descriptor compiles at {@code --release 9}; the rest of the source compiles * at {@code --release 8}. Java 8 runtimes silently ignore {@code module-info.class} at @@ -44,11 +45,8 @@ // the @lombok.Generated annotation carried on generated members has CLASS retention. requires static lombok; - exports net.ladenthin.maven.llamacpp.aiindex.config; - exports net.ladenthin.maven.llamacpp.aiindex.document; - exports net.ladenthin.maven.llamacpp.aiindex.indexer; + // The extracted core library: config/document/indexer/prompt/provider/support now live there. + requires net.ladenthin.srcmorph; + exports net.ladenthin.maven.llamacpp.aiindex.mojo; - exports net.ladenthin.maven.llamacpp.aiindex.prompt; - exports net.ladenthin.maven.llamacpp.aiindex.provider; - exports net.ladenthin.maven.llamacpp.aiindex.support; } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java index 0a1bcaf..c346b62 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java @@ -10,13 +10,13 @@ import java.util.Collections; import java.util.List; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.LlamaCppJniConfig; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.LlamaCppJniConfig; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Parameter; @@ -85,7 +85,7 @@ protected AbstractAiIndexMojo() { * Name of the AI generation provider to use. * Supported values: {@code mock}, {@code llamacpp-jni}. * - * @see net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory + * @see net.ladenthin.srcmorph.provider.AiGenerationProviderFactory */ @Parameter(property = "aiIndex.generationProvider", defaultValue = "mock") protected String generationProvider; @@ -99,8 +99,8 @@ protected AbstractAiIndexMojo() { * Field-generation entries and the provider configuration reference these definitions * by key rather than embedding the full parameter set inline. * - * @see net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition - * @see net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport + * @see net.ladenthin.srcmorph.config.AiModelDefinition + * @see net.ladenthin.srcmorph.config.AiModelDefinitionSupport */ @Parameter protected List aiDefinitions; @@ -212,12 +212,12 @@ protected int sizeOf(final Collection collection) { } /** - * Builds a {@link net.ladenthin.maven.llamacpp.aiindex.provider.LlamaCppJniConfig} for the AI generation provider. + * Builds a {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} for the AI generation provider. * *

When {@link #fieldGenerations} is non-empty, all model parameters * (model path, context size, max output tokens, temperature, threads) are taken from - * the {@link net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition} referenced by the first entry's - * {@link net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig#getAiDefinitionKey()}. This ensures the provider is + * the {@link net.ladenthin.srcmorph.config.AiModelDefinition} referenced by the first entry's + * {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig#getAiDefinitionKey()}. This ensures the provider is * always configured from the same definition that drives field generation.

* *

When {@link #fieldGenerations} is {@code null} or empty, the individual @@ -226,7 +226,7 @@ protected int sizeOf(final Collection collection) { * * @return fully populated llama.cpp configuration * @throws IllegalArgumentException if the first field generation's - * {@link net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig#getAiDefinitionKey()} + * {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig#getAiDefinitionKey()} * does not match any registered definition * @throws MojoExecutionException propagated from {@link #buildAiModelDefinitionSupport()} * if any AI definition is missing a required field @@ -265,11 +265,11 @@ protected LlamaCppJniConfig buildLlamaCppJniConfig() throws MojoExecutionExcepti } /** - * Builds a {@link net.ladenthin.maven.llamacpp.aiindex.provider.LlamaCppJniConfig} from a specific + * Builds a {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} from a specific * AI model definition, identified by its key. Used by the {@code generate} goal to load a separate * model per routing group (one provider per distinct {@code aiDefinitionKey}). * - * @param aiDefinitionKey the {@link net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition} key + * @param aiDefinitionKey the {@link net.ladenthin.srcmorph.config.AiModelDefinition} key * @return fully populated llama.cpp configuration for that definition * @throws IllegalArgumentException if {@code aiDefinitionKey} matches no registered definition * @throws MojoExecutionException propagated from {@link #buildAiModelDefinitionSupport()} @@ -308,7 +308,7 @@ protected LlamaCppJniConfig buildLlamaCppJniConfig(final String aiDefinitionKey) } /** - * Builds an {@link net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport} from the configured {@link #promptDefinitions}. + * Builds an {@link net.ladenthin.srcmorph.prompt.AiPromptSupport} from the configured {@link #promptDefinitions}. * *

If any {@code } entry in the POM is missing its {@code key} * or {@code template}, the underlying constructor throws @@ -330,7 +330,7 @@ protected AiPromptSupport buildPromptSupport() throws MojoExecutionException { } /** - * Builds an {@link net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport} from the configured {@link #aiDefinitions}. + * Builds an {@link net.ladenthin.srcmorph.config.AiModelDefinitionSupport} from the configured {@link #aiDefinitions}. * *

If any {@code } entry in the POM is missing its {@code key}, * the underlying constructor throws {@link NullPointerException} with a message diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java index 15cec8d..7c4fb6a 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java @@ -8,11 +8,11 @@ import java.util.Collections; import java.util.List; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.indexer.PackageIndexer; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.indexer.PackageIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java index fda4c25..5b35888 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java @@ -6,13 +6,13 @@ import java.io.IOException; import java.nio.file.Path; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.indexer.ProjectIndexer; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.indexer.ProjectIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java index fdbc8ee..9267fa6 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java @@ -7,15 +7,15 @@ import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.indexer.AiCalibrationMeasurement; -import net.ladenthin.maven.llamacpp.aiindex.indexer.AiCalibrationRunner; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement; +import net.ladenthin.srcmorph.indexer.AiCalibrationRunner; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java index d2395bc..518a1dd 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java @@ -9,20 +9,20 @@ import java.util.List; import java.util.Map; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactDefinition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationSelector; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.indexer.AiFieldGenerationSupport; -import net.ladenthin.maven.llamacpp.aiindex.indexer.AiIndexPlan; -import net.ladenthin.maven.llamacpp.aiindex.indexer.SourceFileIndexer; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProviderFactory; -import net.ladenthin.maven.llamacpp.aiindex.support.AiGenerationTimeEstimator; -import net.ladenthin.maven.llamacpp.aiindex.support.AiProgressBar; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiFactDefinition; +import net.ladenthin.srcmorph.config.AiFactDefinitionSupport; +import net.ladenthin.srcmorph.config.AiFieldGenerationSelector; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.indexer.AiFieldGenerationSupport; +import net.ladenthin.srcmorph.indexer.AiIndexPlan; +import net.ladenthin.srcmorph.indexer.SourceFileIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; +import net.ladenthin.srcmorph.support.AiProgressBar; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -75,7 +75,7 @@ public GenerateMojo() { * {@code **}{@code /generated/**}). Lets the index stay focused by excluding trivial or generated * sources. Empty by default — nothing is excluded. * - * @see net.ladenthin.maven.llamacpp.aiindex.support.AiSourceExcludeFilter + * @see net.ladenthin.srcmorph.support.AiSourceExcludeFilter */ @Parameter(property = "aiIndex.excludes") private List excludes; @@ -85,7 +85,7 @@ public GenerateMojo() { * fact set (e.g. {@code java-facts}, {@code sql-facts}) is defined once and shared across rules * instead of repeated inline. Resolved onto each rule's {@code facts} before indexing. * - * @see net.ladenthin.maven.llamacpp.aiindex.config.AiFactDefinitionSupport + * @see net.ladenthin.srcmorph.config.AiFactDefinitionSupport */ @Parameter private List factDefinitions; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java b/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java index 2c1b2dd..19a967e 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java +++ b/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java @@ -13,10 +13,16 @@ import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import java.util.Random; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition; import org.slf4j.Logger; +/** + * Architecture rules for the llamacpp-ai-index Maven plugin module itself — the {@code mojo} + * package (Mojo entry points) — after the {@code config}/{@code document}/{@code indexer}/ + * {@code prompt}/{@code provider}/{@code support} packages were extracted into the sibling + * {@code srcmorph} core library. The rules that governed those layers (the internal layered + * architecture, {@code nonMojoIsMavenFree}, {@code jniConfinedToProvider}, etc.) now live in that + * module's own {@code CoreArchitectureTest}; only mojo-relevant rules remain here. + */ @AnalyzeClasses(packages = "net.ladenthin.maven.llamacpp.aiindex", importOptions = ImportOption.DoNotIncludeTests.class) public class PluginArchitectureTest { @@ -44,9 +50,8 @@ public class PluginArchitectureTest { /** * Production code must not write to {@code System.out} / {@code System.err}; all output - * goes through an SLF4J {@link Logger} (mojo-lifecycle messages still use Maven's - * {@code Log} via {@code getLog()}). Currently vacuous (no usage); acts as a regression - * guard. + * goes through Maven's {@code Log} via {@code getLog()}. Currently vacuous (no usage); acts + * as a regression guard. */ @ArchTest static final ArchRule noSystemOutOrErrInProduction = noClasses() @@ -79,53 +84,24 @@ public class PluginArchitectureTest { .allowEmptyShould(true); /** - * Strict layered architecture — one layer per package. Each package's - * {@code mayOnlyBeAccessedByLayers} lists the EXACT set of packages that reference it today - * (verified against the compiled bytecode graph), so even intra-tier edges are governed - * (e.g. {@code provider} may use {@code document}+{@code prompt} but {@code document} and - * {@code prompt} may not use each other beyond what is listed). A new dependency between any - * two packages fails the build unless this rule is updated to intend it. Conceptual tiers: - * {@code Mojo} > {@code Indexer} > {@code Provider} > {@code Document}/{@code Prompt} - * > {@code Config}/{@code Support}. + * Layered architecture — now a single {@code Mojo} layer (the other tiers moved to the + * srcmorph core library and are governed by that module's own {@code CoreArchitectureTest}). + * Kept as a regression guard: nothing inside this module may depend on the {@code mojo} + * package (the plugin's own code never needs to reach "up" into its own entry points). */ @ArchTest static final ArchRule layeredArchitecture = layeredArchitecture() .consideringOnlyDependenciesInLayers() .layer("Mojo") .definedBy("net.ladenthin.maven.llamacpp.aiindex.mojo..") - .layer("Indexer") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.indexer..") - .layer("Provider") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.provider..") - .layer("Document") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.document..") - .layer("Prompt") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.prompt..") - .layer("Config") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.config..") - .layer("Support") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.support..") .whereLayer("Mojo") - .mayNotBeAccessedByAnyLayer() - .whereLayer("Indexer") - .mayOnlyBeAccessedByLayers("Mojo") - .whereLayer("Provider") - .mayOnlyBeAccessedByLayers("Mojo", "Indexer") - .whereLayer("Document") - .mayOnlyBeAccessedByLayers("Indexer", "Prompt", "Provider") - .whereLayer("Prompt") - .mayOnlyBeAccessedByLayers("Indexer", "Mojo", "Provider") - .whereLayer("Config") - .mayOnlyBeAccessedByLayers("Indexer", "Mojo") - .whereLayer("Support") - .mayOnlyBeAccessedByLayers("Config", "Document", "Indexer", "Mojo", "Prompt", "Provider"); + .mayNotBeAccessedByAnyLayer(); /** * Public mutable state forbidden: any non-static field declared * {@code public} must also be {@code final}. Maven plugin configuration - * POJOs use {@code private} fields with setters - * ({@link AiPromptDefinition}, {@link AiModelDefinition}, etc.) and - * therefore pass this rule. + * POJOs (now in the srcmorph module) use {@code private} fields with setters; + * this rule is a regression guard on whatever remains here. */ @ArchTest static final ArchRule noPublicMutableFields = fields().that() @@ -180,32 +156,11 @@ public class PluginArchitectureTest { .callMethod(Thread.class, "sleep", long.class, int.class) .allowEmptyShould(true); - // --------------------------------------------------------------------------------------- - // Per-module banned imports — confine the heavy / framework-specific dependencies to the - // single layer that owns them, keeping the rest of the plugin decoupled and unit-testable. - // --------------------------------------------------------------------------------------- - - /** - * The llama.cpp JNI binding ({@code net.ladenthin.llama..}) may only be referenced from the - * {@code provider} package, where {@code LlamaCppJniAiGenerationProvider} wraps it. Every - * other layer talks to the model exclusively through the {@code AiGenerationProvider} - * interface, so the indexers, document/prompt/config and mojo code carry no JNI dependency - * and stay testable with {@code MockAiGenerationProvider}. - */ - @ArchTest - static final ArchRule jniConfinedToProvider = noClasses() - .that() - .resideOutsideOfPackage("net.ladenthin.maven.llamacpp.aiindex.provider..") - .should() - .dependOnClassesThat() - .resideInAPackage("net.ladenthin.llama..") - .allowEmptyShould(true); - /** * The Maven Mojo SPI annotations ({@code @Mojo} / {@code @Parameter} from * {@code org.apache.maven.plugins.annotations..}) may only appear in the {@code mojo} - * package — the goal entry points. The lower layers are plain Java and must not be - * annotated as Maven components. + * package — the goal entry points. Any future non-mojo package added to this module must + * stay plain Java and must not be annotated as a Maven component. */ @ArchTest static final ArchRule mavenMojoAnnotationsConfinedToMojo = noClasses() @@ -216,29 +171,6 @@ public class PluginArchitectureTest { .resideInAPackage("org.apache.maven.plugins.annotations..") .allowEmptyShould(true); - /** - * Every layer except {@code mojo} must be framework-free: no Maven API at all (not even - * {@code Log} — logging in {@code indexer} goes through an SLF4J {@link Logger} instead). - * They are plain Java, so they can be unit-tested and eventually extracted into a - * standalone library without a Maven runtime. Only {@code mojo} is the Maven Plugin API - * boundary ({@code AbstractMojo}, {@code @Parameter}, {@code MojoExecutionException}, and - * the injected {@code getLog()} for mojo-lifecycle messages). - */ - @ArchTest - static final ArchRule nonMojoIsMavenFree = noClasses() - .that() - .resideInAnyPackage( - "net.ladenthin.maven.llamacpp.aiindex.config..", - "net.ladenthin.maven.llamacpp.aiindex.support..", - "net.ladenthin.maven.llamacpp.aiindex.document..", - "net.ladenthin.maven.llamacpp.aiindex.prompt..", - "net.ladenthin.maven.llamacpp.aiindex.provider..", - "net.ladenthin.maven.llamacpp.aiindex.indexer..") - .should() - .dependOnClassesThat() - .resideInAPackage("org.apache.maven..") - .allowEmptyShould(true); - /** * Every SLF4J {@link Logger} field must be {@code private static final} — a single shared * logger per class, never an instance field or a mutable/visible one. diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java b/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java index b0fb7af..3391515 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java +++ b/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 package net.ladenthin.maven.llamacpp.aiindex.jcstress; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationKind; +import net.ladenthin.srcmorph.config.AiGenerationKind; import org.openjdk.jcstress.annotations.Actor; import org.openjdk.jcstress.annotations.Description; import org.openjdk.jcstress.annotations.Expect; diff --git a/pom.xml b/pom.xml index 31c443f..c99d1be 100644 --- a/pom.xml +++ b/pom.xml @@ -53,12 +53,15 @@ SPDX-License-Identifier: Apache-2.0 + srcmorph llamacpp-ai-index-maven-plugin diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml new file mode 100644 index 0000000..fa5f5c0 --- /dev/null +++ b/srcmorph/pom.xml @@ -0,0 +1,628 @@ + + + + 4.0.0 + + + net.ladenthin + srcmorph-parent + 1.1.0-SNAPSHOT + ../pom.xml + + + srcmorph + jar + + srcmorph + Core library extracted from llamacpp-ai-index-maven-plugin: routing/configuration model, the .ai.md document codec, the file/package/project indexers, prompt templating support, and the AI generation provider abstraction (incl. the llama.cpp JNI binding). Framework-free (no Maven plugin API); the llamacpp-ai-index-maven-plugin module supplies only the Mojo entry points on top of this library. + + + bernardladenthin + 8 + 21 + UTF-8 + + 5.0.6 + + + 6.1.2 + 3.0 + 3.6 + 2.50.0 + 0.13.7 + 1.0.0 + 4.2.1 + + 1.9.3 + 1.4.2 + 4.10.2.0 + 1.18.46 + 7.7.4 + 1.14.0 + 3.8.0 + 2.94.0 + + ${git.commit.time} + + + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.jspecify + jspecify + ${jspecify.version} + + + org.checkerframework + checker-qual + ${checker.version} + + + net.ladenthin + llama + ${llama.version} + ${llama.classifier} + + + + + org.slf4j + slf4j-api + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + net.jqwik + jqwik + ${jqwik.version} + test + + + com.tngtech.archunit + archunit-junit5 + ${archunit.version} + test + + + + org.jetbrains.lincheck + lincheck + ${lincheck.version} + test + + + + ch.qos.logback + logback-classic + test + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + io.github.git-commit-id + git-commit-id-maven-plugin + 10.0.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + + org.apache.maven.plugins + maven-jar-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + @{argLine} -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:ErrorFile=hs_err_pid%p.log + + + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + org.pitest + pitest-maven + 1.25.7 + + + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + get-git-properties + + revision + + initialize + + + + yyyy-MM-dd'T'HH:mm:ss'Z' + UTC + false + false + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + [3.6.3,) + + + [1.8,) + + + + + + commons-logging:commons-logging + + log4j:log4j + + org.hamcrest:hamcrest-core + org.hamcrest:hamcrest-library + org.hamcrest:hamcrest-all + + junit:junit + junit:junit-dep + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.release} + ${maven.compiler.testRelease} + true + true + + + -Xlint:all,-serial,-options,-classfile,-processing + -Werror + + -processor + lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor,org.checkerframework.checker.nullness.NullnessChecker + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepExcludedPaths:.*/generated-sources/.* -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:ExcludedFieldAnnotations=org.apache.maven.plugins.annotations.Parameter,org.apache.maven.plugins.annotations.Component -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:CheckOptionalEmptiness=true -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true -XepOpt:NullAway:AcknowledgeAndroidRecent=true -XepOpt:NullAway:AssertsEnabled=true -Xep:BoxedPrimitiveEquality:ERROR -Xep:EqualsHashCode:ERROR -Xep:EqualsIncompatibleType:ERROR -Xep:IdentityBinaryExpression:ERROR -Xep:SelfAssignment:ERROR -Xep:SelfComparison:ERROR -Xep:SelfEquals:ERROR -Xep:DeadException:ERROR -Xep:FormatString:ERROR -Xep:InvalidPatternSyntax:ERROR -Xep:OptionalEquality:ERROR -Xep:ImpossibleNullComparison:ERROR + + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + org.checkerframework + checker + ${checker.version} + + + + + + default-compile + + + + module-info.java + + + + + module-info-compile + compile + + compile + + + + 9 + + module-info.java + + + + + + + default-testCompile + + + false + + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -Xep:NullAway:OFF -Xep:GuardedBy:OFF -Xep:IdentityBinaryExpression:OFF + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${maven.compiler.release} + true + true + all + + + + attach-javadocs + + jar + + + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + report + test + + report + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + ${palantir-java-format.version} + + + + + + + + + spotless-check + verify + + check + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + Max + Low + true + false + spotbugs-exclude.xml + + + com.mebigfatguy.fb-contrib + fb-contrib + ${fb-contrib.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + + spotbugs-check + verify + + check + + + + + + + org.pitest + pitest-maven + + + org.pitest + pitest-junit5-plugin + 1.2.3 + + + + + + net.ladenthin.srcmorph.config.AiCalibration + net.ladenthin.srcmorph.config.AiCondition + net.ladenthin.srcmorph.config.AiConditionEvaluator + net.ladenthin.srcmorph.config.AiFieldGenerationConfig + net.ladenthin.srcmorph.config.AiFieldGenerationSelector + net.ladenthin.srcmorph.config.AiFileContext + net.ladenthin.srcmorph.config.AiGenerationConfig + net.ladenthin.srcmorph.config.AiRangeCondition + net.ladenthin.srcmorph.config.AiModelDefinition + net.ladenthin.srcmorph.config.AiModelDefinitionSupport + net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement + net.ladenthin.srcmorph.indexer.AiInputWindowCalculator + net.ladenthin.srcmorph.document.AiGenerationRequest + net.ladenthin.srcmorph.document.AiGenerationResult + net.ladenthin.srcmorph.document.AiMdChildEntryLineFormatter + net.ladenthin.srcmorph.document.AiMdDocument + net.ladenthin.srcmorph.document.AiMdHeader + net.ladenthin.srcmorph.document.AiMdHeaderSupport + net.ladenthin.srcmorph.document.AiMdLeadExtractor + net.ladenthin.srcmorph.prompt.AiPreparedPrompt + net.ladenthin.srcmorph.prompt.AiPromptDefinition + net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport + net.ladenthin.srcmorph.prompt.AiPromptSupport + net.ladenthin.srcmorph.provider.AiCompletionParser + net.ladenthin.srcmorph.provider.AiGenerationTimings + net.ladenthin.srcmorph.provider.AiGenerationProvider + net.ladenthin.srcmorph.provider.AiGenerationProviderFactory + net.ladenthin.srcmorph.provider.MockAiGenerationProvider + net.ladenthin.srcmorph.config.AiOversizeStrategy + net.ladenthin.srcmorph.config.AiFactCounter + net.ladenthin.srcmorph.config.AiFactDefinition + net.ladenthin.srcmorph.config.AiFactDefinitionSupport + net.ladenthin.srcmorph.config.AiFactExtractor + net.ladenthin.srcmorph.support.AiChecksumSupport + net.ladenthin.srcmorph.support.AiDeterministicSummary + net.ladenthin.srcmorph.support.AiGenerationTimeEstimator + net.ladenthin.srcmorph.support.AiPathSupport + net.ladenthin.srcmorph.support.AiProgressBar + net.ladenthin.srcmorph.support.AiSourceExcludeFilter + net.ladenthin.srcmorph.support.AiTimeSupport + net.ladenthin.srcmorph.support.Java8CompatibilityHelper + + + net.ladenthin.srcmorph.* + + 100 + 30000 + + + + + + + + + gpu-cuda + + cuda13-windows-x86-64 + + + + gpu-vulkan + + vulkan-windows-x86-64 + + + + + + diff --git a/srcmorph/spotbugs-exclude.xml b/srcmorph/spotbugs-exclude.xml new file mode 100644 index 0000000..76428cb --- /dev/null +++ b/srcmorph/spotbugs-exclude.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcmorph/src/main/java/module-info.java b/srcmorph/src/main/java/module-info.java new file mode 100644 index 0000000..2a30676 --- /dev/null +++ b/srcmorph/src/main/java/module-info.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * JPMS module descriptor for the srcmorph core library. + * + *

The module exports the six framework-free packages extracted from the + * llamacpp-ai-index-maven-plugin's non-mojo layers: {@code config}, {@code document}, + * {@code indexer}, {@code prompt}, {@code provider}, and {@code support}. These hold the + * routing/configuration model, the {@code .ai.md} document codec, the file/package/project + * indexers, the prompt templating support, the AI generation provider abstraction (incl. the + * llama.cpp JNI binding), and the shared stateless helpers. The Maven plugin module + * ({@code net.ladenthin.maven.llamacpp.aiindex}, still in its own reactor module) depends on + * this module and supplies only the {@code mojo} entry points.

+ * + *

JSpecify {@code @NullMarked} is declared at the module level here so that no source + * file compiled at {@code --release 8} references the JSpecify annotation type directly. + * Otherwise javac would emit an unsuppressible {@code unknown enum constant + * ElementType.MODULE} classfile-read warning for each source compiled at release 8 that + * resolves {@code @NullMarked} ({@code @NullMarked} carries + * {@code @Target({MODULE, PACKAGE, TYPE})} and Java 8 does not know about + * {@code ElementType.MODULE}). Confining the reference to {@code module-info.java} — + * which compiles at {@code --release 9} — keeps that warning out of the build entirely.

+ * + *

{@code requires static org.jspecify} is needed only at compile time of this + * descriptor; JSpecify annotations carry {@code RetentionPolicy.CLASS} so module-path + * consumers never need jspecify on their runtime path. Checker Framework qualifiers are + * likewise compile-time only. The classes imported from {@code net.ladenthin:llama} (used by + * the {@code provider} package) are referenced from ordinary source files only; javac in the + * separate {@code module-info-compile} execution compiles {@code module-info.java} in + * isolation and therefore does not need their module name. Maven, in turn, loads any consumer + * of this jar classpath-only and ignores the descriptor at runtime.

+ * + *

This descriptor compiles at {@code --release 9}; the rest of the source compiles + * at {@code --release 8}. Java 8 runtimes silently ignore {@code module-info.class} at + * the JAR root.

+ */ +@org.jspecify.annotations.NullMarked +module net.ladenthin.srcmorph { + requires static org.jspecify; + + // Lombok is `provided` scope: only used at compile time to generate equals/hashCode/toString. + // `requires static` means the runtime does not need the lombok jar on the module path — + // the @lombok.Generated annotation carried on generated members has CLASS retention. + requires static lombok; + + exports net.ladenthin.srcmorph.config; + exports net.ladenthin.srcmorph.document; + exports net.ladenthin.srcmorph.indexer; + exports net.ladenthin.srcmorph.prompt; + exports net.ladenthin.srcmorph.provider; + exports net.ladenthin.srcmorph.support; +} diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibration.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibration.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java index 5e49ea2..156cb6d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibration.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCondition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCondition.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCondition.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCondition.java index 7addf3b..21b0559 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCondition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCondition.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.ArrayList; import java.util.Collection; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluator.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionEvaluator.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluator.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionEvaluator.java index c9ba67d..6eb9cb4 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluator.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionEvaluator.java @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.Collections; import java.util.List; -import net.ladenthin.maven.llamacpp.aiindex.support.AiSourceExcludeFilter; +import net.ladenthin.srcmorph.support.AiSourceExcludeFilter; import org.jspecify.annotations.Nullable; /** diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroup.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionGroup.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroup.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionGroup.java index 2bf337b..548adfb 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroup.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiConditionGroup.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.ArrayList; import java.util.Collection; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounter.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactCounter.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounter.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactCounter.java index 0741395..1d4149c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounter.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactCounter.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinition.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinition.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinition.java index 86b790d..d20face 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinition.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.List; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupport.java similarity index 91% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupport.java index 3178fa2..5c23103 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupport.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.Collections; import java.util.HashMap; @@ -9,7 +9,7 @@ import java.util.Map; import java.util.Objects; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.jspecify.annotations.Nullable; /** @@ -23,9 +23,10 @@ * already-resolved {@code getFacts()} uniformly, whether the counters were inline or shared.

* *

Every entry must have a non-null {@code key} (a null key throws {@link NullPointerException} naming - * the index); a {@code factsKey} that matches no group throws {@link IllegalArgumentException}. Mojos - * wrap these in {@link org.apache.maven.plugin.MojoExecutionException} so a misconfigured POM fails fast - * as a user error.

+ * the index); a {@code factsKey} that matches no group throws {@link IllegalArgumentException}. Callers + * (e.g. the llamacpp-ai-index-maven-plugin module's Mojos) wrap these in + * {@code org.apache.maven.plugin.MojoExecutionException} so a misconfigured POM fails fast as a user + * error. This library itself has no Maven dependency, so that type is referenced here only in prose.

* * @see AiFactDefinition * @see AiFieldGenerationConfig#getFactsKey() diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractor.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactExtractor.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractor.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactExtractor.java index 95f714e..eb45fa3 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractor.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFactExtractor.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.List; import java.util.regex.Matcher; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfig.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfig.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfig.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfig.java index b45baef..8fef70a 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfig.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfig.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.List; import lombok.ToString; @@ -140,7 +140,7 @@ public String getPromptKey() { /** * Sets the prompt template key. * - * @param promptKey key that references an {@link net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition} + * @param promptKey key that references an {@link net.ladenthin.srcmorph.prompt.AiPromptDefinition} */ public void setPromptKey(final String promptKey) { this.promptKey = promptKey; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelector.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelector.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelector.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelector.java index 059df68..8452f8e 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelector.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelector.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import lombok.ToString; import org.jspecify.annotations.Nullable; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContext.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFileContext.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContext.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFileContext.java index b0177c3..79a5656 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContext.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiFileContext.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfig.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationConfig.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfig.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationConfig.java index fbd9b1f..d9d392e 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfig.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationConfig.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.ArrayList; import java.util.Collections; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKind.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationKind.java similarity index 89% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKind.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationKind.java index c10c43a..e5252c2 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKind.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiGenerationKind.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; /** Identifies whether an AI generation operates on a single source file or a whole package. */ public enum AiGenerationKind { diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinition.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java index fbba4df..e4d206c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.ArrayList; import java.util.Collection; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java index 980d375..6882581 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** * Resolves {@link AiModelDefinition} entries by their key, returning the corresponding @@ -25,9 +25,11 @@ * entry. This is the contract enforcement boundary that makes misconfigured POM * {@code } fail at build configuration time rather than silently * dropping the entry and surfacing later as a "Missing AI model definition" failure - * deeper in the goal. Mojos wrap construction in {@link NullPointerException} - * → {@link org.apache.maven.plugin.MojoExecutionException} so the Maven - * framework reports it as a user configuration error rather than a plugin bug.

+ * deeper in the goal. Callers (e.g. the llamacpp-ai-index-maven-plugin module's Mojos) wrap + * construction in {@link NullPointerException} + * → {@code org.apache.maven.plugin.MojoExecutionException} so the Maven + * framework reports it as a user configuration error rather than a plugin bug. This library + * itself has no Maven dependency, so that type is referenced here only in prose.

* *

Lookups for missing keys throw {@link IllegalArgumentException} so that * configuration errors are detected eagerly at runtime.

diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategy.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiOversizeStrategy.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategy.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiOversizeStrategy.java index f20d728..eae9c7b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategy.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiOversizeStrategy.java @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: Apache-2.0 // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import org.jspecify.annotations.Nullable; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeCondition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiRangeCondition.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeCondition.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiRangeCondition.java index 6da350a..794a6cc 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeCondition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiRangeCondition.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/package-info.java similarity index 78% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/config/package-info.java index e30fda8..e749d63 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/config/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/package-info.java @@ -5,4 +5,4 @@ /** * Generation configuration, model definitions and their lookups. */ -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationRequest.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationRequest.java similarity index 85% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationRequest.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationRequest.java index 513fb1a..0e1954c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationRequest.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationRequest.java @@ -1,20 +1,20 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.nio.file.Path; import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** - * Immutable request object passed to an {@link net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider}: identifies the + * Immutable request object passed to an {@link net.ladenthin.srcmorph.provider.AiGenerationProvider}: identifies the * prompt template, the source file being processed, its current text, and the existing * header (if any). * - *

Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. Accessors are record-style. {@code equals}, {@code hashCode} and * {@code toString} are generated by Lombok over all four fields; full value semantics * apply.

diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationResult.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationResult.java similarity index 80% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationResult.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationResult.java index 3ace2fc..79a6b39 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiGenerationResult.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiGenerationResult.java @@ -1,21 +1,21 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** - * Immutable result produced by {@link net.ladenthin.maven.llamacpp.aiindex.indexer.AiFieldGenerationSupport#processFieldGenerations}. + * Immutable result produced by {@link net.ladenthin.srcmorph.indexer.AiFieldGenerationSupport#processFieldGenerations}. * *

Carries the AI-generated document body text produced by a single processing pass. * Defaults to an empty string when no body target is present in the field generation * configuration.

* - *

Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. The accessor methods follow record-style ({@code body()}, not * {@code getBody()}) so the migration is mechanical. {@code equals}, {@code hashCode} * and {@code toString} are generated by Lombok over the single field; full value diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatter.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatter.java similarity index 88% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatter.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatter.java index 8bb8c97..95203d9 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatter.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatter.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import lombok.ToString; @@ -15,10 +15,10 @@ * concatenated string as the input to the parent package's CRC32 checksum.

* *

Kept separate from {@link AiMdHeaderSupport} because the use case is - * narrow (only {@link net.ladenthin.maven.llamacpp.aiindex.indexer.PackageIndexer} calls this) and unrelated to the + * narrow (only {@link net.ladenthin.srcmorph.indexer.PackageIndexer} calls this) and unrelated to the * rewrite-decision logic that lives on {@code AiMdHeaderSupport}.

* - * @see net.ladenthin.maven.llamacpp.aiindex.indexer.PackageIndexer + * @see net.ladenthin.srcmorph.indexer.PackageIndexer */ @ToString public class AiMdChildEntryLineFormatter { diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocument.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocument.java similarity index 83% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocument.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocument.java index 4b8f7cc..9f80495 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocument.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocument.java @@ -1,17 +1,17 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * Immutable representation of an {@code .ai.md} document consisting of a header and a body. * - *

Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. Accessors are record-style ({@code header()}, {@code body()}). {@code equals}, * {@code hashCode} and {@code toString} are generated by Lombok over both fields.

*/ diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodec.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocumentCodec.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodec.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocumentCodec.java index ab49d47..ec7273f 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodec.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdDocumentCodec.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.List; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** Reads and writes {@code .ai.md} documents (header plus body) from and to disk. */ @ToString diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeader.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeader.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeader.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeader.java index e6e2574..52a5901 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeader.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeader.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.util.ArrayList; import java.util.Collection; @@ -10,7 +10,7 @@ import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * Canonical header model for a single {@code .ai.md} document. @@ -55,7 +55,7 @@ *
  • The generator should preserve the body when the structural state did not change.
  • * * - *

    Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

    Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. Accessors are record-style ({@code title()}, {@code h()}, ...). {@code equals}, * {@code hashCode} and {@code toString} are generated by Lombok across all fields (including * {@code children}); full value semantics apply.

    diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodec.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderCodec.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodec.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderCodec.java index 783c399..6d10c98 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodec.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderCodec.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -12,7 +12,7 @@ import java.util.List; import java.util.Map; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** Reads and writes the metadata header section of an {@code .ai.md} document. */ @ToString diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderSupport.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderSupport.java index edc5962..561ae2d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdHeaderSupport.java @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** Header comparison helpers that decide whether an {@code .ai.md} file needs to be rewritten. */ @ToString diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractor.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdLeadExtractor.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractor.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdLeadExtractor.java index 4352f34..7b20f6c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractor.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/AiMdLeadExtractor.java @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** * Extracts the one-line lead from an {@code .ai.md} document body. diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/package-info.java similarity index 76% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/document/package-info.java index 4f938e7..a28e725 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/document/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/document/package-info.java @@ -5,4 +5,4 @@ /** * The {@code .ai.md} document model and its codecs. */ -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurement.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurement.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurement.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurement.java index a227761..bdd243d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurement.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurement.java @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * The result of one model's calibration measurement (see {@link AiCalibrationRunner}): the load time and diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunner.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunner.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java index 3f13c6b..f7727a7 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunner.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java @@ -1,17 +1,17 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationTimings; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationTimings; /** * Measures one model's per-machine timing for the {@code ai-index:calibrate} goal: it triggers the load, diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupport.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupport.java index 5fa364e..915b9cf 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupport.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.io.IOException; import java.nio.file.Path; @@ -10,22 +10,22 @@ import java.util.List; import java.util.Map; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiCalibration; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactExtractor; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.config.AiOversizeStrategy; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPreparedPrompt; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.support.AiDeterministicSummary; -import net.ladenthin.maven.llamacpp.aiindex.support.AiGenerationTimeEstimator; -import net.ladenthin.maven.llamacpp.aiindex.support.AiSourceChunker; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiCalibration; +import net.ladenthin.srcmorph.config.AiFactExtractor; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.AiOversizeStrategy; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.prompt.AiPreparedPrompt; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.support.AiDeterministicSummary; +import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; +import net.ladenthin.srcmorph.support.AiSourceChunker; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +33,8 @@ * Shared field-generation logic used by both {@link SourceFileIndexer} and * {@link PackageIndexer}. * - *

    Iterates over a list of {@link net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig} entries, prepares the - * prompt for each, delegates generation to the configured {@link net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider}, + *

    Iterates over a list of {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig} entries, prepares the + * prompt for each, delegates generation to the configured {@link net.ladenthin.srcmorph.provider.AiGenerationProvider}, * and accumulates the generated text as the document body. * A trim warning is logged whenever the source text had to be truncated to fit within the * configured maximum input character budget.

    @@ -194,7 +194,7 @@ public class AiFieldGenerationSupport { * * @param generationProvider AI backend used to generate text for each field * @param promptPreparationSupport helper that resolves and prepares prompt templates - * @param modelDefinitionSupport lookup for {@link net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig} by key + * @param modelDefinitionSupport lookup for {@link net.ladenthin.srcmorph.config.AiGenerationConfig} by key */ public AiFieldGenerationSupport( final AiGenerationProvider generationProvider, @@ -207,13 +207,13 @@ public AiFieldGenerationSupport( /** * Processes each entry in {@code fieldGenerations}, generates the requested AI text, - * and accumulates the results into an {@link net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult}. + * and accumulates the results into an {@link net.ladenthin.srcmorph.document.AiGenerationResult}. * - *

    For each non-null {@link net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig}:

    + *

    For each non-null {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig}:

    *
      - *
    1. The prompt is prepared via {@link net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport#preparePrompt}.
    2. + *
    3. The prompt is prepared via {@link net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport#preparePrompt}.
    4. *
    5. A trim warning is logged if the source was truncated and - * {@link net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig#isWarnOnTrim()} is {@code true}.
    6. + * {@link net.ladenthin.srcmorph.config.AiGenerationConfig#isWarnOnTrim()} is {@code true}. *
    7. The AI provider generates a value for the trimmed source.
    8. *
    9. If the provider returns a blank body, a single warning is emitted (fail-fast). * Blank output no longer occurs in normal operation with a non-greedy temperature @@ -231,11 +231,11 @@ public AiFieldGenerationSupport( * {@code "package"}); embedded in trim-warning log messages * @param sourceText full source text passed as input to the prompt preparation step * @param baseHeader current header; passed through to each - * {@link net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest} - * @return an {@link net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult} with the generated body; defaults to empty string + * {@link net.ladenthin.srcmorph.document.AiGenerationRequest} + * @return an {@link net.ladenthin.srcmorph.document.AiGenerationResult} with the generated body; defaults to empty string * @throws IOException if the AI provider throws during generation - * @throws IllegalArgumentException if a field's {@link net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig#getAiDefinitionKey()} - * does not match any registered {@link net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition} + * @throws IllegalArgumentException if a field's {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig#getAiDefinitionKey()} + * does not match any registered {@link net.ladenthin.srcmorph.config.AiModelDefinition} */ public AiGenerationResult processFieldGenerations( final List fieldGenerations, @@ -599,10 +599,10 @@ private static String contextName(final Path contextFile) { /** * Returns the effective {@code maxInputChars} for the given field generation entry. * - *

      When the {@link net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig#getCharsPerToken()} value is greater than zero, + *

      When the {@link net.ladenthin.srcmorph.config.AiGenerationConfig#getCharsPerToken()} value is greater than zero, * the maximum is computed once per unique {@code (aiDefinitionKey, promptKey)} pair, * logged in detail, cached, and reused for all subsequent files. When - * {@code charsPerToken} is zero, the static {@link net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig#getMaxInputChars()} + * {@code charsPerToken} is zero, the static {@link net.ladenthin.srcmorph.config.AiGenerationConfig#getMaxInputChars()} * fallback is returned instead.

      * * @param fieldGeneration the field configuration identifying the definition and prompt keys diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlan.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiIndexPlan.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlan.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiIndexPlan.java index 1d1b7c0..fbf1518 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlan.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiIndexPlan.java @@ -1,16 +1,16 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiOversizeStrategy; -import net.ladenthin.maven.llamacpp.aiindex.support.AiGenerationTimeEstimator; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiOversizeStrategy; +import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; /** * The routing plan for one {@code generate} run: which files each AI model will index (with which diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculator.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculator.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculator.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculator.java index c5b082c..431d6e1 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculator.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculator.java @@ -3,10 +3,10 @@ // // SPDX-License-Identifier: Apache-2.0 // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; /** * Pure calculator for the input window: how many source characters fit a model's context window diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexer.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/PackageIndexer.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexer.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/PackageIndexer.java index 14b43cc..43e8e72 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexer.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/PackageIndexer.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.io.IOException; import java.nio.file.Files; @@ -12,22 +12,22 @@ import java.util.List; import java.util.stream.Stream; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdChildEntryLineFormatter; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.support.AiChecksumSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiPathSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiTimeSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdChildEntryLineFormatter; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.document.AiMdHeaderSupport; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.support.AiChecksumSupport; +import net.ladenthin.srcmorph.support.AiPathSupport; +import net.ladenthin.srcmorph.support.AiTimeSupport; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -225,7 +225,7 @@ private boolean shouldCreatePackageFile(final Path directory) throws IOException /** * Returns {@code true} when {@code directory} contains a - * {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file. + * {@link net.ladenthin.srcmorph.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file. * * @param directory the directory to examine * @return {@code true} if the package AI index file exists inside {@code directory} @@ -374,7 +374,7 @@ private String markdownLink(final String label, final String target) { * package contents.

      * *

      No explicit length cap is applied here: the combined text is subject to the existing - * trim logic in {@link net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport#preparePrompt}, which truncates at a line + * trim logic in {@link net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport#preparePrompt}, which truncates at a line * boundary to the computed {@code maxInputChars} budget and triggers the {@code warnOnTrim} * warning — so large packages degrade gracefully without a bespoke truncation scheme.

      * @@ -595,8 +595,8 @@ private String calculatePackageDate(final Path directory) throws IOException { } /** - * Reads and returns the {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader} from the - * {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file inside {@code directory}. + * Reads and returns the {@link net.ladenthin.srcmorph.document.AiMdHeader} from the + * {@link net.ladenthin.srcmorph.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file inside {@code directory}. * The caller must ensure {@link #hasPackageAiMdFile(Path)} is {@code true} first. * * @param directory a directory that contains a {@code package.ai.md} file @@ -625,11 +625,11 @@ private String laterDate(final String current, final String candidate) { /** * Returns {@code true} when {@code name} refers to a child AI index file that should * be included in content listings and checksum calculations. Excludes the - * {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file itself, the - * project-level {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec#PROJECT_AI_MD_FILENAME} + * {@link net.ladenthin.srcmorph.document.AiMdHeaderCodec#PACKAGE_AI_MD_FILENAME} file itself, the + * project-level {@link net.ladenthin.srcmorph.document.AiMdHeaderCodec#PROJECT_AI_MD_FILENAME} * (which the aggregator writes into the output root and must not fold back into its own * checksum or summary on a re-run), and any - * {@link net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec#GENERATED_BY_PREFIX} marker files. + * {@link net.ladenthin.srcmorph.document.AiMdHeaderCodec#GENERATED_BY_PREFIX} marker files. * * @param name file name of the path under examination * @return {@code true} if the file is a regular content AI index entry diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexer.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/ProjectIndexer.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexer.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/ProjectIndexer.java index 6c29756..88ae580 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexer.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/ProjectIndexer.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.io.IOException; import java.nio.file.Files; @@ -12,21 +12,21 @@ import java.util.List; import java.util.stream.Stream; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderSupport; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdLeadExtractor; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.support.AiChecksumSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiTimeSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.document.AiMdHeaderSupport; +import net.ladenthin.srcmorph.document.AiMdLeadExtractor; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.support.AiChecksumSupport; +import net.ladenthin.srcmorph.support.AiTimeSupport; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexer.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/SourceFileIndexer.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexer.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/SourceFileIndexer.java index 82dafef..aabeeca 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexer.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/SourceFileIndexer.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import java.io.IOException; import java.nio.file.Files; @@ -12,28 +12,28 @@ import java.util.List; import java.util.stream.Stream; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.config.AiCalibration; -import net.ladenthin.maven.llamacpp.aiindex.config.AiCondition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiConditionEvaluator; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationSelector; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFileContext; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.config.AiOversizeStrategy; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiChecksumSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiGenerationTimeEstimator; -import net.ladenthin.maven.llamacpp.aiindex.support.AiPathSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.AiSourceExcludeFilter; -import net.ladenthin.maven.llamacpp.aiindex.support.AiTimeSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.AiCalibration; +import net.ladenthin.srcmorph.config.AiCondition; +import net.ladenthin.srcmorph.config.AiConditionEvaluator; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiFieldGenerationSelector; +import net.ladenthin.srcmorph.config.AiFileContext; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.AiOversizeStrategy; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.document.AiMdHeaderSupport; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.support.AiChecksumSupport; +import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; +import net.ladenthin.srcmorph.support.AiPathSupport; +import net.ladenthin.srcmorph.support.AiSourceExcludeFilter; +import net.ladenthin.srcmorph.support.AiTimeSupport; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/package-info.java similarity index 79% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/package-info.java index 4ee43fd..d78b932 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/indexer/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/package-info.java @@ -5,4 +5,4 @@ /** * Orchestration: walks source/package trees and drives field generation. */ -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPrompt.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPreparedPrompt.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPrompt.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPreparedPrompt.java index 21e4d19..b919606 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPrompt.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPreparedPrompt.java @@ -1,18 +1,18 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * Immutable result of preparing a prompt: the substituted prompt text, the source text * actually included, and metrics describing how the source was trimmed (if at all). * - *

      Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

      Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. Accessors are record-style ({@code prompt()}, {@code sourceText()}, ...). * {@code equals}, {@code hashCode} and {@code toString} are generated by Lombok over all * six fields; full value semantics apply.

      diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptDefinition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptDefinition.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptDefinition.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptDefinition.java index f41c61b..5da82f1 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptDefinition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptDefinition.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupport.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupport.java index 6833061..168be92 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupport.java @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiGenerationRequest; /** - * Prepares prompts for {@link net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider} calls by substituting the source + * Prepares prompts for {@link net.ladenthin.srcmorph.provider.AiGenerationProvider} calls by substituting the source * text into a template and trimming it at a line boundary so that the prompt fits * within the configured character budget. */ diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java index b8234ed..dc0a286 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** Registry of prompt templates that renders prompt strings for AI generation requests. */ @ToString @@ -38,10 +38,12 @@ public final class AiPromptSupport { * makes misconfigured POM {@code } fail at build * configuration time rather than silently dropping the entry and * surfacing as a "Missing prompt template for key" failure deeper in - * the goal. Mojos wrap construction in {@link NullPointerException} - * → {@link org.apache.maven.plugin.MojoExecutionException} so the + * the goal. Callers (e.g. the llamacpp-ai-index-maven-plugin module's Mojos) wrap + * construction in {@link NullPointerException} + * → {@code org.apache.maven.plugin.MojoExecutionException} so the * Maven framework reports it as a user configuration error rather - * than a plugin bug. + * than a plugin bug. This library itself has no Maven dependency, so that type is + * referenced here only in prose. * * @param promptDefinitions prompt definitions to register; may be * {@code null} (treated as no definitions); diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/package-info.java similarity index 76% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/package-info.java index 711c7e3..53e7a5b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/prompt/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/package-info.java @@ -5,4 +5,4 @@ /** * Prompt templates, preparation and lookup. */ -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParser.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiCompletionParser.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParser.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiCompletionParser.java index 91e7384..801119f 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParser.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiCompletionParser.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import java.io.IOException; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProvider.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java similarity index 89% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProvider.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java index 3699410..12e20b9 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProvider.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import java.io.IOException; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiGenerationRequest; /** - * Pluggable AI backend that produces text for an {@link net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest}. + * Pluggable AI backend that produces text for an {@link net.ladenthin.srcmorph.document.AiGenerationRequest}. * Implementations may run locally (llama.cpp) or be mock providers for tests. */ public interface AiGenerationProvider extends AutoCloseable { diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactory.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactory.java similarity index 89% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactory.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactory.java index 3a4e0f1..a7aeb34 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactory.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactory.java @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; /** Selects and instantiates an {@link AiGenerationProvider} implementation by name. */ @ToString diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimings.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimings.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java index f493442..c8dedb7 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimings.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * A generated text plus the model's measured timing for that generation, used by the diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProvider.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProvider.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProvider.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProvider.java index ea0aa5a..9614e1b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProvider.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProvider.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import java.io.IOException; import java.util.ArrayList; @@ -18,9 +18,9 @@ import net.ladenthin.llama.value.ChatResponse; import net.ladenthin.llama.value.Pair; import net.ladenthin.llama.value.Timings; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; import org.jspecify.annotations.Nullable; /** diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniConfig.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniConfig.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java index 644a435..7f4fe7f 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniConfig.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java @@ -1,19 +1,19 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import java.util.Collections; import java.util.List; import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord; +import net.ladenthin.srcmorph.support.ConvertToRecord; /** * Immutable configuration for the llama.cpp JNI provider. * - *

      Record-shaped value type marked {@link net.ladenthin.maven.llamacpp.aiindex.support.ConvertToRecord} for the future Java 17+ + *

      Record-shaped value type marked {@link net.ladenthin.srcmorph.support.ConvertToRecord} for the future Java 17+ * migration. Accessors are record-style. {@code equals}, {@code hashCode} and * {@code toString} are generated by Lombok over all twenty-six fields; full value semantics * apply. Lombok emits the exact-bit float comparison diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProvider.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/MockAiGenerationProvider.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProvider.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/MockAiGenerationProvider.java index feadf42..6958459 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProvider.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/MockAiGenerationProvider.java @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import java.io.IOException; import java.nio.file.Path; import lombok.ToString; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiGenerationRequest; /** Deterministic {@link AiGenerationProvider} that returns a mock summary; used for testing. */ @ToString diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/package-info.java similarity index 80% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/provider/package-info.java index 0275a87..ca3475a 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/provider/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/package-info.java @@ -5,4 +5,4 @@ /** * AI generation backends (mock + llama.cpp JNI) and the generation request/result carriers. */ -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiChecksumSupport.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiChecksumSupport.java index e7ee078..3433fc5 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiChecksumSupport.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummary.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiDeterministicSummary.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummary.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiDeterministicSummary.java index aeb4897..380679c 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummary.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiDeterministicSummary.java @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: Apache-2.0 // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; /** * Builds a model-free, deterministic Markdown body for files too large (or too uninteresting) to feed to diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimator.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimator.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimator.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimator.java index 96fc6bc..62ebd8d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimator.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimator.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiPathSupport.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiPathSupport.java index d50d90f..ec1a910 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiPathSupport.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.nio.file.Path; import lombok.ToString; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBar.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiProgressBar.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBar.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiProgressBar.java index 5db1815..fed3c03 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBar.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiProgressBar.java @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: Apache-2.0 // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; /** * Renders a simple ASCII progress bar from a completed/total ratio, e.g. {@code [##### ] 42%}. diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunker.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceChunker.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunker.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceChunker.java index c5884b5..1da9883 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunker.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceChunker.java @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: Apache-2.0 // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.util.ArrayList; import java.util.LinkedHashSet; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilter.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilter.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilter.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilter.java index 83d596e..48c9f41 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilter.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilter.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.util.ArrayList; import java.util.Collection; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiTimeSupport.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupport.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiTimeSupport.java index 60254f2..dcd197e 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/AiTimeSupport.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.time.Instant; import java.time.format.DateTimeFormatter; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/ConvertToRecord.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/ConvertToRecord.java similarity index 85% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/ConvertToRecord.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/ConvertToRecord.java index 8490995..7a7dca2 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/ConvertToRecord.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/ConvertToRecord.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; /** * Marker annotation for classes that should be migrated to a Java {@code record} diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelper.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelper.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelper.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelper.java index a7e7b70..f201d5e 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelper.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelper.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import java.io.IOException; import java.nio.charset.Charset; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/package-info.java similarity index 79% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/package-info.java rename to srcmorph/src/main/java/net/ladenthin/srcmorph/support/package-info.java index 8028cb1..dc32c5b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/support/package-info.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/support/package-info.java @@ -5,4 +5,4 @@ /** * Stateless foundation utilities (checksums, time, paths, Java-8 helpers). */ -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/CommonTestFixtures.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/CommonTestFixtures.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/CommonTestFixtures.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/CommonTestFixtures.java index c7c6bd8..0fb1fd1 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/CommonTestFixtures.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/CommonTestFixtures.java @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex; +package net.ladenthin.srcmorph; import java.util.Arrays; import java.util.List; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; /** * Shared test fixture factory methods used across multiple test classes. diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java new file mode 100644 index 0000000..cbecf09 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static com.tngtech.archunit.library.Architectures.layeredArchitecture; +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import java.util.Random; +import org.slf4j.Logger; + +/** + * Architecture rules for the srcmorph core library (extracted from the + * llamacpp-ai-index-maven-plugin's non-mojo layers: config/document/indexer/prompt/provider/ + * support). This library is framework-free — no Maven plugin API dependency anywhere — so it can + * be unit-tested and consumed without a Maven runtime. The Mojo entry points that build on top of + * it live in the sibling {@code llamacpp-ai-index-maven-plugin} module, whose own + * {@code PluginArchitectureTest} carries the mojo-specific rules (Maven annotations confined to + * {@code mojo}, {@code AbstractMojo} inheritance, etc.) that have no meaning here. + */ +@AnalyzeClasses(packages = "net.ladenthin.srcmorph", importOptions = ImportOption.DoNotIncludeTests.class) +public class CoreArchitectureTest { + + /** + * java.util.logging must not be used; all logging goes through SLF4J. + */ + @ArchTest + static final ArchRule noJavaUtilLogging = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .dependOnClassesThat() + .resideInAPackage("java.util.logging.."); + + /** + * Test-framework classes must not appear in production code. + */ + @ArchTest + static final ArchRule noTestFrameworksInProduction = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.junit..", "net.jqwik..", "com.tngtech.archunit.."); + + /** + * Production code must not write to {@code System.out} / {@code System.err}; all output + * goes through an SLF4J {@link Logger}. Currently vacuous (no usage); acts as a regression + * guard. + */ + @ArchTest + static final ArchRule noSystemOutOrErrInProduction = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .accessField(System.class, "out") + .orShould() + .accessField(System.class, "err"); + + /** + * Production code must not import unsupported / internal JDK packages. + * These are not part of the Java SE API and may change or disappear without notice. + */ + @ArchTest + static final ArchRule noInternalJdkImports = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("sun..", "com.sun..", "jdk.internal.."); + + /** + * No package cycles between the layered sub-packages. + */ + @ArchTest + static final ArchRule noPackageCycles = slices().matching("net.ladenthin.srcmorph.(*)..") + .should() + .beFreeOfCycles() + .allowEmptyShould(true); + + /** + * Strict layered architecture — one layer per package. Each package's + * {@code mayOnlyBeAccessedByLayers} lists the EXACT set of packages that reference it today + * (verified against the compiled bytecode graph), so even intra-tier edges are governed + * (e.g. {@code provider} may use {@code document}+{@code prompt} but {@code document} and + * {@code prompt} may not use each other beyond what is listed). A new dependency between any + * two packages fails the build unless this rule is updated to intend it. Mirrors the pre- + * extraction {@code PluginArchitectureTest} layering with the {@code Mojo} layer removed (the + * mojo package lives in the sibling plugin module and is outside this test's analyzed + * classes; consideringOnlyDependenciesInLayers() means its former accesses to Indexer/ + * Provider/Prompt/Config/Support are simply not part of this in-module rule anymore). + * Conceptual tiers: {@code Indexer} > {@code Provider} > {@code Document}/{@code Prompt} + * > {@code Config}/{@code Support}. + */ + @ArchTest + static final ArchRule layeredArchitecture = layeredArchitecture() + .consideringOnlyDependenciesInLayers() + .layer("Indexer") + .definedBy("net.ladenthin.srcmorph.indexer..") + .layer("Provider") + .definedBy("net.ladenthin.srcmorph.provider..") + .layer("Document") + .definedBy("net.ladenthin.srcmorph.document..") + .layer("Prompt") + .definedBy("net.ladenthin.srcmorph.prompt..") + .layer("Config") + .definedBy("net.ladenthin.srcmorph.config..") + .layer("Support") + .definedBy("net.ladenthin.srcmorph.support..") + .whereLayer("Indexer") + .mayNotBeAccessedByAnyLayer() + .whereLayer("Provider") + .mayOnlyBeAccessedByLayers("Indexer") + .whereLayer("Document") + .mayOnlyBeAccessedByLayers("Indexer", "Prompt", "Provider") + .whereLayer("Prompt") + .mayOnlyBeAccessedByLayers("Indexer", "Provider") + .whereLayer("Config") + .mayOnlyBeAccessedByLayers("Indexer") + .whereLayer("Support") + .mayOnlyBeAccessedByLayers("Config", "Document", "Indexer", "Prompt", "Provider"); + + /** + * Public mutable state forbidden: any non-static field declared + * {@code public} must also be {@code final}. Maven plugin configuration + * POJOs use {@code private} fields with setters and therefore pass this rule. + */ + @ArchTest + static final ArchRule noPublicMutableFields = fields().that() + .arePublic() + .and() + .areNotStatic() + .should() + .beFinal() + .allowEmptyShould(true); // regression guard; passes vacuously today + + /** + * Production code must not call {@link System#exit(int)}; throw a domain exception instead + * so the calling Mojo (or any other embedder) surfaces the failure to its caller. + */ + @ArchTest + static final ArchRule noSystemExit = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .callMethod(System.class, "exit", int.class) + .allowEmptyShould(true); + + /** + * Production code must not construct {@link java.util.Random}; {@code Random} is a non-cryptographic + * PRNG (CWE-338). Use {@link java.security.SecureRandom} or {@link java.util.concurrent.ThreadLocalRandom} + * depending on whether cryptographic strength or thread-local fast jitter is needed. + */ + @ArchTest + static final ArchRule noNewRandom = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .callConstructor(Random.class) + .orShould() + .callConstructor(Random.class, long.class) + .allowEmptyShould(true); + + /** + * Production code must not call {@link Thread#sleep(long)} / {@link Thread#sleep(long, int)}; + * prefer {@link java.util.concurrent.BlockingQueue#poll(long, java.util.concurrent.TimeUnit)} or + * {@link java.util.concurrent.locks.Condition#await(long, java.util.concurrent.TimeUnit)}. + */ + @ArchTest + static final ArchRule noThreadSleep = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .callMethod(Thread.class, "sleep", long.class) + .orShould() + .callMethod(Thread.class, "sleep", long.class, int.class) + .allowEmptyShould(true); + + // --------------------------------------------------------------------------------------- + // Per-module banned imports — confine the heavy / framework-specific dependencies to the + // single layer that owns them, keeping the rest of the library decoupled and unit-testable. + // --------------------------------------------------------------------------------------- + + /** + * The llama.cpp JNI binding ({@code net.ladenthin.llama..}) may only be referenced from the + * {@code provider} package, where {@code LlamaCppJniAiGenerationProvider} wraps it. Every + * other layer talks to the model exclusively through the {@code AiGenerationProvider} + * interface, so the indexers and document/prompt/config code carry no JNI dependency and + * stay testable with {@code MockAiGenerationProvider}. + */ + @ArchTest + static final ArchRule jniConfinedToProvider = noClasses() + .that() + .resideOutsideOfPackage("net.ladenthin.srcmorph.provider..") + .should() + .dependOnClassesThat() + .resideInAPackage("net.ladenthin.llama..") + .allowEmptyShould(true); + + /** + * This library must be entirely framework-free: no Maven API at all (not even {@code Log}). + * Every class here is plain Java, so it can be unit-tested and consumed without a Maven + * runtime — that is the whole point of the extraction. The Maven Plugin API boundary + * ({@code AbstractMojo}, {@code @Parameter}, {@code MojoExecutionException}, the {@code mojo} + * package's Maven Mojo SPI annotations) lives entirely in the sibling + * {@code llamacpp-ai-index-maven-plugin} module, which is outside this test's analyzed + * classes and carries its own {@code mavenMojoAnnotationsConfinedToMojo} rule. + */ + @ArchTest + static final ArchRule coreIsMavenFree = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph..") + .should() + .dependOnClassesThat() + .resideInAPackage("org.apache.maven..") + .allowEmptyShould(true); + + /** + * Every SLF4J {@link Logger} field must be {@code private static final} — a single shared + * logger per class, never an instance field or a mutable/visible one. + */ + @ArchTest + static final ArchRule loggersArePrivateStaticFinal = fields().that() + .haveRawType(Logger.class) + .should() + .bePrivate() + .andShould() + .beStatic() + .andShould() + .beFinal() + .allowEmptyShould(true); +} diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibrationTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiCalibrationTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibrationTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiCalibrationTest.java index 2b292c1..da22534 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiCalibrationTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiCalibrationTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluatorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionEvaluatorTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluatorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionEvaluatorTest.java index c94ab88..5d2a8b9 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionEvaluatorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionEvaluatorTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroupTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionGroupTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroupTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionGroupTest.java index 3e1ff0f..8a7d405 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionGroupTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionGroupTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionTest.java index 9ece2e4..868fb4b 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiConditionTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiConditionTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounterTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactCounterTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounterTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactCounterTest.java index 93d4b3d..c5e1c1e 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactCounterTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactCounterTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupportTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupportTest.java index f888d2a..b69193f 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionTest.java index 0f07be0..c0ecc2f 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactDefinitionTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactDefinitionTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactExtractorTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactExtractorTest.java index e2cf199..dd059b2 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFactExtractorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFactExtractorTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfigTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfigTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfigTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfigTest.java index 9ad8a68..71e0bc7 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationConfigTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationConfigTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelectorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelectorTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelectorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelectorTest.java index 10f08c5..649e537 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFieldGenerationSelectorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFieldGenerationSelectorTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContextTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFileContextTest.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContextTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFileContextTest.java index 63138a9..7c51b97 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiFileContextTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiFileContextTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfigTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationConfigTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfigTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationConfigTest.java index 1fa3bff..18e5400 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationConfigTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationConfigTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKindLincheckTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationKindLincheckTest.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKindLincheckTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationKindLincheckTest.java index aa2d9f2..20ca6f3 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiGenerationKindLincheckTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiGenerationKindLincheckTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import java.util.concurrent.atomic.AtomicInteger; import org.jetbrains.kotlinx.lincheck.LinChecker; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupportTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupportTest.java index 656a5aa..0a7949b 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionTest.java index d618635..62fd14b 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiModelDefinitionTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiModelDefinitionTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategyTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiOversizeStrategyTest.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategyTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiOversizeStrategyTest.java index d38f946..c730fb4 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiOversizeStrategyTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiOversizeStrategyTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeConditionTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiRangeConditionTest.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeConditionTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiRangeConditionTest.java index d597226..fcbf504 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/AiRangeConditionTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/AiRangeConditionTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/JavaStructureFactPatternsTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/JavaStructureFactPatternsTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/JavaStructureFactPatternsTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/config/JavaStructureFactPatternsTest.java index 8bec48d..78ea128 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/config/JavaStructureFactPatternsTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/JavaStructureFactPatternsTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.config; +package net.ladenthin.srcmorph.config; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatterTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatterTest.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatterTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatterTest.java index dcf938b..27d0b58 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdChildEntryLineFormatterTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdChildEntryLineFormatterTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodecTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdDocumentCodecTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodecTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdDocumentCodecTest.java index 222b2ce..cdc40cf 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdDocumentCodecTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdDocumentCodecTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecProperties.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecProperties.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecProperties.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecProperties.java index 5ea5448..a3b5c7b 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecProperties.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecProperties.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import java.util.Arrays; import net.jqwik.api.ForAll; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecTest.java index bcd06b7..77eba11 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderCodecTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderCodecTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderSupportTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderSupportTest.java index d629c63..8c93745 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderTest.java index 6fd6bf7..e1996fd 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdHeaderTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdHeaderTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdLeadExtractorTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdLeadExtractorTest.java index 1a38001..fe17e9c 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/document/AiMdLeadExtractorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/document/AiMdLeadExtractorTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.document; +package net.ladenthin.srcmorph.document; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurementTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurementTest.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurementTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurementTest.java index b3c5a00..9f273b5 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationMeasurementTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationMeasurementTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunnerTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunnerTest.java similarity index 81% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunnerTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunnerTest.java index 01c0dab..c932cdf 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiCalibrationRunnerTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunnerTest.java @@ -1,19 +1,19 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationTimings; -import net.ladenthin.maven.llamacpp.aiindex.provider.MockAiGenerationProvider; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationTimings; +import net.ladenthin.srcmorph.provider.MockAiGenerationProvider; import org.junit.jupiter.api.Test; public class AiCalibrationRunnerTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportRealModelTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportRealModelTest.java similarity index 85% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportRealModelTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportRealModelTest.java index 77e31a5..c8db908 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportRealModelTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportRealModelTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; @@ -12,19 +12,19 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactCounter; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactExtractor; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.LlamaCppJniAiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.LlamaCppJniConfig; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiFactCounter; +import net.ladenthin.srcmorph.config.AiFactExtractor; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.LlamaCppJniAiGenerationProvider; +import net.ladenthin.srcmorph.provider.LlamaCppJniConfig; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportTest.java index b0e62d2..ebbc865 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiFieldGenerationSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiFieldGenerationSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -21,19 +21,19 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactCounter; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFactExtractor; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiModelDefinitionSupport; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationResult; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiFactCounter; +import net.ladenthin.srcmorph.config.AiFactExtractor; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiGenerationResult; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlanTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiIndexPlanTest.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlanTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiIndexPlanTest.java index 539484d..ad40277 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiIndexPlanTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiIndexPlanTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -10,7 +10,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; import org.junit.jupiter.api.Test; public class AiIndexPlanTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculatorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculatorTest.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculatorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculatorTest.java index d7657b4..c93a35d 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/AiInputWindowCalculatorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/AiInputWindowCalculatorTest.java @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; import org.junit.jupiter.api.Test; public class AiInputWindowCalculatorTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexerTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/PackageIndexerTest.java similarity index 94% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexerTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/PackageIndexerTest.java index a23c133..a8beea8 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/PackageIndexerTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/PackageIndexerTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -17,15 +17,15 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.AiGenerationProvider; -import net.ladenthin.maven.llamacpp.aiindex.provider.MockAiGenerationProvider; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.MockAiGenerationProvider; import org.junit.jupiter.api.Test; public class PackageIndexerTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexerTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/ProjectIndexerTest.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexerTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/ProjectIndexerTest.java index 770d0dd..933e71a 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/ProjectIndexerTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/ProjectIndexerTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -15,15 +15,15 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptDefinition; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.MockAiGenerationProvider; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.MockAiGenerationProvider; import org.junit.jupiter.api.Test; public class ProjectIndexerTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexerTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/SourceFileIndexerTest.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexerTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/SourceFileIndexerTest.java index 709a98f..d3f48b7 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/indexer/SourceFileIndexerTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/indexer/SourceFileIndexerTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.indexer; +package net.ladenthin.srcmorph.indexer; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; @@ -15,14 +15,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiCondition; -import net.ladenthin.maven.llamacpp.aiindex.config.AiFieldGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocument; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdDocumentCodec; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptPreparationSupport; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; -import net.ladenthin.maven.llamacpp.aiindex.provider.MockAiGenerationProvider; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiCondition; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.MockAiGenerationProvider; import org.junit.jupiter.api.Test; public class SourceFileIndexerTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPromptTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPreparedPromptTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPromptTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPreparedPromptTest.java index 0938d0f..7ac60b6 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPreparedPromptTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPreparedPromptTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupportTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupportTest.java index f984c31..89ddddf 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptPreparationSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptPreparationSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -10,9 +10,9 @@ import java.nio.file.Paths; import java.util.Arrays; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; import org.junit.jupiter.api.Test; public class AiPromptPreparationSupportTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptSupportTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptSupportTest.java index 37c988b..e65e531 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/prompt/AiPromptSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/prompt/AiPromptSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.prompt; +package net.ladenthin.srcmorph.prompt; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParserTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiCompletionParserTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParserTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiCompletionParserTest.java index 09ce520..e633fc7 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiCompletionParserTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiCompletionParserTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderDefaultTimingsTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderDefaultTimingsTest.java similarity index 87% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderDefaultTimingsTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderDefaultTimingsTest.java index b63cf2d..6a3d479 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderDefaultTimingsTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderDefaultTimingsTest.java @@ -1,14 +1,14 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import java.nio.file.Paths; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdHeader; import org.junit.jupiter.api.Test; public class AiGenerationProviderDefaultTimingsTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactoryTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactoryTest.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactoryTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactoryTest.java index e55fec7..b8f6780 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationProviderFactoryTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationProviderFactoryTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Collections; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; import org.junit.jupiter.api.Test; public class AiGenerationProviderFactoryTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimingsTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationTimingsTest.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimingsTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationTimingsTest.java index 9df4ec4..8a9db1b 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/AiGenerationTimingsTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/AiGenerationTimingsTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProviderTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProviderTest.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProviderTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProviderTest.java index 9bb0b80..d06fb3a 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/LlamaCppJniAiGenerationProviderTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniAiGenerationProviderTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -10,12 +10,12 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collections; -import net.ladenthin.maven.llamacpp.aiindex.CommonTestFixtures; -import net.ladenthin.maven.llamacpp.aiindex.config.AiGenerationConfig; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeaderCodec; -import net.ladenthin.maven.llamacpp.aiindex.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProviderTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/MockAiGenerationProviderTest.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProviderTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/provider/MockAiGenerationProviderTest.java index 602f851..b91c992 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/provider/MockAiGenerationProviderTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/MockAiGenerationProviderTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.provider; +package net.ladenthin.srcmorph.provider; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -9,8 +9,8 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import net.ladenthin.maven.llamacpp.aiindex.document.AiGenerationRequest; -import net.ladenthin.maven.llamacpp.aiindex.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiGenerationRequest; +import net.ladenthin.srcmorph.document.AiMdHeader; import org.junit.jupiter.api.Test; public class MockAiGenerationProviderTest { diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiChecksumSupportTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiChecksumSupportTest.java index f61b4e4..078c9d0 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiChecksumSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiChecksumSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummaryTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiDeterministicSummaryTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummaryTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiDeterministicSummaryTest.java index 34ecbfa..09753aa 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiDeterministicSummaryTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiDeterministicSummaryTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimatorTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimatorTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimatorTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimatorTest.java index e05a926..5914c5f 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiGenerationTimeEstimatorTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiGenerationTimeEstimatorTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiPathSupportTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiPathSupportTest.java index 4c5242a..99a3cc8 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiPathSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiPathSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBarTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiProgressBarTest.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBarTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiProgressBarTest.java index d0d05c6..7c448cc 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiProgressBarTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiProgressBarTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunkerTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceChunkerTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunkerTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceChunkerTest.java index 21d5895..a92d7bf 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceChunkerTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceChunkerTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilterTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilterTest.java similarity index 99% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilterTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilterTest.java index 6ac5ce2..1c38649 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiSourceExcludeFilterTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiSourceExcludeFilterTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiTimeSupportTest.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupportTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiTimeSupportTest.java index 0d5220e..093d173 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/AiTimeSupportTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/AiTimeSupportTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelperTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelperTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelperTest.java rename to srcmorph/src/test/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelperTest.java index b18f6bb..693d33a 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/support/Java8CompatibilityHelperTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/support/Java8CompatibilityHelperTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.support; +package net.ladenthin.srcmorph.support; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf b/srcmorph/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf similarity index 100% rename from llamacpp-ai-index-maven-plugin/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf rename to srcmorph/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf From 9552d667be1dfb1bb18ae126fd9ecd3aa99fcc57 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 15:22:22 +0000 Subject: [PATCH 2/9] Extract engine layer from mojos (migration step 5) Moves the orchestration bodies of GenerateMojo/AggregatePackagesMojo/ AggregateProjectMojo/CalibrateMojo into new framework-free engine classes in the srcmorph core module (net.ladenthin.srcmorph.engine): GenerateEngine, AggregatePackagesEngine, AggregateProjectEngine, CalibrateEngine, plus the new checked SrcMorphException, the GenerateResult/CalibrationReport value types, and a package-private EngineSupport for the logic shared by two or more engines (subtree resolution, prompt/model-support construction, LlamaCppJniConfig resolution). Adds the shared root config.SrcMorphConfiguration JavaBean (field names mirror today's @Parameter names) and provider.LlamaCppJniConfigFactory (the pure extraction of the old 26-arg buildLlamaCppJniConfig mapping). The five mojos shrink to: skip check -> buildConfiguration() -> new XxxEngine(config) .execute() -> wrap SrcMorphException/IOException into MojoExecutionException, with the exact same log/exception-message shapes as before. CoreArchitectureTest gains the new Engine layer on top of Indexer, plus the Provider->Config and Config->Prompt edges the extraction introduced. srcmorph's PIT targetClasses gains SrcMorphConfiguration, LlamaCppJniConfigFactory, EngineSupport, GenerateResult, and CalibrationReport(+ModelMeasurement) at the existing 100% mutation threshold; the four orchestration engines are deliberately left out, mirroring the plugin module's own mojo-PIT-exemption rationale. Verified: full reactor `mvn verify` green, srcmorph PIT 100% (618/618 mutations killed across 43 target classes), plugin.xml parameter parity confirmed for all 5 goals, and a real Maven CLI run of generate/ aggregate-packages/aggregate-project/calibrate against the mock provider produced the expected .ai.md tree end-to-end. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- .../spotbugs-exclude.xml | 115 +--- .../aiindex/mojo/AbstractAiIndexMojo.java | 255 ++------- .../aiindex/mojo/AggregatePackagesMojo.java | 63 +-- .../aiindex/mojo/AggregateProjectMojo.java | 97 +--- .../llamacpp/aiindex/mojo/CalibrateMojo.java | 143 +---- .../llamacpp/aiindex/mojo/GenerateMojo.java | 202 +------ srcmorph/pom.xml | 30 +- srcmorph/spotbugs-exclude.xml | 111 +++- srcmorph/src/main/java/module-info.java | 11 +- .../config/AiModelDefinitionSupport.java | 3 +- .../config/SrcMorphConfiguration.java | 506 ++++++++++++++++++ .../engine/AggregatePackagesEngine.java | 100 ++++ .../engine/AggregateProjectEngine.java | 128 +++++ .../srcmorph/engine/CalibrateEngine.java | 157 ++++++ .../srcmorph/engine/CalibrationReport.java | 118 ++++ .../srcmorph/engine/EngineSupport.java | 149 ++++++ .../srcmorph/engine/GenerateEngine.java | 250 +++++++++ .../srcmorph/engine/GenerateResult.java | 86 +++ .../srcmorph/engine/SrcMorphException.java | 38 ++ .../srcmorph/engine/package-info.java | 10 + .../srcmorph/prompt/AiPromptSupport.java | 3 +- .../srcmorph/provider/LlamaCppJniConfig.java | 7 +- .../provider/LlamaCppJniConfigFactory.java | 119 ++++ .../srcmorph/CoreArchitectureTest.java | 30 +- .../config/SrcMorphConfigurationTest.java | 231 ++++++++ .../engine/AggregatePackagesEngineTest.java | 91 ++++ .../engine/AggregateProjectEngineTest.java | 130 +++++ .../srcmorph/engine/CalibrateEngineTest.java | 97 ++++ .../engine/CalibrationReportTest.java | 92 ++++ .../srcmorph/engine/EngineSupportTest.java | 175 ++++++ .../srcmorph/engine/GenerateEngineTest.java | 157 ++++++ .../srcmorph/engine/GenerateResultTest.java | 54 ++ .../LlamaCppJniConfigFactoryTest.java | 143 +++++ 33 files changed, 3158 insertions(+), 743 deletions(-) create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngine.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregateProjectEngine.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrateEngine.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateEngine.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateResult.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/SrcMorphException.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/engine/package-info.java create mode 100644 srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactory.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/config/SrcMorphConfigurationTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngineTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregateProjectEngineTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrateEngineTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/EngineSupportTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateEngineTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateResultTest.java create mode 100644 srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactoryTest.java diff --git a/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml b/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml index f3dc510..3c9c221 100644 --- a/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml +++ b/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml @@ -116,6 +116,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -157,108 +158,18 @@ SPDX-License-Identifier: Apache-2.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java index c346b62..65c799b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java @@ -4,28 +4,22 @@ package net.ladenthin.maven.llamacpp.aiindex.mojo; import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; import lombok.ToString; import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; -import net.ladenthin.srcmorph.config.AiGenerationConfig; import net.ladenthin.srcmorph.config.AiModelDefinition; -import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.SrcMorphException; import net.ladenthin.srcmorph.prompt.AiPromptDefinition; -import net.ladenthin.srcmorph.prompt.AiPromptSupport; -import net.ladenthin.srcmorph.provider.LlamaCppJniConfig; import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Parameter; /** * Base class for all AI index mojos. Centralises the parameters shared by every goal - * ({@code generate}, {@code summarize-files}, {@code summarize-packages}, - * {@code aggregate-packages}) and provides the utility methods {@link #resolveSubtrees}, - * {@link #sizeOf}, {@link #buildLlamaCppJniConfig}, and {@link #buildPromptSupport}. + * ({@code generate}, {@code aggregate-packages}, {@code aggregate-project}, {@code calibrate}) and + * provides {@link #buildConfiguration()}, which maps the shared {@code @Parameter} fields onto a + * {@link net.ladenthin.srcmorph.config.SrcMorphConfiguration} for the matching + * {@code net.ladenthin.srcmorph.engine} engine to execute. * *

      Concrete subclasses must implement {@link #getLlamaContextSize()} and * {@link #getLlamaThreads()} so that each goal can declare its own @@ -167,7 +161,8 @@ protected AbstractAiIndexMojo() { /** * Returns whether this goal should skip execution: either the global {@link #skip} (which skips * every phase) or this phase's own {@link #isPhaseSkipped()} flag. Centralised here so every goal - * applies the same rule identically. + * applies the same rule identically. Skip flags stay mojo-side (a Maven lifecycle concern); an + * engine built from {@link #buildConfiguration()} always executes when asked. * * @return {@code true} if the goal must not run */ @@ -176,209 +171,45 @@ protected boolean shouldSkip() { } /** - * Resolves the configured {@link #subtrees} strings against {@code basePath}, - * filtering out any paths that do not exist on disk. - * - * @param basePath absolute, normalised project base directory - * @return list of resolved, existing subtree paths; empty if none configured - */ - protected List resolveSubtrees(final Path basePath) { - final List resolved = new ArrayList<>(); - - if (subtrees == null || subtrees.isEmpty()) { - return resolved; - } - - for (String subtree : subtrees) { - final Path path = basePath.resolve(subtree).normalize(); - if (path.toFile().exists()) { - resolved.add(path); - } else { - getLog().warn("Skipping missing subtree: " + path); - } - } - - return resolved; - } - - /** - * Returns the size of {@code collection}, or {@code 0} when {@code collection} is {@code null}. - * - * @param collection any collection, or {@code null} - * @return number of elements, or {@code 0} - */ - protected int sizeOf(final Collection collection) { - return collection == null ? 0 : collection.size(); + * Maps the {@code @Parameter} fields shared by every goal onto a new + * {@link net.ladenthin.srcmorph.config.SrcMorphConfiguration}. Concrete subclasses that need + * additional fields (e.g. {@code pluginVersion}, {@code aiVersion}, {@code fileExtensions}) + * call this first and then set their own goal-specific fields on the returned instance. + * + * @return a configuration populated from every shared {@code @Parameter} field + */ + protected SrcMorphConfiguration buildConfiguration() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setBaseDirectory(baseDirectory); + config.setOutputDirectory(outputDirectory); + config.setForce(force); + config.setSubtrees(subtrees); + config.setGenerationProvider(generationProvider); + config.setPromptDefinitions(promptDefinitions); + config.setAiDefinitions(aiDefinitions); + config.setFieldGenerations(fieldGenerations); + config.setLlamaLibraryPath(llamaLibraryPath); + config.setLlamaModelPath(llamaModelPath); + config.setLlamaContextSize(getLlamaContextSize()); + config.setLlamaMaxOutputTokens(llamaMaxOutputTokens); + config.setLlamaTemperature(llamaTemperature); + config.setLlamaThreads(getLlamaThreads()); + return config; } /** - * Builds a {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} for the AI generation provider. - * - *

      When {@link #fieldGenerations} is non-empty, all model parameters - * (model path, context size, max output tokens, temperature, threads) are taken from - * the {@link net.ladenthin.srcmorph.config.AiModelDefinition} referenced by the first entry's - * {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig#getAiDefinitionKey()}. This ensures the provider is - * always configured from the same definition that drives field generation.

      - * - *

      When {@link #fieldGenerations} is {@code null} or empty, the individual - * {@code llamaModelPath}, {@code llamaContextSize}, {@code llamaMaxOutputTokens}, - * {@code llamaTemperature}, and {@code llamaThreads} parameters are used as a fallback.

      - * - * @return fully populated llama.cpp configuration - * @throws IllegalArgumentException if the first field generation's - * {@link net.ladenthin.srcmorph.config.AiFieldGenerationConfig#getAiDefinitionKey()} - * does not match any registered definition - * @throws MojoExecutionException propagated from {@link #buildAiModelDefinitionSupport()} - * if any AI definition is missing a required field - */ - protected LlamaCppJniConfig buildLlamaCppJniConfig() throws MojoExecutionException { - if (fieldGenerations != null && !fieldGenerations.isEmpty()) { - return buildLlamaCppJniConfig(fieldGenerations.get(0).getAiDefinitionKey()); - } - return new LlamaCppJniConfig( - llamaLibraryPath, - llamaModelPath, - getLlamaContextSize(), - llamaMaxOutputTokens, - llamaTemperature, - getLlamaThreads(), - AiGenerationConfig.DEFAULT_TOP_P, - AiGenerationConfig.DEFAULT_TOP_K, - AiGenerationConfig.DEFAULT_MIN_P, - AiGenerationConfig.DEFAULT_TOP_N_SIGMA, - AiGenerationConfig.DEFAULT_REPEAT_PENALTY, - AiGenerationConfig.DEFAULT_CHAT_TEMPLATE_ENABLE_THINKING, - AiGenerationConfig.DEFAULT_CACHE_PROMPT, - AiGenerationConfig.DEFAULT_SWA_FULL, - AiGenerationConfig.DEFAULT_CACHE_REUSE, - AiGenerationConfig.DEFAULT_GPU_LAYERS, - AiGenerationConfig.DEFAULT_MAIN_GPU, - AiGenerationConfig.DEFAULT_DEVICES, - AiGenerationConfig.DEFAULT_REASONING_EFFORT, - AiGenerationConfig.DEFAULT_REASONING_BUDGET_TOKENS, - AiGenerationConfig.DEFAULT_DRY_MULTIPLIER, - AiGenerationConfig.DEFAULT_DRY_BASE, - AiGenerationConfig.DEFAULT_DRY_ALLOWED_LENGTH, - AiGenerationConfig.DEFAULT_DRY_PENALTY_LAST_N, - Collections.emptyList(), - Collections.emptyList()); - } - - /** - * Builds a {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} from a specific - * AI model definition, identified by its key. Used by the {@code generate} goal to load a separate - * model per routing group (one provider per distinct {@code aiDefinitionKey}). - * - * @param aiDefinitionKey the {@link net.ladenthin.srcmorph.config.AiModelDefinition} key - * @return fully populated llama.cpp configuration for that definition - * @throws IllegalArgumentException if {@code aiDefinitionKey} matches no registered definition - * @throws MojoExecutionException propagated from {@link #buildAiModelDefinitionSupport()} - */ - protected LlamaCppJniConfig buildLlamaCppJniConfig(final String aiDefinitionKey) throws MojoExecutionException { - final AiGenerationConfig config = buildAiModelDefinitionSupport().getConfig(aiDefinitionKey); - final List stopStrings = config.getStopStrings(); - final List drySequenceBreakers = config.getDrySequenceBreakers(); - return new LlamaCppJniConfig( - llamaLibraryPath, - config.getModelPath(), - config.getContextSize(), - config.getMaxOutputTokens(), - config.getTemperature(), - config.getThreads(), - config.getTopP(), - config.getTopK(), - config.getMinP(), - config.getTopNSigma(), - config.getRepeatPenalty(), - config.isChatTemplateEnableThinking(), - config.isCachePrompt(), - config.isSwaFull(), - config.getCacheReuse(), - config.getGpuLayers(), - config.getMainGpu(), - config.getDevices(), - config.getReasoningEffort(), - config.getReasoningBudgetTokens(), - config.getDryMultiplier(), - config.getDryBase(), - config.getDryAllowedLength(), - config.getDryPenaltyLastN(), - drySequenceBreakers != null ? drySequenceBreakers : Collections.emptyList(), - stopStrings != null ? stopStrings : Collections.emptyList()); - } - - /** - * Builds an {@link net.ladenthin.srcmorph.prompt.AiPromptSupport} from the configured {@link #promptDefinitions}. - * - *

      If any {@code } entry in the POM is missing its {@code key} - * or {@code template}, the underlying constructor throws - * {@link NullPointerException} with a message naming the offending list index and - * the bad entry. This method translates that into a - * {@link MojoExecutionException} so Maven reports it as a user configuration - * error (the "Invalid plugin {@code }" framing) rather than as - * a plugin bug.

      - * - * @return prompt support instance backed by the configured definitions - * @throws MojoExecutionException if any prompt definition is missing a required field - */ - protected AiPromptSupport buildPromptSupport() throws MojoExecutionException { - try { - return new AiPromptSupport(promptDefinitions); - } catch (NullPointerException e) { - throw new MojoExecutionException("Invalid plugin configuration: " + e.getMessage(), e); - } - } - - /** - * Builds an {@link net.ladenthin.srcmorph.config.AiModelDefinitionSupport} from the configured {@link #aiDefinitions}. - * - *

      If any {@code } entry in the POM is missing its {@code key}, - * the underlying constructor throws {@link NullPointerException} with a message - * naming the offending list index and the bad entry. This method translates that - * into a {@link MojoExecutionException} so Maven reports it as a user - * configuration error rather than as a plugin bug.

      - * - * @return model definition support instance backed by the configured definitions - * @throws MojoExecutionException if any AI definition is missing a required field - */ - protected AiModelDefinitionSupport buildAiModelDefinitionSupport() throws MojoExecutionException { - try { - return new AiModelDefinitionSupport(aiDefinitions); - } catch (NullPointerException e) { - throw new MojoExecutionException("Invalid plugin configuration: " + e.getMessage(), e); - } - } - - /** - * Logs the standard set of execution parameters that are common to every goal. - * - *

      Always logs: the start message, base directory, output directory, subtrees, - * force flag, and provider name. When {@code resolvedExtensions} is non-{@code null} - * it is also logged; goals that do not use file-extension filtering (e.g. - * {@code aggregate-packages}) pass {@code null} to suppress that line.

      + * Returns {@code cause}'s message for wrapping into a + * {@link org.apache.maven.plugin.MojoExecutionException}, falling back to {@link Throwable#toString()} + * when the message is {@code null}. Every {@link SrcMorphException} constructor requires a non-null + * message in practice, but {@link Throwable#getMessage()} is not statically non-null, so every mojo's + * {@code catch (SrcMorphException e)} block goes through this helper rather than passing + * {@code e.getMessage()} straight through. * - * @param startMessage first log line that identifies which goal is starting - * @param basePath resolved, absolute project base directory - * @param outputPath resolved, absolute output directory - * @param resolvedSubtrees resolved subtree paths; may be empty but not {@code null} - * @param resolvedExtensions file extensions in scope; pass an empty list when not applicable + * @param cause the caught engine exception + * @return a non-null message suitable for {@code MojoExecutionException}'s constructor */ - protected void logExecutionParameters( - final String startMessage, - final Path basePath, - final Path outputPath, - final List resolvedSubtrees, - final List resolvedExtensions) { - getLog().info(startMessage); - getLog().info("Base directory : " + basePath); - getLog().info("Output directory: " + outputPath); - getLog().info("Subtrees : " + resolvedSubtrees); - if (!resolvedExtensions.isEmpty()) { - getLog().info("Extensions : " + resolvedExtensions); - } - getLog().info("Force : " + force); - getLog().info("Provider : " + generationProvider); - getLog().info("LlamaCpp Temperature: " + llamaTemperature); - getLog().info("LlamaCpp Max Output Tokens: " + llamaMaxOutputTokens); + protected static String messageOf(final SrcMorphException cause) { + final String message = cause.getMessage(); + return message != null ? message : cause.toString(); } } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java index 7c4fb6a..50f0d75 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java @@ -5,14 +5,10 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; import lombok.ToString; -import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; -import net.ladenthin.srcmorph.indexer.PackageIndexer; -import net.ladenthin.srcmorph.prompt.AiPromptSupport; -import net.ladenthin.srcmorph.provider.AiGenerationProvider; -import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.AggregatePackagesEngine; +import net.ladenthin.srcmorph.engine.SrcMorphException; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -20,6 +16,9 @@ /** * Maven goal {@code ai-index:aggregate-packages}: aggregates per-package * {@code .ai.md} index files and fills in their AI-generated summary and keyword fields. + * + *

      Thin wrapper: builds a {@link SrcMorphConfiguration} from its {@code @Parameter} fields and + * delegates the whole run to {@link AggregatePackagesEngine}.

      */ // @Parameter fields are populated by the Maven plugin framework via reflection after // construction. NullAway is configured via ExcludedFieldAnnotations to skip them; Checker @@ -70,6 +69,19 @@ protected boolean isPhaseSkipped() { return skipPackage; } + /** + * Maps this goal's own {@code @Parameter} fields onto the shared configuration built by + * {@link AbstractAiIndexMojo#buildConfiguration()}. + * + * @return the fully populated configuration for {@link AggregatePackagesEngine} + */ + private SrcMorphConfiguration buildAggregatePackagesConfiguration() { + final SrcMorphConfiguration config = buildConfiguration(); + config.setPluginVersion(pluginVersion); + config.setAiVersion(aiVersion); + return config; + } + @Override public void execute() throws MojoExecutionException { if (shouldSkip()) { @@ -77,44 +89,13 @@ public void execute() throws MojoExecutionException { return; } - final Path basePath = baseDirectory.toPath().toAbsolutePath().normalize(); final Path outputPath = outputDirectory.toPath().toAbsolutePath().normalize(); - final List resolvedSubtrees = resolveSubtrees(basePath); - - logExecutionParameters( - "Starting AI package aggregation", basePath, outputPath, resolvedSubtrees, Collections.emptyList()); - - if (!outputPath.toFile().exists()) { - getLog().info("AI output directory does not exist, skipping package aggregation: " + outputPath); - return; - } - try { - final AiPromptSupport promptSupport = buildPromptSupport(); - final AiModelDefinitionSupport modelDefinitionSupport = buildAiModelDefinitionSupport(); - final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); - - try (AiGenerationProvider provider = - providerFactory.create(generationProvider, buildLlamaCppJniConfig(), promptSupport)) { - final PackageIndexer packageIndexer = new PackageIndexer( - basePath, - outputPath, - pluginVersion, - aiVersion, - resolvedSubtrees, - force, - provider, - fieldGenerations, - promptSupport, - modelDefinitionSupport); - - final int aggregated = packageIndexer.aggregate(outputPath); - getLog().info("Aggregated packages: " + aggregated); - } + new AggregatePackagesEngine(buildAggregatePackagesConfiguration()).execute(); + } catch (SrcMorphException e) { + throw new MojoExecutionException(messageOf(e), e); } catch (IOException e) { throw new MojoExecutionException("Failed to aggregate package AI index files under " + outputPath, e); } - - getLog().info("AI package aggregation finished."); } } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java index 5b35888..d825212 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java @@ -6,13 +6,9 @@ import java.io.IOException; import java.nio.file.Path; import lombok.ToString; -import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; -import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; -import net.ladenthin.srcmorph.indexer.ProjectIndexer; -import net.ladenthin.srcmorph.prompt.AiPromptSupport; -import net.ladenthin.srcmorph.provider.AiGenerationProvider; -import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; -import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.AggregateProjectEngine; +import net.ladenthin.srcmorph.engine.SrcMorphException; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -29,10 +25,15 @@ * *

      Optional AI overview (opt-in). When a {@code } is configured * (a {@code promptKey} + {@code aiDefinitionKey}), the goal additionally makes one AI call - * to write a short project overview paragraph from the per-package leads (see {@link ProjectIndexer}). - * Because the overview reuses the shared provider/prompt/model parameters, this mojo extends - * {@code AbstractAiIndexMojo} (the provider-oriented base) — but it builds a provider only when a - * field generation is present, so the deterministic default needs no model and pays no model cost.

      + * to write a short project overview paragraph from the per-package leads — see + * {@link net.ladenthin.srcmorph.indexer.ProjectIndexer}. Because the overview reuses the shared + * provider/prompt/model parameters, this mojo extends {@code AbstractAiIndexMojo} (the + * provider-oriented base) — but {@link net.ladenthin.srcmorph.engine.AggregateProjectEngine} builds a + * provider only when a field generation is present, so the deterministic default needs no model and + * pays no model cost.

      + * + *

      Thin wrapper: builds a {@link SrcMorphConfiguration} from its {@code @Parameter} fields and + * delegates the whole run to {@link AggregateProjectEngine}.

      * *

      {@code toString} is generated by Lombok over the {@code @Parameter} fields so Maven debug logs * ({@code -X}) print a useful dump of the goal's resolved configuration. {@code equals}/{@code hashCode} @@ -51,11 +52,6 @@ public AggregateProjectMojo() { // no-op } - /** - * Title used for the project index when the Maven {@code ${project.name}} is absent or blank. - */ - private static final String DEFAULT_PROJECT_TITLE = "project"; - /** * Phase switch for the project phase (the {@code aggregate-project} goal): when * {@code true}, only this phase is skipped. The global {@link #skip} still skips every phase. @@ -86,8 +82,6 @@ public AggregateProjectMojo() { @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") private int llamaThreads; - private final Java8CompatibilityHelper compatibilityHelper = new Java8CompatibilityHelper(); - @Override protected int getLlamaContextSize() { return llamaContextSize; @@ -103,6 +97,20 @@ protected boolean isPhaseSkipped() { return skipProject; } + /** + * Maps this goal's own {@code @Parameter} fields onto the shared configuration built by + * {@link AbstractAiIndexMojo#buildConfiguration()}. + * + * @return the fully populated configuration for {@link AggregateProjectEngine} + */ + private SrcMorphConfiguration buildAggregateProjectConfiguration() { + final SrcMorphConfiguration config = buildConfiguration(); + config.setPluginVersion(pluginVersion); + config.setAiVersion(aiVersion); + config.setProjectName(projectName); + return config; + } + @Override public void execute() throws MojoExecutionException { if (shouldSkip()) { @@ -111,59 +119,12 @@ public void execute() throws MojoExecutionException { } final Path outputPath = outputDirectory.toPath().toAbsolutePath().normalize(); - - getLog().info("Starting AI project index aggregation"); - getLog().info("Output directory: " + outputPath); - getLog().info("Force : " + force); - - if (!outputPath.toFile().exists()) { - getLog().info("AI output directory does not exist, skipping project index: " + outputPath); - return; - } - - final String title = - projectName == null || compatibilityHelper.isBlank(projectName) ? DEFAULT_PROJECT_TITLE : projectName; - try { - final int written; - if (fieldGenerations != null && !fieldGenerations.isEmpty()) { - written = aggregateWithOverview(outputPath, title, fieldGenerations.get(0)); - } else { - getLog().info("Project overview generation: disabled (no fieldGenerations configured)"); - final ProjectIndexer indexer = new ProjectIndexer(title, pluginVersion, aiVersion, force); - written = indexer.aggregate(outputPath); - } - getLog().info("Project index files written: " + written); + new AggregateProjectEngine(buildAggregateProjectConfiguration()).execute(); + } catch (SrcMorphException e) { + throw new MojoExecutionException(messageOf(e), e); } catch (IOException e) { throw new MojoExecutionException("Failed to aggregate project AI index file under " + outputPath, e); } - - getLog().info("AI project index aggregation finished."); - } - - /** - * Aggregates the project index with the optional AI overview paragraph enabled, generated by the - * given field generation. A provider is created (and closed) only on this path, so the - * deterministic default never instantiates a model. - * - * @param outputPath the resolved output directory holding the {@code .ai.md} tree - * @param title the resolved project title - * @param overview the overview field generation (prompt + model) - * @return {@code 1} if the project index was written or refreshed, {@code 0} otherwise - * @throws MojoExecutionException if the provider or model definitions are misconfigured - * @throws IOException if the output tree cannot be read or the index cannot be written - */ - private int aggregateWithOverview(final Path outputPath, final String title, final AiFieldGenerationConfig overview) - throws MojoExecutionException, IOException { - final AiPromptSupport promptSupport = buildPromptSupport(); - final AiModelDefinitionSupport modelDefinitionSupport = buildAiModelDefinitionSupport(); - getLog().info("Project overview generation: enabled (prompt '" + overview.getPromptKey() + "')"); - final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); - try (AiGenerationProvider provider = - providerFactory.create(generationProvider, buildLlamaCppJniConfig(), promptSupport)) { - final ProjectIndexer indexer = new ProjectIndexer( - title, pluginVersion, aiVersion, force, provider, overview, promptSupport, modelDefinitionSupport); - return indexer.aggregate(outputPath); - } } } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java index 9267fa6..8f29ac9 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java @@ -3,19 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 package net.ladenthin.maven.llamacpp.aiindex.mojo; -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; -import net.ladenthin.srcmorph.config.AiGenerationConfig; -import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; -import net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement; -import net.ladenthin.srcmorph.indexer.AiCalibrationRunner; -import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; -import net.ladenthin.srcmorph.prompt.AiPromptSupport; -import net.ladenthin.srcmorph.provider.AiGenerationProvider; -import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.engine.CalibrateEngine; +import net.ladenthin.srcmorph.engine.CalibrationReport; +import net.ladenthin.srcmorph.engine.SrcMorphException; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -30,6 +20,10 @@ * and prints a paste-ready {@code } block. Paste that onto the matching {@code } * so the plan's time estimate reflects this machine instead of the built-in reference-CPU * coefficients. The numbers are per machine (GPU/CPU, quant, context), so re-run on each host.

      + * + *

      Thin wrapper: builds a {@link net.ladenthin.srcmorph.config.SrcMorphConfiguration} from the shared + * {@code @Parameter} fields and delegates the whole run to {@link CalibrateEngine}; only the final + * paste-ready banner and {@link CalibrationReport#renderXml()} output stay in this mojo.

      */ @SuppressWarnings("initialization.fields.uninitialized") @Mojo(name = "calibrate", threadSafe = true) @@ -48,8 +42,6 @@ public CalibrateMojo() { @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") private int llamaThreads; - private final AiCalibrationRunner calibrationRunner = new AiCalibrationRunner(); - @Override protected int getLlamaContextSize() { return llamaContextSize; @@ -73,120 +65,19 @@ public void execute() throws MojoExecutionException { getLog().info("AI index calibration skipped."); return; } - if (fieldGenerations == null || fieldGenerations.isEmpty()) { - throw new MojoExecutionException("No configured; nothing to calibrate."); - } - - final AiPromptSupport promptSupport = buildPromptSupport(); - final AiModelDefinitionSupport modelDefinitionSupport = buildAiModelDefinitionSupport(); - final AiPromptPreparationSupport promptPreparationSupport = new AiPromptPreparationSupport(promptSupport); - final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); - - // Distinct routed models, each mapped to a representative prompt key (skip rules have none). - final Map modelToPrompt = new LinkedHashMap<>(); - for (final AiFieldGenerationConfig rule : fieldGenerations) { - if (rule == null || rule.getAiDefinitionKey() == null || rule.getPromptKey() == null) { - continue; - } - modelToPrompt.putIfAbsent(rule.getAiDefinitionKey(), rule.getPromptKey()); - } - if (modelToPrompt.isEmpty()) { - throw new MojoExecutionException("No routable (model + prompt) rule found to calibrate."); - } - getLog().info("AI index calibration: " + modelToPrompt.size() + " model(s). Provider: " + generationProvider); - final StringBuilder pasteBlocks = new StringBuilder(); - for (final Map.Entry entry : modelToPrompt.entrySet()) { - calibrateModel( - entry.getKey(), - entry.getValue(), - modelDefinitionSupport, - promptSupport, - promptPreparationSupport, - providerFactory, - pasteBlocks); - } - - getLog().info(""); - getLog().info("Paste each onto its matching (numbers are per machine)."); - getLog().info("These are the model's own measured prefill/decode throughput (from the engine's " - + "per-call timings); only the mock/no-timings path falls back to a wall-clock estimate."); - for (final String line : pasteBlocks.toString().split("\n", -1)) { - getLog().info(line); - } - } + try { + final CalibrationReport report = new CalibrateEngine(buildConfiguration()).execute(); - /** - * Loads one model, measures it via {@link AiCalibrationRunner}, logs the result, and appends a - * paste-ready {@code } block for it. - * - * @param modelKey the aiDefinitionKey to calibrate - * @param promptKey a representative prompt key routed to this model - * @param modelDefinitionSupport model lookup - * @param promptSupport prompt lookup (for the provider) - * @param promptPreparationSupport prompt preparation (for the window calculation) - * @param providerFactory provider factory - * @param pasteBlocks accumulator for the printable {@code } blocks - * @throws MojoExecutionException if the model fails to load or generate - */ - private void calibrateModel( - final String modelKey, - final String promptKey, - final AiModelDefinitionSupport modelDefinitionSupport, - final AiPromptSupport promptSupport, - final AiPromptPreparationSupport promptPreparationSupport, - final AiGenerationProviderFactory providerFactory, - final StringBuilder pasteBlocks) - throws MojoExecutionException { - final AiGenerationConfig config = modelDefinitionSupport.getConfig(modelKey); - final long windowChars = calibrationRunner.windowChars(config, promptKey, promptPreparationSupport); - - getLog().info(""); - getLog().info("Model '" + modelKey + "': loading (window ~" + windowChars + " source chars)..."); - try (AiGenerationProvider provider = - providerFactory.create(generationProvider, buildLlamaCppJniConfig(modelKey), promptSupport)) { - final AiCalibrationMeasurement m = - calibrationRunner.measure(provider, config, promptKey, promptPreparationSupport); - - getLog().info(String.format( - Locale.ROOT, - "Model '%s': loaded+first-gen ~%.1fs | prefill %.0f tok/s | decode %.0f tok/s | ~%.2f chars/token", - modelKey, - m.loadSeconds(), - m.prefillTokensPerSecond(), - m.decodeTokensPerSecond(), - m.charsPerToken())); - if (m.prefillTokensPerSecond() > 0 && m.midPrefillTokensPerSecond() > 0) { - getLog().info(String.format( - Locale.ROOT, - " (mid-window prefill %.0f tok/s vs near-window %.0f tok/s - larger gap => more curvature)", - m.midPrefillTokensPerSecond(), - m.prefillTokensPerSecond())); + getLog().info(""); + getLog().info("Paste each onto its matching (numbers are per machine)."); + getLog().info("These are the model's own measured prefill/decode throughput (from the engine's " + + "per-call timings); only the mock/no-timings path falls back to a wall-clock estimate."); + for (final String line : report.renderXml().split("\n", -1)) { + getLog().info(line); } - appendPasteBlock(pasteBlocks, modelKey, m); - } catch (final IOException e) { - throw new MojoExecutionException("Calibration failed for model '" + modelKey + "': " + e.getMessage(), e); + } catch (SrcMorphException e) { + throw new MojoExecutionException(messageOf(e), e); } } - - /** - * Appends a copy-pasteable {@code } block (with a comment naming the model) to the buffer. - * - * @param out the buffer - * @param modelKey the model key (for the comment) - * @param m the measurement - */ - private static void appendPasteBlock( - final StringBuilder out, final String modelKey, final AiCalibrationMeasurement m) { - out.append("\n"); - out.append("\n"); - out.append(String.format( - Locale.ROOT, - " %.1f%n", - m.prefillTokensPerSecond())); - out.append(String.format( - Locale.ROOT, " %.1f%n", m.decodeTokensPerSecond())); - out.append(String.format(Locale.ROOT, " %.2f%n", m.charsPerToken())); - out.append("\n"); - } } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java index 518a1dd..eee736d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java +++ b/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java @@ -5,24 +5,12 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import lombok.ToString; import net.ladenthin.srcmorph.config.AiFactDefinition; -import net.ladenthin.srcmorph.config.AiFactDefinitionSupport; -import net.ladenthin.srcmorph.config.AiFieldGenerationSelector; -import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; -import net.ladenthin.srcmorph.indexer.AiFieldGenerationSupport; -import net.ladenthin.srcmorph.indexer.AiIndexPlan; -import net.ladenthin.srcmorph.indexer.SourceFileIndexer; -import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; -import net.ladenthin.srcmorph.prompt.AiPromptSupport; -import net.ladenthin.srcmorph.provider.AiGenerationProvider; -import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; -import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; -import net.ladenthin.srcmorph.support.AiProgressBar; -import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.GenerateEngine; +import net.ladenthin.srcmorph.engine.SrcMorphException; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; @@ -30,6 +18,9 @@ /** * Maven goal {@code ai-index:generate}: indexes source files and fills in their * AI-generated summary and keyword fields. + * + *

      Thin wrapper: builds a {@link SrcMorphConfiguration} from its {@code @Parameter} fields and + * delegates the whole run to {@link GenerateEngine}.

      */ // @Parameter fields are populated by the Maven plugin framework via reflection after // construction. NullAway is configured via ExcludedFieldAnnotations to skip them; Checker @@ -44,15 +35,6 @@ public GenerateMojo() { // no-op } - /** - * Default file extension used when no explicit {@code fileExtensions} parameter - * is configured. Only files whose names end with this extension are indexed. - */ - private static final String DEFAULT_FILE_EXTENSION = ".java"; - - /** Nanoseconds per second, for converting the measured run elapsed time to whole seconds. */ - private static final long NANOS_PER_SECOND = 1_000_000_000L; - /** * Phase switch for the file phase (the {@code generate} goal): when {@code true}, * only this phase is skipped. The global {@link #skip} still skips every phase. @@ -125,8 +107,6 @@ public GenerateMojo() { @Parameter(property = "aiIndex.planOnly", defaultValue = "false") private boolean planOnly; - private final Java8CompatibilityHelper compatibilityHelper = new Java8CompatibilityHelper(); - @Override protected int getLlamaContextSize() { return llamaContextSize; @@ -142,6 +122,25 @@ protected boolean isPhaseSkipped() { return skipFile; } + /** + * Maps this goal's own {@code @Parameter} fields onto the shared configuration built by + * {@link AbstractAiIndexMojo#buildConfiguration()}. + * + * @return the fully populated configuration for {@link GenerateEngine} + */ + private SrcMorphConfiguration buildGenerateConfiguration() { + final SrcMorphConfiguration config = buildConfiguration(); + config.setPluginVersion(pluginVersion); + config.setAiVersion(aiVersion); + config.setFileExtensions(fileExtensions); + config.setExcludes(excludes); + config.setFactDefinitions(factDefinitions); + config.setMinFileSizeBytes(minFileSizeBytes); + config.setMaxFileSizeBytes(maxFileSizeBytes); + config.setPlanOnly(planOnly); + return config; + } + @Override public void execute() throws MojoExecutionException { if (shouldSkip()) { @@ -151,158 +150,13 @@ public void execute() throws MojoExecutionException { final Path basePath = baseDirectory.toPath().toAbsolutePath().normalize(); final Path outputPath = outputDirectory.toPath().toAbsolutePath().normalize(); - final List resolvedSubtrees = resolveSubtrees(basePath); - final List resolvedExtensions = resolveFileExtensions(); - - logExecutionParameters( - "Starting AI index generation", basePath, outputPath, resolvedSubtrees, resolvedExtensions); - - if (fieldGenerations == null || fieldGenerations.isEmpty()) { - throw new MojoExecutionException("No configured for the generate goal."); - } - - final AiPromptSupport promptSupport = buildPromptSupport(); - final AiModelDefinitionSupport modelDefinitionSupport = buildAiModelDefinitionSupport(); - final AiPromptPreparationSupport promptPreparationSupport = new AiPromptPreparationSupport(promptSupport); - // Resolve each rule's factsKey to its shared factDefinitions group (copies the counters onto the - // rule's facts) BEFORE validation, so the resolved fact patterns are validated too. - resolveSharedFacts(); - - final AiFieldGenerationSelector selector = new AiFieldGenerationSelector(); - // Fail fast on a bad rule set (e.g. >1 fallback, a route rule missing prompt/model). - selector.validate(fieldGenerations); - - final SourceFileIndexer fileIndexer = new SourceFileIndexer( - basePath, - outputPath, - resolvedExtensions, - pluginVersion, - aiVersion, - resolvedSubtrees, - excludes, - minFileSizeBytes, - maxFileSizeBytes, - force); - try { - // 1. Collect candidate files across the configured subtrees. - final List candidates = new ArrayList<>(); - for (final Path subtree : resolvedSubtrees.isEmpty() - ? compatibilityHelper.listOf(basePath.resolve("src/main/java")) - : resolvedSubtrees) { - if (!subtree.toFile().exists()) { - getLog().warn("Skipping missing subtree: " + subtree); - continue; - } - candidates.addAll(fileIndexer.collectCandidates(subtree)); - } - - // 2. Plan the run: which model + prompt each file gets (or skip / unmatched), and whether - // each file fits its routed model's context window (computed up front, same threshold the - // run uses to trim — see AiInputWindowCalculator). - final AiIndexPlan plan = fileIndexer.classify( - candidates, fieldGenerations, modelDefinitionSupport, promptPreparationSupport); - getLog().info("AI index plan (Markdown):\n" + plan.renderMarkdown(basePath)); - - // 3. A file that matched no rule and no fallback is a fatal misconfiguration. - if (!plan.unmatched().isEmpty()) { - throw new MojoExecutionException(plan.unmatched().size() - + " source file(s) matched no rule and no fallback is configured; " - + "add a rule or a matching rule (see the plan above)."); - } - - // 3b. A file larger than its routed model's window would lose content if trimmed. By default - // (onOversize=fail) this is a hard failure: the fix is user configuration, never an - // automatic model choice — route oversized files to a larger-context model, or set the - // rule's onOversize (sample/mapReduce/deterministic) to handle them at run time. Only the - // fail entries abort here; the handled ones are processed during generation. - final int overWindowFailCount = plan.windowFailCount(); - if (overWindowFailCount > 0) { - throw new MojoExecutionException(overWindowFailCount - + " source file(s) exceed their routed model's context window with onOversize=fail " - + "(see the 'Over window' section in the plan above). Route them to a model with a " - + "large enough context window, or set onOversize=sample|mapReduce|deterministic on " - + "the rule. The build does not pick a model for you; this is configuration only."); - } - - if (planOnly) { - getLog().info("planOnly=true: stopping after the plan; no model loaded, nothing generated."); - return; - } - - // 4. Execute model group by model group: load each model once, index its files, close. - // Progress is the running sum of each finished file's PLAN estimate over the grand total - // (no re-estimation), logged as a bar + percent after every file, with the estimated time - // left and the actual wall-clock elapsed for comparison. - final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); - final AiGenerationTimeEstimator estimator = new AiGenerationTimeEstimator(); - final long totalEstimatedSeconds = plan.totalEstimatedSeconds(); - final int totalFiles = plan.routedCount(); - final long runStartNanos = System.nanoTime(); - long doneEstimatedSeconds = 0; - int doneFiles = 0; - int wrote = 0; - int unchanged = 0; - for (final Map.Entry> group : - plan.routesByModel().entrySet()) { - final String aiDefinitionKey = group.getKey(); - getLog().info("Loading model '" + aiDefinitionKey + "' for " - + group.getValue().size() + " file(s)"); - try (AiGenerationProvider provider = providerFactory.create( - generationProvider, buildLlamaCppJniConfig(aiDefinitionKey), promptSupport)) { - final AiFieldGenerationSupport support = - new AiFieldGenerationSupport(provider, promptPreparationSupport, modelDefinitionSupport); - for (final AiIndexPlan.Entry entry : group.getValue()) { - if (fileIndexer.indexFile(entry.file(), entry.rule(), support)) { - wrote++; - } else { - unchanged++; - } - doneEstimatedSeconds += entry.estimatedSeconds(); - doneFiles++; - final long elapsedSeconds = (System.nanoTime() - runStartNanos) / NANOS_PER_SECOND; - final long remainingSeconds = Math.max(0L, totalEstimatedSeconds - doneEstimatedSeconds); - getLog().info(AiProgressBar.render(doneEstimatedSeconds, totalEstimatedSeconds) - + " " + doneFiles + "/" + totalFiles + " files - est. " - + estimator.formatDuration(doneEstimatedSeconds) + "/" - + estimator.formatDuration(totalEstimatedSeconds) + " done, " - + estimator.formatDuration(remainingSeconds) + " left (estimate) | " - + estimator.formatDuration(elapsedSeconds) + " elapsed (actual)"); - } - } - } - - getLog().info("Generated AI files: " + wrote + " written, " + unchanged + " unchanged, " - + plan.skipped().size() + " skipped"); - + new GenerateEngine(buildGenerateConfiguration()).execute(); + } catch (SrcMorphException e) { + throw new MojoExecutionException(messageOf(e), e); } catch (IOException e) { throw new MojoExecutionException( "Failed to generate AI index files under " + outputPath + " from base " + basePath, e); } - - getLog().info("AI index generation finished."); - } - - private List resolveFileExtensions() { - final List configured = fileExtensions; - if (configured == null || configured.isEmpty()) { - return compatibilityHelper.listOf(DEFAULT_FILE_EXTENSION); - } - return configured; - } - - /** - * Resolves each rule's {@code factsKey} to its shared {@code } group, copying the - * counters onto the rule's {@code facts}. Translates a misconfiguration (unknown key, or a definition - * with a null key) into a {@link MojoExecutionException} so Maven reports a user configuration error. - * - * @throws MojoExecutionException if a {@code factsKey} matches no group or a definition has a null key - */ - private void resolveSharedFacts() throws MojoExecutionException { - try { - new AiFactDefinitionSupport(factDefinitions).resolveFactsKeys(fieldGenerations); - } catch (final IllegalArgumentException | NullPointerException e) { - throw new MojoExecutionException("Invalid factDefinitions/factsKey configuration: " + e.getMessage(), e); - } } } diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml index fa5f5c0..1232c55 100644 --- a/srcmorph/pom.xml +++ b/srcmorph/pom.xml @@ -539,7 +539,29 @@ SPDX-License-Identifier: Apache-2.0 - + net.ladenthin.srcmorph.config.AiCalibration net.ladenthin.srcmorph.config.AiCondition net.ladenthin.srcmorph.config.AiConditionEvaluator @@ -581,6 +603,12 @@ SPDX-License-Identifier: Apache-2.0 net.ladenthin.srcmorph.support.AiSourceExcludeFilter net.ladenthin.srcmorph.support.AiTimeSupport net.ladenthin.srcmorph.support.Java8CompatibilityHelper + net.ladenthin.srcmorph.config.SrcMorphConfiguration + net.ladenthin.srcmorph.provider.LlamaCppJniConfigFactory + net.ladenthin.srcmorph.engine.EngineSupport + net.ladenthin.srcmorph.engine.GenerateResult + net.ladenthin.srcmorph.engine.CalibrationReport + net.ladenthin.srcmorph.engine.CalibrationReport$ModelMeasurement net.ladenthin.srcmorph.* diff --git a/srcmorph/spotbugs-exclude.xml b/srcmorph/spotbugs-exclude.xml index 76428cb..4d92b35 100644 --- a/srcmorph/spotbugs-exclude.xml +++ b/srcmorph/spotbugs-exclude.xml @@ -254,6 +254,13 @@ SPDX-License-Identifier: Apache-2.0 / unmatched collections and Entry.rule reference the same single-threaded, throwaway data; the Entry.rule is the shared routing rule (an AiFieldGenerationConfig) and is intentionally aliased, not copied. No untrusted caller can mutate shared state through these accessors. + + SrcMorphConfiguration (migration step 5, engine extraction) is the same kind of bean, one tier + up: the mutable JavaBean bindable from Maven plexus reflection, a future Jackson ObjectMapper, or + plain Java code, holding every List field (subtrees/excludes/fileExtensions/promptDefinitions/ + aiDefinitions/fieldGenerations/factDefinitions) the same way the mojo's own @Parameter fields used + to. One instance is built once per goal invocation and handed to exactly one engine, which reads + it and discards it - the same single-threaded, no-shared-state lifecycle as the beans above. --> @@ -261,6 +268,7 @@ SPDX-License-Identifier: Apache-2.0 + @@ -270,6 +278,23 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + @@ -344,6 +391,11 @@ SPDX-License-Identifier: Apache-2.0 + + + + + @@ -372,7 +424,11 @@ SPDX-License-Identifier: Apache-2.0 @@ -394,8 +450,59 @@ SPDX-License-Identifier: Apache-2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcmorph/src/main/java/module-info.java b/srcmorph/src/main/java/module-info.java index 2a30676..00718a3 100644 --- a/srcmorph/src/main/java/module-info.java +++ b/srcmorph/src/main/java/module-info.java @@ -5,14 +5,16 @@ /** * JPMS module descriptor for the srcmorph core library. * - *

      The module exports the six framework-free packages extracted from the + *

      The module exports the seven framework-free packages extracted from the * llamacpp-ai-index-maven-plugin's non-mojo layers: {@code config}, {@code document}, - * {@code indexer}, {@code prompt}, {@code provider}, and {@code support}. These hold the - * routing/configuration model, the {@code .ai.md} document codec, the file/package/project + * {@code engine}, {@code indexer}, {@code prompt}, {@code provider}, and {@code support}. These + * hold the routing/configuration model (incl. the shared root {@code SrcMorphConfiguration}), the + * {@code .ai.md} document codec, the per-phase orchestration engines, the file/package/project * indexers, the prompt templating support, the AI generation provider abstraction (incl. the * llama.cpp JNI binding), and the shared stateless helpers. The Maven plugin module * ({@code net.ladenthin.maven.llamacpp.aiindex}, still in its own reactor module) depends on - * this module and supplies only the {@code mojo} entry points.

      + * this module and supplies only the {@code mojo} entry points, which now delegate to the + * {@code engine} package.

      * *

      JSpecify {@code @NullMarked} is declared at the module level here so that no source * file compiled at {@code --release 8} references the JSpecify annotation type directly. @@ -47,6 +49,7 @@ exports net.ladenthin.srcmorph.config; exports net.ladenthin.srcmorph.document; + exports net.ladenthin.srcmorph.engine; exports net.ladenthin.srcmorph.indexer; exports net.ladenthin.srcmorph.prompt; exports net.ladenthin.srcmorph.provider; diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java index 6882581..9d4ceb3 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinitionSupport.java @@ -9,6 +9,7 @@ import java.util.Objects; import lombok.ToString; import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import org.jspecify.annotations.Nullable; /** * Resolves {@link AiModelDefinition} entries by their key, returning the corresponding @@ -61,7 +62,7 @@ public final class AiModelDefinitionSupport { * well-formed * @throws NullPointerException if any entry has a {@code null} {@code key} */ - public AiModelDefinitionSupport(final List definitions) { + public AiModelDefinitionSupport(final @Nullable List definitions) { if (definitions == null) { this.configs = new HashMap<>(compatibilityHelper.hashMapCapacityFor(0)); return; diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java new file mode 100644 index 0000000..de76290 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java @@ -0,0 +1,506 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.config; + +import java.io.File; +import java.util.List; +import lombok.ToString; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import org.jspecify.annotations.Nullable; + +/** + * Root mutable JavaBean holding every parameter a {@code srcmorph} run needs, independent of how it is + * bound: today from Maven {@code @Parameter} fields (the {@code llamacpp-ai-index-maven-plugin} module's + * mojos each build one of these from their own annotated fields), tomorrow from a JSON/YAML config file + * (a future CLI) or from plain Java code constructing one directly. + * + *

      Field names intentionally mirror today's Maven {@code @Parameter} field names (e.g. + * {@link #outputDirectory}, {@link #fieldGenerations}, {@link #llamaContextSize}) so that a future + * JSON/YAML config's keys read the same as the existing plugin XML — this class is the single shared + * config object bindable from every surface. See {@code net.ladenthin.maven.llamacpp.aiindex.mojo}'s + * mojo classes (in the sibling plugin module) for the exact provenance of every field.

      + * + *

      Deliberately excluded: the per-goal {@code skip}/{@code skipFile}/{@code skipPackage}/ + * {@code skipProject} flags. Those are a Maven lifecycle concern (whether an execution runs at all) and + * stay mojo-side; an engine constructed from this configuration always executes when asked.

      + * + *

      Note: this class must remain a mutable JavaBean with setters so that every binding + * technology (Maven plexus reflection, a Jackson {@code ObjectMapper}/{@code YAMLMapper}, or a plain + * caller) can populate it the same way.

      + */ +@SuppressWarnings({"NullAway.Init", "initialization.fields.uninitialized"}) +@ToString +public class SrcMorphConfiguration { + + /** Default output directory (relative to {@link #baseDirectory}) when none is configured. */ + public static final String DEFAULT_OUTPUT_DIRECTORY = "src/site/ai"; + + /** Default AI generation provider name when none is configured. */ + public static final String DEFAULT_GENERATION_PROVIDER = "mock"; + + /** Default llama.cpp context window size when none is configured. */ + public static final int DEFAULT_LLAMA_CONTEXT_SIZE = 2048; + + /** Default maximum number of llama.cpp output tokens per call when none is configured. */ + public static final int DEFAULT_LLAMA_MAX_OUTPUT_TOKENS = 128; + + /** Default llama.cpp sampling temperature when none is configured. */ + public static final float DEFAULT_LLAMA_TEMPERATURE = 0.15f; + + /** Default number of llama.cpp CPU threads when none is configured. */ + public static final int DEFAULT_LLAMA_THREADS = 2; + + /** Default AI summarisation logic version when none is configured. */ + public static final String DEFAULT_AI_VERSION = "0.0.0"; + + /** Creates a new {@link SrcMorphConfiguration} with every default applied. */ + public SrcMorphConfiguration() { + // no-op + } + + private File baseDirectory; + private File outputDirectory = new File(DEFAULT_OUTPUT_DIRECTORY); + private boolean force; + private @Nullable List subtrees; + private @Nullable List excludes; + private @Nullable List fileExtensions; + private long minFileSizeBytes; + private long maxFileSizeBytes; + private boolean planOnly; + private String generationProvider = DEFAULT_GENERATION_PROVIDER; + private @Nullable List promptDefinitions; + private @Nullable List aiDefinitions; + private @Nullable List fieldGenerations; + private @Nullable List factDefinitions; + private @Nullable String llamaLibraryPath; + private @Nullable String llamaModelPath; + private int llamaContextSize = DEFAULT_LLAMA_CONTEXT_SIZE; + private int llamaMaxOutputTokens = DEFAULT_LLAMA_MAX_OUTPUT_TOKENS; + private float llamaTemperature = DEFAULT_LLAMA_TEMPERATURE; + private int llamaThreads = DEFAULT_LLAMA_THREADS; + private String pluginVersion = ""; + private String aiVersion = DEFAULT_AI_VERSION; + private @Nullable String projectName; + + /** + * Returns the project base directory. + * + * @return the base directory + */ + public File getBaseDirectory() { + return baseDirectory; + } + + /** + * Sets the project base directory. + * + * @param baseDirectory the base directory + */ + public void setBaseDirectory(final File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + /** + * Returns the directory into which {@code .ai.md} files are written. + * + * @return the output directory; defaults to {@link #DEFAULT_OUTPUT_DIRECTORY} + */ + public File getOutputDirectory() { + return outputDirectory; + } + + /** + * Sets the directory into which {@code .ai.md} files are written. + * + * @param outputDirectory the output directory + */ + public void setOutputDirectory(final File outputDirectory) { + this.outputDirectory = outputDirectory; + } + + /** + * Returns whether AI fields are regenerated even when they already have a value. + * + * @return {@code true} to always regenerate + */ + public boolean isForce() { + return force; + } + + /** + * Sets whether AI fields are regenerated even when they already have a value. + * + * @param force {@code true} to always regenerate + */ + public void setForce(final boolean force) { + this.force = force; + } + + /** + * Returns the source subdirectory paths (relative to {@link #baseDirectory}) that restrict processing. + * + * @return the configured subtrees, or {@code null} when every discovered source root is in scope + */ + public @Nullable List getSubtrees() { + return subtrees; + } + + /** + * Sets the source subdirectory paths that restrict processing. + * + * @param subtrees the subtrees, or {@code null} for every discovered source root + */ + public void setSubtrees(final @Nullable List subtrees) { + this.subtrees = subtrees; + } + + /** + * Returns the glob patterns (base-relative, {@code /} separators) for source files to skip. + * + * @return the configured excludes, or {@code null} when nothing is excluded + */ + public @Nullable List getExcludes() { + return excludes; + } + + /** + * Sets the glob patterns for source files to skip. + * + * @param excludes the exclude globs, or {@code null} for none + */ + public void setExcludes(final @Nullable List excludes) { + this.excludes = excludes; + } + + /** + * Returns the file extensions to index. + * + * @return the configured extensions, or {@code null} to use the engine's own default (typically + * {@code .java}) + */ + public @Nullable List getFileExtensions() { + return fileExtensions; + } + + /** + * Sets the file extensions to index. + * + * @param fileExtensions the extensions, or {@code null} to use the engine's own default + */ + public void setFileExtensions(final @Nullable List fileExtensions) { + this.fileExtensions = fileExtensions; + } + + /** + * Returns the exclusive lower file-size bound in bytes. + * + * @return the lower bound; {@code <= 0} disables it + */ + public long getMinFileSizeBytes() { + return minFileSizeBytes; + } + + /** + * Sets the exclusive lower file-size bound in bytes. + * + * @param minFileSizeBytes the lower bound; {@code <= 0} disables it + */ + public void setMinFileSizeBytes(final long minFileSizeBytes) { + this.minFileSizeBytes = minFileSizeBytes; + } + + /** + * Returns the inclusive upper file-size bound in bytes. + * + * @return the upper bound; {@code <= 0} means unlimited + */ + public long getMaxFileSizeBytes() { + return maxFileSizeBytes; + } + + /** + * Sets the inclusive upper file-size bound in bytes. + * + * @param maxFileSizeBytes the upper bound; {@code <= 0} means unlimited + */ + public void setMaxFileSizeBytes(final long maxFileSizeBytes) { + this.maxFileSizeBytes = maxFileSizeBytes; + } + + /** + * Returns whether a {@code generate} run stops after printing the routing plan, without loading any + * model or generating anything. + * + * @return {@code true} to stop after planning + */ + public boolean isPlanOnly() { + return planOnly; + } + + /** + * Sets whether a {@code generate} run stops after printing the routing plan. + * + * @param planOnly {@code true} to stop after planning + */ + public void setPlanOnly(final boolean planOnly) { + this.planOnly = planOnly; + } + + /** + * Returns the name of the AI generation provider to use. + * + * @return the provider name ({@code mock} or {@code llamacpp-jni}); defaults to + * {@link #DEFAULT_GENERATION_PROVIDER} + */ + public String getGenerationProvider() { + return generationProvider; + } + + /** + * Sets the name of the AI generation provider to use. + * + * @param generationProvider the provider name ({@code mock} or {@code llamacpp-jni}) + */ + public void setGenerationProvider(final String generationProvider) { + this.generationProvider = generationProvider; + } + + /** + * Returns the prompt template definitions referenced by field generation configurations. + * + * @return the prompt definitions, or {@code null} when none are configured + */ + public @Nullable List getPromptDefinitions() { + return promptDefinitions; + } + + /** + * Sets the prompt template definitions. + * + * @param promptDefinitions the prompt definitions, or {@code null} for none + */ + public void setPromptDefinitions(final @Nullable List promptDefinitions) { + this.promptDefinitions = promptDefinitions; + } + + /** + * Returns the AI model definitions that pair a lookup key with a complete set of model parameters. + * + * @return the AI model definitions, or {@code null} when none are configured + */ + public @Nullable List getAiDefinitions() { + return aiDefinitions; + } + + /** + * Sets the AI model definitions. + * + * @param aiDefinitions the AI model definitions, or {@code null} for none + */ + public void setAiDefinitions(final @Nullable List aiDefinitions) { + this.aiDefinitions = aiDefinitions; + } + + /** + * Returns the per-field AI generation configurations (routing rules) controlling which prompt and AI + * definition each matched file uses. + * + * @return the field generation rules, or {@code null} when none are configured + */ + public @Nullable List getFieldGenerations() { + return fieldGenerations; + } + + /** + * Sets the per-field AI generation configurations (routing rules). + * + * @param fieldGenerations the field generation rules, or {@code null} for none + */ + public void setFieldGenerations(final @Nullable List fieldGenerations) { + this.fieldGenerations = fieldGenerations; + } + + /** + * Returns the reusable, named {@code } groups referenced from a rule's + * {@code factsKey}. + * + * @return the fact definitions, or {@code null} when none are configured + */ + public @Nullable List getFactDefinitions() { + return factDefinitions; + } + + /** + * Sets the reusable, named {@code } groups. + * + * @param factDefinitions the fact definitions, or {@code null} for none + */ + public void setFactDefinitions(final @Nullable List factDefinitions) { + this.factDefinitions = factDefinitions; + } + + /** + * Returns the optional native library path passed to the llama.cpp JNI provider. + * + * @return the native library path, or {@code null} to use the bundled native library + */ + public @Nullable String getLlamaLibraryPath() { + return llamaLibraryPath; + } + + /** + * Sets the optional native library path passed to the llama.cpp JNI provider. + * + * @param llamaLibraryPath the native library path, or {@code null} to use the bundled library + */ + public void setLlamaLibraryPath(final @Nullable String llamaLibraryPath) { + this.llamaLibraryPath = llamaLibraryPath; + } + + /** + * Returns the path to the GGUF model file used as the llama.cpp JNI provider fallback (only used + * when {@link #fieldGenerations} is empty). + * + * @return the model path, or {@code null} if not set + */ + public @Nullable String getLlamaModelPath() { + return llamaModelPath; + } + + /** + * Sets the path to the GGUF model file used as the llama.cpp JNI provider fallback. + * + * @param llamaModelPath the model path + */ + public void setLlamaModelPath(final @Nullable String llamaModelPath) { + this.llamaModelPath = llamaModelPath; + } + + /** + * Returns the llama.cpp context window size used as the fallback (only used when + * {@link #fieldGenerations} is empty). + * + * @return the context window size; defaults to {@link #DEFAULT_LLAMA_CONTEXT_SIZE} + */ + public int getLlamaContextSize() { + return llamaContextSize; + } + + /** + * Sets the llama.cpp context window size used as the fallback. + * + * @param llamaContextSize the context window size + */ + public void setLlamaContextSize(final int llamaContextSize) { + this.llamaContextSize = llamaContextSize; + } + + /** + * Returns the maximum number of llama.cpp output tokens per call, used as the fallback. + * + * @return the maximum output tokens; defaults to {@link #DEFAULT_LLAMA_MAX_OUTPUT_TOKENS} + */ + public int getLlamaMaxOutputTokens() { + return llamaMaxOutputTokens; + } + + /** + * Sets the maximum number of llama.cpp output tokens per call, used as the fallback. + * + * @param llamaMaxOutputTokens the maximum output tokens + */ + public void setLlamaMaxOutputTokens(final int llamaMaxOutputTokens) { + this.llamaMaxOutputTokens = llamaMaxOutputTokens; + } + + /** + * Returns the llama.cpp sampling temperature, used as the fallback. + * + * @return the sampling temperature; defaults to {@link #DEFAULT_LLAMA_TEMPERATURE} + */ + public float getLlamaTemperature() { + return llamaTemperature; + } + + /** + * Sets the llama.cpp sampling temperature, used as the fallback. + * + * @param llamaTemperature the sampling temperature + */ + public void setLlamaTemperature(final float llamaTemperature) { + this.llamaTemperature = llamaTemperature; + } + + /** + * Returns the number of CPU threads for llama.cpp inference, used as the fallback. + * + * @return the thread count; defaults to {@link #DEFAULT_LLAMA_THREADS} + */ + public int getLlamaThreads() { + return llamaThreads; + } + + /** + * Sets the number of CPU threads for llama.cpp inference, used as the fallback. + * + * @param llamaThreads the thread count + */ + public void setLlamaThreads(final int llamaThreads) { + this.llamaThreads = llamaThreads; + } + + /** + * Returns the generator (plugin) version recorded in every written {@code .ai.md} header. + * + * @return the generator version + */ + public String getPluginVersion() { + return pluginVersion; + } + + /** + * Sets the generator (plugin) version recorded in every written {@code .ai.md} header. + * + * @param pluginVersion the generator version + */ + public void setPluginVersion(final String pluginVersion) { + this.pluginVersion = pluginVersion; + } + + /** + * Returns the AI summarisation logic version recorded in every written {@code .ai.md} header. + * + * @return the AI version; defaults to {@link #DEFAULT_AI_VERSION} + */ + public String getAiVersion() { + return aiVersion; + } + + /** + * Sets the AI summarisation logic version recorded in every written {@code .ai.md} header. + * + * @param aiVersion the AI version + */ + public void setAiVersion(final String aiVersion) { + this.aiVersion = aiVersion; + } + + /** + * Returns the project title recorded as the {@code aggregate-project} index title. + * + * @return the project name, or {@code null} to fall back to the engine's own default title + */ + public @Nullable String getProjectName() { + return projectName; + } + + /** + * Sets the project title recorded as the {@code aggregate-project} index title. + * + * @param projectName the project name, or {@code null} to use the engine's own default title + */ + public void setProjectName(final @Nullable String projectName) { + this.projectName = projectName; + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngine.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngine.java new file mode 100644 index 0000000..38062e7 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngine.java @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import lombok.ToString; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.indexer.PackageIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Phase 2 orchestration: aggregates per-package {@code .ai.md} index files and fills in their + * AI-generated summary fields. + * + *

      Extracted from what was {@code AggregatePackagesMojo.execute()} in the + * {@code llamacpp-ai-index-maven-plugin} module.

      + */ +@ToString +public final class AggregatePackagesEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(AggregatePackagesEngine.class); + + private final SrcMorphConfiguration config; + + /** + * Creates a new {@link AggregatePackagesEngine} for the given run configuration. + * + * @param config the run configuration + */ + public AggregatePackagesEngine(final SrcMorphConfiguration config) { + this.config = config; + } + + /** + * Aggregates every package beneath the configured output directory. + * + * @return the number of package index files written or refreshed; {@code 0} when the output + * directory does not yet exist + * @throws SrcMorphException if the prompt/model definitions are misconfigured + * @throws IOException if the output tree cannot be read or written + */ + public int execute() throws SrcMorphException, IOException { + final Path basePath = + config.getBaseDirectory().toPath().toAbsolutePath().normalize(); + final Path outputPath = + config.getOutputDirectory().toPath().toAbsolutePath().normalize(); + final List resolvedSubtrees = EngineSupport.resolveSubtrees(basePath, config.getSubtrees()); + + LOGGER.info("Starting AI package aggregation"); + LOGGER.info("Base directory : {}", basePath); + LOGGER.info("Output directory: {}", outputPath); + LOGGER.info("Subtrees : {}", resolvedSubtrees); + LOGGER.info("Force : {}", config.isForce()); + LOGGER.info("Provider : {}", config.getGenerationProvider()); + LOGGER.info("LlamaCpp Temperature: {}", config.getLlamaTemperature()); + LOGGER.info("LlamaCpp Max Output Tokens: {}", config.getLlamaMaxOutputTokens()); + + if (!outputPath.toFile().exists()) { + LOGGER.info("AI output directory does not exist, skipping package aggregation: {}", outputPath); + return 0; + } + + final AiPromptSupport promptSupport = EngineSupport.buildPromptSupport(config.getPromptDefinitions()); + final AiModelDefinitionSupport modelDefinitionSupport = + EngineSupport.buildAiModelDefinitionSupport(config.getAiDefinitions()); + final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); + + final int aggregated; + try (AiGenerationProvider provider = providerFactory.create( + config.getGenerationProvider(), + EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport), + promptSupport)) { + final PackageIndexer packageIndexer = new PackageIndexer( + basePath, + outputPath, + config.getPluginVersion(), + config.getAiVersion(), + resolvedSubtrees, + config.isForce(), + provider, + config.getFieldGenerations(), + promptSupport, + modelDefinitionSupport); + + aggregated = packageIndexer.aggregate(outputPath); + } + + LOGGER.info("Aggregated packages: {}", aggregated); + LOGGER.info("AI package aggregation finished."); + return aggregated; + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregateProjectEngine.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregateProjectEngine.java new file mode 100644 index 0000000..8867eb6 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/AggregateProjectEngine.java @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import lombok.ToString; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.indexer.ProjectIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Phase 3 orchestration: aggregates every per-package {@code package.ai.md} into a single project-level + * {@code project.ai.md} — the top of the three-level index. + * + *

      Extracted from what was {@code AggregateProjectMojo.execute()} in the + * {@code llamacpp-ai-index-maven-plugin} module. By default this is fully deterministic (a per-package + * lead/link table of contents, no model call); when + * {@link SrcMorphConfiguration#getFieldGenerations()} carries at least one entry, its first entry is used + * as the optional AI overview generation (one extra call synthesising a short project overview + * paragraph from the per-package leads) — see {@link ProjectIndexer}.

      + */ +@ToString +public final class AggregateProjectEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(AggregateProjectEngine.class); + + /** Title used for the project index when {@link SrcMorphConfiguration#getProjectName()} is absent or blank. */ + private static final String DEFAULT_PROJECT_TITLE = "project"; + + private final SrcMorphConfiguration config; + private final Java8CompatibilityHelper compatibilityHelper = new Java8CompatibilityHelper(); + + /** + * Creates a new {@link AggregateProjectEngine} for the given run configuration. + * + * @param config the run configuration + */ + public AggregateProjectEngine(final SrcMorphConfiguration config) { + this.config = config; + } + + /** + * Aggregates the project index beneath the configured output directory. + * + * @return {@code 1} if the project index was written or refreshed, {@code 0} otherwise (no package + * index files yet, the output directory does not exist, or the existing file is up to date) + * @throws SrcMorphException if the overview's prompt/model definitions are misconfigured + * @throws IOException if the output tree cannot be read or the project index cannot be written + */ + public int execute() throws SrcMorphException, IOException { + final Path outputPath = + config.getOutputDirectory().toPath().toAbsolutePath().normalize(); + + LOGGER.info("Starting AI project index aggregation"); + LOGGER.info("Output directory: {}", outputPath); + LOGGER.info("Force : {}", config.isForce()); + + if (!outputPath.toFile().exists()) { + LOGGER.info("AI output directory does not exist, skipping project index: {}", outputPath); + return 0; + } + + final String projectName = config.getProjectName(); + final String title = + projectName == null || compatibilityHelper.isBlank(projectName) ? DEFAULT_PROJECT_TITLE : projectName; + + final int written; + final List fieldGenerations = config.getFieldGenerations(); + if (fieldGenerations != null && !fieldGenerations.isEmpty()) { + written = aggregateWithOverview(outputPath, title, fieldGenerations.get(0)); + } else { + LOGGER.info("Project overview generation: disabled (no fieldGenerations configured)"); + final ProjectIndexer indexer = + new ProjectIndexer(title, config.getPluginVersion(), config.getAiVersion(), config.isForce()); + written = indexer.aggregate(outputPath); + } + LOGGER.info("Project index files written: {}", written); + + LOGGER.info("AI project index aggregation finished."); + return written; + } + + /** + * Aggregates the project index with the optional AI overview paragraph enabled, generated by the + * given field generation. A provider is created (and closed) only on this path, so the deterministic + * default never instantiates a model. + * + * @param outputPath the resolved output directory holding the {@code .ai.md} tree + * @param title the resolved project title + * @param overview the overview field generation (prompt + model) + * @return {@code 1} if the project index was written or refreshed, {@code 0} otherwise + * @throws SrcMorphException if the provider or model definitions are misconfigured + * @throws IOException if the output tree cannot be read or written + */ + private int aggregateWithOverview(final Path outputPath, final String title, final AiFieldGenerationConfig overview) + throws SrcMorphException, IOException { + final AiPromptSupport promptSupport = EngineSupport.buildPromptSupport(config.getPromptDefinitions()); + final AiModelDefinitionSupport modelDefinitionSupport = + EngineSupport.buildAiModelDefinitionSupport(config.getAiDefinitions()); + LOGGER.info("Project overview generation: enabled (prompt '{}')", overview.getPromptKey()); + final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); + try (AiGenerationProvider provider = providerFactory.create( + config.getGenerationProvider(), + EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport), + promptSupport)) { + final ProjectIndexer indexer = new ProjectIndexer( + title, + config.getPluginVersion(), + config.getAiVersion(), + config.isForce(), + provider, + overview, + promptSupport, + modelDefinitionSupport); + return indexer.aggregate(outputPath); + } + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrateEngine.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrateEngine.java new file mode 100644 index 0000000..2fd0ba3 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrateEngine.java @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import lombok.ToString; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement; +import net.ladenthin.srcmorph.indexer.AiCalibrationRunner; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Preflight + per-machine timing pass that sits between {@code planOnly} (no model loaded) and a real + * {@link GenerateEngine} run. + * + *

      Extracted from what was {@code CalibrateMojo.execute()} in the {@code llamacpp-ai-index-maven-plugin} + * module. For each distinct model a {@link GenerateEngine} run would load (the {@code aiDefinitionKey}s + * referenced by {@link SrcMorphConfiguration#getFieldGenerations()}), loads the model once (catching a + * bad path / OOM / wrong native early), runs a couple of representative generations via + * {@link AiCalibrationRunner}, and reads the model's own measured prefill/decode throughput. The caller + * pastes {@link CalibrationReport#renderXml()} onto the matching {@code } so the plan's time + * estimate reflects this machine instead of the built-in reference-CPU coefficients.

      + */ +@ToString +public final class CalibrateEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(CalibrateEngine.class); + + private final SrcMorphConfiguration config; + private final AiCalibrationRunner calibrationRunner = new AiCalibrationRunner(); + + /** + * Creates a new {@link CalibrateEngine} for the given run configuration. + * + * @param config the run configuration + */ + public CalibrateEngine(final SrcMorphConfiguration config) { + this.config = config; + } + + /** + * Calibrates every distinct model referenced by the configured routing rules. + * + * @return the calibration report, one measurement per distinct routed model + * @throws SrcMorphException if no routable rule is configured, or a model fails to load/generate (any + * {@link IOException} from the provider is caught per-model and rewrapped + * with the failing model's key) + */ + public CalibrationReport execute() throws SrcMorphException { + final List fieldGenerations = config.getFieldGenerations(); + if (fieldGenerations == null || fieldGenerations.isEmpty()) { + throw new SrcMorphException("No configured; nothing to calibrate."); + } + + final AiPromptSupport promptSupport = EngineSupport.buildPromptSupport(config.getPromptDefinitions()); + final AiModelDefinitionSupport modelDefinitionSupport = + EngineSupport.buildAiModelDefinitionSupport(config.getAiDefinitions()); + final AiPromptPreparationSupport promptPreparationSupport = new AiPromptPreparationSupport(promptSupport); + final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); + + // Distinct routed models, each mapped to a representative prompt key (skip rules have none). + final Map modelToPrompt = new LinkedHashMap<>(); + for (final AiFieldGenerationConfig rule : fieldGenerations) { + if (rule == null || rule.getAiDefinitionKey() == null || rule.getPromptKey() == null) { + continue; + } + modelToPrompt.putIfAbsent(rule.getAiDefinitionKey(), rule.getPromptKey()); + } + if (modelToPrompt.isEmpty()) { + throw new SrcMorphException("No routable (model + prompt) rule found to calibrate."); + } + + LOGGER.info( + "AI index calibration: {} model(s). Provider: {}", + modelToPrompt.size(), + config.getGenerationProvider()); + final List measurements = new ArrayList<>(modelToPrompt.size()); + for (final Map.Entry entry : modelToPrompt.entrySet()) { + measurements.add(calibrateModel( + entry.getKey(), + entry.getValue(), + modelDefinitionSupport, + promptSupport, + promptPreparationSupport, + providerFactory)); + } + + return new CalibrationReport(measurements); + } + + /** + * Loads one model, measures it via {@link AiCalibrationRunner}, and logs the result. + * + * @param modelKey the aiDefinitionKey to calibrate + * @param promptKey a representative prompt key routed to this model + * @param modelDefinitionSupport model lookup + * @param promptSupport prompt lookup (for the provider) + * @param promptPreparationSupport prompt preparation (for the window calculation) + * @param providerFactory provider factory + * @return the model's key paired with its measurement + * @throws SrcMorphException if the model fails to load or generate + */ + private CalibrationReport.ModelMeasurement calibrateModel( + final String modelKey, + final String promptKey, + final AiModelDefinitionSupport modelDefinitionSupport, + final AiPromptSupport promptSupport, + final AiPromptPreparationSupport promptPreparationSupport, + final AiGenerationProviderFactory providerFactory) + throws SrcMorphException { + final AiGenerationConfig modelConfig = modelDefinitionSupport.getConfig(modelKey); + final long windowChars = calibrationRunner.windowChars(modelConfig, promptKey, promptPreparationSupport); + + LOGGER.info(""); + LOGGER.info("Model '{}': loading (window ~{} source chars)...", modelKey, windowChars); + try (AiGenerationProvider provider = providerFactory.create( + config.getGenerationProvider(), + EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport, modelKey), + promptSupport)) { + final AiCalibrationMeasurement m = + calibrationRunner.measure(provider, modelConfig, promptKey, promptPreparationSupport); + + LOGGER.info(String.format( + Locale.ROOT, + "Model '%s': loaded+first-gen ~%.1fs | prefill %.0f tok/s | decode %.0f tok/s | ~%.2f chars/token", + modelKey, + m.loadSeconds(), + m.prefillTokensPerSecond(), + m.decodeTokensPerSecond(), + m.charsPerToken())); + if (m.prefillTokensPerSecond() > 0 && m.midPrefillTokensPerSecond() > 0) { + LOGGER.info(String.format( + Locale.ROOT, + " (mid-window prefill %.0f tok/s vs near-window %.0f tok/s - larger gap => more curvature)", + m.midPrefillTokensPerSecond(), + m.prefillTokensPerSecond())); + } + return new CalibrationReport.ModelMeasurement(modelKey, m); + } catch (final IOException e) { + throw new SrcMorphException("Calibration failed for model '" + modelKey + "': " + e.getMessage(), e); + } + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java new file mode 100644 index 0000000..81044ec --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement; + +/** + * The outcome of one {@link CalibrateEngine#execute()} run: one {@link ModelMeasurement} per distinct + * routed model, in calibration order. + * + *

      {@link #renderXml()} is the pure, paste-ready {@code } XML block renderer — the same + * text the {@code ai-index:calibrate} goal has always printed, extracted here so it is unit-testable + * without a Maven {@code Log} and reusable by any future caller (e.g. a CLI).

      + */ +@ToString +@EqualsAndHashCode +public final class CalibrationReport { + + private final List measurements; + + /** + * Creates a new {@link CalibrationReport}. + * + * @param measurements one measurement per distinct routed model, in calibration order + */ + public CalibrationReport(final List measurements) { + this.measurements = new ArrayList<>(measurements); + } + + /** + * Returns an unmodifiable view of the per-model measurements, in calibration order. + * + * @return the measurements + */ + public List measurements() { + return Collections.unmodifiableList(measurements); + } + + /** + * Renders one paste-ready {@code } XML block per measured model, each preceded by a + * comment naming the model key, in calibration order. + * + * @return the rendered blocks, or an empty string when no model was measured + */ + public String renderXml() { + final StringBuilder out = new StringBuilder(); + for (final ModelMeasurement entry : measurements) { + appendPasteBlock(out, entry.modelKey(), entry.measurement()); + } + return out.toString(); + } + + /** + * Appends a copy-pasteable {@code } block (with a comment naming the model) to the buffer. + * + * @param out the buffer + * @param modelKey the model key (for the comment) + * @param m the measurement + */ + private static void appendPasteBlock( + final StringBuilder out, final String modelKey, final AiCalibrationMeasurement m) { + out.append("\n"); + out.append("\n"); + out.append(String.format( + Locale.ROOT, + " %.1f%n", + m.prefillTokensPerSecond())); + out.append(String.format( + Locale.ROOT, " %.1f%n", m.decodeTokensPerSecond())); + out.append(String.format(Locale.ROOT, " %.2f%n", m.charsPerToken())); + out.append("\n"); + } + + /** One model's key paired with its {@link AiCalibrationMeasurement}. */ + @ToString + @EqualsAndHashCode + public static final class ModelMeasurement { + + private final String modelKey; + private final AiCalibrationMeasurement measurement; + + /** + * Creates a new {@link ModelMeasurement}. + * + * @param modelKey the {@code aiDefinitionKey} that was calibrated + * @param measurement the measurement + */ + public ModelMeasurement(final String modelKey, final AiCalibrationMeasurement measurement) { + this.modelKey = modelKey; + this.measurement = measurement; + } + + /** + * Returns the calibrated model's key. + * + * @return the {@code aiDefinitionKey} + */ + public String modelKey() { + return modelKey; + } + + /** + * Returns the measurement. + * + * @return the calibration measurement + */ + public AiCalibrationMeasurement measurement() { + return measurement; + } + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java new file mode 100644 index 0000000..469e83b --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/EngineSupport.java @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.LlamaCppJniConfig; +import net.ladenthin.srcmorph.provider.LlamaCppJniConfigFactory; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Package-private helpers shared by at least two of the {@code engine} package's per-phase engines. + * Anything used by only one engine stays local to that engine class instead. + */ +final class EngineSupport { + + private static final Logger LOGGER = LoggerFactory.getLogger(EngineSupport.class); + + private EngineSupport() { + // utility class — not instantiable + } + + /** + * Resolves the configured subtree strings against {@code basePath}, filtering out any paths that do + * not exist on disk. Used by {@link GenerateEngine} and {@link AggregatePackagesEngine}. + * + * @param basePath absolute, normalised project base directory + * @param subtrees configured subtree strings, or {@code null}/empty for none + * @return list of resolved, existing subtree paths; empty if none configured or none exist + */ + static List resolveSubtrees(final Path basePath, final @Nullable List subtrees) { + final List resolved = new ArrayList<>(); + + if (subtrees == null || subtrees.isEmpty()) { + return resolved; + } + + for (final String subtree : subtrees) { + final Path path = basePath.resolve(subtree).normalize(); + if (path.toFile().exists()) { + resolved.add(path); + } else { + LOGGER.warn("Skipping missing subtree: {}", path); + } + } + + return resolved; + } + + /** + * Builds an {@link AiPromptSupport} from the configured prompt definitions, translating a + * misconfiguration ({@link NullPointerException} from a missing {@code key}/{@code template}) into + * a {@link SrcMorphException} so the caller reports it as a configuration error. + * + * @param promptDefinitions the configured prompt definitions, or {@code null} for none + * @return prompt support instance backed by the configured definitions + * @throws SrcMorphException if any prompt definition is missing a required field + */ + static AiPromptSupport buildPromptSupport(final @Nullable List promptDefinitions) + throws SrcMorphException { + try { + return new AiPromptSupport(promptDefinitions); + } catch (final NullPointerException e) { + throw new SrcMorphException("Invalid plugin configuration: " + e.getMessage(), e); + } + } + + /** + * Builds an {@link AiModelDefinitionSupport} from the configured AI model definitions, translating a + * misconfiguration ({@link NullPointerException} from a missing {@code key}) into a + * {@link SrcMorphException} so the caller reports it as a configuration error. + * + * @param aiDefinitions the configured AI model definitions, or {@code null} for none + * @return model definition support instance backed by the configured definitions + * @throws SrcMorphException if any AI definition is missing a required field + */ + static AiModelDefinitionSupport buildAiModelDefinitionSupport(final @Nullable List aiDefinitions) + throws SrcMorphException { + try { + return new AiModelDefinitionSupport(aiDefinitions); + } catch (final NullPointerException e) { + throw new SrcMorphException("Invalid plugin configuration: " + e.getMessage(), e); + } + } + + /** + * Resolves the {@link LlamaCppJniConfig} for a run with no specific routed model in mind: when + * {@link SrcMorphConfiguration#getFieldGenerations()} is non-empty, all model parameters come from the + * {@link AiModelDefinition} referenced by the first entry's + * {@link AiFieldGenerationConfig#getAiDefinitionKey()}; otherwise the configuration's individual + * {@code llama*} fallback fields are used. Used by {@link AggregatePackagesEngine} and + * {@link AggregateProjectEngine}, which drive a single provider for the whole run. + * + * @param config the run configuration + * @param modelDefinitionSupport model lookup built from {@link SrcMorphConfiguration#getAiDefinitions()} + * @return the fully populated llama.cpp configuration + * @throws IllegalArgumentException if the first field generation's {@code aiDefinitionKey} matches no + * registered definition + */ + static LlamaCppJniConfig resolveLlamaCppJniConfig( + final SrcMorphConfiguration config, final AiModelDefinitionSupport modelDefinitionSupport) { + final List fieldGenerations = config.getFieldGenerations(); + if (fieldGenerations != null && !fieldGenerations.isEmpty()) { + return resolveLlamaCppJniConfig( + config, modelDefinitionSupport, fieldGenerations.get(0).getAiDefinitionKey()); + } + final String modelPath = config.getLlamaModelPath(); + if (modelPath == null) { + throw new NullPointerException("llamaModelPath"); + } + return LlamaCppJniConfigFactory.fromFallbackParameters( + config.getLlamaLibraryPath(), + modelPath, + config.getLlamaContextSize(), + config.getLlamaMaxOutputTokens(), + config.getLlamaTemperature(), + config.getLlamaThreads()); + } + + /** + * Resolves the {@link LlamaCppJniConfig} for one specific {@link AiModelDefinition}, identified by its + * key. Used by {@link GenerateEngine} (one provider per routing group) and {@link CalibrateEngine} + * (one provider per calibrated model). + * + * @param config the run configuration (supplies {@code llamaLibraryPath}) + * @param modelDefinitionSupport model lookup built from {@link SrcMorphConfiguration#getAiDefinitions()} + * @param aiDefinitionKey the {@link AiModelDefinition} key + * @return the fully populated llama.cpp configuration for that definition + * @throws IllegalArgumentException if {@code aiDefinitionKey} matches no registered definition + */ + static LlamaCppJniConfig resolveLlamaCppJniConfig( + final SrcMorphConfiguration config, + final AiModelDefinitionSupport modelDefinitionSupport, + final String aiDefinitionKey) { + final AiGenerationConfig modelConfig = modelDefinitionSupport.getConfig(aiDefinitionKey); + return LlamaCppJniConfigFactory.fromGenerationConfig(config.getLlamaLibraryPath(), modelConfig); + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateEngine.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateEngine.java new file mode 100644 index 0000000..45ee06d --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateEngine.java @@ -0,0 +1,250 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.ToString; +import net.ladenthin.srcmorph.config.AiFactDefinitionSupport; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiFieldGenerationSelector; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.indexer.AiFieldGenerationSupport; +import net.ladenthin.srcmorph.indexer.AiIndexPlan; +import net.ladenthin.srcmorph.indexer.SourceFileIndexer; +import net.ladenthin.srcmorph.prompt.AiPromptPreparationSupport; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.AiGenerationProvider; +import net.ladenthin.srcmorph.provider.AiGenerationProviderFactory; +import net.ladenthin.srcmorph.support.AiGenerationTimeEstimator; +import net.ladenthin.srcmorph.support.AiProgressBar; +import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Phase 1 orchestration: indexes source files and fills in their AI-generated summary fields. + * + *

      Extracted from what was {@code GenerateMojo.execute()} in the {@code llamacpp-ai-index-maven-plugin} + * module. Plan-then-execute and rule-routed: {@link #execute()} first plans the whole run (which model + + * prompt each file gets, or skip/unmatched, and whether each file fits its routed model's context + * window), fails fast on an unmatched file or a hard oversize failure, stops after the plan when + * {@link SrcMorphConfiguration#isPlanOnly()}, and otherwise loads each distinct routed model exactly once + * (one provider resident at a time) and indexes that group's files.

      + */ +@ToString +public final class GenerateEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(GenerateEngine.class); + + /** + * Default file extension used when {@link SrcMorphConfiguration#getFileExtensions()} is unset. Only + * files whose names end with this extension are indexed. + */ + private static final String DEFAULT_FILE_EXTENSION = ".java"; + + /** Default source subtree used when no {@link SrcMorphConfiguration#getSubtrees()} is configured. */ + private static final String DEFAULT_SOURCE_SUBTREE = "src/main/java"; + + /** Nanoseconds per second, for converting the measured run elapsed time to whole seconds. */ + private static final long NANOS_PER_SECOND = 1_000_000_000L; + + private final SrcMorphConfiguration config; + private final Java8CompatibilityHelper compatibilityHelper = new Java8CompatibilityHelper(); + + /** + * Creates a new {@link GenerateEngine} for the given run configuration. + * + * @param config the run configuration + */ + public GenerateEngine(final SrcMorphConfiguration config) { + this.config = config; + } + + /** + * Runs the full plan-then-execute Phase 1 pipeline. + * + * @return the outcome of the run (a plan-only result when {@link SrcMorphConfiguration#isPlanOnly()}) + * @throws SrcMorphException if the run is misconfigured (no {@code fieldGenerations}, an unmatched + * file with no fallback, a hard oversize failure, or an invalid rule set) + * @throws IOException if the source tree cannot be walked or a target file cannot be written + */ + public GenerateResult execute() throws SrcMorphException, IOException { + final Path basePath = + config.getBaseDirectory().toPath().toAbsolutePath().normalize(); + final Path outputPath = + config.getOutputDirectory().toPath().toAbsolutePath().normalize(); + final List resolvedSubtrees = EngineSupport.resolveSubtrees(basePath, config.getSubtrees()); + final List resolvedExtensions = resolveFileExtensions(); + + LOGGER.info("Starting AI index generation"); + LOGGER.info("Base directory : {}", basePath); + LOGGER.info("Output directory: {}", outputPath); + LOGGER.info("Subtrees : {}", resolvedSubtrees); + if (!resolvedExtensions.isEmpty()) { + LOGGER.info("Extensions : {}", resolvedExtensions); + } + LOGGER.info("Force : {}", config.isForce()); + LOGGER.info("Provider : {}", config.getGenerationProvider()); + LOGGER.info("LlamaCpp Temperature: {}", config.getLlamaTemperature()); + LOGGER.info("LlamaCpp Max Output Tokens: {}", config.getLlamaMaxOutputTokens()); + + final List fieldGenerations = config.getFieldGenerations(); + if (fieldGenerations == null || fieldGenerations.isEmpty()) { + throw new SrcMorphException("No configured for the generate goal."); + } + + final AiPromptSupport promptSupport = EngineSupport.buildPromptSupport(config.getPromptDefinitions()); + final AiModelDefinitionSupport modelDefinitionSupport = + EngineSupport.buildAiModelDefinitionSupport(config.getAiDefinitions()); + final AiPromptPreparationSupport promptPreparationSupport = new AiPromptPreparationSupport(promptSupport); + // Resolve each rule's factsKey to its shared factDefinitions group (copies the counters onto the + // rule's facts) BEFORE validation, so the resolved fact patterns are validated too. + resolveSharedFacts(fieldGenerations); + + final AiFieldGenerationSelector selector = new AiFieldGenerationSelector(); + // Fail fast on a bad rule set (e.g. >1 fallback, a route rule missing prompt/model). + selector.validate(fieldGenerations); + + final SourceFileIndexer fileIndexer = new SourceFileIndexer( + basePath, + outputPath, + resolvedExtensions, + config.getPluginVersion(), + config.getAiVersion(), + resolvedSubtrees, + config.getExcludes(), + config.getMinFileSizeBytes(), + config.getMaxFileSizeBytes(), + config.isForce()); + + // 1. Collect candidate files across the configured subtrees. + final List candidates = new ArrayList<>(); + for (final Path subtree : resolvedSubtrees.isEmpty() + ? compatibilityHelper.listOf(basePath.resolve(DEFAULT_SOURCE_SUBTREE)) + : resolvedSubtrees) { + if (!subtree.toFile().exists()) { + LOGGER.warn("Skipping missing subtree: {}", subtree); + continue; + } + candidates.addAll(fileIndexer.collectCandidates(subtree)); + } + + // 2. Plan the run: which model + prompt each file gets (or skip / unmatched), and whether + // each file fits its routed model's context window (computed up front, same threshold the + // run uses to trim — see AiInputWindowCalculator). + final AiIndexPlan plan = + fileIndexer.classify(candidates, fieldGenerations, modelDefinitionSupport, promptPreparationSupport); + LOGGER.info("AI index plan (Markdown):\n{}", plan.renderMarkdown(basePath)); + + // 3. A file that matched no rule and no fallback is a fatal misconfiguration. + if (!plan.unmatched().isEmpty()) { + throw new SrcMorphException(plan.unmatched().size() + + " source file(s) matched no rule and no fallback is configured; " + + "add a rule or a matching rule (see the plan above)."); + } + + // 3b. A file larger than its routed model's window would lose content if trimmed. By default + // (onOversize=fail) this is a hard failure: the fix is user configuration, never an + // automatic model choice — route oversized files to a larger-context model, or set the + // rule's onOversize (sample/mapReduce/deterministic) to handle them at run time. Only the + // fail entries abort here; the handled ones are processed during generation. + final int overWindowFailCount = plan.windowFailCount(); + if (overWindowFailCount > 0) { + throw new SrcMorphException(overWindowFailCount + + " source file(s) exceed their routed model's context window with onOversize=fail " + + "(see the 'Over window' section in the plan above). Route them to a model with a " + + "large enough context window, or set onOversize=sample|mapReduce|deterministic on " + + "the rule. The build does not pick a model for you; this is configuration only."); + } + + if (config.isPlanOnly()) { + LOGGER.info("planOnly=true: stopping after the plan; no model loaded, nothing generated."); + return GenerateResult.planned(); + } + + // 4. Execute model group by model group: load each model once, index its files, close. + // Progress is the running sum of each finished file's PLAN estimate over the grand total + // (no re-estimation), logged as a bar + percent after every file, with the estimated time + // left and the actual wall-clock elapsed for comparison. + final AiGenerationProviderFactory providerFactory = new AiGenerationProviderFactory(); + final AiGenerationTimeEstimator estimator = new AiGenerationTimeEstimator(); + final long totalEstimatedSeconds = plan.totalEstimatedSeconds(); + final int totalFiles = plan.routedCount(); + final long runStartNanos = System.nanoTime(); + long doneEstimatedSeconds = 0; + int doneFiles = 0; + int wrote = 0; + int unchanged = 0; + for (final Map.Entry> group : + plan.routesByModel().entrySet()) { + final String aiDefinitionKey = group.getKey(); + LOGGER.info( + "Loading model '{}' for {} file(s)", + aiDefinitionKey, + group.getValue().size()); + try (AiGenerationProvider provider = providerFactory.create( + config.getGenerationProvider(), + EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport, aiDefinitionKey), + promptSupport)) { + final AiFieldGenerationSupport support = + new AiFieldGenerationSupport(provider, promptPreparationSupport, modelDefinitionSupport); + for (final AiIndexPlan.Entry entry : group.getValue()) { + if (fileIndexer.indexFile(entry.file(), entry.rule(), support)) { + wrote++; + } else { + unchanged++; + } + doneEstimatedSeconds += entry.estimatedSeconds(); + doneFiles++; + final long elapsedSeconds = (System.nanoTime() - runStartNanos) / NANOS_PER_SECOND; + final long remainingSeconds = Math.max(0L, totalEstimatedSeconds - doneEstimatedSeconds); + LOGGER.info( + "{} {}/{} files - est. {}/{} done, {} left (estimate) | {} elapsed (actual)", + AiProgressBar.render(doneEstimatedSeconds, totalEstimatedSeconds), + doneFiles, + totalFiles, + estimator.formatDuration(doneEstimatedSeconds), + estimator.formatDuration(totalEstimatedSeconds), + estimator.formatDuration(remainingSeconds), + estimator.formatDuration(elapsedSeconds)); + } + } + } + + final int skippedCount = plan.skipped().size(); + LOGGER.info("Generated AI files: {} written, {} unchanged, {} skipped", wrote, unchanged, skippedCount); + LOGGER.info("AI index generation finished."); + + return new GenerateResult(false, wrote, unchanged, skippedCount); + } + + private List resolveFileExtensions() { + final List configured = config.getFileExtensions(); + if (configured == null || configured.isEmpty()) { + return compatibilityHelper.listOf(DEFAULT_FILE_EXTENSION); + } + return configured; + } + + /** + * Resolves each rule's {@code factsKey} to its shared {@code } group, copying the + * counters onto the rule's {@code facts}. Translates a misconfiguration (unknown key, or a definition + * with a null key) into a {@link SrcMorphException} so the caller reports a configuration error. + * + * @param fieldGenerations the routing rules whose {@code factsKey} references are resolved in place + * @throws SrcMorphException if a {@code factsKey} matches no group or a definition has a null key + */ + private void resolveSharedFacts(final List fieldGenerations) throws SrcMorphException { + try { + new AiFactDefinitionSupport(config.getFactDefinitions()).resolveFactsKeys(fieldGenerations); + } catch (final IllegalArgumentException | NullPointerException e) { + throw new SrcMorphException("Invalid factDefinitions/factsKey configuration: " + e.getMessage(), e); + } + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateResult.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateResult.java new file mode 100644 index 0000000..cade778 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/GenerateResult.java @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import net.ladenthin.srcmorph.support.ConvertToRecord; + +/** + * The outcome of one {@link GenerateEngine#execute()} run. + * + *

      Record-shaped value type marked {@link ConvertToRecord} for the future Java 17+ migration.

      + */ +@ConvertToRecord +@ToString +@EqualsAndHashCode +public final class GenerateResult { + + private final boolean planOnly; + private final int written; + private final int unchanged; + private final int skipped; + + /** + * Creates a new {@link GenerateResult}. + * + * @param planOnly {@code true} when the run stopped after printing the routing plan (no model was + * loaded and nothing was generated) + * @param written number of {@code .ai.md} files written or refreshed + * @param unchanged number of routed files left untouched (already up to date) + * @param skipped number of files matched by a {@code } rule + */ + public GenerateResult(final boolean planOnly, final int written, final int unchanged, final int skipped) { + this.planOnly = planOnly; + this.written = written; + this.unchanged = unchanged; + this.skipped = skipped; + } + + /** + * Creates a {@link GenerateResult} for a {@code planOnly} run: the plan was printed and validated, but + * no model was loaded and nothing was generated. + * + * @return a plan-only result with every count at zero + */ + public static GenerateResult planned() { + return new GenerateResult(true, 0, 0, 0); + } + + /** + * Returns whether the run stopped after printing the routing plan. + * + * @return {@code true} when the run was {@code planOnly} + */ + public boolean planOnly() { + return planOnly; + } + + /** + * Returns the number of {@code .ai.md} files written or refreshed. + * + * @return the written count + */ + public int written() { + return written; + } + + /** + * Returns the number of routed files left untouched (already up to date). + * + * @return the unchanged count + */ + public int unchanged() { + return unchanged; + } + + /** + * Returns the number of files matched by a {@code } rule. + * + * @return the skipped count + */ + public int skipped() { + return skipped; + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/SrcMorphException.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/SrcMorphException.java new file mode 100644 index 0000000..4277e78 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/SrcMorphException.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +/** + * Checked exception thrown by the {@code engine} package for a run that fails because of misconfiguration + * (an invalid rule set, an unmatched file with no fallback, an oversized file with {@code onOversize=fail}, + * a bad prompt/model definition, …) rather than an I/O failure. + * + *

      This is the framework-free replacement for {@code org.apache.maven.plugin.MojoExecutionException} + * inside core code: the {@code llamacpp-ai-index-maven-plugin} module's mojos catch this (alongside a + * plain {@link java.io.IOException} for genuine I/O failures) and rewrap it into a + * {@code MojoExecutionException} so Maven still reports the same user-facing error it always has.

      + */ +public class SrcMorphException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Creates a new {@link SrcMorphException} with the given message. + * + * @param message the detail message + */ + public SrcMorphException(final String message) { + super(message); + } + + /** + * Creates a new {@link SrcMorphException} with the given message and cause. + * + * @param message the detail message + * @param cause the underlying cause + */ + public SrcMorphException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/package-info.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/package-info.java new file mode 100644 index 0000000..fbc9705 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/package-info.java @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Per-phase orchestration engines, extracted from the {@code llamacpp-ai-index-maven-plugin} module's + * mojo {@code execute()} bodies so a run can be driven from plain Java (and, eventually, a CLI) without a + * Maven runtime. Sits above every other package in this library. + */ +package net.ladenthin.srcmorph.engine; diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java index dc0a286..f61b423 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/prompt/AiPromptSupport.java @@ -10,6 +10,7 @@ import lombok.ToString; import net.ladenthin.srcmorph.document.AiGenerationRequest; import net.ladenthin.srcmorph.support.Java8CompatibilityHelper; +import org.jspecify.annotations.Nullable; /** Registry of prompt templates that renders prompt strings for AI generation requests. */ @ToString @@ -51,7 +52,7 @@ public final class AiPromptSupport { * @throws NullPointerException if any entry has a {@code null} {@code key} * or {@code null} {@code template} */ - public AiPromptSupport(final List promptDefinitions) { + public AiPromptSupport(final @Nullable List promptDefinitions) { if (promptDefinitions == null) { this.templates = new HashMap<>(compatibilityHelper.hashMapCapacityFor(0)); return; diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java index 7f4fe7f..8867878 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfig.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import net.ladenthin.srcmorph.support.ConvertToRecord; +import org.jspecify.annotations.Nullable; /** * Immutable configuration for the llama.cpp JNI provider. @@ -23,7 +24,7 @@ @ToString @EqualsAndHashCode public final class LlamaCppJniConfig { - private final String libraryPath; + private final @Nullable String libraryPath; private final String modelPath; private final int contextSize; private final int maxOutputTokens; @@ -81,7 +82,7 @@ public final class LlamaCppJniConfig { * @param stopStrings stop strings; may be {@code null} (treated as empty) */ public LlamaCppJniConfig( - String libraryPath, + @Nullable String libraryPath, String modelPath, int contextSize, int maxOutputTokens, @@ -141,7 +142,7 @@ public LlamaCppJniConfig( * * @return native library path, or {@code null} to use the bundled library */ - public String libraryPath() { + public @Nullable String libraryPath() { return libraryPath; } diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactory.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactory.java new file mode 100644 index 0000000..f5a8ed7 --- /dev/null +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactory.java @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.provider; + +import java.util.Collections; +import java.util.List; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import org.jspecify.annotations.Nullable; + +/** + * Pure mapping from a resolved {@link AiGenerationConfig} (or a small set of fallback parameters) to an + * immutable {@link LlamaCppJniConfig}. + * + *

      Extracted from what was {@code AbstractAiIndexMojo.buildLlamaCppJniConfig} in the + * {@code llamacpp-ai-index-maven-plugin} module: that method's model-lookup and + * fieldGenerations-vs-fallback branching now lives in the {@code engine} package's + * {@code EngineSupport}, which calls the two static methods here to do the actual field-by-field + * translation. Both methods are pure — no I/O, no lookups, no thrown exceptions — so they are fully + * unit-testable and are the project's PIT mutation-coverage target for this translation.

      + */ +public final class LlamaCppJniConfigFactory { + + private LlamaCppJniConfigFactory() { + // utility class — not instantiable + } + + /** + * Builds a {@link LlamaCppJniConfig} by copying every field from a resolved + * {@link AiGenerationConfig} (an {@link net.ladenthin.srcmorph.config.AiModelDefinition} looked up by + * key). {@code null} {@link AiGenerationConfig#getStopStrings()} / + * {@link AiGenerationConfig#getDrySequenceBreakers()} are normalised to an empty list. + * + * @param libraryPath native library path; may be {@code null} to use the bundled native library + * @param config the resolved AI model generation config + * @return the fully populated llama.cpp configuration + */ + public static LlamaCppJniConfig fromGenerationConfig( + final @Nullable String libraryPath, final AiGenerationConfig config) { + final List stopStrings = config.getStopStrings(); + final List drySequenceBreakers = config.getDrySequenceBreakers(); + return new LlamaCppJniConfig( + libraryPath, + config.getModelPath(), + config.getContextSize(), + config.getMaxOutputTokens(), + config.getTemperature(), + config.getThreads(), + config.getTopP(), + config.getTopK(), + config.getMinP(), + config.getTopNSigma(), + config.getRepeatPenalty(), + config.isChatTemplateEnableThinking(), + config.isCachePrompt(), + config.isSwaFull(), + config.getCacheReuse(), + config.getGpuLayers(), + config.getMainGpu(), + config.getDevices(), + config.getReasoningEffort(), + config.getReasoningBudgetTokens(), + config.getDryMultiplier(), + config.getDryBase(), + config.getDryAllowedLength(), + config.getDryPenaltyLastN(), + drySequenceBreakers != null ? drySequenceBreakers : Collections.emptyList(), + stopStrings != null ? stopStrings : Collections.emptyList()); + } + + /** + * Builds a {@link LlamaCppJniConfig} from the small set of individual fallback parameters (used when + * no {@code fieldGenerations}/routing rule is configured), applying every other + * {@link AiGenerationConfig} default (sampling, DRY, GPU, …) unchanged. + * + * @param libraryPath native library path; may be {@code null} to use the bundled native library + * @param modelPath path to the GGUF model file + * @param contextSize context window size in tokens + * @param maxOutputTokens maximum number of output tokens per call + * @param temperature sampling temperature + * @param threads number of CPU threads + * @return the fully populated llama.cpp configuration + */ + public static LlamaCppJniConfig fromFallbackParameters( + final @Nullable String libraryPath, + final String modelPath, + final int contextSize, + final int maxOutputTokens, + final float temperature, + final int threads) { + return new LlamaCppJniConfig( + libraryPath, + modelPath, + contextSize, + maxOutputTokens, + temperature, + threads, + AiGenerationConfig.DEFAULT_TOP_P, + AiGenerationConfig.DEFAULT_TOP_K, + AiGenerationConfig.DEFAULT_MIN_P, + AiGenerationConfig.DEFAULT_TOP_N_SIGMA, + AiGenerationConfig.DEFAULT_REPEAT_PENALTY, + AiGenerationConfig.DEFAULT_CHAT_TEMPLATE_ENABLE_THINKING, + AiGenerationConfig.DEFAULT_CACHE_PROMPT, + AiGenerationConfig.DEFAULT_SWA_FULL, + AiGenerationConfig.DEFAULT_CACHE_REUSE, + AiGenerationConfig.DEFAULT_GPU_LAYERS, + AiGenerationConfig.DEFAULT_MAIN_GPU, + AiGenerationConfig.DEFAULT_DEVICES, + AiGenerationConfig.DEFAULT_REASONING_EFFORT, + AiGenerationConfig.DEFAULT_REASONING_BUDGET_TOKENS, + AiGenerationConfig.DEFAULT_DRY_MULTIPLIER, + AiGenerationConfig.DEFAULT_DRY_BASE, + AiGenerationConfig.DEFAULT_DRY_ALLOWED_LENGTH, + AiGenerationConfig.DEFAULT_DRY_PENALTY_LAST_N, + Collections.emptyList(), + Collections.emptyList()); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java index cbecf09..48f8818 100644 --- a/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/CoreArchitectureTest.java @@ -94,13 +94,25 @@ public class CoreArchitectureTest { * extraction {@code PluginArchitectureTest} layering with the {@code Mojo} layer removed (the * mojo package lives in the sibling plugin module and is outside this test's analyzed * classes; consideringOnlyDependenciesInLayers() means its former accesses to Indexer/ - * Provider/Prompt/Config/Support are simply not part of this in-module rule anymore). - * Conceptual tiers: {@code Indexer} > {@code Provider} > {@code Document}/{@code Prompt} - * > {@code Config}/{@code Support}. + * Provider/Prompt/Config/Support are simply not part of this in-module rule anymore) — and with + * a new {@code Engine} layer on top, added for the per-phase orchestration engines (migration + * step 5): {@code engine} depends on {@code indexer} (the file/package/project indexers), + * {@code provider} (constructing/closing an {@code AiGenerationProvider} directly), + * {@code prompt} ({@code AiPromptSupport}/{@code AiPromptPreparationSupport}), {@code config} + * (the shared {@code SrcMorphConfiguration} plus the routing/model-lookup types), and + * {@code support} (the progress bar / time estimator / Java 8 compatibility helpers). The new + * {@code provider.LlamaCppJniConfigFactory} also introduced a {@code Provider -> Config} edge + * (it maps a {@code config.AiGenerationConfig} to a {@code provider.LlamaCppJniConfig}), and the + * new root {@code config.SrcMorphConfiguration} introduced a {@code Config -> Prompt} edge (it + * holds a {@code List}). Conceptual tiers: {@code Engine} > + * {@code Indexer} > {@code Provider} > {@code Document}/{@code Prompt} > + * {@code Config}/{@code Support}. */ @ArchTest static final ArchRule layeredArchitecture = layeredArchitecture() .consideringOnlyDependenciesInLayers() + .layer("Engine") + .definedBy("net.ladenthin.srcmorph.engine..") .layer("Indexer") .definedBy("net.ladenthin.srcmorph.indexer..") .layer("Provider") @@ -113,18 +125,20 @@ public class CoreArchitectureTest { .definedBy("net.ladenthin.srcmorph.config..") .layer("Support") .definedBy("net.ladenthin.srcmorph.support..") - .whereLayer("Indexer") + .whereLayer("Engine") .mayNotBeAccessedByAnyLayer() + .whereLayer("Indexer") + .mayOnlyBeAccessedByLayers("Engine") .whereLayer("Provider") - .mayOnlyBeAccessedByLayers("Indexer") + .mayOnlyBeAccessedByLayers("Indexer", "Engine") .whereLayer("Document") .mayOnlyBeAccessedByLayers("Indexer", "Prompt", "Provider") .whereLayer("Prompt") - .mayOnlyBeAccessedByLayers("Indexer", "Provider") + .mayOnlyBeAccessedByLayers("Indexer", "Provider", "Engine", "Config") .whereLayer("Config") - .mayOnlyBeAccessedByLayers("Indexer") + .mayOnlyBeAccessedByLayers("Indexer", "Engine", "Provider") .whereLayer("Support") - .mayOnlyBeAccessedByLayers("Config", "Document", "Indexer", "Prompt", "Provider"); + .mayOnlyBeAccessedByLayers("Config", "Document", "Indexer", "Prompt", "Provider", "Engine"); /** * Public mutable state forbidden: any non-static field declared diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/config/SrcMorphConfigurationTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/SrcMorphConfigurationTest.java new file mode 100644 index 0000000..bdcc32f --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/config/SrcMorphConfigurationTest.java @@ -0,0 +1,231 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.config; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.File; +import java.util.Arrays; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import org.junit.jupiter.api.Test; + +public class SrcMorphConfigurationTest { + + @Test + public void defaults_matchDocumentedConstants() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + + assertThat(c.getOutputDirectory(), is(new File(SrcMorphConfiguration.DEFAULT_OUTPUT_DIRECTORY))); + assertThat(c.isForce(), is(false)); + assertThat(c.isPlanOnly(), is(false)); + assertThat(c.getGenerationProvider(), is(SrcMorphConfiguration.DEFAULT_GENERATION_PROVIDER)); + assertThat(c.getLlamaContextSize(), is(SrcMorphConfiguration.DEFAULT_LLAMA_CONTEXT_SIZE)); + assertThat(c.getLlamaMaxOutputTokens(), is(SrcMorphConfiguration.DEFAULT_LLAMA_MAX_OUTPUT_TOKENS)); + assertThat(c.getLlamaTemperature(), is(SrcMorphConfiguration.DEFAULT_LLAMA_TEMPERATURE)); + assertThat(c.getLlamaThreads(), is(SrcMorphConfiguration.DEFAULT_LLAMA_THREADS)); + assertThat(c.getAiVersion(), is(SrcMorphConfiguration.DEFAULT_AI_VERSION)); + assertThat(c.getMinFileSizeBytes(), is(0L)); + assertThat(c.getMaxFileSizeBytes(), is(0L)); + assertThat(c.getPluginVersion(), is("")); + } + + @Test + public void nullableCollectionsAndStringsAreNullByDefault() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + + assertThat(c.getSubtrees(), is(nullValue())); + assertThat(c.getExcludes(), is(nullValue())); + assertThat(c.getFileExtensions(), is(nullValue())); + assertThat(c.getPromptDefinitions(), is(nullValue())); + assertThat(c.getAiDefinitions(), is(nullValue())); + assertThat(c.getFieldGenerations(), is(nullValue())); + assertThat(c.getFactDefinitions(), is(nullValue())); + assertThat(c.getLlamaLibraryPath(), is(nullValue())); + assertThat(c.getLlamaModelPath(), is(nullValue())); + assertThat(c.getProjectName(), is(nullValue())); + } + + @Test + public void baseDirectoryRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final File dir = new File("/tmp/base"); + c.setBaseDirectory(dir); + assertThat(c.getBaseDirectory(), is(sameInstance(dir))); + } + + @Test + public void outputDirectoryRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final File dir = new File("/tmp/out"); + c.setOutputDirectory(dir); + assertThat(c.getOutputDirectory(), is(sameInstance(dir))); + } + + @Test + public void forceRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setForce(true); + assertThat(c.isForce(), is(true)); + } + + @Test + public void planOnlyRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setPlanOnly(true); + assertThat(c.isPlanOnly(), is(true)); + } + + @Test + public void subtreesRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setSubtrees(Arrays.asList("src/main/java/a")); + assertThat(c.getSubtrees(), hasItem("src/main/java/a")); + } + + @Test + public void excludesRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setExcludes(Arrays.asList("**/generated/**")); + assertThat(c.getExcludes(), hasItem("**/generated/**")); + } + + @Test + public void fileExtensionsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setFileExtensions(Arrays.asList(".kt")); + assertThat(c.getFileExtensions(), hasItem(".kt")); + } + + @Test + public void minFileSizeBytesRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setMinFileSizeBytes(123L); + assertThat(c.getMinFileSizeBytes(), is(123L)); + } + + @Test + public void maxFileSizeBytesRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setMaxFileSizeBytes(456L); + assertThat(c.getMaxFileSizeBytes(), is(456L)); + } + + @Test + public void generationProviderRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setGenerationProvider("llamacpp-jni"); + assertThat(c.getGenerationProvider(), is("llamacpp-jni")); + } + + @Test + public void promptDefinitionsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final AiPromptDefinition p = new AiPromptDefinition(); + p.setKey("k"); + p.setTemplate("t"); + c.setPromptDefinitions(Arrays.asList(p)); + assertThat(c.getPromptDefinitions(), hasItem(p)); + } + + @Test + public void aiDefinitionsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final AiModelDefinition m = new AiModelDefinition(); + m.setKey("m"); + c.setAiDefinitions(Arrays.asList(m)); + assertThat(c.getAiDefinitions(), hasItem(m)); + } + + @Test + public void fieldGenerationsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final AiFieldGenerationConfig f = new AiFieldGenerationConfig(); + f.setPromptKey("p"); + c.setFieldGenerations(Arrays.asList(f)); + assertThat(c.getFieldGenerations(), hasItem(f)); + } + + @Test + public void factDefinitionsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + final AiFactDefinition fd = new AiFactDefinition(); + fd.setKey("fd"); + c.setFactDefinitions(Arrays.asList(fd)); + assertThat(c.getFactDefinitions(), hasItem(fd)); + } + + @Test + public void llamaLibraryPathRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaLibraryPath("/opt/lib/libjllama.so"); + assertThat(c.getLlamaLibraryPath(), is("/opt/lib/libjllama.so")); + } + + @Test + public void llamaModelPathRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaModelPath("model.gguf"); + assertThat(c.getLlamaModelPath(), is("model.gguf")); + } + + @Test + public void llamaContextSizeRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaContextSize(8192); + assertThat(c.getLlamaContextSize(), is(8192)); + } + + @Test + public void llamaMaxOutputTokensRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaMaxOutputTokens(4096); + assertThat(c.getLlamaMaxOutputTokens(), is(4096)); + } + + @Test + public void llamaTemperatureRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaTemperature(0.9f); + assertThat(c.getLlamaTemperature(), is(0.9f)); + } + + @Test + public void llamaThreadsRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setLlamaThreads(16); + assertThat(c.getLlamaThreads(), is(16)); + } + + @Test + public void pluginVersionRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setPluginVersion("1.2.3"); + assertThat(c.getPluginVersion(), is("1.2.3")); + } + + @Test + public void aiVersionRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setAiVersion("2.0.0"); + assertThat(c.getAiVersion(), is("2.0.0")); + } + + @Test + public void projectNameRoundTrips() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setProjectName("my-project"); + assertThat(c.getProjectName(), is("my-project")); + } + + @Test + public void toStringContainsDistinguishingField() { + final SrcMorphConfiguration c = new SrcMorphConfiguration(); + c.setGenerationProvider("llamacpp-jni"); + assertThat(c.toString(), org.hamcrest.CoreMatchers.containsString("llamacpp-jni")); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngineTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngineTest.java new file mode 100644 index 0000000..5069532 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregatePackagesEngineTest.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class AggregatePackagesEngineTest { + + @TempDir + Path tempDir; + + private final AiMdDocumentCodec documentCodec = new AiMdDocumentCodec(); + + /** + * A model definition with a (dummy, never loaded) {@code modelPath} set: even with the mock + * provider, the engine always builds a + * {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} value object, which requires a + * non-null model path. + */ + private static AiModelDefinition mockModelDefinition() { + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + definition.setModelPath("mock.gguf"); + definition.setCharsPerToken(0); + return definition; + } + + @Test + public void execute_missingOutputDirectoryReturnsZeroWithoutError() throws Exception { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setBaseDirectory(tempDir.toFile()); + config.setOutputDirectory(tempDir.resolve("out-does-not-exist").toFile()); + config.setGenerationProvider("mock"); + + final int aggregated = new AggregatePackagesEngine(config).execute(); + + assertThat(aggregated, is(0)); + } + + @Test + public void execute_aggregatesOneChildIntoPackageAiMd() throws Exception { + final Path outputRoot = tempDir.resolve("ai"); + final Path packageDirectory = outputRoot.resolve("com/example"); + final Path childAiFile = packageDirectory.resolve("Test.java.ai.md"); + Files.createDirectories(packageDirectory); + + final AiMdHeader childHeader = new AiMdHeader( + "Test.java", + AiMdHeaderCodec.HEADER_VERSION_1_0, + "AAAAAAAA", + "2026-03-16T00:00:00Z", + "2026-03-16T00:00:10Z", + "1.0.0", + "0.0.0", + AiMdHeaderCodec.NODE_TYPE_FILE); + documentCodec.write(childAiFile, new AiMdDocument(childHeader, "")); + + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setBaseDirectory(tempDir.toFile()); + config.setOutputDirectory(outputRoot.toFile()); + config.setGenerationProvider("mock"); + config.setPluginVersion("1.0.0"); + config.setAiVersion("0.0.0"); + config.setPromptDefinitions(CommonTestFixtures.createPackagePromptDefinitions()); + config.setAiDefinitions(Collections.singletonList(mockModelDefinition())); + config.setFieldGenerations(CommonTestFixtures.createPackageFieldGenerations()); + + final int aggregated = new AggregatePackagesEngine(config).execute(); + + // One package.ai.md per directory from the output root down to the child's own package + // (root "ai/", "com/", and "com/example/"), matching PackageIndexer's recursive aggregation. + assertThat(aggregated, is(equalTo(3))); + assertThat(Files.exists(packageDirectory.resolve(AiMdHeaderCodec.PACKAGE_AI_MD_FILENAME)), is(true)); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregateProjectEngineTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregateProjectEngineTest.java new file mode 100644 index 0000000..6f2970c --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/AggregateProjectEngineTest.java @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.document.AiMdDocument; +import net.ladenthin.srcmorph.document.AiMdDocumentCodec; +import net.ladenthin.srcmorph.document.AiMdHeader; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class AggregateProjectEngineTest { + + @TempDir + Path tempDir; + + private final AiMdDocumentCodec documentCodec = new AiMdDocumentCodec(); + + /** + * A model definition with a (dummy, never loaded) {@code modelPath} set: even with the mock + * provider, the engine always builds a + * {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} value object, which requires a + * non-null model path. + */ + private static AiModelDefinition mockModelDefinition() { + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + definition.setModelPath("mock.gguf"); + definition.setCharsPerToken(0); + return definition; + } + + private void writePackageFile(final Path packageFile, final String title, final String body) throws Exception { + Files.createDirectories(packageFile.getParent()); + final AiMdHeader header = new AiMdHeader( + title, + AiMdHeaderCodec.HEADER_VERSION_1_0, + "AAAAAAAA", + "2026-03-16T00:00:00Z", + "2026-03-16T00:00:10Z", + "1.0.0", + "0.0.0", + AiMdHeaderCodec.NODE_TYPE_PACKAGE); + documentCodec.write(packageFile, new AiMdDocument(header, body)); + } + + @Test + public void execute_missingOutputDirectoryReturnsZero() throws Exception { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setOutputDirectory(tempDir.resolve("out-does-not-exist").toFile()); + + assertThat(new AggregateProjectEngine(config).execute(), is(0)); + } + + @Test + public void execute_deterministicNoOverview_writesProjectIndex() throws Exception { + final Path outputRoot = tempDir.resolve("ai"); + writePackageFile( + outputRoot.resolve("com/example/package.ai.md"), "com/example", "> Handles the example domain.\n"); + + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setOutputDirectory(outputRoot.toFile()); + config.setPluginVersion("1.0.0"); + config.setAiVersion("0.0.0"); + config.setProjectName("my-project"); + + final int written = new AggregateProjectEngine(config).execute(); + + assertThat(written, is(equalTo(1))); + final Path projectFile = outputRoot.resolve(AiMdHeaderCodec.PROJECT_AI_MD_FILENAME); + assertThat(Files.exists(projectFile), is(true)); + final AiMdDocument document = documentCodec.read(projectFile); + assertThat(document.header().title(), is(equalTo("my-project"))); + assertThat(document.body(), containsString("Handles the example domain.")); + } + + @Test + public void execute_blankProjectNameFallsBackToDefaultTitle() throws Exception { + final Path outputRoot = tempDir.resolve("ai"); + writePackageFile(outputRoot.resolve("com/example/package.ai.md"), "com/example", "> Lead.\n"); + + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setOutputDirectory(outputRoot.toFile()); + config.setPluginVersion("1.0.0"); + config.setAiVersion("0.0.0"); + config.setProjectName(" "); + + new AggregateProjectEngine(config).execute(); + + final AiMdDocument document = documentCodec.read(outputRoot.resolve(AiMdHeaderCodec.PROJECT_AI_MD_FILENAME)); + assertThat(document.header().title(), is(equalTo("project"))); + } + + @Test + public void execute_withOverviewFieldGeneration_generatesOverviewSection() throws Exception { + final Path outputRoot = tempDir.resolve("ai"); + writePackageFile(outputRoot.resolve("com/example/package.ai.md"), "com/example", "> Handles billing.\n"); + + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setOutputDirectory(outputRoot.toFile()); + config.setPluginVersion("1.0.0"); + config.setAiVersion("0.0.0"); + config.setProjectName("my-project"); + config.setGenerationProvider("mock"); + config.setPromptDefinitions(CommonTestFixtures.createPackagePromptDefinitions()); + config.setAiDefinitions(Collections.singletonList(mockModelDefinition())); + final AiFieldGenerationConfig overview = new AiFieldGenerationConfig(); + overview.setPromptKey(CommonTestFixtures.PROMPT_KEY_PACKAGE_BODY); + overview.setAiDefinitionKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + config.setFieldGenerations(Collections.singletonList(overview)); + + new AggregateProjectEngine(config).execute(); + + final AiMdDocument document = documentCodec.read(outputRoot.resolve(AiMdHeaderCodec.PROJECT_AI_MD_FILENAME)); + assertThat(document.body(), containsString("#### Overview")); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrateEngineTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrateEngineTest.java new file mode 100644 index 0000000..a12fdc9 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrateEngineTest.java @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Collections; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import org.junit.jupiter.api.Test; + +public class CalibrateEngineTest { + + /** + * A model definition with a (dummy, never loaded) {@code modelPath} set: even with the mock + * provider, the engine always builds a + * {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} value object, which requires a + * non-null model path. + */ + private static AiModelDefinition mockModelDefinition() { + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + definition.setModelPath("mock.gguf"); + definition.setCharsPerToken(0); + return definition; + } + + private SrcMorphConfiguration baseConfig() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setGenerationProvider("mock"); + config.setPromptDefinitions(CommonTestFixtures.createFilePromptDefinitions()); + config.setAiDefinitions(Collections.singletonList(mockModelDefinition())); + return config; + } + + @Test + public void execute_missingFieldGenerationsThrowsSrcMorphException() { + final SrcMorphConfiguration config = baseConfig(); + config.setFieldGenerations(null); + + final SrcMorphException e = assertThrows(SrcMorphException.class, () -> new CalibrateEngine(config).execute()); + assertThat(e.getMessage(), containsString("nothing to calibrate")); + } + + @Test + public void execute_noRoutableRuleThrowsSrcMorphException() { + final SrcMorphConfiguration config = baseConfig(); + // A rule with neither promptKey nor aiDefinitionKey set is not routable. + config.setFieldGenerations(Collections.singletonList(new AiFieldGenerationConfig())); + + final SrcMorphException e = assertThrows(SrcMorphException.class, () -> new CalibrateEngine(config).execute()); + assertThat(e.getMessage(), containsString("No routable")); + } + + @Test + public void execute_calibratesTheRoutedModelAndRendersXml() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + config.setFieldGenerations(CommonTestFixtures.createFileFieldGenerations()); + + final CalibrationReport report = new CalibrateEngine(config).execute(); + + assertThat(report.measurements().size(), is(1)); + final CalibrationReport.ModelMeasurement measurement = + report.measurements().get(0); + assertThat(measurement.modelKey(), is(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT)); + // The mock provider reports fixed synthetic throughput; the engine surfaces it unchanged. + assertThat(measurement.measurement().prefillTokensPerSecond(), is(1000.0d)); + assertThat(measurement.measurement().decodeTokensPerSecond(), is(100.0d)); + + final String xml = report.renderXml(); + assertThat( + xml, containsString("calibration for aiDefinition '" + CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT)); + assertThat(xml, containsString("1000.0")); + } + + @Test + public void execute_dedupesMultipleRulesRoutedToTheSameModel() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + final AiFieldGenerationConfig ruleA = new AiFieldGenerationConfig(); + ruleA.setPromptKey(CommonTestFixtures.PROMPT_KEY_FILE_BODY); + ruleA.setAiDefinitionKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + final AiFieldGenerationConfig ruleB = new AiFieldGenerationConfig(); + ruleB.setPromptKey(CommonTestFixtures.PROMPT_KEY_FILE_BODY); + ruleB.setAiDefinitionKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + config.setFieldGenerations(java.util.Arrays.asList(ruleA, ruleB)); + + final CalibrationReport report = new CalibrateEngine(config).execute(); + + assertThat(report.measurements().size(), is(1)); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java new file mode 100644 index 0000000..dafff90 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/CalibrationReportTest.java @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import net.ladenthin.srcmorph.indexer.AiCalibrationMeasurement; +import org.junit.jupiter.api.Test; + +public class CalibrationReportTest { + + @Test + public void modelMeasurement_accessorsReflectConstructorArguments() { + final AiCalibrationMeasurement measurement = new AiCalibrationMeasurement(1.5d, 100.0d, 50.0d, 4.0d, 90.0d); + final CalibrationReport.ModelMeasurement m = new CalibrationReport.ModelMeasurement("my-model", measurement); + + assertThat(m.modelKey(), is("my-model")); + assertThat(m.measurement(), is(measurement)); + } + + @Test + public void measurements_returnsDefensiveCopyInOrder() { + final CalibrationReport.ModelMeasurement m1 = + new CalibrationReport.ModelMeasurement("a", new AiCalibrationMeasurement(1.0d, 1.0d, 1.0d, 1.0d, 1.0d)); + final CalibrationReport.ModelMeasurement m2 = + new CalibrationReport.ModelMeasurement("b", new AiCalibrationMeasurement(2.0d, 2.0d, 2.0d, 2.0d, 2.0d)); + final List source = new ArrayList<>(Arrays.asList(m1, m2)); + final CalibrationReport report = new CalibrationReport(source); + + // Mutating the source list after construction must not affect the report (defensive copy). + source.clear(); + + assertThat(report.measurements(), is(equalTo(Arrays.asList(m1, m2)))); + } + + @Test + public void measurements_isUnmodifiable() { + final CalibrationReport report = new CalibrationReport(new ArrayList()); + try { + report.measurements() + .add(new CalibrationReport.ModelMeasurement( + "x", new AiCalibrationMeasurement(0.0d, 0.0d, 0.0d, 0.0d, 0.0d))); + org.junit.jupiter.api.Assertions.fail("expected UnsupportedOperationException"); + } catch (final UnsupportedOperationException expected) { + // expected: measurements() must not allow external mutation + } + } + + @Test + public void renderXml_isEmptyForNoMeasurements() { + final CalibrationReport report = new CalibrationReport(new ArrayList()); + assertThat(report.renderXml(), is("")); + } + + @Test + public void renderXml_rendersOnePasteBlockPerModelInOrder() { + final AiCalibrationMeasurement measurementA = + new AiCalibrationMeasurement(1.234d, 1000.5d, 200.3d, 4.57d, 900.0d); + final AiCalibrationMeasurement measurementB = new AiCalibrationMeasurement(2.0d, 500.0d, 100.0d, 3.0d, 450.0d); + final CalibrationReport report = new CalibrationReport(Arrays.asList( + new CalibrationReport.ModelMeasurement("model-a", measurementA), + new CalibrationReport.ModelMeasurement("model-b", measurementB))); + + final String expected = "\n" + + "\n" + + " 1000.5\n" + + " 200.3\n" + + " 4.57\n" + + "\n" + + "\n" + + "\n" + + " 500.0\n" + + " 100.0\n" + + " 3.00\n" + + "\n"; + + assertThat(report.renderXml(), is(expected)); + } + + @Test + public void toString_containsModelKey() { + final CalibrationReport report = new CalibrationReport(Arrays.asList(new CalibrationReport.ModelMeasurement( + "unique-model-key", new AiCalibrationMeasurement(0.0d, 0.0d, 0.0d, 0.0d, 0.0d)))); + assertThat(report.toString(), org.hamcrest.CoreMatchers.containsString("unique-model-key")); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/EngineSupportTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/EngineSupportTest.java new file mode 100644 index 0000000..a3dce2b --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/EngineSupportTest.java @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.AiModelDefinitionSupport; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import net.ladenthin.srcmorph.prompt.AiPromptSupport; +import net.ladenthin.srcmorph.provider.LlamaCppJniConfig; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class EngineSupportTest { + + @TempDir + Path tempDir; + + // + @Test + public void resolveSubtrees_nullReturnsEmpty() { + assertThat(EngineSupport.resolveSubtrees(tempDir, null), is(Collections.emptyList())); + } + + @Test + public void resolveSubtrees_emptyReturnsEmpty() { + assertThat( + EngineSupport.resolveSubtrees(tempDir, Collections.emptyList()), + is(Collections.emptyList())); + } + + @Test + public void resolveSubtrees_earlyReturnResultIsMutable() { + // The early-return (null/empty subtrees) branch returns the same mutable ArrayList as the + // loop-completion branch, not an immutable Collections.emptyList(), so callers can treat every + // result uniformly. Mutating it here must not throw UnsupportedOperationException. + final List resolved = EngineSupport.resolveSubtrees(tempDir, null); + resolved.add(tempDir); + assertThat(resolved, hasItem(tempDir)); + } + + @Test + public void resolveSubtrees_existingSubtreeIsResolved() throws IOException { + final Path sub = tempDir.resolve("exists"); + Files.createDirectories(sub); + + final List resolved = EngineSupport.resolveSubtrees(tempDir, Arrays.asList("exists")); + + assertThat(resolved, hasItem(sub.normalize())); + } + + @Test + public void resolveSubtrees_missingSubtreeIsSkipped() { + final List resolved = EngineSupport.resolveSubtrees(tempDir, Arrays.asList("does-not-exist")); + + assertThat(resolved, is(Collections.emptyList())); + } + // + + // + @Test + public void buildPromptSupport_nullListBuildsEmptySupport() throws SrcMorphException { + final AiPromptSupport support = EngineSupport.buildPromptSupport(null); + assertThat(support, is(notNullValue())); + } + + @Test + public void buildPromptSupport_missingKeyThrowsSrcMorphException() { + final AiPromptDefinition bad = new AiPromptDefinition(); + bad.setTemplate("template with no key"); + + final SrcMorphException e = + assertThrows(SrcMorphException.class, () -> EngineSupport.buildPromptSupport(Arrays.asList(bad))); + assertThat(e.getMessage(), containsString("Invalid plugin configuration:")); + } + // + + // + @Test + public void buildAiModelDefinitionSupport_nullListBuildsEmptySupport() throws SrcMorphException { + assertThat(EngineSupport.buildAiModelDefinitionSupport(null), is(notNullValue())); + } + + @Test + public void buildAiModelDefinitionSupport_missingKeyThrowsSrcMorphException() { + final AiModelDefinition bad = new AiModelDefinition(); + bad.setModelPath("model.gguf"); + + final SrcMorphException e = assertThrows( + SrcMorphException.class, () -> EngineSupport.buildAiModelDefinitionSupport(Arrays.asList(bad))); + assertThat(e.getMessage(), containsString("Invalid plugin configuration:")); + } + // + + // + @Test + public void resolveLlamaCppJniConfig_noKey_usesFallbackWhenNoFieldGenerations() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setLlamaModelPath("fallback.gguf"); + config.setLlamaContextSize(2048); + config.setLlamaMaxOutputTokens(64); + config.setLlamaTemperature(0.2f); + config.setLlamaThreads(3); + + final AiModelDefinitionSupport modelDefinitionSupport = new AiModelDefinitionSupport(null); + final LlamaCppJniConfig result = EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport); + + assertThat(result.modelPath(), is("fallback.gguf")); + assertThat(result.contextSize(), is(2048)); + assertThat(result.maxOutputTokens(), is(64)); + assertThat(result.temperature(), is(0.2f)); + assertThat(result.threads(), is(3)); + } + + @Test + public void resolveLlamaCppJniConfig_noKey_usesFirstFieldGenerationsEntryWhenPresent() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + final AiFieldGenerationConfig rule = new AiFieldGenerationConfig(); + rule.setPromptKey("prompt"); + rule.setAiDefinitionKey("routed-model"); + config.setFieldGenerations(Arrays.asList(rule)); + + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey("routed-model"); + definition.setModelPath("routed.gguf"); + final AiModelDefinitionSupport modelDefinitionSupport = new AiModelDefinitionSupport(Arrays.asList(definition)); + + final LlamaCppJniConfig result = EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport); + + assertThat(result.modelPath(), is("routed.gguf")); + } + + @Test + public void resolveLlamaCppJniConfig_byKey_looksUpNamedDefinition() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setLlamaLibraryPath("/opt/lib"); + + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey("named"); + definition.setModelPath("named.gguf"); + final AiModelDefinitionSupport modelDefinitionSupport = new AiModelDefinitionSupport(Arrays.asList(definition)); + + final LlamaCppJniConfig result = + EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport, "named"); + + assertThat(result.libraryPath(), is("/opt/lib")); + assertThat(result.modelPath(), is("named.gguf")); + } + + @Test + public void resolveLlamaCppJniConfig_byKey_missingKeyThrowsIllegalArgumentException() { + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + final AiModelDefinitionSupport modelDefinitionSupport = new AiModelDefinitionSupport(null); + + assertThrows( + IllegalArgumentException.class, + () -> EngineSupport.resolveLlamaCppJniConfig(config, modelDefinitionSupport, "missing")); + } + // +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateEngineTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateEngineTest.java new file mode 100644 index 0000000..b1f5232 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateEngineTest.java @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import net.ladenthin.srcmorph.CommonTestFixtures; +import net.ladenthin.srcmorph.config.AiCondition; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class GenerateEngineTest { + + @TempDir + Path tempDir; + + /** + * A routable rule matching {@code .java} files, with the {@code default} AI definition key. Unlike + * {@link CommonTestFixtures#createFileFieldGenerations()} (whose rule carries no condition — fine for + * the indexer-level tests that never call {@link net.ladenthin.srcmorph.config.AiFieldGenerationSelector#validate}), + * {@link GenerateEngine#execute()} does call {@code validate}, so the rule here needs a real condition. + */ + private static AiFieldGenerationConfig javaFileRule() { + final AiFieldGenerationConfig rule = new AiFieldGenerationConfig(); + rule.setPromptKey(CommonTestFixtures.PROMPT_KEY_FILE_BODY); + rule.setAiDefinitionKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + final AiCondition condition = new AiCondition(); + condition.setExtensions(Arrays.asList(".java")); + rule.setCondition(condition); + return rule; + } + + /** + * A model definition with a (dummy, never loaded) {@code modelPath} set: even with the mock provider, + * the engine always builds a {@link net.ladenthin.srcmorph.provider.LlamaCppJniConfig} value object + * before dispatching to {@link net.ladenthin.srcmorph.provider.AiGenerationProviderFactory}, and that + * value object requires a non-null model path. + */ + private static AiModelDefinition mockModelDefinition() { + final AiModelDefinition definition = new AiModelDefinition(); + definition.setKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + definition.setModelPath("mock.gguf"); + // Disable automatic maxInputChars calculation so the 13-byte test source never trips the + // oversize path in the "happy path" tests below. + definition.setCharsPerToken(0); + return definition; + } + + private SrcMorphConfiguration baseConfig() throws IOException { + final Path sourceRoot = tempDir.resolve("src/main/java"); + Files.createDirectories(sourceRoot.resolve("com/example")); + Files.write(sourceRoot.resolve("com/example/Foo.java"), "class Foo {}\n".getBytes(StandardCharsets.UTF_8)); + + final SrcMorphConfiguration config = new SrcMorphConfiguration(); + config.setBaseDirectory(tempDir.toFile()); + config.setOutputDirectory(tempDir.resolve("out").toFile()); + config.setGenerationProvider("mock"); + config.setPluginVersion("1.0.0"); + config.setAiVersion("0.0.0"); + config.setPromptDefinitions(CommonTestFixtures.createFilePromptDefinitions()); + config.setAiDefinitions(Collections.singletonList(mockModelDefinition())); + config.setFieldGenerations(Collections.singletonList(javaFileRule())); + return config; + } + + @Test + public void execute_missingFieldGenerationsThrowsSrcMorphException() throws IOException { + final SrcMorphConfiguration config = baseConfig(); + config.setFieldGenerations(null); + + final SrcMorphException e = assertThrows(SrcMorphException.class, () -> new GenerateEngine(config).execute()); + assertThat(e.getMessage(), containsString("No configured")); + } + + @Test + public void execute_planOnlyStopsBeforeGenerating() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + config.setPlanOnly(true); + + final GenerateResult result = new GenerateEngine(config).execute(); + + assertThat(result.planOnly(), is(true)); + assertThat(result.written(), is(0)); + assertThat(Files.exists(tempDir.resolve("out")), is(false)); + } + + @Test + public void execute_writesMatchedFileAndReportsCounts() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + + final GenerateResult result = new GenerateEngine(config).execute(); + + assertThat(result.planOnly(), is(false)); + assertThat(result.written(), is(1)); + assertThat(result.unchanged(), is(0)); + assertThat(result.skipped(), is(0)); + // SourceFileIndexer relativises against the "src" root but keeps the "main/java/..." tail. + assertThat(Files.exists(tempDir.resolve("out/main/java/com/example/Foo.java.ai.md")), is(true)); + } + + @Test + public void execute_secondRunWithoutForceReportsUnchanged() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + new GenerateEngine(config).execute(); + + final GenerateResult second = new GenerateEngine(config).execute(); + + assertThat(second.written(), is(0)); + assertThat(second.unchanged(), is(1)); + } + + @Test + public void execute_unmatchedFileWithNoFallbackThrowsSrcMorphException() throws IOException { + final SrcMorphConfiguration config = baseConfig(); + final AiFieldGenerationConfig onlyMatchesTxt = new AiFieldGenerationConfig(); + onlyMatchesTxt.setPromptKey(CommonTestFixtures.PROMPT_KEY_FILE_BODY); + onlyMatchesTxt.setAiDefinitionKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + final AiCondition condition = new AiCondition(); + condition.setExtensions(Arrays.asList(".txt")); + onlyMatchesTxt.setCondition(condition); + config.setFieldGenerations(Arrays.asList(onlyMatchesTxt)); + + final SrcMorphException e = assertThrows(SrcMorphException.class, () -> new GenerateEngine(config).execute()); + assertThat(e.getMessage(), containsString("matched no rule and no fallback")); + } + + @Test + public void execute_oversizeFailThrowsSrcMorphException() throws Exception { + final SrcMorphConfiguration config = baseConfig(); + + // Force the routed model's window to be smaller than the source, with the default + // onOversize=fail strategy, so the file is a hard failure. The window check happens entirely + // during planning, before any LlamaCppJniConfig/provider is built, so modelPath is irrelevant here. + final AiModelDefinition tinyWindow = new AiModelDefinition(); + tinyWindow.setKey(CommonTestFixtures.AI_DEFINITION_KEY_DEFAULT); + tinyWindow.setCharsPerToken(1); + tinyWindow.setContextSize(1); + tinyWindow.setMaxOutputTokens(0); + config.setAiDefinitions(Collections.singletonList(tinyWindow)); + + final SrcMorphException e = assertThrows(SrcMorphException.class, () -> new GenerateEngine(config).execute()); + assertThat(e.getMessage(), containsString("exceed their routed model's context window")); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateResultTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateResultTest.java new file mode 100644 index 0000000..3ecdb08 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/engine/GenerateResultTest.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.engine; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.junit.jupiter.api.Test; + +public class GenerateResultTest { + + @Test + public void constructor_threadsEveryFieldThrough() { + final GenerateResult result = new GenerateResult(false, 3, 5, 7); + + assertThat(result.planOnly(), is(false)); + assertThat(result.written(), is(3)); + assertThat(result.unchanged(), is(5)); + assertThat(result.skipped(), is(7)); + } + + @Test + public void planned_isPlanOnlyWithEveryCountAtZero() { + final GenerateResult result = GenerateResult.planned(); + + assertThat(result.planOnly(), is(true)); + assertThat(result.written(), is(0)); + assertThat(result.unchanged(), is(0)); + assertThat(result.skipped(), is(0)); + } + + @Test + public void equalsAndHashCode_reflectAllFields() { + final GenerateResult a = new GenerateResult(false, 1, 2, 3); + final GenerateResult b = new GenerateResult(false, 1, 2, 3); + final GenerateResult differentWritten = new GenerateResult(false, 9, 2, 3); + final GenerateResult differentPlanOnly = new GenerateResult(true, 1, 2, 3); + + assertThat(a, is(equalTo(b))); + assertThat(a.hashCode(), is(equalTo(b.hashCode()))); + assertThat(a, org.hamcrest.CoreMatchers.not(equalTo(differentWritten))); + assertThat(a, org.hamcrest.CoreMatchers.not(equalTo(differentPlanOnly))); + } + + @Test + public void toString_containsDistinguishingCounts() { + final GenerateResult result = new GenerateResult(false, 11, 22, 33); + assertThat(result.toString(), org.hamcrest.CoreMatchers.containsString("11")); + assertThat(result.toString(), org.hamcrest.CoreMatchers.containsString("22")); + assertThat(result.toString(), org.hamcrest.CoreMatchers.containsString("33")); + } +} diff --git a/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactoryTest.java b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactoryTest.java new file mode 100644 index 0000000..9ac4bf4 --- /dev/null +++ b/srcmorph/src/test/java/net/ladenthin/srcmorph/provider/LlamaCppJniConfigFactoryTest.java @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.provider; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Arrays; +import java.util.Collections; +import net.ladenthin.srcmorph.config.AiGenerationConfig; +import org.junit.jupiter.api.Test; + +public class LlamaCppJniConfigFactoryTest { + + /** + * Every field set to a distinct, distinguishable value so a mutant that swaps two getters (or drops + * an argument) in {@link LlamaCppJniConfigFactory#fromGenerationConfig} is killed. + */ + private static AiGenerationConfig fullConfig() { + final AiGenerationConfig config = new AiGenerationConfig(); + config.setModelPath("model.gguf"); + config.setContextSize(1111); + config.setMaxOutputTokens(222); + config.setTemperature(0.33f); + config.setThreads(4); + config.setTopP(0.55f); + config.setTopK(66); + config.setMinP(0.07f); + config.setTopNSigma(0.88f); + config.setRepeatPenalty(1.09f); + config.setChatTemplateEnableThinking(false); + config.setCachePrompt(false); + config.setSwaFull(false); + config.setCacheReuse(101); + config.setGpuLayers(12); + config.setMainGpu(3); + config.setDevices("Vulkan1"); + config.setReasoningEffort("high"); + config.setReasoningBudgetTokens(512); + config.setDryMultiplier(0.5f); + config.setDryBase(1.3f); + config.setDryAllowedLength(7); + config.setDryPenaltyLastN(999); + config.setDrySequenceBreakers(Arrays.asList("\n", ".")); + config.setStopStrings(Arrays.asList("")); + return config; + } + + @Test + public void fromGenerationConfig_threadsEveryFieldThrough() { + final LlamaCppJniConfig result = LlamaCppJniConfigFactory.fromGenerationConfig("libpath", fullConfig()); + + assertThat(result.libraryPath(), is("libpath")); + assertThat(result.modelPath(), is("model.gguf")); + assertThat(result.contextSize(), is(1111)); + assertThat(result.maxOutputTokens(), is(222)); + assertThat(result.temperature(), is(0.33f)); + assertThat(result.threads(), is(4)); + assertThat(result.topP(), is(0.55f)); + assertThat(result.topK(), is(66)); + assertThat(result.minP(), is(0.07f)); + assertThat(result.topNSigma(), is(0.88f)); + assertThat(result.repeatPenalty(), is(1.09f)); + assertThat(result.chatTemplateEnableThinking(), is(false)); + assertThat(result.cachePrompt(), is(false)); + assertThat(result.swaFull(), is(false)); + assertThat(result.cacheReuse(), is(101)); + assertThat(result.gpuLayers(), is(12)); + assertThat(result.mainGpu(), is(3)); + assertThat(result.devices(), is("Vulkan1")); + assertThat(result.reasoningEffort(), is("high")); + assertThat(result.reasoningBudgetTokens(), is(512)); + assertThat(result.dryMultiplier(), is(0.5f)); + assertThat(result.dryBase(), is(1.3f)); + assertThat(result.dryAllowedLength(), is(7)); + assertThat(result.dryPenaltyLastN(), is(999)); + assertThat(result.drySequenceBreakers(), hasItem("\n")); + assertThat(result.drySequenceBreakers(), hasItem(".")); + assertThat(result.stopStrings(), hasItem("")); + } + + @Test + public void fromGenerationConfig_libraryPathNullIsPreserved() { + final AiGenerationConfig config = fullConfig(); + final LlamaCppJniConfig result = LlamaCppJniConfigFactory.fromGenerationConfig(null, config); + assertThat(result.libraryPath(), is(nullValue())); + } + + @Test + public void fromGenerationConfig_nullStopStringsAndDrySequenceBreakersBecomeEmpty() { + final AiGenerationConfig config = fullConfig(); + config.setStopStrings(null); + config.setDrySequenceBreakers(null); + + final LlamaCppJniConfig result = LlamaCppJniConfigFactory.fromGenerationConfig("lib", config); + + assertThat(result.stopStrings(), is(Collections.emptyList())); + assertThat(result.drySequenceBreakers(), is(Collections.emptyList())); + } + + @Test + public void fromFallbackParameters_threadsFallbackArgumentsThrough() { + final LlamaCppJniConfig result = + LlamaCppJniConfigFactory.fromFallbackParameters("lib", "fallback.gguf", 4096, 256, 0.42f, 6); + + assertThat(result.libraryPath(), is("lib")); + assertThat(result.modelPath(), is("fallback.gguf")); + assertThat(result.contextSize(), is(4096)); + assertThat(result.maxOutputTokens(), is(256)); + assertThat(result.temperature(), is(0.42f)); + assertThat(result.threads(), is(6)); + } + + @Test + public void fromFallbackParameters_appliesEveryAiGenerationConfigDefault() { + final LlamaCppJniConfig result = + LlamaCppJniConfigFactory.fromFallbackParameters(null, "fallback.gguf", 4096, 256, 0.42f, 6); + + assertThat(result.topP(), is(AiGenerationConfig.DEFAULT_TOP_P)); + assertThat(result.topK(), is(AiGenerationConfig.DEFAULT_TOP_K)); + assertThat(result.minP(), is(AiGenerationConfig.DEFAULT_MIN_P)); + assertThat(result.topNSigma(), is(AiGenerationConfig.DEFAULT_TOP_N_SIGMA)); + assertThat(result.repeatPenalty(), is(AiGenerationConfig.DEFAULT_REPEAT_PENALTY)); + assertThat(result.chatTemplateEnableThinking(), is(AiGenerationConfig.DEFAULT_CHAT_TEMPLATE_ENABLE_THINKING)); + assertThat(result.cachePrompt(), is(AiGenerationConfig.DEFAULT_CACHE_PROMPT)); + assertThat(result.swaFull(), is(AiGenerationConfig.DEFAULT_SWA_FULL)); + assertThat(result.cacheReuse(), is(AiGenerationConfig.DEFAULT_CACHE_REUSE)); + assertThat(result.gpuLayers(), is(AiGenerationConfig.DEFAULT_GPU_LAYERS)); + assertThat(result.mainGpu(), is(AiGenerationConfig.DEFAULT_MAIN_GPU)); + assertThat(result.devices(), is(AiGenerationConfig.DEFAULT_DEVICES)); + assertThat(result.reasoningEffort(), is(AiGenerationConfig.DEFAULT_REASONING_EFFORT)); + assertThat(result.reasoningBudgetTokens(), is(AiGenerationConfig.DEFAULT_REASONING_BUDGET_TOKENS)); + assertThat(result.dryMultiplier(), is(AiGenerationConfig.DEFAULT_DRY_MULTIPLIER)); + assertThat(result.dryBase(), is(AiGenerationConfig.DEFAULT_DRY_BASE)); + assertThat(result.dryAllowedLength(), is(AiGenerationConfig.DEFAULT_DRY_ALLOWED_LENGTH)); + assertThat(result.dryPenaltyLastN(), is(AiGenerationConfig.DEFAULT_DRY_PENALTY_LAST_N)); + assertThat(result.drySequenceBreakers(), is(Collections.emptyList())); + assertThat(result.stopStrings(), is(Collections.emptyList())); + } +} From a4e49c194916337f64e4cf00a2a3c264db7d013f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 15:49:13 +0000 Subject: [PATCH 3/9] Add srcmorph-cli module (migration step 6): standalone JSON/YAML CLI New net.ladenthin:srcmorph-cli module wrapping the core engines behind a BitcoinAddressFinder-style cli.Main: single positional config-file argument, .json/.js via Jackson ObjectMapper, .yaml/.yml via YAMLMapper (both strict, FAIL_ON_UNKNOWN_PROPERTIES enabled), echo-back of the parsed configuration, and a Java-8 switch over CCommand (Plan/GenerateFileIndex/AggregatePackages/ AggregateProject/All/Calibrate) with no System.exit anywhere. Plan forces planOnly=true on a deep copy of SrcMorphConfiguration so it never mutates the caller's object nor accidentally loads a model. Fat jar (jar-with-dependencies) binds unconditionally to the package phase since it is this module's actual deliverable artifact. Adds MainTest/ConfigBindingTest/CliEndToEndTest/CliArchitectureTest plus a private test-fixtures pair (minimal-generate.json/.yaml). Root pom.xml gains the new module and jackson-databind/jackson-dataformat-yaml dependency management entries. Verified end-to-end via java -jar against both JSON and YAML configs (mock provider): file/package/project .ai.md trees written correctly for both. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- pom.xml | 41 +- srcmorph-cli/pom.xml | 590 ++++++++++++++++++ srcmorph-cli/spotbugs-exclude.xml | 127 ++++ srcmorph-cli/src/main/java/module-info.java | 71 +++ .../java/net/ladenthin/srcmorph/cli/Main.java | 321 ++++++++++ .../srcmorph/cli/configuration/CCommand.java | 49 ++ .../cli/configuration/CConfiguration.java | 39 ++ .../cli/configuration/package-info.java | 11 + .../ladenthin/srcmorph/cli/package-info.java | 10 + .../srcmorph/cli/CliArchitectureTest.java | 129 ++++ .../srcmorph/cli/CliEndToEndTest.java | 99 +++ .../net/ladenthin/srcmorph/cli/MainTest.java | 142 +++++ .../cli/configuration/ConfigBindingTest.java | 91 +++ .../test-fixtures/minimal-generate.json | 29 + .../test-fixtures/minimal-generate.yaml | 17 + 15 files changed, 1761 insertions(+), 5 deletions(-) create mode 100644 srcmorph-cli/pom.xml create mode 100644 srcmorph-cli/spotbugs-exclude.xml create mode 100644 srcmorph-cli/src/main/java/module-info.java create mode 100644 srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/Main.java create mode 100644 srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CCommand.java create mode 100644 srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CConfiguration.java create mode 100644 srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/package-info.java create mode 100644 srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/package-info.java create mode 100644 srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliArchitectureTest.java create mode 100644 srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliEndToEndTest.java create mode 100644 srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/MainTest.java create mode 100644 srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/configuration/ConfigBindingTest.java create mode 100644 srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.json create mode 100644 srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.yaml diff --git a/pom.xml b/pom.xml index c99d1be..9aa9007 100644 --- a/pom.xml +++ b/pom.xml @@ -52,16 +52,28 @@ SPDX-License-Identifier: Apache-2.0 + + + 2.22.0 + + srcmorph + srcmorph-cli llamacpp-ai-index-maven-plugin @@ -87,6 +99,25 @@ SPDX-License-Identifier: Apache-2.0 logback-classic 1.5.37 + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + diff --git a/srcmorph-cli/pom.xml b/srcmorph-cli/pom.xml new file mode 100644 index 0000000..f504dfc --- /dev/null +++ b/srcmorph-cli/pom.xml @@ -0,0 +1,590 @@ + + + + 4.0.0 + + + net.ladenthin + srcmorph-parent + 1.1.0-SNAPSHOT + ../pom.xml + + + srcmorph-cli + jar + + srcmorph-cli + Standalone CLI for srcmorph, driven by a single JSON/YAML configuration file (BitcoinAddressFinder cli/Main.java pattern). Wraps the srcmorph core library's engines; ships as a jar-with-dependencies fat jar. + + + bernardladenthin + 8 + 21 + UTF-8 + + 6.1.2 + 3.0 + 2.50.0 + 0.13.7 + 1.0.0 + 4.2.1 + 1.4.2 + 4.10.2.0 + 1.18.46 + 7.7.4 + 1.14.0 + 3.8.0 + 2.94.0 + + ${git.commit.time} + + + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.jspecify + jspecify + ${jspecify.version} + + + org.checkerframework + checker-qual + ${checker.version} + + + + + net.ladenthin + srcmorph + ${project.version} + + + + + org.slf4j + slf4j-api + + + + + ch.qos.logback + logback-classic + runtime + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + + com.tngtech.archunit + archunit-junit5 + ${archunit.version} + test + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + io.github.git-commit-id + git-commit-id-maven-plugin + 10.0.0 + + + org.apache.maven.plugins + maven-assembly-plugin + 3.8.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + + org.apache.maven.plugins + maven-jar-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + @{argLine} -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:ErrorFile=hs_err_pid%p.log + + false + + + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + get-git-properties + + revision + + initialize + + + + yyyy-MM-dd'T'HH:mm:ss'Z' + UTC + false + false + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + [3.6.3,) + + + [1.8,) + + + + + + commons-logging:commons-logging + + log4j:log4j + + org.hamcrest:hamcrest-core + org.hamcrest:hamcrest-library + org.hamcrest:hamcrest-all + + junit:junit + junit:junit-dep + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.release} + ${maven.compiler.testRelease} + true + true + + + -Xlint:all,-serial,-options,-classfile,-processing + -Werror + + -processor + lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor,org.checkerframework.checker.nullness.NullnessChecker + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepExcludedPaths:.*/generated-sources/.* -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:ExcludedFieldAnnotations=org.apache.maven.plugins.annotations.Parameter,org.apache.maven.plugins.annotations.Component -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:CheckOptionalEmptiness=true -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true -XepOpt:NullAway:AcknowledgeAndroidRecent=true -XepOpt:NullAway:AssertsEnabled=true -Xep:BoxedPrimitiveEquality:ERROR -Xep:EqualsHashCode:ERROR -Xep:EqualsIncompatibleType:ERROR -Xep:IdentityBinaryExpression:ERROR -Xep:SelfAssignment:ERROR -Xep:SelfComparison:ERROR -Xep:SelfEquals:ERROR -Xep:DeadException:ERROR -Xep:FormatString:ERROR -Xep:InvalidPatternSyntax:ERROR -Xep:OptionalEquality:ERROR -Xep:ImpossibleNullComparison:ERROR + + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + org.checkerframework + checker + ${checker.version} + + + + + + default-compile + + + + module-info.java + + + + + module-info-compile + compile + + compile + + + + 9 + + module-info.java + + + + + + + default-testCompile + + + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -Xep:NullAway:OFF -Xep:GuardedBy:OFF -Xep:IdentityBinaryExpression:OFF + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${maven.compiler.release} + true + true + all + + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + net.ladenthin.srcmorph.cli.Main + net.ladenthin.srcmorph.cli + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + net.ladenthin.srcmorph.cli.Main + + + + + + build-fat-jar + package + + single + + + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + report + test + + report + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + ${palantir-java-format.version} + + + + + + + + + spotless-check + verify + + check + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + Max + Low + true + false + spotbugs-exclude.xml + + + com.mebigfatguy.fb-contrib + fb-contrib + ${fb-contrib.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + + spotbugs-check + verify + + check + + + + + + + + + diff --git a/srcmorph-cli/spotbugs-exclude.xml b/srcmorph-cli/spotbugs-exclude.xml new file mode 100644 index 0000000..2f41447 --- /dev/null +++ b/srcmorph-cli/spotbugs-exclude.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/srcmorph-cli/src/main/java/module-info.java b/srcmorph-cli/src/main/java/module-info.java new file mode 100644 index 0000000..03f4222 --- /dev/null +++ b/srcmorph-cli/src/main/java/module-info.java @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * JPMS module descriptor for the srcmorph CLI. + * + *

      The module exports the two packages that make up the CLI: {@code cli} (the {@code Main} + * entry point) and {@code cli.configuration} ({@code CConfiguration} + {@code CCommand}, the + * JSON/YAML-bindable wrapper around the core library's own {@code SrcMorphConfiguration}). It + * requires the {@code net.ladenthin.srcmorph} core module (for the shared configuration object and + * the {@code engine} orchestration classes {@code Main} drives) and Jackson's {@code databind} + * module (for the JSON/YAML binding); the {@code configuration} package is {@code opens} to + * {@code com.fasterxml.jackson.databind} because Jackson's field-based binding style — the same + * public-mutable-field JavaBean convention {@code SrcMorphConfiguration} and BitcoinAddressFinder's + * own {@code CConfiguration} use — needs reflective access under the Java Platform Module System's + * strong encapsulation, which {@code exports} alone does not grant for field/method injection + * (only compile-time type visibility).

      + * + *

      JSpecify {@code @NullMarked} is declared at the module level here so that no source + * file compiled at {@code --release 8} references the JSpecify annotation type directly. + * Otherwise javac would emit an unsuppressible {@code unknown enum constant + * ElementType.MODULE} classfile-read warning for each source compiled at release 8 that + * resolves {@code @NullMarked} ({@code @NullMarked} carries + * {@code @Target({MODULE, PACKAGE, TYPE})} and Java 8 does not know about + * {@code ElementType.MODULE}). Confining the reference to {@code module-info.java} — + * which compiles at {@code --release 9} — keeps that warning out of the build entirely.

      + * + *

      {@code requires static org.jspecify} is needed only at compile time of this + * descriptor; JSpecify annotations carry {@code RetentionPolicy.CLASS} so module-path + * consumers never need jspecify on their runtime path. Checker Framework qualifiers are + * likewise compile-time only. The fat jar produced by {@code maven-assembly-plugin} is a + * classpath-only artifact and flattens multiple {@code module-info.class} entries harmlessly, + * same as any other jar-with-dependencies build.

      + * + *

      This descriptor is NOT purely decorative — unlike the core/plugin modules' + * own {@code module-info.java} (which only need to satisfy the isolated, release-9 + * {@code module-info-compile} execution), Maven Surefire's default module-path detection + * (triggered by the presence of {@code module-info.class} in {@code target/classes} once the + * {@code compile} phase has run, i.e. before {@code test}) resolves this module's runtime + * dependency graph from the {@code requires} clauses below. Both real JPMS modules Jackson's + * side pulls in — {@code com.fasterxml.jackson.databind} and + * {@code com.fasterxml.jackson.dataformat.yaml} — must be listed explicitly or a real + * {@link java.lang.IllegalAccessError} is thrown at test time (not merely a compile-time + * warning): {@code YAMLMapper} lives in a distinct named module from {@code ObjectMapper}, and + * JPMS strong encapsulation blocks any access this module does not declare a {@code requires} + * read-edge for, even though both jars sit on the same dependency list.

      + * + *

      This descriptor compiles at {@code --release 9}; the rest of the source compiles + * at {@code --release 8}. Java 8 runtimes silently ignore {@code module-info.class} at + * the JAR root.

      + */ +@org.jspecify.annotations.NullMarked +module net.ladenthin.srcmorph.cli { + requires static org.jspecify; + + // Lombok is `provided` scope: only used at compile time to generate equals/hashCode/toString. + // `requires static` means the runtime does not need the lombok jar on the module path — + // the @lombok.Generated annotation carried on generated members has CLASS retention. + requires static lombok; + requires net.ladenthin.srcmorph; + requires org.slf4j; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + + opens net.ladenthin.srcmorph.cli.configuration to + com.fasterxml.jackson.databind; + + exports net.ladenthin.srcmorph.cli; + exports net.ladenthin.srcmorph.cli.configuration; +} diff --git a/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/Main.java b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/Main.java new file mode 100644 index 0000000..27bbb95 --- /dev/null +++ b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/Main.java @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; +import lombok.ToString; +import net.ladenthin.srcmorph.cli.configuration.CCommand; +import net.ladenthin.srcmorph.cli.configuration.CConfiguration; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.AggregatePackagesEngine; +import net.ladenthin.srcmorph.engine.AggregateProjectEngine; +import net.ladenthin.srcmorph.engine.CalibrateEngine; +import net.ladenthin.srcmorph.engine.CalibrationReport; +import net.ladenthin.srcmorph.engine.GenerateEngine; +import net.ladenthin.srcmorph.engine.SrcMorphException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * CLI entry point: loads a single JSON/YAML {@link CConfiguration} file (the program's sole + * argument) and dispatches to the configured {@link CCommand}. + * + *

      Mirrors the BitcoinAddressFinder {@code cli.Main} pattern: extension-dispatched + * deserialization ({@code .json}/{@code .js} via Jackson's {@code ObjectMapper}, + * {@code .yaml}/{@code .yml} via {@code YAMLMapper}), an echo-back of the parsed configuration for + * review before anything runs, and no {@link System#exit(int)} anywhere — a failure propagates as + * an unchecked exception out of {@link #main(String[])} so the JVM's own uncaught-exception + * handling reports it (a non-zero process exit without bypassing normal shutdown).

      + */ +@ToString +public class Main { + + /** + * File extension for JavaScript configuration files; treated identically to {@link #FILE_EXTENSION_JSON}. + */ + static final String FILE_EXTENSION_JS = ".js"; + + /** + * Standard file extension for JSON configuration files. + * + * @see #FILE_EXTENSION_JS + */ + static final String FILE_EXTENSION_JSON = ".json"; + + /** + * Standard long-form file extension for YAML configuration files. + * + * @see #FILE_EXTENSION_YML + */ + static final String FILE_EXTENSION_YAML = ".yaml"; + + /** Short-form file extension for YAML configuration files; treated identically to {@link #FILE_EXTENSION_YAML}. */ + static final String FILE_EXTENSION_YML = ".yml"; + + /** SLF4J logger for the CLI entry point. */ + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); + + private final CConfiguration configuration; + + /** + * Creates a new main instance for the given, already-loaded configuration. + * + * @param configuration the loaded configuration + */ + public Main(final CConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Reads the entire contents of {@code path} as a UTF-8 string. + * + * @param path the file to read + * @return the file contents + * @throws IOException if the file cannot be read + */ + public static String readString(final Path path) throws IOException { + return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + } + + /** + * Parses a JSON configuration string. + * + * @param configurationString the JSON document + * @return the parsed {@link CConfiguration} + * @throws IOException if the JSON cannot be deserialised, including an unknown property (the + * mapper is strict — see {@link #newJsonMapper()}) + */ + public static CConfiguration fromJson(final String configurationString) throws IOException { + final ObjectMapper mapper = newJsonMapper(); + return mapper.readValue(configurationString, CConfiguration.class); + } + + /** + * Parses a YAML configuration string. + * + * @param configurationString the YAML document + * @return the parsed {@link CConfiguration} + * @throws IOException if the YAML cannot be deserialised, including an unknown property (the + * mapper is strict — see {@link #newYamlMapper()}) + */ + public static CConfiguration fromYaml(final String configurationString) throws IOException { + final YAMLMapper mapper = newYamlMapper(); + return mapper.readValue(configurationString, CConfiguration.class); + } + + /** + * Serialises a {@link CConfiguration} as indented JSON. + * + * @param configuration the configuration to serialise + * @return the JSON representation + * @throws IOException if the configuration cannot be serialised + */ + public static String configurationToJson(final CConfiguration configuration) throws IOException { + final ObjectMapper mapper = newJsonMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + return mapper.writeValueAsString(configuration); + } + + /** + * Serialises a {@link CConfiguration} as YAML. + * + * @param configuration the configuration to serialise + * @return the YAML representation + * @throws IOException if the configuration cannot be serialised + */ + public static String configurationToYAML(final CConfiguration configuration) throws IOException { + final YAMLMapper mapper = newYamlMapper(); + return mapper.writeValueAsString(configuration); + } + + /** + * Loads a configuration file from disk, picking the parser based on the file extension. + * + *

      Supported extensions (case-insensitive): + *

        + *
      • {@link #FILE_EXTENSION_JSON} / {@link #FILE_EXTENSION_JS} → parsed as JSON via {@link #fromJson(String)}
      • + *
      • {@link #FILE_EXTENSION_YAML} / {@link #FILE_EXTENSION_YML} → parsed as YAML via {@link #fromYaml(String)}
      • + *
      + * + * @param configurationPath the configuration file + * @return the parsed configuration + * @throws IOException if the file cannot be read or its content cannot be deserialised + * @throws IllegalArgumentException if {@code configurationPath} does not end with a supported extension + */ + public static CConfiguration loadConfiguration(final Path configurationPath) throws IOException { + final String configurationAsString = readString(configurationPath); + final String lowerPath = configurationPath.toString().toLowerCase(Locale.ROOT); + if (lowerPath.endsWith(FILE_EXTENSION_JS) || lowerPath.endsWith(FILE_EXTENSION_JSON)) { + return fromJson(configurationAsString); + } else if (lowerPath.endsWith(FILE_EXTENSION_YAML) || lowerPath.endsWith(FILE_EXTENSION_YML)) { + return fromYaml(configurationAsString); + } else { + throw new IllegalArgumentException("Unknown file ending for: " + configurationPath); + } + } + + /** + * Java entry point. Loads the configuration file passed as the first argument, logs the parsed + * configuration back for review, and runs the configured command. + * + * @param args the program arguments; expects a single path to a JSON or YAML configuration file + * @throws IOException if the configuration file cannot be read or deserialised + */ + public static void main(final String[] args) throws IOException { + if (args.length != 1) { + LOGGER.error("Invalid arguments. Pass path to configuration as first argument."); + return; + } + final Path configurationPath = Paths.get(args[0]); + final CConfiguration configuration = loadConfiguration(configurationPath); + final Main main = new Main(configuration); + main.logConfigurationTransformation(); + main.run(); + } + + /** + * Logs the JSON and YAML representations of the loaded configuration for review, so a user can + * confirm exactly what was parsed before any model loads or file is written. + * + * @throws IOException if the configuration cannot be serialised + */ + public void logConfigurationTransformation() throws IOException { + final String json = configurationToJson(configuration); + final String yaml = configurationToYAML(configuration); + LOGGER.info("Please review the transformed configuration to ensure it aligns with your expectations " + + "and requirements before proceeding.:\n" + + "########## BEGIN transformed JSON configuration ##########\n" + + json + + "\n" + + "########## END transformed JSON configuration ##########\n" + + "\n" + + "########## BEGIN transformed YAML configuration ##########\n" + + yaml + + "\n" + + "########## END transformed YAML configuration ##########\n"); + } + + /** + * Dispatches to the engine(s) selected by {@link CConfiguration#command} and runs them. + * + *

      No {@link System#exit(int)} is called anywhere: a failure is wrapped as an unchecked + * {@link IllegalStateException} and rethrown, so it propagates out of {@link #main(String[])} + * as an ordinary uncaught exception.

      + */ + public void run() { + LOGGER.info(configuration.command.name()); + try { + switch (configuration.command) { + case Plan: { + new GenerateEngine(copyWithPlanOnlyForced(configuration.srcMorph)).execute(); + break; + } + case GenerateFileIndex: { + new GenerateEngine(configuration.srcMorph).execute(); + break; + } + case AggregatePackages: { + new AggregatePackagesEngine(configuration.srcMorph).execute(); + break; + } + case AggregateProject: { + new AggregateProjectEngine(configuration.srcMorph).execute(); + break; + } + case All: { + new GenerateEngine(configuration.srcMorph).execute(); + new AggregatePackagesEngine(configuration.srcMorph).execute(); + new AggregateProjectEngine(configuration.srcMorph).execute(); + break; + } + case Calibrate: { + final CalibrationReport report = new CalibrateEngine(configuration.srcMorph).execute(); + // Deliberate System.out, not SLF4J: this is a paste-ready XML + // snippet meant for the user to copy into their POM's , not a log + // line, so it must not carry a logger's timestamp/level prefix. + System.out.println(report.renderXml()); // NOPMD - intentional stdout, see above + break; + } + default: + throw new UnsupportedOperationException( + "Command: " + configuration.command.name() + " currently not supported."); + } + LOGGER.info("Main#run end."); + } catch (final SrcMorphException | IOException e) { + LOGGER.error("Fatal error during Main.run.", e); + throw new IllegalStateException( + "Main.run() failed on thread " + Thread.currentThread().getName(), e); + } + } + + /** + * Builds a deep copy of {@code original} with {@link SrcMorphConfiguration#setPlanOnly(boolean)} + * forced to {@code true}, regardless of what the configuration file said. Used by the + * {@link CCommand#Plan} command so it never accidentally runs a real (model-loading) generation + * even if the file's own {@code planOnly} was left {@code false} — and never mutates the shared + * {@link CConfiguration#srcMorph} instance the caller already logged. + * + *

      The copy is a round-trip through a JSON mapper, which keeps this in lockstep with every + * field {@link SrcMorphConfiguration} gains in the future with no manual field-by-field copy to + * maintain. Deliberately uses a lenient mapper (unlike {@link #newJsonMapper()}, which + * loads user-authored config files and must fail fast on a typo): the source object also carries + * derived, read-only, setter-less getters that a strict mapper's serialize-then-deserialize + * round-trip would otherwise reject as "unknown properties" (e.g. + * {@code AiFieldGenerationConfig#getOversizeStrategy()}, computed from the raw + * {@code onOversize} string field). This copy step never touches user input — {@code original} + * was already accepted by the strict mapper when the configuration file was first loaded — so + * leniency here does not weaken the config-file-typo-fails-fast contract.

      + * + * @param original the configuration to copy + * @return a deep copy of {@code original} with {@code planOnly} forced {@code true} + */ + private static SrcMorphConfiguration copyWithPlanOnlyForced(final SrcMorphConfiguration original) { + try { + final ObjectMapper mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + final SrcMorphConfiguration copy = + mapper.readValue(mapper.writeValueAsString(original), SrcMorphConfiguration.class); + copy.setPlanOnly(true); + return copy; + } catch (final IOException e) { + throw new IllegalStateException("Failed to copy the configuration for the Plan command.", e); + } + } + + /** + * Creates the JSON mapper used for both the CLI's own {@link CConfiguration} parsing and the + * internal {@link #copyWithPlanOnlyForced(SrcMorphConfiguration)} round-trip. + * + * @return a new {@link ObjectMapper} with {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} + * explicitly enabled (already Jackson's own default; enabled explicitly here so the strict + * intent — a typo in the config file fails fast rather than silently no-op'ing, mirroring + * what plexus does for the Maven XML side — is documented in code, not left implicit) + */ + private static ObjectMapper newJsonMapper() { + final ObjectMapper mapper = new ObjectMapper(); + mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return mapper; + } + + /** + * Creates the YAML mapper used for {@link CConfiguration} parsing. + * + * @return a new {@link YAMLMapper} with {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} + * explicitly enabled (see {@link #newJsonMapper()} for the rationale) + */ + private static YAMLMapper newYamlMapper() { + final YAMLMapper mapper = new YAMLMapper(); + mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + return mapper; + } +} diff --git a/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CCommand.java b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CCommand.java new file mode 100644 index 0000000..5537c72 --- /dev/null +++ b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CCommand.java @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli.configuration; + +/** + * Selects which {@code srcmorph} phase (or phases) {@link net.ladenthin.srcmorph.cli.Main} runs for + * a given {@link CConfiguration}. + */ +public enum CCommand { + + /** + * Builds and logs the Phase 1 routing plan only ({@code GenerateEngine} with {@code planOnly} + * forced {@code true} regardless of what the configuration file says); no model is loaded and + * nothing is written. The safe default (see {@link CConfiguration#command}). + */ + Plan, + + /** + * Phase 1: indexes source files and fills in their AI-generated summary fields + * ({@code net.ladenthin.srcmorph.engine.GenerateEngine}, exactly as configured). + */ + GenerateFileIndex, + + /** + * Phase 2: aggregates per-package {@code .ai.md} index files + * ({@code net.ladenthin.srcmorph.engine.AggregatePackagesEngine}). + */ + AggregatePackages, + + /** + * Phase 3: aggregates the single project-level {@code .ai.md} index + * ({@code net.ladenthin.srcmorph.engine.AggregateProjectEngine}). + */ + AggregateProject, + + /** + * Runs all three phases in order: {@link #GenerateFileIndex}, then {@link #AggregatePackages}, + * then {@link #AggregateProject}, stopping at the first phase that fails. + */ + All, + + /** + * Calibrates every distinct model referenced by the configured routing rules + * ({@code net.ladenthin.srcmorph.engine.CalibrateEngine}) and prints a paste-ready + * {@code } XML block per model to standard output. + */ + Calibrate +} diff --git a/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CConfiguration.java b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CConfiguration.java new file mode 100644 index 0000000..d82b54e --- /dev/null +++ b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/CConfiguration.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli.configuration; + +import lombok.ToString; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; + +/** + * Root configuration object loaded from the CLI's JSON/YAML config file. + * + *

      Public mutable-field JavaBean (BitcoinAddressFinder {@code cli.configuration.CConfiguration} + * convention), bindable via Jackson's {@code ObjectMapper}/{@code YAMLMapper} without any getters or + * setters. Carved out of the {@code noPublicMutableFields} ArchUnit rule via this package's explicit + * exception (see {@code CliArchitectureTest}).

      + */ +@ToString +public class CConfiguration { + + /** Creates a new {@link CConfiguration} with every default applied. */ + public CConfiguration() { + // no-op + } + + /** + * The command selecting which {@code srcmorph} phase(s) {@link net.ladenthin.srcmorph.cli.Main} + * runs. Defaults to {@link CCommand#Plan} so that a missing or misconfigured command never + * accidentally triggers a real (possibly expensive, model-loading) run. + */ + public CCommand command = CCommand.Plan; + + /** + * The shared core configuration passed to whichever engine {@link #command} selects — the SAME + * {@link SrcMorphConfiguration} type the {@code llamacpp-ai-index-maven-plugin} module's mojos + * build from their own {@code @Parameter} fields, so a JSON/YAML key here reads identically to + * the matching Maven {@code } XML element. + */ + public SrcMorphConfiguration srcMorph = new SrcMorphConfiguration(); +} diff --git a/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/package-info.java b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/package-info.java new file mode 100644 index 0000000..56b7587 --- /dev/null +++ b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/configuration/package-info.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * The CLI's own configuration wrapper: {@link net.ladenthin.srcmorph.cli.configuration.CConfiguration} + * (the JSON/YAML document root) and {@link net.ladenthin.srcmorph.cli.configuration.CCommand} (the + * selected phase), bindable via Jackson's {@code ObjectMapper} / {@code YAMLMapper} + * (BitcoinAddressFinder {@code configuration} package convention: public mutable-field JavaBeans). + */ +package net.ladenthin.srcmorph.cli.configuration; diff --git a/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/package-info.java b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/package-info.java new file mode 100644 index 0000000..88f5fa2 --- /dev/null +++ b/srcmorph-cli/src/main/java/net/ladenthin/srcmorph/cli/package-info.java @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * The srcmorph CLI entry point ({@link net.ladenthin.srcmorph.cli.Main}), driven by a single + * JSON/YAML {@link net.ladenthin.srcmorph.cli.configuration.CConfiguration} file passed as the + * program's sole argument. + */ +package net.ladenthin.srcmorph.cli; diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliArchitectureTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliArchitectureTest.java new file mode 100644 index 0000000..a331724 --- /dev/null +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliArchitectureTest.java @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import org.slf4j.Logger; + +/** + * Architecture rules for the srcmorph CLI ({@code net.ladenthin.srcmorph.cli}). + * + *

      Mirrors the conventions established by {@code CoreArchitectureTest} (the {@code srcmorph} + * module) and BitcoinAddressFinder's own {@code BitcoinAddressFinderArchitectureTest}: no + * {@code System.exit}, no public mutable fields outside the {@code configuration} package's + * Jackson-bound JavaBeans, no reverse dependency onto this module (it is the reactor's leaf-most + * consumer, nothing else may depend on it), and no accidental dependency on the Maven plugin API + * (this is a plain standalone CLI, not a Maven-aware component).

      + */ +@AnalyzeClasses(packages = "net.ladenthin.srcmorph.cli", importOptions = ImportOption.DoNotIncludeTests.class) +public class CliArchitectureTest { + + /** + * The CLI is the reactor's leaf-most consumer: {@code srcmorph-cli} is not a compile dependency + * of any sibling reactor module (only {@code srcmorph}'s core library is — see the root + * {@code pom.xml}'s module comment), so nothing else in the reactor can ever depend on it by + * construction (a reverse dependency would simply fail to resolve/compile). Because + * {@code @AnalyzeClasses} here only scans this module's own {@code net.ladenthin.srcmorph.cli} + * package tree, the in-module half of that check is necessarily vacuous + * ({@code allowEmptyShould(true)}); this rule exists as a documentation-level regression guard + * should a future package ever be added to this module outside {@code cli..} that reaches back + * into it incorrectly. + */ + @ArchTest + static final ArchRule cliIsLeaf = noClasses() + .that() + .resideOutsideOfPackage("net.ladenthin.srcmorph.cli..") + .should() + .dependOnClassesThat() + .resideInAPackage("net.ladenthin.srcmorph.cli..") + .allowEmptyShould(true); + + /** + * Public mutable state forbidden: any non-static field declared {@code public} must also be + * {@code final}. The single documented exception is + * {@code net.ladenthin.srcmorph.cli.configuration} — the Jackson-bound JavaBeans + * ({@code CConfiguration}) that mirror the core library's own {@code SrcMorphConfiguration} and + * BitcoinAddressFinder's {@code cli.configuration.CConfiguration} convention. + */ + @ArchTest + static final ArchRule noPublicMutableFields = fields().that() + .arePublic() + .and() + .areNotStatic() + .and() + .areDeclaredInClassesThat() + .resideOutsideOfPackage("net.ladenthin.srcmorph.cli.configuration..") + .should() + .beFinal() + .allowEmptyShould(true); // vacuous today: Main has no public instance fields + + /** + * Production code must not call {@link System#exit(int)}; a failure propagates as an unchecked + * exception out of {@link Main#main(String[])} instead, so the JVM's own uncaught-exception + * handling reports it without bypassing normal shutdown. + */ + @ArchTest + static final ArchRule noSystemExit = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph.cli..") + .should() + .callMethod(System.class, "exit", int.class) + .allowEmptyShould(true); + + /** + * This is a plain standalone CLI, not a Maven-aware component: it must never depend on the + * Maven Plugin API (the {@code llamacpp-ai-index-maven-plugin} sibling module owns that + * boundary). + */ + @ArchTest + static final ArchRule mavenFree = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph.cli..") + .should() + .dependOnClassesThat() + .resideInAPackage("org.apache.maven..") + .allowEmptyShould(true); + + /** + * Test-framework classes must not appear in production code. + */ + @ArchTest + static final ArchRule noTestFrameworksInProduction = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph.cli..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.junit..", "net.jqwik..", "com.tngtech.archunit.."); + + /** + * Production code must not import unsupported / internal JDK packages. + */ + @ArchTest + static final ArchRule noInternalJdkImports = noClasses() + .that() + .resideInAPackage("net.ladenthin.srcmorph.cli..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("sun..", "com.sun..", "jdk.internal.."); + + /** + * Every SLF4J {@link Logger} field must be {@code private static final} — a single shared + * logger per class, never an instance field or a mutable/visible one. + */ + @ArchTest + static final ArchRule loggersArePrivateStaticFinal = fields().that() + .haveRawType(Logger.class) + .should() + .bePrivate() + .andShould() + .beStatic() + .andShould() + .beFinal(); +} diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliEndToEndTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliEndToEndTest.java new file mode 100644 index 0000000..157c1d5 --- /dev/null +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/CliEndToEndTest.java @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import net.ladenthin.srcmorph.cli.configuration.CCommand; +import net.ladenthin.srcmorph.cli.configuration.CConfiguration; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.document.AiMdHeaderCodec; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * End-to-end exercise of the {@link CCommand#All} command against the {@code mock} provider (no + * model load, no forked {@code java -jar} process — {@link Main#run()} is invoked directly), proving + * the CLI drives all three engines and the expected {@code .ai.md} tree lands on disk. + */ +public class CliEndToEndTest { + + @TempDir + Path tempDir; + + private CConfiguration buildAllCommandConfiguration() throws IOException { + final Path sourceRoot = tempDir.resolve("src/main/java"); + Files.createDirectories(sourceRoot.resolve("com/example")); + Files.write(sourceRoot.resolve("com/example/Foo.java"), "class Foo {}\n".getBytes(StandardCharsets.UTF_8)); + + final CConfiguration configuration = new CConfiguration(); + configuration.command = CCommand.All; + configuration.srcMorph.setBaseDirectory(tempDir.toFile()); + configuration.srcMorph.setOutputDirectory(tempDir.resolve("out").toFile()); + configuration.srcMorph.setGenerationProvider("mock"); + configuration.srcMorph.setPluginVersion("1.0.0-test"); + configuration.srcMorph.setAiVersion("0.0.0"); + + final AiPromptDefinition prompt = new AiPromptDefinition(); + prompt.setKey("file-body"); + prompt.setTemplate("Summarize:\n%s"); + configuration.srcMorph.setPromptDefinitions(Collections.singletonList(prompt)); + + final AiModelDefinition model = new AiModelDefinition(); + model.setKey("mock-model"); + model.setModelPath("mock.gguf"); + // Disable automatic maxInputChars calculation so the tiny test source never trips the + // oversize path (mirrors GenerateEngineTest's own mock model definition). + model.setCharsPerToken(0); + configuration.srcMorph.setAiDefinitions(Collections.singletonList(model)); + + final AiFieldGenerationConfig rule = new AiFieldGenerationConfig(); + rule.setPromptKey("file-body"); + rule.setAiDefinitionKey("mock-model"); + rule.setFallback(true); + configuration.srcMorph.setFieldGenerations(Collections.singletonList(rule)); + + return configuration; + } + + @Test + public void run_allCommand_writesFileAndPackageAndProjectIndex() throws Exception { + final CConfiguration configuration = buildAllCommandConfiguration(); + + new Main(configuration).run(); + + final Path outputRoot = tempDir.resolve("out"); + // Phase 1: SourceFileIndexer relativises against the "src" root but keeps "main/java/...". + assertThat(Files.exists(outputRoot.resolve("main/java/com/example/Foo.java.ai.md")), is(true)); + // Phase 2: one package.ai.md per directory from the output root down to the leaf package. + assertThat( + Files.exists(outputRoot.resolve("main/java/com/example/" + AiMdHeaderCodec.PACKAGE_AI_MD_FILENAME)), + is(true)); + assertThat(Files.exists(outputRoot.resolve(AiMdHeaderCodec.PACKAGE_AI_MD_FILENAME)), is(true)); + // Phase 3: the single project-level index. + assertThat(Files.exists(outputRoot.resolve(AiMdHeaderCodec.PROJECT_AI_MD_FILENAME)), is(true)); + } + + @Test + public void run_planCommand_forcesPlanOnlyAndWritesNothing() throws Exception { + final CConfiguration configuration = buildAllCommandConfiguration(); + // The file explicitly asks NOT to plan-only; Plan must force it anyway. + configuration.command = CCommand.Plan; + configuration.srcMorph.setPlanOnly(false); + + new Main(configuration).run(); + + assertThat(Files.exists(tempDir.resolve("out")), is(false)); + // The original configuration object handed to Main must not have been mutated in place. + assertThat(configuration.srcMorph.isPlanOnly(), is(false)); + } +} diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/MainTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/MainTest.java new file mode 100644 index 0000000..94ac0ee --- /dev/null +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/MainTest.java @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import net.ladenthin.srcmorph.cli.configuration.CCommand; +import net.ladenthin.srcmorph.cli.configuration.CConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Argument validation and JSON/YAML parsing behaviour of {@link Main}, all against the {@code mock} + * provider (never loads a real model), plus one dedicated ad-hoc test fixture pair (kept private to + * this module's {@code src/test/resources} — the public, documented {@code examples/} fixture set is + * a later migration step). + */ +public class MainTest { + + @TempDir + Path tempDir; + + private static final Path MINIMAL_GENERATE_JSON = Path.of("src/test/resources/test-fixtures/minimal-generate.json"); + private static final Path MINIMAL_GENERATE_YAML = Path.of("src/test/resources/test-fixtures/minimal-generate.yaml"); + + private static final String MINIMAL_JSON_STRING = "{\"command\":\"Plan\"}"; + private static final String MINIMAL_YAML_STRING = "command: Plan\n"; + + @Test + public void main_noArgumentGiven_returnsWithoutException() { + assertDoesNotThrow(() -> Main.main(new String[0])); + } + + @Test + public void main_tooManyArgumentsGiven_returnsWithoutException() { + assertDoesNotThrow(() -> Main.main(new String[] {"one", "two"})); + } + + @Test + public void main_unknownExtensionPath_throwsIllegalArgumentException() throws IOException { + final Path configFile = tempDir.resolve("config.txt"); + Files.write(configFile, MINIMAL_JSON_STRING.getBytes(StandardCharsets.UTF_8)); + + assertThrows(IllegalArgumentException.class, () -> Main.main(new String[] {configFile.toString()})); + } + + @Test + public void loadConfiguration_jsonExtension_parsesFixture() throws IOException { + final CConfiguration configuration = Main.loadConfiguration(MINIMAL_GENERATE_JSON); + + assertThat(configuration, is(notNullValue())); + assertThat(configuration.command, is(equalTo(CCommand.GenerateFileIndex))); + assertThat(configuration.srcMorph.getGenerationProvider(), is(equalTo("mock"))); + } + + @Test + public void loadConfiguration_yamlExtension_parsesFixture() throws IOException { + final CConfiguration configuration = Main.loadConfiguration(MINIMAL_GENERATE_YAML); + + assertThat(configuration, is(notNullValue())); + assertThat(configuration.command, is(equalTo(CCommand.GenerateFileIndex))); + assertThat(configuration.srcMorph.getGenerationProvider(), is(equalTo("mock"))); + } + + @Test + public void fromJson_validJsonString_returnsExpectedConfiguration() throws IOException { + final CConfiguration configuration = Main.fromJson(MINIMAL_JSON_STRING); + + assertThat(configuration, is(notNullValue())); + assertThat(configuration.command, is(equalTo(CCommand.Plan))); + } + + @Test + public void fromYaml_validYamlString_returnsExpectedConfiguration() throws IOException { + final CConfiguration configuration = Main.fromYaml(MINIMAL_YAML_STRING); + + assertThat(configuration, is(notNullValue())); + assertThat(configuration.command, is(equalTo(CCommand.Plan))); + } + + @Test + public void fromJson_unknownTopLevelProperty_throwsIOException() { + assertThrows(IOException.class, () -> Main.fromJson("{\"thisFieldDoesNotExist\":true}")); + } + + @Test + public void fromYaml_unknownTopLevelProperty_throwsIOException() { + assertThrows(IOException.class, () -> Main.fromYaml("thisFieldDoesNotExist: true\n")); + } + + @Test + public void configurationToJsonThenFromJson_roundTripsCommand() throws IOException { + final CConfiguration original = new CConfiguration(); + original.command = CCommand.AggregateProject; + + final String json = Main.configurationToJson(original); + final CConfiguration roundTripped = Main.fromJson(json); + + assertThat(roundTripped.command, is(equalTo(CCommand.AggregateProject))); + } + + @Test + public void configurationToYAMLThenFromYaml_roundTripsCommand() throws IOException { + final CConfiguration original = new CConfiguration(); + original.command = CCommand.Calibrate; + + final String yaml = Main.configurationToYAML(original); + final CConfiguration roundTripped = Main.fromYaml(yaml); + + assertThat(roundTripped.command, is(equalTo(CCommand.Calibrate))); + } + + @Test + public void logConfigurationTransformation_defaultConfiguration_doesNotThrow() { + final Main main = new Main(new CConfiguration()); + + assertDoesNotThrow(main::logConfigurationTransformation); + } + + @Test + public void run_planCommandWithNoFieldGenerations_throwsIllegalStateException() { + final CConfiguration configuration = new CConfiguration(); + configuration.command = CCommand.Plan; + configuration.srcMorph.setBaseDirectory(tempDir.toFile()); + configuration.srcMorph.setOutputDirectory(tempDir.resolve("out").toFile()); + // No configured -> GenerateEngine.execute() throws SrcMorphException, + // which Main.run() wraps as an unchecked IllegalStateException (no System.exit anywhere). + final Main main = new Main(configuration); + + assertThrows(IllegalStateException.class, main::run); + } +} diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/configuration/ConfigBindingTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/configuration/ConfigBindingTest.java new file mode 100644 index 0000000..89f1900 --- /dev/null +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/configuration/ConfigBindingTest.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli.configuration; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.file.Path; +import net.ladenthin.srcmorph.cli.Main; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; +import org.junit.jupiter.api.Test; + +/** + * Round-trips the ad-hoc {@code test-fixtures/minimal-generate.{json,yaml}} pair (kept private to + * this module — the public, documented {@code examples/} fixture set is a later migration step) + * through both {@link Main#fromJson(String)} and {@link Main#fromYaml(String)} and asserts the + * resulting {@link CConfiguration}/{@link SrcMorphConfiguration} object graphs are equivalent. + * + *

      Locks in the shared-config-object contract described in {@link SrcMorphConfiguration}'s own + * Javadoc: the same field names bind identically whether the source is Maven XML (plexus, not + * exercised here), JSON, or YAML.

      + */ +public class ConfigBindingTest { + + private static final Path MINIMAL_GENERATE_JSON = Path.of("src/test/resources/test-fixtures/minimal-generate.json"); + private static final Path MINIMAL_GENERATE_YAML = Path.of("src/test/resources/test-fixtures/minimal-generate.yaml"); + + @Test + public void jsonFixture_bindsExpectedObjectGraph() throws IOException { + final CConfiguration configuration = Main.fromJson(Main.readString(MINIMAL_GENERATE_JSON)); + + assertConfigurationMatchesFixture(configuration); + } + + @Test + public void yamlFixture_bindsExpectedObjectGraph() throws IOException { + final CConfiguration configuration = Main.fromYaml(Main.readString(MINIMAL_GENERATE_YAML)); + + assertConfigurationMatchesFixture(configuration); + } + + @Test + public void jsonAndYamlFixtures_bindToEquivalentConfigurations() throws IOException { + final CConfiguration fromJson = Main.fromJson(Main.readString(MINIMAL_GENERATE_JSON)); + final CConfiguration fromYaml = Main.fromYaml(Main.readString(MINIMAL_GENERATE_YAML)); + + assertThat(fromJson.command, is(equalTo(fromYaml.command))); + assertThat(fromJson.srcMorph.getGenerationProvider(), is(equalTo(fromYaml.srcMorph.getGenerationProvider()))); + assertThat( + fromJson.srcMorph.getPromptDefinitions().get(0).getKey(), + is(equalTo(fromYaml.srcMorph.getPromptDefinitions().get(0).getKey()))); + assertThat( + fromJson.srcMorph.getAiDefinitions().get(0).getKey(), + is(equalTo(fromYaml.srcMorph.getAiDefinitions().get(0).getKey()))); + assertThat( + fromJson.srcMorph.getFieldGenerations().get(0).getPromptKey(), + is(equalTo(fromYaml.srcMorph.getFieldGenerations().get(0).getPromptKey()))); + } + + private static void assertConfigurationMatchesFixture(final CConfiguration configuration) { + assertThat(configuration.command, is(equalTo(CCommand.GenerateFileIndex))); + + final SrcMorphConfiguration srcMorph = configuration.srcMorph; + assertThat(srcMorph.getGenerationProvider(), is(equalTo("mock"))); + + assertThat(srcMorph.getPromptDefinitions().size(), is(equalTo(1))); + final AiPromptDefinition prompt = srcMorph.getPromptDefinitions().get(0); + assertThat(prompt.getKey(), is(equalTo("file-body"))); + assertThat(prompt.getTemplate(), is(equalTo("Summarize this file:\n%s"))); + + assertThat(srcMorph.getAiDefinitions().size(), is(equalTo(1))); + final AiModelDefinition model = srcMorph.getAiDefinitions().get(0); + assertThat(model.getKey(), is(equalTo("mock-model"))); + assertThat(model.getModelPath(), is(equalTo("unused-with-mock-provider.gguf"))); + assertThat(model.getContextSize(), is(equalTo(2048))); + assertThat(model.getMaxOutputTokens(), is(equalTo(64))); + assertThat(model.getThreads(), is(equalTo(2))); + + assertThat(srcMorph.getFieldGenerations().size(), is(equalTo(1))); + final AiFieldGenerationConfig rule = srcMorph.getFieldGenerations().get(0); + assertThat(rule.getPromptKey(), is(equalTo("file-body"))); + assertThat(rule.getAiDefinitionKey(), is(equalTo("mock-model"))); + assertThat(rule.isFallback(), is(true)); + } +} diff --git a/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.json b/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.json new file mode 100644 index 0000000..4aaaa4f --- /dev/null +++ b/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.json @@ -0,0 +1,29 @@ +{ + "command": "GenerateFileIndex", + "srcMorph": { + "generationProvider": "mock", + "promptDefinitions": [ + { + "key": "file-body", + "template": "Summarize this file:\n%s" + } + ], + "aiDefinitions": [ + { + "key": "mock-model", + "modelPath": "unused-with-mock-provider.gguf", + "contextSize": 2048, + "maxOutputTokens": 64, + "temperature": 0.15, + "threads": 2 + } + ], + "fieldGenerations": [ + { + "promptKey": "file-body", + "aiDefinitionKey": "mock-model", + "fallback": true + } + ] + } +} diff --git a/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.yaml b/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.yaml new file mode 100644 index 0000000..9056963 --- /dev/null +++ b/srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.yaml @@ -0,0 +1,17 @@ +command: GenerateFileIndex +srcMorph: + generationProvider: mock + promptDefinitions: + - key: file-body + template: "Summarize this file:\n%s" + aiDefinitions: + - key: mock-model + modelPath: unused-with-mock-provider.gguf + contextSize: 2048 + maxOutputTokens: 64 + temperature: 0.15 + threads: 2 + fieldGenerations: + - promptKey: file-body + aiDefinitionKey: mock-model + fallback: true From cb5b3ea7fb6373d4a38a5d5b637de133134ad10f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 15:55:21 +0000 Subject: [PATCH 4/9] Fix missing requires org.slf4j in srcmorph's module-info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The indexer and engine packages log via SLF4J at runtime, but the module descriptor never declared `requires org.slf4j` — a real gap masked by Maven always loading these jars classpath-only. Adding the requires clause is enough to flip Surefire's module-path auto-detection on for this module (and transitively for the plugin module, which requires net.ladenthin.srcmorph), which then fails patching test-only dependencies (logback-classic, archunit-junit5) into the named module. Both modules now pin useModulePath=false for tests, matching the same setting already added to srcmorph-cli for the identical reason. --- llamacpp-ai-index-maven-plugin/pom.xml | 13 +++++++++++++ srcmorph/pom.xml | 17 +++++++++++++++++ srcmorph/src/main/java/module-info.java | 5 +++++ 3 files changed, 35 insertions(+) diff --git a/llamacpp-ai-index-maven-plugin/pom.xml b/llamacpp-ai-index-maven-plugin/pom.xml index 7800049..9b2ad01 100644 --- a/llamacpp-ai-index-maven-plugin/pom.xml +++ b/llamacpp-ai-index-maven-plugin/pom.xml @@ -297,6 +297,19 @@ SPDX-License-Identifier: Apache-2.0 **/VmlensInterleavingSmokeTest.java + + false
      diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml index 1232c55..c4e78d7 100644 --- a/srcmorph/pom.xml +++ b/srcmorph/pom.xml @@ -215,6 +215,23 @@ SPDX-License-Identifier: Apache-2.0 directory, and a JVM fatal-error log named per crashing PID. --> @{argLine} -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:ErrorFile=hs_err_pid%p.log + + false
      diff --git a/srcmorph/src/main/java/module-info.java b/srcmorph/src/main/java/module-info.java index 00718a3..6849132 100644 --- a/srcmorph/src/main/java/module-info.java +++ b/srcmorph/src/main/java/module-info.java @@ -47,6 +47,11 @@ // the @lombok.Generated annotation carried on generated members has CLASS retention. requires static lombok; + // indexer/ and engine/ classes log via SLF4J at runtime (not compile-time-only, unlike the + // annotation-only requires above), so this must be a real (non-static) requires: a + // module-path consumer needs org.slf4j resolvable at runtime too, not just here. + requires org.slf4j; + exports net.ladenthin.srcmorph.config; exports net.ladenthin.srcmorph.document; exports net.ladenthin.srcmorph.engine; From 161b641b2d2fde8bf507b1e991eaef127a6c041d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 16:15:28 +0000 Subject: [PATCH 5/9] Migration step 7: examples + reactor-level docs (plugin coordinates unchanged) Adds examples/ (config_Plan/GenerateFileIndex/All/Calibrate.json + a YAML twin, paired run_*.sh/.bat launchers, logbackConfiguration.xml) covering every srcmorph-cli command with the mock provider, plus a new ExamplesConfigBindingTest that sweeps every shipped fixture through the CLI's strict Jackson mappers. Rewrites the root README.md/CLAUDE.md for the 3-module reactor (srcmorph / srcmorph-cli / llamacpp-ai-index-maven-plugin), adds per-module READMEs (srcmorph: API tour of SrcMorphConfiguration + the four engines; srcmorph-cli: config-file reference + command table; llamacpp-ai-index-maven-plugin: relocated plugin doc, now noting its srcmorph dependency), and updates TODO.md/CHANGELOG.md for the current migration status. The Maven plugin's own coordinates, package, goal prefix, and aiIndex.* properties are explicitly documented as unchanged in this release -- the srcmorph-maven-plugin rename remains a deferred, separate step (tracked in TODO.md). Extends REUSE.toml for the new JSON/YAML example fixtures (and a pre-existing gap in the CLI's private test fixtures); reuse lint is fully compliant. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- CHANGELOG.md | 34 + CLAUDE.md | 629 ++++++++--------- README.md | 654 +++--------------- REUSE.toml | 10 + TODO.md | 56 +- examples/config_All.json | 56 ++ examples/config_Calibrate.json | 31 + examples/config_GenerateFileIndex.json | 37 + examples/config_GenerateFileIndex.yaml | 23 + examples/config_Plan.json | 37 + examples/logbackConfiguration.xml | 22 + examples/run_all.bat | 23 + examples/run_all.sh | 22 + examples/run_calibrate.bat | 24 + examples/run_calibrate.sh | 23 + examples/run_generate.bat | 23 + examples/run_generate.sh | 22 + examples/run_plan.bat | 23 + examples/run_plan.sh | 22 + llamacpp-ai-index-maven-plugin/README.md | 622 +++++++++++++++++ srcmorph-cli/README.md | 107 +++ .../cli/ExamplesConfigBindingTest.java | 107 +++ srcmorph/README.md | 147 ++++ 23 files changed, 1845 insertions(+), 909 deletions(-) create mode 100644 examples/config_All.json create mode 100644 examples/config_Calibrate.json create mode 100644 examples/config_GenerateFileIndex.json create mode 100644 examples/config_GenerateFileIndex.yaml create mode 100644 examples/config_Plan.json create mode 100644 examples/logbackConfiguration.xml create mode 100644 examples/run_all.bat create mode 100755 examples/run_all.sh create mode 100644 examples/run_calibrate.bat create mode 100755 examples/run_calibrate.sh create mode 100644 examples/run_generate.bat create mode 100755 examples/run_generate.sh create mode 100644 examples/run_plan.bat create mode 100755 examples/run_plan.sh create mode 100644 llamacpp-ai-index-maven-plugin/README.md create mode 100644 srcmorph-cli/README.md create mode 100644 srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java create mode 100644 srcmorph/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ac9c7ce..88163c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,40 @@ The release procedure (prompt template and step-by-step instructions) lives in [ ## [Unreleased] +### Added +- **Reactor split**: the former single-module `llamacpp-ai-index-maven-plugin` is now a 3-module + Maven reactor under a new parent, `net.ladenthin:srcmorph-parent` — `srcmorph` (new core library, + `net.ladenthin:srcmorph`, framework-free — no Maven Plugin API dependency), `srcmorph-cli` (new + standalone CLI, `net.ladenthin:srcmorph-cli`, driven by a single JSON/YAML configuration file, + ships as a `java -jar`-ready fat jar), and `llamacpp-ai-index-maven-plugin` (the original plugin, + now a thin wrapper depending on `srcmorph`). All three (plus the parent pom) release together at + one shared version. +- New engine layer in `srcmorph` (`GenerateEngine`, `AggregatePackagesEngine`, + `AggregateProjectEngine`, `CalibrateEngine`) extracted from what used to be each mojo's + `execute()` body, plus a new shared root configuration object, + `net.ladenthin.srcmorph.config.SrcMorphConfiguration`, bindable identically from Maven plexus XML, + Jackson JSON/YAML (the new CLI), or plain Java code. +- New `examples/` directory at the repo root: paired `config_*.json`/`.yaml` fixtures for every + `srcmorph-cli` command (`Plan`, `GenerateFileIndex`, `All`, `Calibrate`), paired `run_*.sh`/`.bat` + launcher scripts, and an example `logbackConfiguration.xml` — all runnable out of the box with the + `mock` provider (no GGUF model required). +- Per-module `README.md` files (`srcmorph/README.md`, `srcmorph-cli/README.md`) and a rewritten, + product-level root `README.md`/`CLAUDE.md` describing the reactor. + +### Changed +- Logging in the extracted core/CLI layers moved from a constructor-injected Maven `Log` to + `org.slf4j.Logger` (see the `1.0.x` entries below for the indexer-layer half of this change, + already shipped before the reactor split). + +### Notes +- **This release does not change the Maven plugin's own coordinates, package, goal prefix, or + `@Parameter` property names.** `net.ladenthin:llamacpp-ai-index-maven-plugin`, package + `net.ladenthin.maven.llamacpp.aiindex`, goal prefix `ai-index`, and every `aiIndex.*` property are + unchanged — existing consumers of the plugin see no breaking change in this release. A + **future** release will rename the plugin to `net.ladenthin:srcmorph-maven-plugin` with a Maven + Central relocation POM for the old coordinates; that rename is a deliberately separate, later step + (tracked in `TODO.md`) and has not happened yet. + ## [1.0.2] - 2026-07-02 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 481db1e..1670822 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,339 +1,257 @@ -# CLAUDE.md — llamacpp-ai-index-maven-plugin +# CLAUDE.md — srcmorph (reactor) -This document provides guidance for AI assistants working on the llamacpp-ai-index-maven-plugin codebase. +This document provides guidance for AI assistants working on this codebase. --- ## Project Overview -**llamacpp-ai-index-maven-plugin** is a free Maven plugin that generates AI-readable hierarchical index and summary files for Java source code projects using llama.cpp-compatible local models (GGUF format). It produces structured `.ai.md` files with metadata headers and AI-generated summaries for both individual source files and packages. +**srcmorph** is a prompt-driven source-tree transformer: it walks a source tree and processes each +file through a configurable local LLM prompt (via llama.cpp / GGUF models, no cloud calls), producing +layered output — per-file, then per-package, then per-project. Today it emits structured `.ai.md` +Markdown summaries for AI-assisted code navigation; the same rule-routed pipeline is generic enough +to eventually emit wikis, architecture docs, diagrams, or source-to-source transformations. + +**This repository is mid-migration.** It started as a single Maven plugin +(`net.ladenthin:llamacpp-ai-index-maven-plugin`) and is being restructured into a 3-module Maven +reactor: a framework-free core library, a standalone CLI, and the original plugin (now a thin +wrapper around the library). The migration is deliberately staged so every intermediate commit stays +green; **the plugin's own Maven coordinates, package, and property names have NOT changed yet** — +that rename (to `net.ladenthin:srcmorph-maven-plugin`) is a separate, deferred final step (tracked in +`TODO.md`), done only once the rest of the reactor has shipped at least one real release. Do not +assume the rename has happened, and do not invent `srcmorph.*` Maven `@Parameter` property names — +the plugin still uses `aiIndex.*` (see the plugin module's own section below). - **Group ID:** `net.ladenthin` -- **Artifact ID:** `llamacpp-ai-index-maven-plugin` -- **Version:** 1.0.2-SNAPSHOT -- **Java:** target bytecode 1.8, built with JDK 21 +- **Java:** target bytecode 1.8 (production code), Java 21 test sources, built with JDK 21 - **License:** Apache 2.0 - **Author:** Bernard Ladenthin (Copyright 2026) -- **Plugin goal prefix:** `ai-index` +- **Reactor version:** `1.1.0-SNAPSHOT` (single shared version across all reactor modules) --- -## Build System +## Repository layout — Maven reactor -The project uses **Maven** (minimum 3.6.3). - -### Common Commands - -```bash -# Compile only -mvn compile - -# Run all tests -mvn test - -# Build the plugin JAR -mvn package - -# Build without tests -mvn package -DskipTests - -# Run the plugin against itself (self-test profile) -mvn ai-index:generate -P ai-index-selftest - -# Install to local Maven repository -mvn install +``` +llamacpp-ai-index-maven-plugin/ (repo root; reactor parent) +├── pom.xml net.ladenthin:srcmorph-parent:1.1.0-SNAPSHOT (packaging=pom) +│ shared build plugins + dependencyManagement + release profile +├── srcmorph/ CORE LIBRARY net.ladenthin:srcmorph (Java 8, Maven-API-free) +│ └── src/main/java/net/ladenthin/srcmorph/ +│ ├── config/ 18+ POJOs (incl. the shared root SrcMorphConfiguration) +│ ├── engine/ GenerateEngine, AggregatePackagesEngine, AggregateProjectEngine, +│ │ CalibrateEngine, SrcMorphException, GenerateResult, CalibrationReport, EngineSupport +│ ├── indexer/ SourceFileIndexer, PackageIndexer, ProjectIndexer, AiFieldGenerationSupport, ... +│ ├── document/ prompt/ provider/ support/ +│ └── src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf (real-model tests live here) +├── srcmorph-cli/ CLI net.ladenthin:srcmorph-cli (fat jar = deliverable) +│ └── src/main/java/net/ladenthin/srcmorph/cli/ +│ ├── Main.java BitcoinAddressFinder cli/Main.java pattern +│ └── configuration/ CConfiguration + CCommand (BAF public-field style) +├── llamacpp-ai-index-maven-plugin/ Maven plugin net.ladenthin:llamacpp-ai-index-maven-plugin +│ └── src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/ (5 mojos; UNCHANGED coordinates/package) +├── examples/ config_*.json/.yaml + run_*.sh/.bat + logbackConfiguration.xml +├── docs/ RELEASE.md + the ai-index model-benchmark writeups +└── .github/workflows/ CI (still keyed to the pre-reactor layout in places; step 8) ``` -### JVM / Compiler Configuration +Every module inherits its `` from the parent (`net.ladenthin:srcmorph-parent`), so they ship +in lockstep by construction. Bump the version reactor-wide with +`mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false` from the repo root. + +### `srcmorph` — the core library + +Framework-free: **no dependency on `org.apache.maven..`** anywhere (enforced by +`CoreArchitectureTest#coreIsMavenFree`, the load-bearing ArchUnit rule for this module). Depends on +`net.ladenthin:llama` (the llama.cpp JNI binding, used only by the `provider` package), SLF4J, jspecify ++ checker-qual, Lombok (provided). Package root: `net.ladenthin.srcmorph`. + +- **`config/`** — mutable JavaBeans (no Maven annotations) bindable structurally from Maven plexus XML, + a Jackson `ObjectMapper`/`YAMLMapper`, or plain Java code. The root object is + **`SrcMorphConfiguration`**: one bean holding everything a run needs (`baseDirectory`, + `outputDirectory`, `subtrees`, `excludes`, `fileExtensions`, the size band, `force`, `planOnly`, + `generationProvider`, `promptDefinitions`, `aiDefinitions`, `fieldGenerations`, `factDefinitions`, the + `llama*` fallback params, `pluginVersion`/`aiVersion`/`projectName`). **Field names intentionally + mirror the Maven plugin's current `@Parameter` names** so a JSON/YAML key reads identically to the + matching `` XML element — see `SrcMorphConfiguration`'s own Javadoc. +- **`engine/`** — one class per phase, each constructed from a `SrcMorphConfiguration` and owning its + own AI provider lifecycle (try-with-resources; one model resident at a time): + `GenerateEngine` (plan → validate → planOnly early-out → per-model-group indexing loop + progress + bar), `AggregatePackagesEngine`, `AggregateProjectEngine` (deterministic listing + optional one-call + AI overview), `CalibrateEngine` (per-model preflight + timing). All four throw the checked + `SrcMorphException` on misconfiguration, never a Maven `MojoExecutionException` — callers (the + plugin's mojos, the CLI's `Main`) wrap it into whatever their own surface expects. +- **`indexer/`** — the walk/plan/write orchestration (`SourceFileIndexer`, `PackageIndexer`, + `ProjectIndexer`, `AiFieldGenerationSupport`, `AiIndexPlan`, `AiCalibrationRunner`, ...). Logs via + `org.slf4j.Logger` (a private static final field per class), not a Maven `Log` — this is what makes + the module Maven-free; Maven's own `maven-slf4j-provider` (ships since Maven ≥ 3.1) makes these lines + surface as ordinary `[INFO]`/`[WARN]` output inside a plugin execution with zero glue, and the CLI + ships a logback binding for the same log lines outside Maven. +- **`document/`** — the `.ai.md` model + codecs (`AiMdDocument`, `AiMdHeader`, `AiMdDocumentCodec`, + `AiMdHeaderCodec`, `AiMdHeaderSupport`, `AiMdChildEntryLineFormatter`, `AiMdLeadExtractor`, + `AiGenerationRequest`/`AiGenerationResult`). +- **`prompt/`** — `AiPromptDefinition`, `AiPreparedPrompt`, `AiPromptSupport`, + `AiPromptPreparationSupport`. +- **`provider/`** — the AI backend abstraction: `AiGenerationProvider` (`Closeable`), + `AiGenerationProviderFactory` (looks up `"mock"` / `"llamacpp-jni"`), `MockAiGenerationProvider`, + `LlamaCppJniAiGenerationProvider`, `LlamaCppJniConfig` + `LlamaCppJniConfigFactory` (the pure 26-field + mapping from a resolved `AiGenerationConfig` to the native binding's parameter objects — extracted + from the old mojo so it is unit- and PIT-testable without a Maven runtime), `AiCompletionParser`. +- **`support/`** — foundation helpers with no dependency on anything above them: `AiChecksumSupport`, + `AiTimeSupport`, `AiPathSupport`, `AiSourceExcludeFilter`, `AiProgressBar`, `AiSourceChunker`, + `AiDeterministicSummary`, `AiGenerationTimeEstimator`, `Java8CompatibilityHelper`, `ConvertToRecord`. + +**Architecture rules** (`CoreArchitectureTest`, ArchUnit): `coreIsMavenFree` (the load-bearing rule +above), `layeredArchitecture` (`engine` on top → `indexer` → `provider`/`document`/`prompt` → `config` +→ `support`), `noPackageCycles`, `loggersArePrivateStaticFinal`, `noSystemExit`, +`noTestFrameworksInProduction`, `noJavaUtilLogging`, `noSystemOutOrErrInProduction`, +`noInternalJdkImports`, `noPublicMutableFields`, `noNewRandom`, `noThreadSleep`, +`jniConfinedToProvider` (only the `provider` package may touch the llama.cpp JNI binding). + +**Test suite** (`srcmorph/src/test/java/net/ladenthin/srcmorph/`, package-renamed 1:1 with production): +~63 test files, incl. jqwik properties, an ArchUnit suite, a Lincheck race test +(`AiGenerationKindLincheckTest`), and the model-backed real tests gated on +`src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf`. **PIT mutation testing**: `mutationThreshold` +100 over an explicit `targetClasses` list in `srcmorph/pom.xml` — currently 47 classes across +config/document/indexer/prompt/provider/support, all killed at 100%. `srcmorph-cli` and the plugin +module do not have a PIT gate yet (see `TODO.md`). The `gpu-cuda`/`gpu-vulkan` profiles (swap the +`net.ladenthin:llama` classifier via the `llama.classifier` property) live here; the `jcstress` and +`vmlens` profiles/tests currently still live in the **plugin** module (they were not moved in the +extraction — see that module's section below), not here. + +### `srcmorph-cli` — the standalone CLI + +`net.ladenthin:srcmorph-cli`, packaging `jar`, package root `net.ladenthin.srcmorph.cli`. A BAF-style +CLI driven by a single JSON or YAML configuration file: + +- **`cli/Main.java`** — extension-dispatched loader (`.json`/`.js` → Jackson `ObjectMapper`, + `.yaml`/`.yml` → `YAMLMapper`, both with `FAIL_ON_UNKNOWN_PROPERTIES` enabled so a config typo fails + fast, mirroring what plexus does on the Maven XML side); echoes the parsed configuration back + (re-serialized as both JSON and YAML) for review before anything runs; no `System.exit` anywhere — a + failure propagates as an unchecked exception out of `main(String[])`. Dispatches on `CConfiguration`'s + `command` field to one or more `net.ladenthin.srcmorph.engine.*` engines. +- **`cli/configuration/CConfiguration`** / **`CCommand`** — public-mutable-field JavaBeans (the BAF + `cli.configuration.CConfiguration` convention; carved out of the `noPublicMutableFields` ArchUnit rule + via this package's explicit exception). `CConfiguration.srcMorph` is the **same** + `net.ladenthin.srcmorph.config.SrcMorphConfiguration` the Maven plugin's mojos build from their own + `@Parameter` fields — a JSON/YAML key under `srcMorph` reads identically to the matching plugin XML + element. `CCommand` is `Plan | GenerateFileIndex | AggregatePackages | AggregateProject | All | + Calibrate`; the default is `Plan` (safe: no model load, nothing written). +- The fat jar (`srcmorph-cli--jar-with-dependencies.jar`, main class + `net.ladenthin.srcmorph.cli.Main`) is bound **unconditionally** to the `package` phase (a deliberate + divergence from BAF's `-P assembly` opt-in — for this module the fat jar IS the deliverable). +- Ships its own logback binding (`ch.qos.logback:logback-classic`, runtime scope) — unlike the library + (consumer picks any SLF4J binding) and the plugin (gets one for free from Maven's own + `maven-slf4j-provider`), a standalone `java -jar` process needs to bring its own or every log line is + silently dropped. +- **Architecture rules** (`CliArchitectureTest`): `cliIsLeaf` (nothing else in the reactor may depend on + this module — it is the leaf-most consumer), `noPublicMutableFields` (with the `configuration` + package carve-out), `noSystemExit`, `mavenFree` (must never depend on the Maven Plugin API — that + boundary belongs to the plugin module), `noTestFrameworksInProduction`, `noInternalJdkImports`, + `loggersArePrivateStaticFinal`. +- **Tests**: `MainTest`, `configuration.ConfigBindingTest` (round-trips a private + `src/test/resources/test-fixtures/minimal-generate.{json,yaml}` pair through both parsers), + `ExamplesConfigBindingTest` (sweeps every shipped `examples/config_*.{json,yaml}` fixture — the + public, documented examples — through the same strict mappers), `CliEndToEndTest` (drives the `All` + and `Plan` commands against the mock provider end to end, no forked process). + +### `llamacpp-ai-index-maven-plugin` — the Maven plugin (pre-rename coordinates) + +**Unchanged in this migration step**: coordinates `net.ladenthin:llamacpp-ai-index-maven-plugin`, +package `net.ladenthin.maven.llamacpp.aiindex`, goal prefix `ai-index`, every `@Parameter` property +still spelled `aiIndex.*` (e.g. `aiIndex.skip`, `aiIndex.file.skip`, `aiIndex.planOnly`, +`aiIndex.generationProvider`, `aiIndex.llama.modelPath`). Only the plugin's *contents* shrank: the +former ~65-class single module now depends on `net.ladenthin:srcmorph` (compile scope) for everything +except the 5 mojo classes themselves. + +- **`AbstractAiIndexMojo`** — shared `@Parameter` fields + `buildConfiguration()`, which maps them onto + a new `SrcMorphConfiguration` for the matching engine to run. Concrete mojos + (`GenerateMojo`/`AggregatePackagesMojo`/`AggregateProjectMojo`/`CalibrateMojo`) each add their own + goal-specific `@Parameter`s (e.g. `skipFile`/`skipPackage`/`skipProject`, `planOnly`, `fileExtensions`, + `excludes`, `factDefinitions`), build the configuration, and delegate the whole run to one + `net.ladenthin.srcmorph.engine.*` engine — mojos are now thin (≤ ~30 lines of actual logic each) and + translate a caught `SrcMorphException`/`IOException` into a `MojoExecutionException`. +- **Skip flags stay mojo-side** (`skip`, `skipFile`, `skipPackage`, `skipProject`) — a Maven lifecycle + concern, not part of `SrcMorphConfiguration`; an engine built from a configuration always executes + when asked. See `MojoPhaseSkipTest`. +- **A future release will relocate this plugin** to `net.ladenthin:srcmorph-maven-plugin` (goal prefix + `srcmorph`, package `net.ladenthin.maven.srcmorph.mojo`, properties `srcmorph.*`) with a Maven Central + relocation POM for the old coordinates. **That has not happened yet** — do not write plugin + documentation, XML examples, or property names as if it had (see `TODO.md` for the tracked step). +- **Architecture rules** (`PluginArchitectureTest`): Maven-annotation confinement to `mojo`, every mojo + extends `AbstractMojo`, plus this module's slice of the shared conventions. +- **jcstress** (`jcstress/AiGenerationKindRace.java`) and **vmlens** + (`vmlens/VmlensInterleavingSmokeTest.java`) tests/profiles currently live in this module, not in + `srcmorph` — they were not relocated during the core extraction. +- Full goal/parameter reference: `llamacpp-ai-index-maven-plugin/README.md`. -- Java 1.8 source and target (compiled with JDK 21) -- UTF-8 encoding -- `maven-enforcer-plugin` requires Maven ≥ 3.6.3 +--- -### Offline / Restricted-Network Environments +## Build Commands -When Maven cannot reach the internet (proxy, air-gap, restricted CI), use the options below. +### Whole reactor (repo root) -**Offline Maven (requires warm `~/.m2/repository` cache):** ```bash -# Run tests without downloading anything -mvn test -o - -# Package without downloading anything -mvn package -o -DskipTests +mvn compile # Compiles every module (Java + generates nothing native; pure Java reactor) +mvn test # Runs every module's tests +mvn package # Builds all four artifacts (parent pom + 3 jars, incl. the CLI's fat jar) +mvn install # Installs all four into ~/.m2 (needed before iterating on a single module — see below) ``` -The cache is warm after any previous successful `mvn test` or `mvn install` run. +### Iterating on one module -**Direct `javac` compilation (fallback, no Maven required):** -```bash -# Gather classpath from already-downloaded JARs -CP=$(find ~/.m2/repository -name "*.jar" | tr '\n' ':') -OUT=/tmp/aiindex-classes && mkdir -p "$OUT" +Maven resolves inter-module dependencies (`llamacpp-ai-index-maven-plugin` and `srcmorph-cli` both +depend on `net.ladenthin:srcmorph`) via the local repository, not in-reactor classes, unless you use +`-pl`/`-am`: -# Compile production sources -find src/main/java -name "*.java" | xargs javac -cp "$CP" -d "$OUT" --release 8 +```bash +# Build/install the core first if you're iterating on the CLI or the plugin against local core changes: +mvn -pl srcmorph -am install -DskipTests -# Compile test sources (after production classes are compiled) -TOUT=/tmp/aiindex-test-classes && mkdir -p "$TOUT" -find src/test/java -name "*.java" | xargs javac -cp "$CP:$OUT" -d "$TOUT" --release 8 +# Then work on just one module: +mvn -pl srcmorph-cli test +mvn -pl llamacpp-ai-index-maven-plugin test ``` -Zero compiler output means zero errors. +### Offline / restricted-network environments ---- - -## Project Structure - -``` -llamacpp-ai-index-maven-plugin/ -├── src/ -│ ├── main/ -│ │ └── java/net/ladenthin/maven/llamacpp/aiindex/ # layered packages (top → bottom) -│ │ ├── mojo/ # Entry: AbstractAiIndexMojo, GenerateMojo, AggregatePackagesMojo -│ │ ├── indexer/ # Orchestration: SourceFileIndexer, PackageIndexer, AiFieldGenerationSupport -│ │ ├── provider/ # AI backends: AiGenerationProvider(+Factory), Mock/LlamaCppJni providers, -│ │ │ # AiCompletionParser, LlamaCppJniConfig -│ │ ├── document/ # .ai.md model + codecs: AiMdDocument, AiMdHeader, AiMd*Codec, -│ │ │ # AiMdHeaderSupport, AiMdChildEntryLineFormatter, -│ │ │ # AiGenerationRequest, AiGenerationResult (carry an AiMdHeader) -│ │ ├── prompt/ # AiPromptDefinition, AiPreparedPrompt, AiPromptSupport, AiPromptPreparationSupport -│ │ ├── config/ # AiGenerationConfig, AiGenerationKind, AiFieldGenerationConfig, -│ │ │ # AiFieldGenerationSelector, AiModelDefinition, AiModelDefinitionSupport -│ │ └── support/ # Foundation: AiChecksumSupport, AiTimeSupport, AiPathSupport, -│ │ # AiSourceExcludeFilter, Java8CompatibilityHelper, ConvertToRecord -│ ├── site/ -│ │ └── ai/ # Output directory for .ai.md files -│ └── test/ -│ ├── java/net/ladenthin/maven/llamacpp/aiindex/ -│ │ └── *.java # JUnit Jupiter tests -│ └── resources/ -│ └── SmolLM2-135M-Instruct-Q3_K_M.gguf # Small test model -├── .github/workflows/ # CI/CD pipelines -├── pom.xml -└── README.md +```bash +mvn test -o # requires a warm ~/.m2/repository cache +mvn package -o -DskipTests ``` ---- +### Run the self-test profile (plugin module only) -## Core Architecture - -### Three-Phase Operation - -The plugin operates in three logical phases, building a navigable index from fine to coarse: - -**Phase 1 — File Indexing & Summarization** -``` -[Source .java files] → SourceFileIndexer → [*.java.ai.md files (deterministic header + AI body)] -``` -The `generate` goal is **plan-then-execute** and **rule-routed**: it first walks all candidate files, -routes each via the `` rules to a `(model, prompt)` — or a *skip*, or the explicit -*fallback* — and logs a tree grouped by model (which file gets which model + prompt). It then loads -**each model once** (groups sharing an `aiDefinitionKey`, sequentially → one model resident at a time, -bounded RAM) and indexes that group's files. A rule matches by a composable **`` tree** — ``/`` (each wraps its children in -``), `` (a single nested condition), over leaves -``, `` (`min`/`max` bytes), `` (`min`/`max`), ``/`` -(ISO-8601 instant vs file mtime), `` (base-relative glob) — evaluated by `AiConditionEvaluator` -against an `AiFileContext`; new leaf kinds are one field + one branch. Among matches the highest -`priority` wins (ties by declaration order). `true` ignores matching files (a high-priority -skip beats routes and the fallback); exactly one `true` (no condition) catches the -rest; a file matching no rule and no fallback **fails the build**. `aiIndex.planOnly=true` prints the -Markdown plan (file → rule id → prompt → rough time estimate, summed per model and overall) and stops -(no model loaded). The plan also computes each routed file's **context-window fit** up front (via -`AiInputWindowCalculator`, the same threshold the run uses to trim): a file larger than its routed -model's window is flagged in the plan and, **by default (`onOversize=fail`), fails the build** (a hard -abort) — the plugin never auto-picks a model. The resolution is **configuration only**, via the rule's -**``** strategy (`AiOversizeStrategy`): `fail` (default), `sample` (trim to the window head, -one call), `mapReduce` (chunk at line boundaries → summarize each → combine via a **hierarchical reduce** -that batches partials to fit the window so whole-file `=0` coverage isn't trimmed away; -`` bounds the time — `AiSourceChunker`; the chunk window carries a token-safety headroom so a -token-dense chunk can't overflow the model context), or `deterministic` (model-free metadata+sample body -— `AiDeterministicSummary`). -So oversized files are either routed to a larger-context model or handled by a strategy; only `fail` -entries abort. A rule may also carry an orthogonal, language-agnostic **``** list (`AiFactCounter` -+ `AiFactExtractor`): each `{label, pattern}` reports its regex match count over the **whole** source, -prepended to the body of **every** file the rule matches (oversize or not) — exact structural counts -(SQL `INSERT` rows / tables / views, Java type declarations / `\bboolean\b` fields, …) that give -downstream agents authoritative numbers the (possibly sampled) AI prose cannot reliably produce. A fact -set can be defined once in a shared `` group and referenced from many rules by -`` (`AiFactDefinition` + `AiFactDefinitionSupport`, resolved onto each rule before indexing), -instead of repeating the `` block inline. Generation is also guarded by a **retry-on-overflow**: -if the provider rejects a prompt as exceeding the context (the char-based trim under-counted tokens on -dense content), the call is re-trimmed to a smaller window and retried rather than failing the build. See `AiFieldGenerationSelector` (selection + validation), `AiCondition`/ -`AiConditionEvaluator` (the tree), and `AiIndexPlan` (the rendered plan). This is how one run can index -different file kinds/sizes with different models *and* prompts. - -**Phase 2 — Package Aggregation & Summarization** -``` -[*.java.ai.md files] → PackageIndexer → [package.ai.md files (deterministic header + AI body)] +```bash +mvn -pl llamacpp-ai-index-maven-plugin ai-index:generate -P ai-index-selftest ``` -**Phase 3 — Project Index (deterministic listing; optional AI overview)** -``` -[package.ai.md files] → ProjectIndexer → [one project.ai.md: per-package lead (body) + link (header F)] -``` -Phase 3 harvests the one-sentence blockquote lead each `package.ai.md` already begins with -(via `AiMdLeadExtractor`) and writes a single, always-loadable table of contents linking to -every package — the entry point an agent reads first to navigate project → package → file → raw. -The per-package listing calls no model, so it is cheap and scales to hundreds of packages (no -embeddings/vector DB). **Optional (opt-in):** when a `` is configured on the -goal, *one* extra AI call writes a short `#### Overview` paragraph from the package leads (never -the full bodies). Incrementality is preserved by folding the overview generation signature -(`promptKey:aiDefinitionKey`) — not the AI output — into the `c` checksum, so an unchanged project -is never re-inferred but enabling/switching the overview, or a package change, rebuilds it. - -**Each phase is independently switchable.** Every goal is a separate Maven execution, and each can be -toggled on/off on its own via a phase-specific skip flag named after the index level (`aiIndex.file.skip`, -`aiIndex.package.skip`, `aiIndex.project.skip` — the `x` node types), with `aiIndex.skip` as a global -"skip all" switch — so you can run nothing, all three, or any subset (e.g. file + project only). The -mechanism is generalized symmetrically: `AbstractAiIndexMojo` owns the global `skip`, the abstract -`isPhaseSkipped()` seam, and `shouldSkip() = skip || isPhaseSkipped()`; each concrete mojo adds exactly -one `skip` `@Parameter` plus a one-line `isPhaseSkipped()` override and calls `shouldSkip()` at -the top of `execute()`. Covered by `MojoPhaseSkipTest`. - -### Key Components - -| Class | Role | -|---|---| -| `AbstractAiIndexMojo` | Shared `@Parameter` fields and utilities for the AI generate/aggregate-packages mojos | -| `GenerateMojo` | Phase 1: index + summarize source files | -| `AggregatePackagesMojo` | Phase 2: aggregate + summarize package index files | -| `AggregateProjectMojo` | Phase 3: build the single `project.ai.md` (deterministic listing; extends `AbstractAiIndexMojo` and builds a provider **only** when a `` opts into the AI overview) | -| `SourceFileIndexer` | `collectCandidates` (walk + filters) / `classify` (→ `AiIndexPlan`) / `indexFile` (write one file with a given rule + provider) — split so a run plans first and loads one model per group | -| `AiFieldGenerationSelector` | Routes a file to a rule by its `` + priority; explicit fallback; `skip`; plus `validate(rules)` (fail-fast config check) | -| `AiCondition` / `AiConditionEvaluator` | Composable and/or/not condition tree (leaves: extensions/size/lines/modifiedAfter/modifiedBefore/pathGlob) + its evaluator (`matches`/`validate`/`usesLines`) | -| `AiIndexPlan` | The routing plan: routes grouped by model id, skips, unmatched, per-file context-window fit; renders the up-front tree | -| `AiInputWindowCalculator` | Pure calculator for the input window (max source chars before trimming + whether a source exceeds it); single source of truth shared by the run-time trim (`AiFieldGenerationSupport`) and the plan-time over-window check (`SourceFileIndexer.classify`) | -| `AiOversizeStrategy` | Enum of the per-rule `` strategies (`fail`/`sample`/`mapReduce`/`deterministic`) + case-insensitive parser; default `fail` | -| `AiSourceChunker` | Pure line-boundary chunker for `mapReduce` (overlap + `maxChunks` representative sampling) | -| `AiFactCounter` | Maven `@Parameter` POJO for one `` — a `{label, pattern}` deterministic counter | -| `AiFactExtractor` | Pure: counts each `` regex over the whole source → the exact "facts" block prepended to the body (+ fail-fast pattern validation) | -| `AiFactDefinition` | Maven `@Parameter` POJO for a named, reusable `` group `{key, facts}` | -| `AiFactDefinitionSupport` | Key-indexed lookup: resolves each rule's `` to its shared group's counters (copies onto the rule) | -| `CalibrateMojo` / `AiCalibrationRunner` | `ai-index:calibrate` goal (thin orchestration) + the indexer-layer measurer (warmup + two sized generations, prefers the binding's reported throughput, else a wall-clock differential) → `AiCalibrationMeasurement` | -| `AiCalibration` | `` `@Parameter` POJO on `` (`prefillTokensPerSecond`/`decodeTokensPerSecond`/`charsPerToken`); makes `AiGenerationTimeEstimator` use measured per-machine rates instead of the built-in reference-CPU model | -| `AiDeterministicSummary` | Pure model-free body builder for `deterministic` (size, line count, head/tail sample) | -| `AiProgressBar` | Pure ASCII progress-bar renderer (`[##### ] 42%`); `GenerateMojo` logs it after each file, advancing by the running sum of per-file plan estimates over the grand total (with estimated time left + actual elapsed) | -| `PackageIndexer` | Creates `package.ai.md` files with contents listings, calls AI to fill the document body | -| `ProjectIndexer` | Phase 3: harvests each package's lead + relative link into one `project.ai.md`; deterministic listing, with an optional one-call AI `#### Overview` from the leads | -| `AiMdLeadExtractor` | Pure extraction of the one-line blockquote lead from an `.ai.md` body (fallback: first non-blank line) | -| `AiGenerationProvider` | Interface for AI backends (llama.cpp JNI or mock) | -| `AiFieldGenerationSupport` | Shared field-generation loop extracted from both indexers | -| `AiGenerationResult` | Immutable carrier for the AI-generated body text out of the loop | -| `AiModelDefinition` | Maven `@Parameter` POJO for a named AI model definition | -| `AiModelDefinitionSupport` | Key-indexed lookup: converts `AiModelDefinition` → `AiGenerationConfig` | -| `AiMdDocumentCodec` | Reads and writes `.ai.md` files | -| `AiMdHeaderCodec` | Encodes/decodes the YAML-like metadata header | -| `AiPromptSupport` | Looks up prompt templates by key | -| `AiPromptPreparationSupport` | Prepares prompts with source substitution and trimming | - -### Document Format - -Each `.ai.md` file begins with a metadata header block: +### Run the CLI -``` -### main/java/com/example -- H: 1.0 -- C: A1B2C3D4 -- D: 2026-01-01T00:00:00Z -- T: 2026-01-01T00:01:00Z -- G: 0.1.0 -- A: 1.0.0 -- X: package -- F: [MyClass.java](MyClass.java.ai.md) -- F: [sub/](sub/package.ai.md) ---- -> Handles example domain logic for ... -(AI-generated body text continues here) +```bash +mvn -pl srcmorph-cli package +java -jar srcmorph-cli/target/srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar examples/config_All.json ``` -The header carries only deterministic metadata. All AI-generated -content lives in the document body after the `---` separator, keeping -the header machine-parseable without AI involvement (see -`AiMdHeader.java` Javadoc for the rationale). The repeatable `F` field -holds the deterministic child links — for a `package`, one per child -`.ai.md` (files and sub-packages); for a `project`, one per package — -so navigation (project → package → file) stays uniformly in the header -while the body stays free-form. `AiMdDocumentCodec` parses only the -header block, so a `- F:` line in the body is never read as a link. - -| Field | Meaning | -|---|---| -| `h` | Header format version | -| `title` | File or package path | -| `c` | CRC32 checksum of the source file (8-char uppercase hex; see `AiChecksumSupport`) | -| `d` | Index creation timestamp (ISO-8601) | -| `t` | Last generation timestamp | -| `g` | Plugin version (`project.version`) | -| `a` | AI model version | -| `x` | Node type: `file`, `package`, or `project` | -| `F` | Repeatable child link (markdown), e.g. `[Foo.java](Foo.java.ai.md)`; the navigation list for `package`/`project` nodes, absent for `file` | - -### Provider Pattern - -`AiGenerationProvider` is a `Closeable` interface for AI backends: - -| Implementation | Description | -|---|---| -| `LlamaCppJniAiGenerationProvider` | Uses the `net.ladenthin:llama` JNI binding to run local GGUF models | -| `MockAiGenerationProvider` | Returns deterministic mock responses; used in all tests | - -`AiGenerationProviderFactory` selects the provider by name (`"llamacpp-jni"` or `"mock"`). - ---- - -## Maven Plugin Goals - -| Goal | Description | -|---|---| -| `ai-index:generate` | Phase 1: index source files and fill the AI-generated document body | -| `ai-index:aggregate-packages` | Phase 2: aggregate package index files and fill the AI-generated document body | -| `ai-index:aggregate-project` | Phase 3: harvest per-package leads into one `project.ai.md` (deterministic listing; optional one-call AI `#### Overview` when a `` is configured) | -| `ai-index:calibrate` | Preflight + per-machine timing (between `planOnly` and a real run): loads each routed model once (catches bad path / OOM / wrong native), measures prefill/decode throughput, and prints a paste-ready `` block per `` | - -### Key Parameters (`GenerateMojo`) - -| Parameter | Property | Default | Description | -|---|---|---|---| -| `outputDirectory` | `aiIndex.outputDirectory` | `${basedir}/src/site/ai` | Where `.ai.md` files are written | -| `skip` | `aiIndex.skip` | `false` | Global switch: skip **every** phase | -| `skipFile` | `aiIndex.file.skip` | `false` | Skip only the **file** phase (`generate` goal) | -| `skipPackage` | `aiIndex.package.skip` | `false` | Skip only the **package** phase (`aggregate-packages` goal) | -| `skipProject` | `aiIndex.project.skip` | `false` | Skip only the **project** phase (`aggregate-project` goal) | -| `force` | `aiIndex.force` | `false` | Regenerate even if summary exists | -| `subtrees` | `aiIndex.subtrees` | *(all)* | Limit to specific source subdirectories | -| `fileExtensions` | `aiIndex.fileExtensions` | `.java` | File extensions to index | -| `excludes` | `aiIndex.excludes` | *(none)* | Glob patterns (base-relative, `/` separators) for source files to skip, e.g. `**/package-info.java`, `**/generated/**` (`AiSourceExcludeFilter`) | -| `minFileSizeBytes` | `aiIndex.file.minSizeBytes` | `0` | Exclusive lower size bound; files `<=` this are skipped (`0` = no lower bound). For size-tiering across multiple `generate` executions | -| `maxFileSizeBytes` | `aiIndex.file.maxSizeBytes` | `0` | Inclusive upper size bound; files `>` this are skipped (`0` = unlimited). Pairs with `minFileSizeBytes` to form a band | -| `planOnly` | `aiIndex.planOnly` | `false` | Print the routing plan tree (model → files → prompt → window fit) and stop; no model loaded, nothing generated | -| `generationProvider` | `aiIndex.generationProvider` | `mock` | `mock` or `llamacpp-jni` | -| `llamaModelPath` | `aiIndex.llama.modelPath` | — | Path to GGUF model file | -| `llamaContextSize` | `aiIndex.llama.contextSize` | `2048` | Context window size | -| `llamaMaxOutputTokens` | `aiIndex.llama.maxOutputTokens` | `128` | Max generated output tokens | -| `llamaTemperature` | `aiIndex.llama.temperature` | `0.15` | Sampling temperature | -| `llamaThreads` | `aiIndex.llama.threads` | `2` | CPU threads for inference | +See `examples/` (repo root) for ready-to-run `config_*.json`/`.yaml` + paired `run_*.sh`/`.bat` +launcher scripts, all using the `mock` provider (no GGUF model required). --- ## Testing -### Frameworks - -- **JUnit Jupiter** (6.1.0) — test runner (`@Test`, `@BeforeEach`, `@TempDir`) -- **Hamcrest** — matchers (`assertThat`, `is`, `equalTo`) -- **`MockAiGenerationProvider`** — deterministic AI responses for all tests +Every module uses JUnit Jupiter + Hamcrest; `MockAiGenerationProvider` gives fully deterministic tests +with no model or JNI dependency. Model-backed tests (in `srcmorph`) are gated on +`srcmorph/src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf` and self-skip when the native library +is unavailable. -### Test Model +- `srcmorph` — the bulk of the test suite (framework-free logic); see that module's section above. +- `srcmorph-cli` — `MainTest`, `ConfigBindingTest`, `ExamplesConfigBindingTest`, `CliArchitectureTest`, + `CliEndToEndTest`. +- `llamacpp-ai-index-maven-plugin` — `PluginArchitectureTest`, `MojoPhaseSkipTest`, plus the jcstress/ + vmlens tests noted above. -`src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf` is a small (≈90 MB) GGUF model used by integration tests that exercise the real `LlamaCppJniAiGenerationProvider`. These tests are skipped when the JNI native library is unavailable. - -### Conventions - -- All tests that invoke the real llama.cpp JNI must guard with an availability check. -- Tests that only exercise Java logic use `MockAiGenerationProvider`. -- Use `Files.createTempDirectory(...)` for temporary file system state. -- See `TEST_WRITING_GUIDE.md` for full conventions. +See `TEST_WRITING_GUIDE.md` (repo root, applies to every module) for full conventions. --- @@ -341,24 +259,44 @@ header block, so a `- F:` line in the body is never read as a link. ### Logging -Production code uses `org.apache.maven.plugin.logging.Log` (not SLF4J), obtained via `AbstractMojo.getLog()` or passed via constructor. For constructor-based logger injection see `CODE_WRITING_GUIDE.md`. +`srcmorph` and `srcmorph-cli` log via `org.slf4j.Logger` (`private static final Logger LOGGER = ...`, +enforced by each module's own `loggersArePrivateStaticFinal` ArchUnit rule). The plugin module's mojos +still use `AbstractMojo#getLog()` (a Maven `Log`) at the mojo boundary only — everything they delegate +to below that boundary is SLF4J. ### Null Safety -- Mark nullable return types and parameters explicitly. -- Prefer early null/empty guards with `log.warn(...)` over silent skips. +- JSpecify `@Nullable`/`@NonNull` (default) annotations; NullAway + Checker Framework enforce this at + compile time in every module. Mark nullable return types and parameters explicitly. +- Prefer early null/empty guards with a logged warning over silent skips. ### Named Constants -Every meaningful literal (string keys, header field names, node types, version strings) must be a `public static final` or `private static final` named constant with Javadoc. See `CODE_WRITING_GUIDE.md`. +Every meaningful literal (string keys, header field names, node types, version strings) must be a +`public static final` or `private static final` named constant with Javadoc. ### License Headers -All source files must include the Apache 2.0 license header wrapped in `// @formatter:off` / `// @formatter:on`. See any existing source file for the template. +All source files must include the Apache 2.0 license header wrapped in `// @formatter:off` / +`// @formatter:on` (or the file type's equivalent comment syntax — see `examples/` for the `#`/`REM`/ +XML-comment conventions used there; JSON/YAML example fixtures carry no inline header, see +`REUSE.toml`). ### Records -Immutable value types are implemented as Java `record` types (e.g., `AiMdDocument`, `AiMdHeader`, `AiPreparedPrompt`, `AiGenerationRequest`). Prefer records for data carriers. +Immutable value types are Java `record`s where practical (e.g. `AiMdDocument`, `AiMdHeader`, +`AiPreparedPrompt`, `AiGenerationRequest`, `GenerateResult`). + +### `useModulePath=false` (all three modules) + +Every module's `maven-surefire-plugin` configuration forces `false`. +Each module ships a `module-info.java`, but it is release metadata for module-path *consumers* only — +this reactor's own build, tests, and every real consumer today load these jars on the plain classpath +(the production bytecode targets Java 8, where `module-info.class` is inert). Leaving Surefire's +module-path auto-detection on would patch test classes into the named module and then also require +every test-only dependency (e.g. `archunit-junit5`) to be explicitly module-readable, which +`module-info.java` intentionally does not declare — so classpath mode is not a workaround, it is the +actually-representative test environment. --- @@ -367,45 +305,50 @@ Immutable value types are implemented as Java `record` types (e.g., `AiMdDocumen | Workflow | Trigger | Purpose | |---|---|---| | `publish.yml` | Push, PR, manual dispatch | Unified build/test/coverage/package pipeline; publishes snapshots and Maven Central releases | -| `codeql.yml` | Schedule / Push | GitHub CodeQL security scanning | +| `codeql.yml` | Schedule/Push | GitHub CodeQL security scanning | | `scorecard.yml` | Schedule / Push | OpenSSF Scorecard supply-chain security analysis | | `osv-scanner.yml` | Schedule / Push / PR | Google OSV-Scanner dependency vulnerability scan | -| `reuse.yml` | Push / PR | FSFE REUSE license-compliance check | +| `reuse.yml` | Push / PR | FSFE REUSE license-compliance check (`fsfe/reuse-action`) | | `claude-code-review.yml` | PR | AI-powered code review | | `claude.yml` | Issue/PR comment with `@claude` | Claude Code interactive assistant | +`publish.yml` still reflects the pre-reactor single-module layout in places (report globs, artifact +paths); adapting it fully to the 3-module reactor is a separate, later step (see `TODO.md`) — do not +assume it has already been updated. + --- ## Dependencies Summary -| Dependency | Version | Purpose | +| Dependency | Version | Used by | |---|---|---| -| `net.ladenthin:llama` | 5.0.6 | llama.cpp JNI binding (GGUF inference); released on Maven Central, carries the prompt-cache/slot APIs + GPU classifier jars. Brings `slf4j-api` transitively, converged to 2.0.18 via ``. | -| `org.apache.maven:maven-plugin-api` | 3.9.13 | Maven plugin API (provided) | -| `org.apache.maven.plugin-tools:maven-plugin-annotations` | 3.15.1 | `@Mojo`, `@Parameter` annotations (provided) | - -Test-only: - -| Dependency | Version | Purpose | -|---|---|---| -| `org.junit.jupiter:junit-jupiter` | 6.1.0 | Test runner | -| `org.hamcrest:hamcrest` | 3.0 | Matchers | +| `net.ladenthin:llama` | 5.0.6 | `srcmorph` (`provider` package only) — llama.cpp JNI binding | +| `org.slf4j:slf4j-api` | 2.0.18 (converged in the parent) | `srcmorph`, `srcmorph-cli`, the plugin | +| `ch.qos.logback:logback-classic` | 1.5.37 (converged in the parent) | `srcmorph-cli` (runtime binding) | +| `com.fasterxml.jackson.core:jackson-databind` | pinned in parent | `srcmorph-cli` (JSON config) | +| `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml` | pinned in parent | `srcmorph-cli` (YAML config) | +| `org.apache.maven:maven-plugin-api` | 3.9.16 | `llamacpp-ai-index-maven-plugin` (provided) | +| `org.apache.maven.plugin-tools:maven-plugin-annotations` | 3.15.2 | `llamacpp-ai-index-maven-plugin` (provided) | + +Test-only (every module): `org.junit.jupiter:junit-jupiter`, `org.hamcrest:hamcrest`, +`com.tngtech.archunit:archunit-junit5`. `srcmorph` additionally uses jqwik (pinned ≤ 1.9.3 — see the +jqwik policy link below), JMH, jcstress, Lincheck, vmlens. --- ## Test / Code Writing Compliance -After modifying or creating any `.java` file: +After modifying or creating any `.java` file, in whichever module it lives: - For `*Test.java` files, follow the workspace version chain: [`../workspace/guides/test/TEST_WRITING_GUIDE-8.md`](../workspace/guides/test/TEST_WRITING_GUIDE-8.md) - (this repo is Java 8) **and** this repo's own `TEST_WRITING_GUIDE.md` - (plugin-specific supplement). + (baseline) **and** this repo's own `TEST_WRITING_GUIDE.md` (repo-wide supplement; applies to every + module — there is one guide file at the repo root, not one per module). - For production sources, follow the workspace version chain: [`../workspace/guides/src/CODE_WRITING_GUIDE-8.md`](../workspace/guides/src/CODE_WRITING_GUIDE-8.md) - (this repo is Java 8) **and** this repo's own `CODE_WRITING_GUIDE.md`. -- Apply all fixable violations automatically; report only those that - cannot be resolved without a large refactor. + (baseline) **and** this repo's own `CODE_WRITING_GUIDE.md`. +- Apply all fixable violations automatically; report only those that cannot be resolved without a + large refactor. --- @@ -417,12 +360,24 @@ See [`../workspace/workflows/pull-request-workflow.md`](../workspace/workflows/p ## Key Design Principles -1. **Local-first** — all AI inference runs locally via llama.cpp; no cloud API calls, no data leaves the machine. -2. **Deterministic indexing** — same source produces the same `.ai.md` skeleton (deterministic header); only the AI-generated body varies. -3. **Incremental updates** — files with existing summaries are skipped unless `force=true`; checksums detect source changes. -4. **Unified indexing and summarization** — each indexer (`SourceFileIndexer`, `PackageIndexer`) both creates the `.ai.md` skeleton and fills in AI fields in a single pass; no separate summarization step is needed. -5. **Provider abstraction** — AI backends are pluggable through `AiGenerationProvider`; mock provider enables fully deterministic tests. -6. **Configuration-driven prompts & rule-based routing** — prompt templates are defined in POM configuration, not hardcoded in Java; changing a prompt requires no code change. For the `generate` goal each `` is a routing **rule** (`AiFieldGenerationSelector`): a composable `` tree (``/``/`` over leaves extensions/size/lines/modifiedAfter/modifiedBefore/pathGlob — `AiCondition`/`AiConditionEvaluator`), a `` (highest matching wins, ties by order), and a kind — route (``+``), `true` (ignore), or exactly one explicit `true`. So one run can route different file kinds/sizes to different models **and** prompts; the model id (`aiDefinitionKey`) carries the full parameter set (path, context, sampling…), and the run loads each model once. A file matching no rule and no fallback fails the build; `aiIndex.planOnly=true` previews the plan tree without loading a model. Prompts live once in a shared plugin-level `` and are referenced by id across all rules/executions (no duplication). +1. **Local-first** — all AI inference runs locally via llama.cpp; no cloud API calls, no data leaves + the machine. +2. **Deterministic indexing** — the same source produces the same `.ai.md` skeleton (deterministic + header); only the AI-generated body varies. +3. **Incremental updates** — files with existing summaries are skipped unless `force=true`; checksums + detect source changes. +4. **One shared configuration object** — `net.ladenthin.srcmorph.config.SrcMorphConfiguration` is + bindable from Maven plexus XML, Jackson JSON/YAML (the CLI), or plain Java code, so a JSON/YAML key + always reads identically to the matching Maven `` XML element. +5. **Provider abstraction** — AI backends are pluggable through `AiGenerationProvider`; the mock + provider enables fully deterministic tests everywhere. +6. **Configuration-driven prompts & rule-based routing** — prompt templates and the `` + routing rules (composable `` tree, priority, skip, exactly one fallback) are data, never + hardcoded in Java; see `SrcMorphConfiguration`'s Javadoc and each engine's own Javadoc for the + `generate`/`aggregate-packages`/`aggregate-project`/`calibrate` semantics. +7. **Staged, always-green migration** — the plugin's public coordinates never change out from under an + existing consumer mid-migration; the rename to `srcmorph-maven-plugin` is a deliberately isolated, + later step (see `TODO.md`). ## Javadoc Conventions @@ -431,15 +386,25 @@ See [`../workspace/policies/javadoc-conventions.md`](../workspace/policies/javad ## SpotBugs Suppressions See [`../workspace/policies/spotbugs-suppressions.md`](../workspace/policies/spotbugs-suppressions.md). +Each module has its own `spotbugs-exclude.xml` (`srcmorph/spotbugs-exclude.xml`, +`srcmorph-cli/spotbugs-exclude.xml`, `llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml`). ## Spotless Formatting See [`../workspace/policies/spotless-formatting.md`](../workspace/policies/spotless-formatting.md). -Run `mvn spotless:apply` before every commit that touches `.java` files. +Run `mvn spotless:apply` before every commit that touches `.java` files (reactor-wide from the root, or +scoped to one module with `-pl`). ## jqwik Policy See [`../workspace/policies/jqwik-prompt-injection.md`](../workspace/policies/jqwik-prompt-injection.md). +jqwik is a test dependency of `srcmorph` only. + +## Lombok Config + +See [`../workspace/policies/lombok-config.md`](../workspace/policies/lombok-config.md). +`lombok.config` lives once at the repo root and is inherited by every module (Lombok walks up the +directory tree from each source file looking for `lombok.config`). ## CI Test Diagnostics @@ -448,18 +413,16 @@ See [`../workspace/policies/ci-test-diagnostics.md`](../workspace/policies/ci-te ## PIT Mutation Testing See [`../workspace/policies/pit-mutation-testing.md`](../workspace/policies/pit-mutation-testing.md). -Run PIT with the lifecycle prefix — `mvn test-compile org.pitest:pitest-maven:mutationCoverage`. - -## Lombok Config - -See [`../workspace/policies/lombok-config.md`](../workspace/policies/lombok-config.md). +Run PIT with the lifecycle prefix, scoped to the gated module — +`mvn test-compile org.pitest:pitest-maven:mutationCoverage -f srcmorph/pom.xml` (only `srcmorph` is +PIT-gated today; see `TODO.md`). ## JPMS Module Descriptor -This repo ships a `module-info.java` compiled in a separate `release 9` execution. Javadoc -currently runs in **classpath mode** (javadoc `` resolves to `8`), which is the *only* -thing keeping it clear of the JPMS module-mode javadoc trap that bit BAF. **Before raising the -Java / javadoc source level to ≥ 9, read** +Each module ships a `module-info.java` compiled in a separate `release 9` execution, and each module's +Javadoc runs in **classpath mode** (`` resolves to `8`), which is the *only* thing keeping it +clear of the JPMS module-mode javadoc trap that bit BAF. **Before raising the Java / javadoc source +level to ≥ 9 in any module, read** [`../workspace/policies/jpms-module-descriptor.md`](../workspace/policies/jpms-module-descriptor.md). ## Open TODOs diff --git a/README.md b/README.md index 4a42e75..153f5ca 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ [![Coverage Status](https://coveralls.io/repos/github/bernardladenthin/llamacpp-ai-index-maven-plugin/badge.svg?branch=main)](https://coveralls.io/github/bernardladenthin/llamacpp-ai-index-maven-plugin?branch=main) [![codecov](https://codecov.io/gh/bernardladenthin/llamacpp-ai-index-maven-plugin/graph/badge.svg)](https://codecov.io/gh/bernardladenthin/llamacpp-ai-index-maven-plugin) [![JaCoCo](https://img.shields.io/codecov/c/github/bernardladenthin/llamacpp-ai-index-maven-plugin?label=JaCoCo&logo=java)](https://codecov.io/gh/bernardladenthin/llamacpp-ai-index-maven-plugin) -[![PIT Mutation](https://img.shields.io/badge/PIT%20mutation-100%25%20(24%20classes)-brightgreen)](https://github.com/bernardladenthin/llamacpp-ai-index-maven-plugin/actions/workflows/publish.yml) +[![PIT Mutation](https://img.shields.io/badge/PIT%20mutation-100%25%20(47%20classes)-brightgreen)](https://github.com/bernardladenthin/llamacpp-ai-index-maven-plugin/actions/workflows/publish.yml) **Quality:** [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=bernardladenthin_llamacpp-ai-index-maven-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=bernardladenthin_llamacpp-ai-index-maven-plugin) @@ -36,12 +36,6 @@ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=bernardladenthin_llamacpp-ai-index-maven-plugin&metric=security_rating)](https://sonarcloud.io/dashboard?id=bernardladenthin_llamacpp-ai-index-maven-plugin) **Security:** - [![Known Vulnerabilities](https://snyk.io/test/github/bernardladenthin/llamacpp-ai-index-maven-plugin/badge.svg?targetFile=pom.xml)](https://snyk.io/test/github/bernardladenthin/llamacpp-ai-index-maven-plugin?targetFile=pom.xml) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fbernardladenthin%2Fllamacpp-ai-index-maven-plugin.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fbernardladenthin%2Fllamacpp-ai-index-maven-plugin?ref=badge_shield) [![Dependencies](https://img.shields.io/librariesio/github/bernardladenthin/llamacpp-ai-index-maven-plugin)](https://libraries.io/github/bernardladenthin/llamacpp-ai-index-maven-plugin) @@ -72,594 +66,130 @@ using the assigned ID: [![Treeware](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=Treeware&query=%24.total&url=https%3A%2F%2Fpublic.offset.earth%2Fusers%2Ftreeware%2Ftrees)](https://treeware.earth) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) -# llamacpp-ai-index-maven-plugin -A Maven plugin for generating hierarchical, AI-readable documentation of source code projects using local llama.cpp-compatible models. -It creates structured `.ai.md` files per source file and aggregates them into package-level summaries for fast semantic navigation and retrieval. -## Features -- Generate AI summaries for source files -- Per-language prompts — Java, SQL schema, and a generic fallback — selected by file extension -- Weave searchable type, API and domain names into every summary -- Aggregate summaries at package level -- Build a single project index (one line + link per package) for top-down navigation, optionally with a one-call AI `#### Overview` -- Run any phase independently — toggle file / package / project on or off -- Exclude trivial or generated files with glob patterns -- Uses local models via llama.cpp (no cloud dependency) -- Incremental updates (skips unchanged files) -- Logs a rough per-file duration estimate before each generation (size, token count, expected time) -- Optimized for AI-assisted code understanding -## How It Works -The plugin runs in three phases, building a navigable index from fine to coarse. -### 1. File Generation (generate) -- Scans configured source directories -- Creates `.ai.md` files per source file -- Each file contains metadata header and markdown summary -### 2. Package Aggregation (aggregate-packages) -- Traverses generated `.ai.md` files -- Builds hierarchical package summaries -- Produces `package.ai.md` files; the header carries a deterministic `F` link list to each child (package → file navigation) -### 3. Project Index (aggregate-project) -- Harvests the one-line lead from every `package.ai.md` — the per-package listing is deterministic, no AI call -- Produces a single `project.ai.md`: one body line per package (its lead) with the clickable links in the header `F` list -- A compact, always-loadable table of contents an agent reads first to navigate down -- **Optional** (opt-in): configure a `` on this goal and it makes *one* extra AI call to - write a short `#### Overview` paragraph from the package leads. The deterministic per-package listing is - unchanged; with no field generation the goal stays purely deterministic and calls no model. -## Example Output -``` -### AiMdDocument.java -- H: 1.0 -- C: A48CED8C -- D: 2026-03-15T23:31:52Z -- T: 2026-03-19T18:13:31Z -- G: 1.0.0 -- A: 0.0.0 -- X: file ---- -> Immutable value type pairing a deterministic metadata header with the AI-generated markdown body of one .ai.md document. - -#### Purpose -- Hold one parsed `.ai.md` document as an `AiMdHeader` plus its markdown body. - -#### Type -- record-shaped value class (Java); marked `@ConvertToRecord`. - -#### Public API -- `header() -> AiMdHeader` — the document's metadata header. -- `body() -> String` — the AI-generated markdown body. -``` -## Requirements -- Java 8+ (production code targets Java 8; CI builds on Java 8 via temurin) -- Maven 3.6.3+ -- Local GGUF model (llama.cpp compatible) - -### Running under Java 8: override `checker-qual` - -The plugin itself is compiled to Java 8 bytecode, but it pulls in -[`org.checkerframework:checker-qual`](https://central.sonatype.com/artifact/org.checkerframework/checker-qual) -transitively, and the version it builds against (`4.2.0`) ships its classes as **Java 11 -bytecode** (class-file major version 55). On a **Java 8** JVM, loading those annotation -classes fails with `UnsupportedClassVersionError`. - -If you run the plugin under a Java 8 Maven/JVM, **override `checker-qual` to `3.55.1`** — the -**last release whose runtime classes are Java 8 bytecode** (major 52). The `checker-qual` line -switched to Java 11 bytecode in `4.0.0`; every `3.x` release (`3.42.0` … `3.55.1`) is Java 8 -loadable, and `3.55.1` is the newest of them. (`checker-qual` `3.43.0`–`3.55.1` additionally -carry a root `module-info.class`, which a Java 8 classpath simply ignores; if you want a jar -with no `module-info.class` at all, `3.42.0` is the last such release.) - -Because this is a *plugin* dependency, the override goes inside the plugin's own -`` block (a project-level `` does **not** affect a plugin's -classpath): +# srcmorph -```xml - - net.ladenthin - llamacpp-ai-index-maven-plugin - - - - - org.checkerframework - checker-qual - 3.55.1 - - - -``` +**srcmorph** is a prompt-driven source-tree transformer: it walks a source tree and processes each +file through a configurable local LLM prompt — via [llama.cpp](https://github.com/ggerganov/llama.cpp) +GGUF models, entirely local, no cloud calls — producing layered output: per-file, then per-package, +then per-project. Today it emits structured, AI-readable `.ai.md` Markdown summaries for fast semantic +code navigation; the same rule-routed pipeline (composable file-matching conditions, per-rule +model + prompt, oversize handling) is generic enough to eventually emit wikis, architecture docs, +diagrams, or even source-to-source transformations. + +> **This repository is mid-migration.** It began life as a single Maven plugin and is being +> restructured into a small reactor of three artifacts — see "Module overview" below. The **Maven +> plugin keeps its original coordinates and property names in this release**; a later, separately +> tagged release will relocate it to `net.ladenthin:srcmorph-maven-plugin` with a Maven Central +> relocation POM, so existing consumers are never broken out from under them. See +> [`CLAUDE.md`](CLAUDE.md) and [`TODO.md`](TODO.md) for the full migration status. -Running the plugin under Java 11 or newer needs no override. +## Module overview -## Dependency +| Module | Artifact | What it is | +|---|---|---| +| [`srcmorph/`](srcmorph/README.md) | `net.ladenthin:srcmorph` | The core library: a framework-free Java API (no Maven dependency) — the shared `SrcMorphConfiguration` plus the `generate`/`aggregate-packages`/`aggregate-project`/`calibrate` engines. Use this to embed srcmorph in your own tooling. | +| [`srcmorph-cli/`](srcmorph-cli/README.md) | `net.ladenthin:srcmorph-cli` | A standalone CLI driven by one JSON or YAML configuration file (ships as a `java -jar`-ready fat jar). No Maven project required. | +| [`llamacpp-ai-index-maven-plugin/`](llamacpp-ai-index-maven-plugin/README.md) | `net.ladenthin:llamacpp-ai-index-maven-plugin` | The original Maven plugin, now a thin wrapper around `srcmorph`. **Coordinates/goal-prefix/properties unchanged in this release** — see the note above. | -The plugin depends on [`net.ladenthin:llama`](https://central.sonatype.com/artifact/net.ladenthin/llama), the Java/JNI binding for llama.cpp. -It is published on Maven Central and resolves automatically — no manual installation required. +All three (plus the reactor parent pom) are released together from this one repository at the same +version. -```xml - - net.ladenthin - llama - 5.0.3 - -``` -## Configuration -The plugin is configured from three building blocks, declared on the plugin inside -``: - -1. **``** — define each GGUF model once (path + sampling parameters), each with a ``. -2. **``** — define each prompt template once, each with a ``. A template takes - two `%s` placeholders: the file/package name and the source (for packages, the child summaries; for - the optional project overview, the per-package leads). -3. **``** — per goal, the routing **rules**. Each rule maps a `` to an - `` (model id, which carries the full parameter set) and selects files with a - composable **``** tree: ``/``/`` over the leaves ``, `` - (``/`` bytes), `` (``/``), ``/`` (ISO-8601 - instant vs the file's last-modified time), and `` (base-relative glob). When several rules - match, the highest `` wins (ties by declaration order). A rule may instead be - `true` (ignore matching files — a high-priority skip beats routes and the fallback), and - **exactly one** `true` (no condition) catches everything else. A file that - matches no rule and no fallback **fails the build**. So one `generate` run can index different file - kinds/sizes with **different models *and* prompts**; it loads each model once. Run with - `-DaiIndex.planOnly=true` to print the routing plan (a copy-pasteable Markdown table: file → rule id → - prompt → context-window fit → rough time estimate, summed per model and overall) and stop before - loading any model. The plan also checks each file against its routed model's **context window**: a - file too large for the window would lose content if trimmed. What happens then is **configuration - only** — the plugin never picks a model for you — and is chosen per rule with ``: - - **`fail`** *(default)* — abort the build (a hard fail). The fix is to add a `` rule - with a size `` that routes oversized files to a model with a large enough window (see the - `granite-4.0-h-tiny-bigwindow` definition + the `big-window-java` / `big-window-sql` rules in the - POM — IBM Granite 4.0-H-Tiny, Apache-2.0, a hybrid Mamba model whose KV cache grows only linearly, - configured at a 384K window to cover files up to ~1 MB; verified summarizing a ~995 KB / ~268K-token - file on an 8 GB GPU with no OOM). - - **`sample`** — feed only the head of the file (trimmed to the window) in a single call. Fast and - bounded; good for repetitive data where the head represents the whole. - - **`mapReduce`** — split the file into window-sized chunks at line boundaries (with overlap so - records are never torn mid-syntax), summarize each chunk, then combine the partial summaries in one - final call. A `` cap bounds the time (a representative subset — always head + tail, - evenly spaced — is summarized when the file would exceed the cap), so an arbitrarily large file - (e.g. 7 MB) stays within a chosen per-file budget. **Route oversized files to a *small*, fast model - for this — not the big-window one.** Prefill is `O(n²)` in prompt length, so chunks should be small: - many cheap small-window passes are far faster than a few giant ones (one 384K-token pass alone can - dwarf a whole run). E.g. a 16K-window model with `maxChunks=6` is ~7 calls (~1 h order on a - reference CPU; less on GPU) and samples a representative slice — the right trade-off for repetitive - data. mapReduce *on* a big window is the slowest possible combination; avoid it. - - **`deterministic`** — no model at all: emit a deterministic body (size, line count, head/tail - sample). Instant; for pure data where no AI analysis is needed. - - Quality from a real model is best within Granite's validated 128K (~500 KB) and degrades gradually - beyond it — a whole-file (or chunked) summary still beats a trimmed one. - - **Exact counts with `` (optional, every file — not just oversize).** A sampled/chunked AI - summary can't reliably *count* things (no single call sees the whole file — it will guess "25 rows"). - Add a `` list to a rule and each `{label, pattern}` reports its regex match count over the - **whole** source, prepended to the body of every file the rule matches as an exact facts line — so - downstream agents get authoritative structural counts in every summary. It's fully generic — the - meaning is in the regex, so the same mechanism counts SQL `INSERT` rows or Java `\bboolean\b` fields; - multi-line matching is opt-in via the inline `(?m)` flag. Keep patterns robust (a fact that miscounts - is worse than none). Example: `**Facts (exact, whole file):** INSERT rows: 36738; tables: 122; views: 4`. +## Quickstart + +### 1. Maven plugin (current coordinates) ```xml net.ladenthin llamacpp-ai-index-maven-plugin 1.0.2 - - - - src/main/java/com/example - - - llamacpp-jni - - + mock + + + file-body + + + + + + file-body + coder + true + + coder /path/to/model.gguf - 32768 - 1536 - 0.7 - 8 + + +``` - - - - file-body-java - - - - file-body-fallback - - - - package-body - - - - - project-body - - - - +### 3. Library dependency - - - - ai-generate - generate-resources - generate - - - - - java - file-body-java - coder - - .java - - - - fallback - true - file-body-fallback - coder - - - - - - ai-aggregate-packages - process-resources - aggregate-packages - - - - package-body - coder - - - - - - - ai-aggregate-project - prepare-package - aggregate-project - - - - project-body - coder - - - - - - +```xml + + net.ladenthin + srcmorph + 1.1.0 + ``` -### Routing rules (conditions, priority, skip, plan) -Within one `generate` run you can route files to different models **and** prompts by size, language, -age or path. Example — small/medium/large Java files to three context presets, skip generated sources, -and a fallback for everything else: - -```xml - - - skip-generatedtrue100 - **/generated/** - - - java-smallfile-body-java-tersegpt-oss-20B-c16k - - .java - 16384 - - - - java-midfile-body-javagpt-oss-20B-c48k - - .java - 1638449152 - - - - java-largefile-body-java-detailedgpt-oss-20B-c96k - - .java - 49152 - - - - java-hugefile-body-javagpt-oss-20B-c16k - mapReduce6 - - \bboolean\b - (?m)^\s+(public|private|protected).*\( - - - .java - 1048576 - - - - fallbacktrue - file-body-fallbackgpt-oss-20B-c96k - - +```java +SrcMorphConfiguration config = new SrcMorphConfiguration(); +config.setBaseDirectory(new File(".")); +// ... set outputDirectory, promptDefinitions, aiDefinitions, fieldGenerations ... +new GenerateEngine(config).execute(); ``` -Notes: size bounds are **min-exclusive / max-inclusive** so `band2.min == band1.max` is non-overlapping; -`` works the same way; `2026-01-01T00:00:00Z` only (re)indexes -recently changed files. `` (`fail` *(default)* / `sample` / `mapReduce` / `deterministic` — -see the context-window note above) chooses what a rule does when a file is still larger than its routed -model's window; `` caps how many chunks `mapReduce` summarizes. Preview the mapping without -running a model: +See [`srcmorph/README.md`](srcmorph/README.md) for the full API tour (the four engines, exception +types, and a complete embedding example). -``` -mvn ai-index:generate -DaiIndex.planOnly=true -``` +## How It Works -## Usage -Run AI index generation: -``` -mvn clean install -Pai-index-selftest -``` -With native llama tests: -``` -mvn clean install -Pai-index-selftest -DrunNativeLlamaTests=true -``` -## Plugin Configuration -Run-level parameters (set in ``): -- `outputDirectory` — target directory for `.ai.md` files (default: `${project.basedir}/src/site/ai`) -- `subtrees` — source directories to index, relative to the project base dir (default: `src/main/java`) -- `fileExtensions` — file extensions to index (default: `.java`) -- `excludes` — glob patterns for source files to skip, matched against each file's path relative to - the base dir with `/` separators, e.g. `**/package-info.java`, `**/generated/**` (default: none). - `*` stays within one path segment, `**` spans directories, `?` is a single character. -- `generationProvider` — AI backend: `mock` (default) or `llamacpp-jni` -- `force` — regenerate even when a body already exists (default: `false`) -- `skip` — global switch: skip **every** phase (default: `false`) -- Per-phase switches — turn any of the three phases on/off independently (each default `false`). - Named after the three index levels (`file` / `package` / `project`, the `x` node types): - - `aiIndex.file.skip` — skip the **file** phase (the `generate` goal) - - `aiIndex.package.skip` — skip the **package** phase (the `aggregate-packages` goal) - - `aiIndex.project.skip` — skip the **project** phase (the `aggregate-project` goal) - - So `-DaiIndex.package.skip=true` runs file + project only, `-DaiIndex.skip=true` runs - nothing, and the defaults run all three. -- `aiDefinitions` / `promptDefinitions` — named models / prompt templates, referenced by key -- `fieldGenerations` — per goal: which `promptKey` runs with which `aiDefinitionKey` (**required**) - -### Per-model `` parameters - -Every model knob lives inside its `` (referenced by `aiDefinitionKey`), not as a top-level -plugin parameter. Only `key` and `modelPath` are required; everything else has a default. The defaults -below are the shipped values (`AiGenerationConfig.DEFAULT_*`). - -| Element | Default | Description | -|---|---|---| -| `key` | *(required)* | Identifier referenced by a rule's `aiDefinitionKey` | -| `modelPath` | *(required)* | Path to the GGUF model file | -| `contextSize` | `32768` | Context window in tokens | -| `maxOutputTokens` | `128` | Max generated tokens per call | -| `threads` | `8` | CPU threads for inference | -| `temperature` | `0.15` | Sampling temperature | -| `topP` | `0.9` | Nucleus (top-p) sampling threshold | -| `topK` | `40` | Top-k sampling limit (`0` = disabled) | -| `minP` | `0.0` | Min-p sampling threshold (`0.0` = disabled) | -| `topNSigma` | `-1.0` | Top-n-sigma sampling threshold (`-1.0` = disabled) | -| `repeatPenalty` | `1.0` | Repetition penalty (`1.0` = disabled) | -| `charsPerToken` | `4` | Chars-per-token estimate; drives the automatic `maxInputChars` trim budget (`maxInputChars` itself is derived, not a field). Use a value at or below your model's real ratio so the budget stays conservative | -| `warnOnTrim` | `true` | Log a warning when the source is trimmed to fit the window | -| `cachePrompt` | `true` | Reuse the shared prompt-prefix KV across files (`cache_prompt`) | -| `swaFull` | `true` | Keep the full-size sliding-window-attention KV cache (`--swa-full`) | -| `cacheReuse` | `256` | KV prefix-reuse minimum chunk size in tokens (`--cache-reuse`; `0` = off) | -| `gpuLayers` | `-1` | GPU layers to offload (`--gpu-layers`); `-1` = auto-fit to free VRAM, `0` = force CPU, `>0` = partial. GPU native only | -| `mainGpu` | `-1` | Primary GPU index (`--main-gpu`); `-1` = leave default. Matters on multi-GPU hosts (e.g. a Vulkan build enumerates every GPU) | -| `devices` | *(empty)* | Explicit device selection (`--device`), comma-separated backend device names (e.g. `Vulkan1`); takes precedence over `mainGpu` | -| `chatTemplateEnableThinking` | `true` | Enable the chat template's thinking mode | -| `reasoningEffort` | `low` | gpt-oss harmony reasoning effort (`low`/`medium`/`high`); empty omits the kwarg (e.g. for non-gpt-oss models) | -| `reasoningBudgetTokens` | `-1` | Cap on harmony reasoning tokens (`-1` = unrestricted) | -| `dryMultiplier` | `0.0` | DRY repetition-penalty multiplier (`0.0` = disabled); the other `dry*` knobs only apply when this is `> 0` | -| `dryBase` | `1.75` | DRY exponential base | -| `dryAllowedLength` | `2` | Longest n-gram that may repeat without DRY penalty | -| `dryPenaltyLastN` | `-1` | DRY look-back window in tokens (`-1` = whole context, `0` = off) | -| `drySequenceBreakers` | *(empty)* | DRY sequence-breaker strings; empty = the binding defaults | -| `stopStrings` | *(empty)* | Extra stop strings that end generation | - -## Prompt System -Prompts are defined in the plugin configuration (``) and referenced by key -from ``. The self-test profile defines five: -- `file-body-java` — summarizes a single Java source file (types, public API, dependencies) -- `file-body-sql` — summarizes a single SQL file as schema (tables/views/procedures, columns, - the tables it reads vs writes, and relationships) -- `file-body-fallback` — generic multi-language summary for any other source file -- `package-body` — synthesizes a package summary from the already-generated file summaries -- `project-body` — (optional) synthesizes the short project `#### Overview` paragraph from the package leads - -For the `generate` goal the file-level prompt is selected per file by extension: the first field -generation whose `` matches the file name wins; otherwise the first entry without a -`` filter is the fallback. A single field generation with no filter (the historical -shape) keeps working — it is simply the fallback for every file. - -Each summary begins with a one-sentence blockquote lead, followed by structured `####` sections. -Prompts are optimized to avoid code blocks, formatter artifacts, and empty outputs, and to produce structured markdown. -## Output Structure -``` -src/site/ai/ -└── main/ - └── java/ - └── com/ - └── example/ - ├── MyClass.java.ai.md - ├── AnotherClass.java.ai.md - └── package.ai.md -``` -## Design Principles -- Deterministic metadata (hash-based change detection) -- Separation of concerns (header = metadata, body = summary) -- AI-friendly structure (predictable and hierarchical) -- Local-first (no external APIs required) -## Known Limitations -- Model output may require normalization (handled in code) -- Large models increase runtime -- Output quality depends on chosen model - -## TODO -- **Expand PIT mutation-testing scope.** `` in `pom.xml` lists an explicit subset of classes verified at 100% mutation parity; widen it incrementally toward the whole `net.ladenthin.maven.llamacpp.aiindex.*` tree (the streambuffer whole-package model) as more classes reach parity. Generic PIT setup and invocation: see the [PIT policy](../workspace/policies/pit-mutation-testing.md). -## Recommended Models -Based on an 8-model × 2-prompt benchmark run against this codebase — full results, per-model -pros/cons, a source-faithfulness deep-dive, and reproduction steps in -[docs/ai-index-benchmark](docs/ai-index-benchmark/COMPARISON.md): - -- **`gpt-oss-20B-mxfp4` — the production default** (switch with `-Dai.model=`). The native MXFP4 - quant at a 96K window; it inherits the benchmark's accuracy lead (gpt-oss-20b was most *accurate* per - file, won 5/6 in the per-file matrix — measured on the `c96k`/UD-Q4_K_XL quant, but E5 shows quant - choice is within noise so the native MXFP4 is the better-quality swap), run at `reasoningEffort=low` - and a 96K window so it covers files up to ~250 KB untrimmed. Slowest of the set (~2× the 30B) — the - accepted cost for accuracy. See the preset/timing details below. -- **Qwen3-Coder-30B-A3B-Instruct** — throughput alternative (and best of the non-reasoning models for - large Java files): most complete/faithful of the fast models, code-specialized, Apache-2.0, - ~3.3B-active MoE, 262K context. Pick it when throughput beats the last points of fidelity. -- **Granite-4.0-H-Tiny** — fastest on CPU (~4×, flat-KV hybrid, Apache-2.0); best for very large - or many files when throughput beats the last points of fidelity. -- **Seed-Coder-8B-Instruct** — clean, permissive (MIT) small dense coder. -- Avoid `Qwen3.5-4B` (thinking tax, no quality gain). - -### gpt-oss-20b presets, large files, and timing - -`gpt-oss` is a *reasoning* model whose analysis tokens share the output budget, so use -`reasoningEffort=low` for code summaries (best quality here) and size the budget to the file. The pom -ships three ready presets, tiered by the largest file you must cover — full rationale and measurements -in [COMPARISON.md §11](docs/ai-index-benchmark/COMPARISON.md): - -| Preset | context | covers up to | ~ time (CPU) | -|---|---|---|---| -| `gpt-oss-20B-c16k` | 16K | ~40 KB | ~1–2 min | -| `gpt-oss-20B-c48k` | 48K | ~125 KB | ~25 min @ 100 KB | -| **`gpt-oss-20B-c96k` (default)** | 96K | ~260 KB | ~80 min @ 250 KB | - -- **`c96k` is the default:** a measured A/B shows a wider context window costs only RAM, not per-file - time, so it covers every file up to ~250 KB with no trimming while small files stay just as fast. - Downshift only to save RAM. Hard ceiling is the 128K window (~480–500 KB of code). -- **Timing is quadratic, not linear:** prefill ≈ `24.4·n + 0.000674·n²` ms (n = prompt tokens), - because attention is O(n) per token — which is why throughput drops as files grow. The plugin logs - this estimate per file. - -## GPU acceleration (opt-in) -The default native is CPU (Ninja build, bundled in the main `net.ladenthin:llama` jar). On an NVIDIA -RTX 3070 a CUDA build measured **~4.5× the CPU decode speed**; Vulkan also works (AMD + NVIDIA) but pays -a one-time shader-compilation cost on the first run. OpenCL is intentionally not offered (llama.cpp's -OpenCL backend does not support NVIDIA GPUs). - -How the native is found: `net.ladenthin.llama.loader.LlamaLoader` tries `net.ladenthin.llama.lib.path`, -then `java.library.path`, then **extracts the native bundled in whatever `net.ladenthin:llama` jar is on -the classpath**. So there are two ways to enable a GPU when running the *published* plugin in your own -build. - -**Recommended — add the GPU classifier to the plugin's own classpath.** Declare the matching -`net.ladenthin:llama` classifier as a dependency of the plugin in your POM; the loader then extracts the -GPU `jllama.dll` from it — no library path to manage: +srcmorph runs in three phases, building a navigable index from fine to coarse — see each module's own +README for the full details (routing rules, oversize strategies, exact-count facts, the project +overview) and [`CLAUDE.md`](CLAUDE.md) for the architecture. -```xml - - net.ladenthin - llamacpp-ai-index-maven-plugin - ... - - - net.ladenthin - llama - 5.0.3 - cuda13-windows-x86-64 - - - -``` +1. **File generation** — scans configured source directories, writes one `.ai.md` file per source + file (deterministic metadata header + AI-generated Markdown body). +2. **Package aggregation** — traverses the generated `.ai.md` files, writes one `package.ai.md` per + directory with a deterministic child-link list plus an AI-generated summary. +3. **Project index** — harvests the one-line lead from every `package.ai.md` into a single + `project.ai.md` table of contents (deterministic; an optional one-call AI overview paragraph can be + enabled). -``` -mvn ai-index:generate -Dai.gpuLayers=20 # + the GPU runtime on PATH (see below) -``` +Each phase is independently switchable and incremental (checksums skip unchanged files); a rule-based +router lets one run send different files to different models **and** prompts by extension, size, age, +or path. -**Alternative — runtime library override (no POM change).** Point `net.ladenthin.llama.lib.path` at a -folder holding the GPU `jllama.dll` (extracted once from the classifier jar); it is tried before the -bundled native: +## Snapshot builds -``` -mvn ai-index:generate -Dnet.ladenthin.llama.lib.path=C:\path\to\gpu-native -Dai.gpuLayers=20 -``` +Snapshots are published to the Central Snapshots repository on every push to `main`: -In both cases: - -- **CUDA** needs a matching CUDA 13 toolkit + driver, and the toolkit's `bin\x64` (with `cudart64_13.dll`, - `cublas64_13.dll`) on `PATH` — the classifier jar bundles only `jllama.dll`, not the CUDA runtime. -- **`ai.gpuLayers`** (on the gpt-oss presets): `-1` (default) does **not** pin a layer count, so - llama.cpp **auto-fits** as many layers as fit the card's free VRAM — the robust "runs on any card" - setting (it never over-commits, so no OOM on a 6 GB card, and uses more layers on a bigger one). Pin a - positive number only to force a specific **partial** split (a fixed count disables auto-fit), or `0` to - force CPU. Measured on an 8 GB RTX 3070, auto-fit gpt-oss-20b ≈ 29 decode t/s (vs ≈ 8 on CPU); a card - with ≥ 16 GB fits all layers and is far faster. -- **Picking a GPU on a multi-GPU host** (`ai.mainGpu` / `ai.devices` on the gpt-oss presets): a **CUDA** - build only enumerates NVIDIA devices, so a single-NVIDIA host needs nothing. A **Vulkan** build - enumerates *every* GPU (an integrated GPU is often device `0`), so the default may pick the slower one — - set `-Dai.mainGpu=1` to select the discrete GPU, or `-Dai.devices=Vulkan1` for explicit device names - (these map to the binding's `--main-gpu` / `--device`). On any model definition the same knobs are the - `` / `` elements. - -**Profiles (this repo's own reactor build only — test/benchmark).** `-P gpu-cuda` / `-P gpu-vulkan` -swap the `net.ladenthin:llama` classifier (via the `llama.classifier` property) for the reactor's -test/compile classpath — handy for the native test or benchmarking on GPU here. They do **not** change -the native used when the *published* plugin runs in another build (the POM is not flattened, so the -classifier stays a property that resolves to the CPU default downstream) — use one of the two methods -above for real indexing. - -## Development - -Run full build: -``` -mvn clean install -``` -Skip AI generation: ``` -mvn clean install -DaiIndex.skip=true +https://central.sonatype.com/repository/maven-snapshots/net/ladenthin/ ``` -### Contributors: do not upgrade jqwik past 1.9.3 - -> ⚠️ **DO NOT UPGRADE jqwik past 1.9.3.** jqwik 1.10.0 added an anti-AI prompt-injection string to test stdout; the 1.10.1 user guide states the library "is not meant to be used by any 'AI' coding agents at all." 1.9.3 is the last pre-disclosure release and is the pinned version. See `CLAUDE.md` section "jqwik prompt-injection in test output" for the full context. Dependabot is configured to ignore **all** `net.jqwik` updates (every version, including patches) — see the `ignore` rule in [`.github/dependabot.yml`](./.github/dependabot.yml). +Current development version: `1.1.0-SNAPSHOT`. ## A Note on History @@ -669,13 +199,5 @@ on April 4, 2026. Google Cloud eventually formalized the very same pattern into announced on June 12, 2026. ## License -Apache License 2.0 ---- - -
      -🍺 Beer-driven architectural review (2026-05-26) - -> _Sometimes you spend two beers debating "architektonisch" vs "architekturiell" and the only conclusion is: ship the code anyway. Cheers!_ - -
      \ No newline at end of file +Apache License 2.0 diff --git a/REUSE.toml b/REUSE.toml index 1a2dcb1..b82ae1e 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -12,6 +12,16 @@ path = [ ".github/ISSUE_TEMPLATE/bug_report.md", ".github/ISSUE_TEMPLATE/feature_request.md", ".github/PULL_REQUEST_TEMPLATE.md", + # JSON/YAML example configuration fixtures (no comment syntax; same precedent as + # BitcoinAddressFinder's examples/config_*.json entries in its own REUSE.toml). + "examples/config_Plan.json", + "examples/config_GenerateFileIndex.json", + "examples/config_GenerateFileIndex.yaml", + "examples/config_All.json", + "examples/config_Calibrate.json", + # Pre-existing private test fixtures, same "no comment syntax" JSON/YAML precedent. + "srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.json", + "srcmorph-cli/src/test/resources/test-fixtures/minimal-generate.yaml", ] precedence = "aggregate" SPDX-FileCopyrightText = "2026 Bernard Ladenthin " diff --git a/TODO.md b/TODO.md index edef143..dcf47dc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,20 +1,56 @@ -# TODO — llamacpp-ai-index-maven-plugin +# TODO — srcmorph (llamacpp-ai-index-maven-plugin reactor) Open work items for this repo. Cross-cutting tracking lives in [`../workspace/crossrepostatus.md`](../workspace/crossrepostatus.md); items here are -plugin-specific or this repo's slice of a cross-cutting initiative. Completed work is +repo-specific or this repo's slice of a cross-cutting initiative. Completed work is recorded in git history and `crossrepostatus.md`, not here. ## Open -- **jqwik pin policy** — see [`../workspace/policies/jqwik-prompt-injection.md`](../workspace/policies/jqwik-prompt-injection.md). `jqwik.version ≤ 1.9.3` is mandatory. - -- **`@VisibleForTesting` audit.** No usages currently. Walk the production tree for package-private/protected methods or fields that exist purely so tests can reach them, and either annotate (`com.google.common.annotations.VisibleForTesting`) or move into the test source tree. - -- **Null-safety refinement.** JSpecify + NullAway are enforced at compile time in strict JSpecify mode (see `pom.xml`); `@NullMarked` on the package; framework-populated POJOs carry class-level `@SuppressWarnings({"NullAway.Init","initialization.fields.uninitialized"})`. Open follow-up: review remaining unannotated public API surfaces for places where `@Nullable` would be more precise than the implicit non-null default. - -- **Expand PIT mutation scope (optional).** `pom.xml` wires `100` over an explicit `` list (config / document / prompt / provider / support value+logic classes, plus `indexer.AiInputWindowCalculator` and `support.AiProgressBar`), all killed at 100%. Still out (optional, need careful fixtures): `document.AiMdDocumentCodec` / `AiMdHeaderCodec`, `prompt.AiPromptPreparationSupport`, and the newer `indexer.AiIndexPlan` / `config.AiConditionGroup`. The orchestration layers (`indexer.*` walk, `mojo.*`) and the JNI provider stay out of PIT — they need a Maven/native context rather than pure-unit mutation (see crossrepostatus "Deliberate non-parity"). +- **Migration step 8 — CI full pass + first reactor release.** `.github/workflows/publish.yml` + still reflects the pre-reactor single-module layout in places (report globs, artifact paths, + coverage aggregation). Adapt it to the 3-module reactor (`srcmorph` / `srcmorph-cli` / + `llamacpp-ai-index-maven-plugin`), then cut the first real `1.1.0` release under the plugin's + **current** (pre-rename) coordinates via `mvn -P release deploy`. This is the gate the user asked + for before step 9 ("if all is working stat I can safely do the final rename"). + +- **Migration step 9 — plugin rename + Maven Central relocation (deferred, do only after step 8 + ships a real release).** Rename `llamacpp-ai-index-maven-plugin`'s own coordinates to + `net.ladenthin:srcmorph-maven-plugin` (goal prefix `srcmorph`, package + `net.ladenthin.maven.srcmorph.mojo`, properties `aiIndex.*` → `srcmorph.*`), and add a + relocation-stub module/POM for the old `net.ladenthin:llamacpp-ai-index-maven-plugin` coordinates + (only `groupId`/`artifactId`/`version` + `` — no source, no + dependencies) so existing consumers resolving the old artifact get redirected. This is the last, + isolated step of the migration; do not do it early, and do not describe it as already done in any + documentation until it actually ships. + +- **PIT mutation-testing gate for `srcmorph-cli` and the plugin module.** Only `srcmorph` + (`srcmorph/pom.xml`) is PIT-gated today — `100` over an + explicit `` list of 47 classes across config/document/engine/indexer/prompt/ + provider/support, all killed at 100%. Neither `srcmorph-cli` nor + `llamacpp-ai-index-maven-plugin` has a `pitest-maven` execution of its own yet (both poms document + this explicitly with a comment at the spot a PIT plugin block would go). For the CLI: the pure + helpers worth mutation-gating are the config copy/round-trip (`Main#copyWithPlanOnlyForced`) and + command dispatch; `Main`'s I/O-heavy entry points (`main`, `loadConfiguration`) are better served by + integration tests than unit-mutation gating. For the plugin: the 5 mojo classes are Maven-lifecycle + orchestration (typically integration-tested via Maven invoker/executor, not unit-mutation-tested) — + confirm that reasoning still holds before assuming it's a permanent exemption rather than a gap. + +- **Expand `srcmorph`'s own PIT mutation scope (optional).** `srcmorph/pom.xml` wires + `100` over an explicit `` list (config / + document / prompt / provider / support value+logic classes, plus `indexer.AiInputWindowCalculator` + and `support.AiProgressBar`), all killed at 100%. Still out (optional, need careful fixtures): + `document.AiMdDocumentCodec` / `AiMdHeaderCodec`, `prompt.AiPromptPreparationSupport`, and the newer + `indexer.AiIndexPlan` / `config.AiConditionGroup`. The orchestration layers (`indexer.*` walk, + the plugin's `mojo.*`) and the JNI provider stay out of PIT — they need a Maven/native context + rather than pure-unit mutation (see crossrepostatus "Deliberate non-parity"). + +- **jqwik pin policy** — see [`../workspace/policies/jqwik-prompt-injection.md`](../workspace/policies/jqwik-prompt-injection.md). `jqwik.version ≤ 1.9.3` is mandatory (declared in `srcmorph/pom.xml`, the only reactor module with a jqwik test dependency). + +- **`@VisibleForTesting` audit.** No usages currently in any module. Walk each module's production tree for package-private/protected methods or fields that exist purely so tests can reach them, and either annotate (`com.google.common.annotations.VisibleForTesting`) or move into the test source tree. + +- **Null-safety refinement.** JSpecify + NullAway are enforced at compile time in strict JSpecify mode in every module (see each module's own `pom.xml`); `@NullMarked` on the package; framework-populated POJOs carry class-level `@SuppressWarnings({"NullAway.Init","initialization.fields.uninitialized"})`. Open follow-up: review remaining unannotated public API surfaces for places where `@Nullable` would be more precise than the implicit non-null default. - **Cross-repo code-quality TODOs** — see [`../workspace/policies/code-quality-todos.md`](../workspace/policies/code-quality-todos.md) for the canonical `@VisibleForTesting` design-fit review, package hierarchy review, and class/method naming review. This repo has no `@VisibleForTesting` usages today; the package and naming reviews are still open here. -- **SLF4J gap CLOSED.** The four `indexer.*` classes (`SourceFileIndexer`, `PackageIndexer`, `ProjectIndexer`, `AiFieldGenerationSupport`) now log via `org.slf4j.Logger` instead of the constructor-injected Maven `Log`; only the 5 mojos (and the `PluginArchitectureTest` `mavenMojoAnnotationsConfinedToMojo`/`nonMojoIsMavenFree` rules) still touch the Maven Plugin API. Maven's `maven-slf4j-provider` binds `slf4j-api` inside plugin executions, so every `LOGGER.info(...)`/`.warn(...)`/`.debug(...)` call surfaces as a normal `[INFO]`/`[WARN]`/`[DEBUG]` line in `mvn` output with zero glue (verified end-to-end with the `generate` goal against a scratch project). `AiFieldGenerationSupportTest` exercises the binding/configuration directly via a logback `ListAppender` attached to the class logger (the LogCaptor-smoke-test role this note used to flag as future work). +- **SLF4J gap CLOSED (core + CLI).** `srcmorph`'s `indexer.*` classes (`SourceFileIndexer`, `PackageIndexer`, `ProjectIndexer`, `AiFieldGenerationSupport`) and `engine.*` classes all log via `org.slf4j.Logger` instead of a Maven `Log`; `srcmorph-cli`'s `Main` does the same. Only the plugin module's 5 mojos (and `PluginArchitectureTest`'s Maven-confinement rules) still touch the Maven Plugin API directly — that boundary is deliberate (see `CLAUDE.md`), not a gap. Maven's `maven-slf4j-provider` binds `slf4j-api` inside plugin executions, so every `LOGGER.info(...)`/`.warn(...)`/`.debug(...)` call in the delegated-to `srcmorph` code surfaces as a normal `[INFO]`/`[WARN]`/`[DEBUG]` line in `mvn` output with zero glue; the CLI ships its own logback binding for the same log lines outside Maven. `AiFieldGenerationSupportTest` (in `srcmorph`) exercises the binding/configuration directly via a logback `ListAppender` attached to the class logger. diff --git a/examples/config_All.json b/examples/config_All.json new file mode 100644 index 0000000..9ba3c8f --- /dev/null +++ b/examples/config_All.json @@ -0,0 +1,56 @@ +{ + "command": "All", + "srcMorph": { + "baseDirectory": ".", + "outputDirectory": "target/srcmorph-ai", + "subtrees": [ + "src/main/java" + ], + "fileExtensions": [ + ".java" + ], + "excludes": [ + "**/package-info.java" + ], + "generationProvider": "mock", + "projectName": "my-project", + "promptDefinitions": [ + { + "key": "file-body-java", + "template": "Summarize ONE Java source file as structured markdown: types, public API, dependencies." + }, + { + "key": "file-body-fallback", + "template": "Summarize ONE source file as structured markdown." + } + ], + "aiDefinitions": [ + { + "key": "mock-model", + "modelPath": "unused-with-mock-provider.gguf", + "contextSize": 2048, + "maxOutputTokens": 64, + "temperature": 0.15, + "threads": 2 + } + ], + "fieldGenerations": [ + { + "id": "java", + "promptKey": "file-body-java", + "aiDefinitionKey": "mock-model", + "condition": { + "extensions": [ + ".java" + ] + } + }, + { + "id": "fallback", + "fallback": true, + "promptKey": "file-body-fallback", + "aiDefinitionKey": "mock-model" + } + ] + } +} diff --git a/examples/config_Calibrate.json b/examples/config_Calibrate.json new file mode 100644 index 0000000..3de914a --- /dev/null +++ b/examples/config_Calibrate.json @@ -0,0 +1,31 @@ +{ + "command": "Calibrate", + "srcMorph": { + "baseDirectory": ".", + "outputDirectory": "target/srcmorph-ai", + "generationProvider": "mock", + "promptDefinitions": [ + { + "key": "file-body", + "template": "Summarize this file as structured markdown." + } + ], + "aiDefinitions": [ + { + "key": "mock-model", + "modelPath": "unused-with-mock-provider.gguf", + "contextSize": 2048, + "maxOutputTokens": 64, + "temperature": 0.15, + "threads": 2 + } + ], + "fieldGenerations": [ + { + "promptKey": "file-body", + "aiDefinitionKey": "mock-model", + "fallback": true + } + ] + } +} diff --git a/examples/config_GenerateFileIndex.json b/examples/config_GenerateFileIndex.json new file mode 100644 index 0000000..406cac4 --- /dev/null +++ b/examples/config_GenerateFileIndex.json @@ -0,0 +1,37 @@ +{ + "command": "GenerateFileIndex", + "srcMorph": { + "baseDirectory": ".", + "outputDirectory": "target/srcmorph-ai", + "subtrees": [ + "src/main/java" + ], + "fileExtensions": [ + ".java" + ], + "generationProvider": "mock", + "promptDefinitions": [ + { + "key": "file-body", + "template": "Summarize this file as structured markdown." + } + ], + "aiDefinitions": [ + { + "key": "mock-model", + "modelPath": "unused-with-mock-provider.gguf", + "contextSize": 2048, + "maxOutputTokens": 64, + "temperature": 0.15, + "threads": 2 + } + ], + "fieldGenerations": [ + { + "promptKey": "file-body", + "aiDefinitionKey": "mock-model", + "fallback": true + } + ] + } +} diff --git a/examples/config_GenerateFileIndex.yaml b/examples/config_GenerateFileIndex.yaml new file mode 100644 index 0000000..ab4aa40 --- /dev/null +++ b/examples/config_GenerateFileIndex.yaml @@ -0,0 +1,23 @@ +command: GenerateFileIndex +srcMorph: + baseDirectory: "." + outputDirectory: "target/srcmorph-ai" + subtrees: + - "src/main/java" + fileExtensions: + - ".java" + generationProvider: mock + promptDefinitions: + - key: file-body + template: "Summarize this file as structured markdown." + aiDefinitions: + - key: mock-model + modelPath: unused-with-mock-provider.gguf + contextSize: 2048 + maxOutputTokens: 64 + temperature: 0.15 + threads: 2 + fieldGenerations: + - promptKey: file-body + aiDefinitionKey: mock-model + fallback: true diff --git a/examples/config_Plan.json b/examples/config_Plan.json new file mode 100644 index 0000000..97ef6d1 --- /dev/null +++ b/examples/config_Plan.json @@ -0,0 +1,37 @@ +{ + "command": "Plan", + "srcMorph": { + "baseDirectory": ".", + "outputDirectory": "target/srcmorph-ai", + "subtrees": [ + "src/main/java" + ], + "fileExtensions": [ + ".java" + ], + "generationProvider": "mock", + "promptDefinitions": [ + { + "key": "file-body", + "template": "Summarize this file as structured markdown." + } + ], + "aiDefinitions": [ + { + "key": "mock-model", + "modelPath": "unused-with-mock-provider.gguf", + "contextSize": 2048, + "maxOutputTokens": 64, + "temperature": 0.15, + "threads": 2 + } + ], + "fieldGenerations": [ + { + "promptKey": "file-body", + "aiDefinitionKey": "mock-model", + "fallback": true + } + ] + } +} diff --git a/examples/logbackConfiguration.xml b/examples/logbackConfiguration.xml new file mode 100644 index 0000000..afb09ad --- /dev/null +++ b/examples/logbackConfiguration.xml @@ -0,0 +1,22 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/examples/run_all.bat b/examples/run_all.bat new file mode 100644 index 0000000..dfdae77 --- /dev/null +++ b/examples/run_all.bat @@ -0,0 +1,23 @@ +REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin +REM +REM SPDX-License-Identifier: Apache-2.0 + +@echo off +rem Runs the srcmorph CLI against config_All.json: all three phases in order (GenerateFileIndex, +rem AggregatePackages, AggregateProject). Uses the mock provider, so no GGUF model is required - +rem point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. +rem +rem The fat jar's file name is version-qualified (e.g. +rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +setlocal +cd /d "%~dp0" + +set "JARFILE=" +for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" +if "%JARFILE%"=="" ( + echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 + exit /b 1 +) + +java -jar "%JARFILE%" config_All.json diff --git a/examples/run_all.sh b/examples/run_all.sh new file mode 100755 index 0000000..ee00b8c --- /dev/null +++ b/examples/run_all.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: Apache-2.0 + +# Runs the srcmorph CLI against config_All.json: all three phases in order (GenerateFileIndex, +# AggregatePackages, AggregateProject). Uses the mock provider, so no GGUF model is required - +# point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. +# +# The fat jar's file name is version-qualified (e.g. +# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +set -euo pipefail +cd "$(dirname "$0")" + +JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) +if [ -z "$JAR" ]; then + echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 + exit 1 +fi + +java -jar "$JAR" config_All.json diff --git a/examples/run_calibrate.bat b/examples/run_calibrate.bat new file mode 100644 index 0000000..37a019e --- /dev/null +++ b/examples/run_calibrate.bat @@ -0,0 +1,24 @@ +REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin +REM +REM SPDX-License-Identifier: Apache-2.0 + +@echo off +rem Runs the srcmorph CLI against config_Calibrate.json: loads each distinct routed model once +rem and prints a paste-ready block per model. With the mock provider (as shipped) +rem this is a no-op smoke check; swap generationProvider to "llamacpp-jni" and set a real +rem modelPath to calibrate an actual GGUF model on this machine. +rem +rem The fat jar's file name is version-qualified (e.g. +rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +setlocal +cd /d "%~dp0" + +set "JARFILE=" +for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" +if "%JARFILE%"=="" ( + echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 + exit /b 1 +) + +java -jar "%JARFILE%" config_Calibrate.json diff --git a/examples/run_calibrate.sh b/examples/run_calibrate.sh new file mode 100755 index 0000000..10ec18d --- /dev/null +++ b/examples/run_calibrate.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: Apache-2.0 + +# Runs the srcmorph CLI against config_Calibrate.json: loads each distinct routed model once and +# prints a paste-ready block per model. With the mock provider (as shipped) this is +# a no-op smoke check; swap generationProvider to "llamacpp-jni" and set a real modelPath to +# calibrate an actual GGUF model on this machine. +# +# The fat jar's file name is version-qualified (e.g. +# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +set -euo pipefail +cd "$(dirname "$0")" + +JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) +if [ -z "$JAR" ]; then + echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 + exit 1 +fi + +java -jar "$JAR" config_Calibrate.json diff --git a/examples/run_generate.bat b/examples/run_generate.bat new file mode 100644 index 0000000..0dbb769 --- /dev/null +++ b/examples/run_generate.bat @@ -0,0 +1,23 @@ +REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin +REM +REM SPDX-License-Identifier: Apache-2.0 + +@echo off +rem Runs the srcmorph CLI against config_GenerateFileIndex.json (Phase 1 only: index source files +rem and fill in their AI-generated summary bodies). Uses the mock provider, so no GGUF model is +rem required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. +rem +rem The fat jar's file name is version-qualified (e.g. +rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +setlocal +cd /d "%~dp0" + +set "JARFILE=" +for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" +if "%JARFILE%"=="" ( + echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 + exit /b 1 +) + +java -jar "%JARFILE%" config_GenerateFileIndex.json diff --git a/examples/run_generate.sh b/examples/run_generate.sh new file mode 100755 index 0000000..7a884a2 --- /dev/null +++ b/examples/run_generate.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: Apache-2.0 + +# Runs the srcmorph CLI against config_GenerateFileIndex.json (Phase 1 only: index source files +# and fill in their AI-generated summary bodies). Uses the mock provider, so no GGUF model is +# required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. +# +# The fat jar's file name is version-qualified (e.g. +# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +set -euo pipefail +cd "$(dirname "$0")" + +JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) +if [ -z "$JAR" ]; then + echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 + exit 1 +fi + +java -jar "$JAR" config_GenerateFileIndex.json diff --git a/examples/run_plan.bat b/examples/run_plan.bat new file mode 100644 index 0000000..f66bc2f --- /dev/null +++ b/examples/run_plan.bat @@ -0,0 +1,23 @@ +REM SPDX-FileCopyrightText: 2026 Bernard Ladenthin +REM +REM SPDX-License-Identifier: Apache-2.0 + +@echo off +rem Runs the srcmorph CLI against config_Plan.json: builds and logs the routing plan only (the +rem GenerateFileIndex phase with planOnly forced true by the Plan command) - no model is loaded +rem and nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). +rem +rem The fat jar's file name is version-qualified (e.g. +rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. +setlocal +cd /d "%~dp0" + +set "JARFILE=" +for %%f in (..\srcmorph-cli\target\srcmorph-cli-*-jar-with-dependencies.jar) do set "JARFILE=%%f" +if "%JARFILE%"=="" ( + echo srcmorph-cli fat jar not found under ..\srcmorph-cli\target\ - run "mvn package" first. 1>&2 + exit /b 1 +) + +java -jar "%JARFILE%" config_Plan.json diff --git a/examples/run_plan.sh b/examples/run_plan.sh new file mode 100755 index 0000000..cbda0ac --- /dev/null +++ b/examples/run_plan.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: Apache-2.0 + +# Runs the srcmorph CLI against config_Plan.json: builds and logs the routing plan only (the +# GenerateFileIndex phase with planOnly forced true by the Plan command) - no model is loaded and +# nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). +# +# The fat jar's file name is version-qualified (e.g. +# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. +set -euo pipefail +cd "$(dirname "$0")" + +JAR=$(ls ../srcmorph-cli/target/srcmorph-cli-*-jar-with-dependencies.jar 2>/dev/null | head -n 1) +if [ -z "$JAR" ]; then + echo "srcmorph-cli fat jar not found under ../srcmorph-cli/target/ - run 'mvn package' first." >&2 + exit 1 +fi + +java -jar "$JAR" config_Plan.json diff --git a/llamacpp-ai-index-maven-plugin/README.md b/llamacpp-ai-index-maven-plugin/README.md new file mode 100644 index 0000000..b94bdd9 --- /dev/null +++ b/llamacpp-ai-index-maven-plugin/README.md @@ -0,0 +1,622 @@ +# llamacpp-ai-index-maven-plugin + +A Maven plugin for generating hierarchical, AI-readable documentation of source code projects +using local llama.cpp-compatible models. It creates structured `.ai.md` files per source file and +aggregates them into package-level (and project-level) summaries for fast semantic navigation and +retrieval. + +> This module is one of three in the [srcmorph](../README.md) Maven reactor — see the root README +> for the product overview, badges, and build status. **This plugin's Maven coordinates, package, +> and property names are unchanged** (`net.ladenthin:llamacpp-ai-index-maven-plugin`, goal prefix +> `ai-index`, properties `aiIndex.*`); a future release will relocate it to +> `net.ladenthin:srcmorph-maven-plugin`, but that has not happened yet. Internally, the plugin now +> depends on [`net.ladenthin:srcmorph`](../srcmorph/README.md) (a sibling reactor module) for all of +> its engine/indexing logic — the 5 Mojo classes in this module are thin wrappers that map their +> `@Parameter` fields onto a shared `SrcMorphConfiguration` and delegate to one of `srcmorph`'s +> `engine.*` classes. That split is an internal implementation detail; nothing below changes for a +> user of this plugin. + +## Features +- Generate AI summaries for source files +- Per-language prompts — Java, SQL schema, and a generic fallback — selected by file extension +- Weave searchable type, API and domain names into every summary +- Aggregate summaries at package level +- Build a single project index (one line + link per package) for top-down navigation, optionally with a one-call AI `#### Overview` +- Run any phase independently — toggle file / package / project on or off +- Exclude trivial or generated files with glob patterns +- Uses local models via llama.cpp (no cloud dependency) +- Incremental updates (skips unchanged files) +- Logs a rough per-file duration estimate before each generation (size, token count, expected time) +- Optimized for AI-assisted code understanding +## How It Works +The plugin runs in three phases, building a navigable index from fine to coarse. +### 1. File Generation (generate) +- Scans configured source directories +- Creates `.ai.md` files per source file +- Each file contains metadata header and markdown summary +### 2. Package Aggregation (aggregate-packages) +- Traverses generated `.ai.md` files +- Builds hierarchical package summaries +- Produces `package.ai.md` files; the header carries a deterministic `F` link list to each child (package → file navigation) +### 3. Project Index (aggregate-project) +- Harvests the one-line lead from every `package.ai.md` — the per-package listing is deterministic, no AI call +- Produces a single `project.ai.md`: one body line per package (its lead) with the clickable links in the header `F` list +- A compact, always-loadable table of contents an agent reads first to navigate down +- **Optional** (opt-in): configure a `` on this goal and it makes *one* extra AI call to + write a short `#### Overview` paragraph from the package leads. The deterministic per-package listing is + unchanged; with no field generation the goal stays purely deterministic and calls no model. +## Example Output +``` +### AiMdDocument.java +- H: 1.0 +- C: A48CED8C +- D: 2026-03-15T23:31:52Z +- T: 2026-03-19T18:13:31Z +- G: 1.0.0 +- A: 0.0.0 +- X: file +--- +> Immutable value type pairing a deterministic metadata header with the AI-generated markdown body of one .ai.md document. + +#### Purpose +- Hold one parsed `.ai.md` document as an `AiMdHeader` plus its markdown body. + +#### Type +- record-shaped value class (Java); marked `@ConvertToRecord`. + +#### Public API +- `header() -> AiMdHeader` — the document's metadata header. +- `body() -> String` — the AI-generated markdown body. +``` +## Requirements +- Java 8+ (production code targets Java 8; CI builds on Java 8 via temurin) +- Maven 3.6.3+ +- Local GGUF model (llama.cpp compatible) + +### Running under Java 8: override `checker-qual` + +The plugin itself is compiled to Java 8 bytecode, but it pulls in +[`org.checkerframework:checker-qual`](https://central.sonatype.com/artifact/org.checkerframework/checker-qual) +transitively, and the version it builds against (`4.2.1`) ships its classes as **Java 11 +bytecode** (class-file major version 55). On a **Java 8** JVM, loading those annotation +classes fails with `UnsupportedClassVersionError`. + +If you run the plugin under a Java 8 Maven/JVM, **override `checker-qual` to `3.55.1`** — the +**last release whose runtime classes are Java 8 bytecode** (major 52). The `checker-qual` line +switched to Java 11 bytecode in `4.0.0`; every `3.x` release (`3.42.0` … `3.55.1`) is Java 8 +loadable, and `3.55.1` is the newest of them. (`checker-qual` `3.43.0`–`3.55.1` additionally +carry a root `module-info.class`, which a Java 8 classpath simply ignores; if you want a jar +with no `module-info.class` at all, `3.42.0` is the last such release.) + +Because this is a *plugin* dependency, the override goes inside the plugin's own +`` block (a project-level `` does **not** affect a plugin's +classpath): + +```xml + + net.ladenthin + llamacpp-ai-index-maven-plugin + + + + + org.checkerframework + checker-qual + 3.55.1 + + + +``` + +Running the plugin under Java 11 or newer needs no override. + +## Dependency + +The plugin depends on [`net.ladenthin:llama`](https://central.sonatype.com/artifact/net.ladenthin/llama), the Java/JNI binding for llama.cpp (via the `srcmorph` core library it wraps). +It is published on Maven Central and resolves automatically — no manual installation required. + +```xml + + net.ladenthin + llama + 5.0.6 + +``` +## Configuration +The plugin is configured from three building blocks, declared on the plugin inside +``: + +1. **``** — define each GGUF model once (path + sampling parameters), each with a ``. +2. **``** — define each prompt template once, each with a ``. A template takes + two `%s` placeholders: the file/package name and the source (for packages, the child summaries; for + the optional project overview, the per-package leads). +3. **``** — per goal, the routing **rules**. Each rule maps a `` to an + `` (model id, which carries the full parameter set) and selects files with a + composable **``** tree: ``/``/`` over the leaves ``, `` + (``/`` bytes), `` (``/``), ``/`` (ISO-8601 + instant vs the file's last-modified time), and `` (base-relative glob). When several rules + match, the highest `` wins (ties by declaration order). A rule may instead be + `true` (ignore matching files — a high-priority skip beats routes and the fallback), and + **exactly one** `true` (no condition) catches the rest. A file that + matches no rule and no fallback **fails the build**. So one `generate` run can index different file + kinds/sizes with **different models *and* prompts**; it loads each model once. Run with + `-DaiIndex.planOnly=true` to print the routing plan (a copy-pasteable Markdown table: file → rule id → + prompt → context-window fit → rough time estimate, summed per model and overall) and stop before + loading any model. The plan also checks each file against its routed model's **context window**: a + file too large for the window would lose content if trimmed. What happens then is **configuration + only** — the plugin never picks a model for you — and is chosen per rule with ``: + - **`fail`** *(default)* — abort the build (a hard fail). The fix is to add a `` rule + with a size `` that routes oversized files to a model with a large enough window (see the + `granite-4.0-h-tiny-bigwindow` definition + the `big-window-java` / `big-window-sql` rules in the + POM — IBM Granite 4.0-H-Tiny, Apache-2.0, a hybrid Mamba model whose KV cache grows only linearly, + configured at a 384K window to cover files up to ~1 MB; verified summarizing a ~995 KB / ~268K-token + file on an 8 GB GPU with no OOM). + - **`sample`** — feed only the head of the file (trimmed to the window) in a single call. Fast and + bounded; good for repetitive data where the head represents the whole. + - **`mapReduce`** — split the file into window-sized chunks at line boundaries (with overlap so + records are never torn mid-syntax), summarize each chunk, then combine the partial summaries in one + final call. A `` cap bounds the time (a representative subset — always head + tail, + evenly spaced — is summarized when the file would exceed the cap), so an arbitrarily large file + (e.g. 7 MB) stays within a chosen per-file budget. **Route oversized files to a *small*, fast model + for this — not the big-window one.** Prefill is `O(n²)` in prompt length, so chunks should be small: + many cheap small-window passes are far faster than a few giant ones (one 384K-token pass alone can + dwarf a whole run). E.g. a 16K-window model with `maxChunks=6` is ~7 calls (~1 h order on a + reference CPU; less on GPU) and samples a representative slice — the right trade-off for repetitive + data. mapReduce *on* a big window is the slowest possible combination; avoid it. + - **`deterministic`** — no model at all: emit a deterministic body (size, line count, head/tail + sample). Instant; for pure data where no AI analysis is needed. + + Quality from a real model is best within Granite's validated 128K (~500 KB) and degrades gradually + beyond it — a whole-file (or chunked) summary still beats a trimmed one. + + **Exact counts with `` (optional, every file — not just oversize).** A sampled/chunked AI + summary can't reliably *count* things (no single call sees the whole file — it will guess "25 rows"). + Add a `` list to a rule and each `{label, pattern}` reports its regex match count over the + **whole** source, prepended to the body of every file the rule matches as an exact facts line — so + downstream agents get authoritative structural counts in every summary. It's fully generic — the + meaning is in the regex, so the same mechanism counts SQL `INSERT` rows or Java `\bboolean\b` fields; + multi-line matching is opt-in via the inline `(?m)` flag. Keep patterns robust (a fact that miscounts + is worse than none). Example: `**Facts (exact, whole file):** INSERT rows: 36738; tables: 122; views: 4`. + +```xml + + net.ladenthin + llamacpp-ai-index-maven-plugin + 1.0.2 + + + + + src/main/java/com/example + + + llamacpp-jni + + + + + coder + /path/to/model.gguf + 32768 + 1536 + 0.7 + 8 + + + + + + + file-body-java + + + + file-body-fallback + + + + package-body + + + + + project-body + + + + + + + + + ai-generate + generate-resources + generate + + + + + java + file-body-java + coder + + .java + + + + fallback + true + file-body-fallback + coder + + + + + + ai-aggregate-packages + process-resources + aggregate-packages + + + + package-body + coder + + + + + + + ai-aggregate-project + prepare-package + aggregate-project + + + + project-body + coder + + + + + + +``` + +### Routing rules (conditions, priority, skip, plan) +Within one `generate` run you can route files to different models **and** prompts by size, language, +age or path. Example — small/medium/large Java files to three context presets, skip generated sources, +and a fallback for everything else: + +```xml + + + skip-generatedtrue100 + **/generated/** + + + java-smallfile-body-java-tersegpt-oss-20B-c16k + + .java + 16384 + + + + java-midfile-body-javagpt-oss-20B-c48k + + .java + 1638449152 + + + + java-largefile-body-java-detailedgpt-oss-20B-c96k + + .java + 49152 + + + + java-hugefile-body-javagpt-oss-20B-c16k + mapReduce6 + + \bboolean\b + (?m)^\s+(public|private|protected).*\( + + + .java + 1048576 + + + + fallbacktrue + file-body-fallbackgpt-oss-20B-c96k + + +``` + +Notes: size bounds are **min-exclusive / max-inclusive** so `band2.min == band1.max` is non-overlapping; +`` works the same way; `2026-01-01T00:00:00Z` only (re)indexes +recently changed files. `` (`fail` *(default)* / `sample` / `mapReduce` / `deterministic` — +see the context-window note above) chooses what a rule does when a file is still larger than its routed +model's window; `` caps how many chunks `mapReduce` summarizes. Preview the mapping without +running a model: + +``` +mvn ai-index:generate -DaiIndex.planOnly=true +``` + +## Usage +Run AI index generation: +``` +mvn clean install -Pai-index-selftest +``` +With native llama tests: +``` +mvn clean install -Pai-index-selftest -DrunNativeLlamaTests=true +``` +## Plugin Configuration +Run-level parameters (set in ``): +- `outputDirectory` — target directory for `.ai.md` files (default: `${project.basedir}/src/site/ai`) +- `subtrees` — source directories to index, relative to the project base dir (default: `src/main/java`) +- `fileExtensions` — file extensions to index (default: `.java`) +- `excludes` — glob patterns for source files to skip, matched against each file's path relative to + the base dir with `/` separators, e.g. `**/package-info.java`, `**/generated/**` (default: none). + `*` stays within one path segment, `**` spans directories, `?` is a single character. +- `generationProvider` — AI backend: `mock` (default) or `llamacpp-jni` +- `force` — regenerate even when a body already exists (default: `false`) +- `skip` — global switch: skip **every** phase (default: `false`) +- Per-phase switches — turn any of the three phases on/off independently (each default `false`). + Named after the three index levels (`file` / `package` / `project`, the `x` node types): + - `aiIndex.file.skip` — skip the **file** phase (the `generate` goal) + - `aiIndex.package.skip` — skip the **package** phase (the `aggregate-packages` goal) + - `aiIndex.project.skip` — skip the **project** phase (the `aggregate-project` goal) + + So `-DaiIndex.package.skip=true` runs file + project only, `-DaiIndex.skip=true` runs + nothing, and the defaults run all three. +- `aiDefinitions` / `promptDefinitions` — named models / prompt templates, referenced by key +- `fieldGenerations` — per goal: which `promptKey` runs with which `aiDefinitionKey` (**required**) + +### Per-model `` parameters + +Every model knob lives inside its `` (referenced by `aiDefinitionKey`), not as a top-level +plugin parameter. Only `key` and `modelPath` are required; everything else has a default. The defaults +below are the shipped values (`AiGenerationConfig.DEFAULT_*`). + +| Element | Default | Description | +|---|---|---| +| `key` | *(required)* | Identifier referenced by a rule's `aiDefinitionKey` | +| `modelPath` | *(required)* | Path to the GGUF model file | +| `contextSize` | `32768` | Context window in tokens | +| `maxOutputTokens` | `128` | Max generated tokens per call | +| `threads` | `8` | CPU threads for inference | +| `temperature` | `0.15` | Sampling temperature | +| `topP` | `0.9` | Nucleus (top-p) sampling threshold | +| `topK` | `40` | Top-k sampling limit (`0` = disabled) | +| `minP` | `0.0` | Min-p sampling threshold (`0.0` = disabled) | +| `topNSigma` | `-1.0` | Top-n-sigma sampling threshold (`-1.0` = disabled) | +| `repeatPenalty` | `1.0` | Repetition penalty (`1.0` = disabled) | +| `charsPerToken` | `4` | Chars-per-token estimate; drives the automatic `maxInputChars` trim budget (`maxInputChars` itself is derived, not a field). Use a value at or below your model's real ratio so the budget stays conservative | +| `warnOnTrim` | `true` | Log a warning when the source is trimmed to fit the window | +| `cachePrompt` | `true` | Reuse the shared prompt-prefix KV across files (`cache_prompt`) | +| `swaFull` | `true` | Keep the full-size sliding-window-attention KV cache (`--swa-full`) | +| `cacheReuse` | `256` | KV prefix-reuse minimum chunk size in tokens (`--cache-reuse`; `0` = off) | +| `gpuLayers` | `-1` | GPU layers to offload (`--gpu-layers`); `-1` = auto-fit to free VRAM, `0` = force CPU, `>0` = partial. GPU native only | +| `mainGpu` | `-1` | Primary GPU index (`--main-gpu`); `-1` = leave default. Matters on multi-GPU hosts (e.g. a Vulkan build enumerates every GPU) | +| `devices` | *(empty)* | Explicit device selection (`--device`), comma-separated backend device names (e.g. `Vulkan1`); takes precedence over `mainGpu` | +| `chatTemplateEnableThinking` | `true` | Enable the chat template's thinking mode | +| `reasoningEffort` | `low` | gpt-oss harmony reasoning effort (`low`/`medium`/`high`); empty omits the kwarg (e.g. for non-gpt-oss models) | +| `reasoningBudgetTokens` | `-1` | Cap on harmony reasoning tokens (`-1` = unrestricted) | +| `dryMultiplier` | `0.0` | DRY repetition-penalty multiplier (`0.0` = disabled); the other `dry*` knobs only apply when this is `> 0` | +| `dryBase` | `1.75` | DRY exponential base | +| `dryAllowedLength` | `2` | Longest n-gram that may repeat without DRY penalty | +| `dryPenaltyLastN` | `-1` | DRY look-back window in tokens (`-1` = whole context, `0` = off) | +| `drySequenceBreakers` | *(empty)* | DRY sequence-breaker strings; empty = the binding defaults | +| `stopStrings` | *(empty)* | Extra stop strings that end generation | + +## Prompt System +Prompts are defined in the plugin configuration (``) and referenced by key +from ``. The self-test profile defines five: +- `file-body-java` — summarizes a single Java source file (types, public API, dependencies) +- `file-body-sql` — summarizes a single SQL file as schema (tables/views/procedures, columns, + the tables it reads vs writes, and relationships) +- `file-body-fallback` — generic multi-language summary for any other source file +- `package-body` — synthesizes a package summary from the already-generated file summaries +- `project-body` — (optional) synthesizes the short project `#### Overview` paragraph from the package leads + +For the `generate` goal the file-level prompt is selected per file by extension: the first field +generation whose `` matches the file name wins; otherwise the first entry without a +`` filter is the fallback. A single field generation with no filter (the historical +shape) keeps working — it is simply the fallback for every file. + +Each summary begins with a one-sentence blockquote lead, followed by structured `####` sections. +Prompts are optimized to avoid code blocks, formatter artifacts, and empty outputs, and to produce structured markdown. +## Output Structure +``` +src/site/ai/ +└── main/ + └── java/ + └── com/ + └── example/ + ├── MyClass.java.ai.md + ├── AnotherClass.java.ai.md + └── package.ai.md +``` +## Design Principles +- Deterministic metadata (hash-based change detection) +- Separation of concerns (header = metadata, body = summary) +- AI-friendly structure (predictable and hierarchical) +- Local-first (no external APIs required) +## Known Limitations +- Model output may require normalization (handled in code) +- Large models increase runtime +- Output quality depends on chosen model + +## TODO +- **Expand PIT mutation-testing scope.** `srcmorph/pom.xml`'s `` lists an explicit subset of classes verified at 100% mutation parity; this plugin module has no PIT gate of its own yet. Generic PIT setup and invocation: see the [PIT policy](../../workspace/policies/pit-mutation-testing.md). +## Recommended Models +Based on an 8-model × 2-prompt benchmark run against this codebase — full results, per-model +pros/cons, a source-faithfulness deep-dive, and reproduction steps in +[docs/ai-index-benchmark](../docs/ai-index-benchmark/COMPARISON.md): + +- **`gpt-oss-20B-mxfp4` — the production default** (switch with `-Dai.model=`). The native MXFP4 + quant at a 96K window; it inherits the benchmark's accuracy lead (gpt-oss-20b was most *accurate* per + file, won 5/6 in the per-file matrix — measured on the `c96k`/UD-Q4_K_XL quant, but E5 shows quant + choice is within noise so the native MXFP4 is the better-quality swap), run at `reasoningEffort=low` + and a 96K window so it covers files up to ~250 KB untrimmed. Slowest of the set (~2× the 30B) — the + accepted cost for accuracy. See the preset/timing details below. +- **Qwen3-Coder-30B-A3B-Instruct** — throughput alternative (and best of the non-reasoning models for + large Java files): most complete/faithful of the fast models, code-specialized, Apache-2.0, + ~3.3B-active MoE, 262K context. Pick it when throughput beats the last points of fidelity. +- **Granite-4.0-H-Tiny** — fastest on CPU (~4×, flat-KV hybrid, Apache-2.0); best for very large + or many files when throughput beats the last points of fidelity. +- **Seed-Coder-8B-Instruct** — clean, permissive (MIT) small dense coder. +- Avoid `Qwen3.5-4B` (thinking tax, no quality gain). + +### gpt-oss-20b presets, large files, and timing + +`gpt-oss` is a *reasoning* model whose analysis tokens share the output budget, so use +`reasoningEffort=low` for code summaries (best quality here) and size the budget to the file. The pom +ships three ready presets, tiered by the largest file you must cover — full rationale and measurements +in [COMPARISON.md §11](../docs/ai-index-benchmark/COMPARISON.md): + +| Preset | context | covers up to | ~ time (CPU) | +|---|---|---|---| +| `gpt-oss-20B-c16k` | 16K | ~40 KB | ~1–2 min | +| `gpt-oss-20B-c48k` | 48K | ~125 KB | ~25 min @ 100 KB | +| **`gpt-oss-20B-c96k` (default)** | 96K | ~260 KB | ~80 min @ 250 KB | + +- **`c96k` is the default:** a measured A/B shows a wider context window costs only RAM, not per-file + time, so it covers every file up to ~250 KB with no trimming while small files stay just as fast. + Downshift only to save RAM. Hard ceiling is the 128K window (~480–500 KB of code). +- **Timing is quadratic, not linear:** prefill ≈ `24.4·n + 0.000674·n²` ms (n = prompt tokens), + because attention is O(n) per token — which is why throughput drops as files grow. The plugin logs + this estimate per file. + +## GPU acceleration (opt-in) +The default native is CPU (Ninja build, bundled in the main `net.ladenthin:llama` jar). On an NVIDIA +RTX 3070 a CUDA build measured **~4.5× the CPU decode speed**; Vulkan also works (AMD + NVIDIA) but pays +a one-time shader-compilation cost on the first run. OpenCL is intentionally not offered (llama.cpp's +OpenCL backend does not support NVIDIA GPUs). + +How the native is found: `net.ladenthin.llama.loader.LlamaLoader` tries `net.ladenthin.llama.lib.path`, +then `java.library.path`, then **extracts the native bundled in whatever `net.ladenthin:llama` jar is on +the classpath**. So there are two ways to enable a GPU when running the *published* plugin in your own +build. + +**Recommended — add the GPU classifier to the plugin's own classpath.** Declare the matching +`net.ladenthin:llama` classifier as a dependency of the plugin in your POM; the loader then extracts the +GPU `jllama.dll` from it — no library path to manage: + +```xml + + net.ladenthin + llamacpp-ai-index-maven-plugin + ... + + + net.ladenthin + llama + 5.0.6 + cuda13-windows-x86-64 + + + +``` + +``` +mvn ai-index:generate -Dai.gpuLayers=20 # + the GPU runtime on PATH (see below) +``` + +**Alternative — runtime library override (no POM change).** Point `net.ladenthin.llama.lib.path` at a +folder holding the GPU `jllama.dll` (extracted once from the classifier jar); it is tried before the +bundled native: + +``` +mvn ai-index:generate -Dnet.ladenthin.llama.lib.path=C:\path\to\gpu-native -Dai.gpuLayers=20 +``` + +In both cases: + +- **CUDA** needs a matching CUDA 13 toolkit + driver, and the toolkit's `bin\x64` (with `cudart64_13.dll`, + `cublas64_13.dll`) on `PATH` — the classifier jar bundles only `jllama.dll`, not the CUDA runtime. +- **`ai.gpuLayers`** (on the gpt-oss presets): `-1` (default) does **not** pin a layer count, so + llama.cpp **auto-fits** as many layers as fit the card's free VRAM — the robust "runs on any card" + setting (it never over-commits, so no OOM on a 6 GB card, and uses more layers on a bigger one). Pin a + positive number only to force a specific **partial** split (a fixed count disables auto-fit), or `0` to + force CPU. Measured on an 8 GB RTX 3070, auto-fit gpt-oss-20b ≈ 29 decode t/s (vs ≈ 8 on CPU); a card + with ≥ 16 GB fits all layers and is far faster. +- **Picking a GPU on a multi-GPU host** (`ai.mainGpu` / `ai.devices` on the gpt-oss presets): a **CUDA** + build only enumerates NVIDIA devices, so a single-NVIDIA host needs nothing. A **Vulkan** build + enumerates *every* GPU (an integrated GPU is often device `0`), so the default may pick the slower one — + set `-Dai.mainGpu=1` to select the discrete GPU, or `-Dai.devices=Vulkan1` for explicit device names + (these map to the binding's `--main-gpu` / `--device`). On any model definition the same knobs are the + `` / `` elements. + +**Profiles (this repo's own reactor build only — test/benchmark).** `-P gpu-cuda` / `-P gpu-vulkan` +swap the `net.ladenthin:llama` classifier (via the `llama.classifier` property) for `srcmorph`'s own +test/compile classpath — handy for the native test or benchmarking on GPU here. They do **not** change +the native used when the *published* plugin runs in another build (the POM is not flattened, so the +classifier stays a property that resolves to the CPU default downstream) — use one of the two methods +above for real indexing. + +## Development + +Run full build: +``` +mvn clean install +``` +Skip AI generation: +``` +mvn clean install -DaiIndex.skip=true +``` + +### Contributors: do not upgrade jqwik past 1.9.3 + +> ⚠️ **DO NOT UPGRADE jqwik past 1.9.3.** jqwik 1.10.0 added an anti-AI prompt-injection string to test stdout; the 1.10.1 user guide states the library "is not meant to be used by any 'AI' coding agents at all." 1.9.3 is the last pre-disclosure release and is the pinned version (declared in `srcmorph/pom.xml`, the only module with a jqwik test dependency). See `../CLAUDE.md` section "jqwik prompt-injection in test output" for the full context. Dependabot is configured to ignore **all** `net.jqwik` updates (every version, including patches) — see the `ignore` rule in [`../.github/dependabot.yml`](../.github/dependabot.yml). + +## A Note on History + +Somewhere in early 2026, the same idea apparently occurred more than once. This repository +started with an implementation on March 15, 2026. Andrej Karpathy published his ["LLM wiki" gist](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) +on April 4, 2026. Google Cloud eventually formalized the very same pattern into the [Open Knowledge Format](https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/), +announced on June 12, 2026. + +## License +Apache License 2.0 + +--- + +
      +🍺 Beer-driven architectural review (2026-05-26) + +> _Sometimes you spend two beers debating "architektonisch" vs "architekturiell" and the only conclusion is: ship the code anyway. Cheers!_ + +
      diff --git a/srcmorph-cli/README.md b/srcmorph-cli/README.md new file mode 100644 index 0000000..d17abf8 --- /dev/null +++ b/srcmorph-cli/README.md @@ -0,0 +1,107 @@ +# srcmorph-cli + +`net.ladenthin:srcmorph-cli` is a standalone command-line front end for +[srcmorph](../README.md): a single `java -jar` invocation driven by one JSON or YAML configuration +file, with no Maven project or build required. It wraps the +[`srcmorph`](../srcmorph/README.md) core library's engines exactly the way the +[Maven plugin](../llamacpp-ai-index-maven-plugin/README.md) does, following the +[BitcoinAddressFinder](https://github.com/bernardladenthin/BitcoinAddressFinder) `cli.Main` pattern. + +## Running it + +```bash +java -jar srcmorph-cli--jar-with-dependencies.jar +``` + +The fat jar (`srcmorph-cli--jar-with-dependencies.jar`, built by `mvn package`) bundles every +dependency, including a logback binding, so it runs standalone. Both `.json`/`.js` (parsed via a +Jackson `ObjectMapper`) and `.yaml`/`.yml` (parsed via `YAMLMapper`) are supported — pick whichever you +prefer; both mappers are configured strictly (`FAIL_ON_UNKNOWN_PROPERTIES`), so a typo'd key fails the +run immediately rather than being silently ignored. On startup the CLI logs the parsed configuration +back to you (re-serialized as both JSON and YAML) so you can confirm exactly what will run before any +model loads or file is written. + +See [`../examples/`](../examples/) for a complete, ready-to-run set of `config_*.json`/`.yaml` files +(all using the `mock` provider, so they run with no GGUF model on disk) plus paired +`run_*.sh`/`run_*.bat` launcher scripts and an example `logbackConfiguration.xml`. + +## Config-file reference + +The root object is `net.ladenthin.srcmorph.cli.configuration.CConfiguration` — a plain, public-field +JavaBean (Jackson binds it directly, no getters/setters needed): + +```java +public class CConfiguration { + public CCommand command = CCommand.Plan; + public SrcMorphConfiguration srcMorph = new SrcMorphConfiguration(); +} +``` + +- **`command`** — one of the six `CCommand` values below. Defaults to `Plan` — the safe default: no + command means no model load and nothing written. +- **`srcMorph`** — the **same** `net.ladenthin.srcmorph.config.SrcMorphConfiguration` object the Maven + plugin's mojos build from their own `@Parameter` fields. **Every key under `srcMorph` in your JSON/YAML + file reads identically to the matching Maven `` XML element** — `baseDirectory`, + `outputDirectory`, `subtrees`, `excludes`, `fileExtensions`, `generationProvider`, + `promptDefinitions`, `aiDefinitions`, `fieldGenerations`, `factDefinitions`, the `llama*` fallback + parameters, `force`, `planOnly`, `projectName`. If you already know the plugin's XML shape, you + already know this shape — see + [`../llamacpp-ai-index-maven-plugin/README.md`](../llamacpp-ai-index-maven-plugin/README.md)'s + "Configuration" section for the full field-by-field reference (routing-rule conditions, oversize + strategies, per-model sampling parameters, etc.), which applies verbatim here. + +### The six `CCommand` values + +| Command | What it does | +|---|---| +| `Plan` | Runs `GenerateEngine` with `planOnly` **forced** to `true`, regardless of what the file says — builds and logs the routing plan (which model + prompt each file would get) and stops. No model is loaded, nothing is written. The default; always safe to run. | +| `GenerateFileIndex` | Phase 1, exactly as configured: indexes source files and fills in their AI-generated summaries (`GenerateEngine`). | +| `AggregatePackages` | Phase 2: aggregates per-package `.ai.md` index files (`AggregatePackagesEngine`). | +| `AggregateProject` | Phase 3: aggregates the single project-level `.ai.md` index (`AggregateProjectEngine`). | +| `All` | Runs `GenerateFileIndex`, then `AggregatePackages`, then `AggregateProject` in order, stopping at the first phase that fails. | +| `Calibrate` | Loads each distinct model your `fieldGenerations` would route to, measures its prefill/decode throughput, and prints a paste-ready `` XML block per model to standard output (`CalibrateEngine`). | + +### One shared `srcMorph` object across commands — a nuance to know + +Unlike the Maven plugin, where each goal execution gets its **own** `` block in the +POM, the CLI's `All` and `Calibrate` commands reuse the **one** `srcMorph.fieldGenerations` list across +every engine they run. That list means different things to different engines: + +- **`GenerateFileIndex`** *routes*: each file is matched to exactly one rule by `condition` + `priority` + (the explicit `fallback` catches anything unmatched). +- **`AggregatePackages`** does **not** route by condition — it applies **every** entry in + `fieldGenerations` to every package, in list order, and the **last** entry's output is kept. +- **`AggregateProject`**'s optional AI overview paragraph uses only `fieldGenerations[0]`. + +If your `fieldGenerations` list has more than one entry (e.g. a conditioned Java-file rule plus a +fallback, as in [`../examples/config_All.json`](../examples/config_All.json)), running `All` will +still aggregate every package using the *entire* list's rules layered on top of each other (last one +wins) — which is usually fine (the rules typically write similar structured Markdown), but is worth +knowing if you see a package's body reflecting your fallback rule's prompt rather than the +file-specific one. A single fallback-only rule sidesteps the question entirely and behaves identically +across all three phases. See [`../srcmorph/README.md`](../srcmorph/README.md) for the engine-level +detail. + +## Testing + +- `MainTest` — unit tests for the extension-dispatched loader and the JSON/YAML mappers. +- `configuration.ConfigBindingTest` — round-trips a private ad-hoc + `src/test/resources/test-fixtures/minimal-generate.{json,yaml}` fixture pair. +- `ExamplesConfigBindingTest` — sweeps every shipped `../examples/config_*.{json,yaml}` file (the + public, documented examples) through the same strict mappers, so an example can never silently drift + out of sync with `SrcMorphConfiguration`'s field set. +- `CliEndToEndTest` — drives `Main#run()` directly (no forked process) against the `mock` provider for + the `All` and `Plan` commands, asserting the expected `.ai.md` tree lands on disk (or, for `Plan`, + that nothing is written and the original configuration object is never mutated). +- `CliArchitectureTest` — ArchUnit rules: this module is the reactor's leaf-most consumer, no public + mutable fields outside `configuration`, no `System.exit`, no dependency on the Maven Plugin API. + +## Building + +```bash +mvn -pl srcmorph-cli -am package +``` + +produces `srcmorph-cli/target/srcmorph-cli-.jar` (plain jar) and +`srcmorph-cli/target/srcmorph-cli--jar-with-dependencies.jar` (the fat jar you actually run — +bound unconditionally to the `package` phase, since for this module the fat jar *is* the deliverable). diff --git a/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java new file mode 100644 index 0000000..2a722a0 --- /dev/null +++ b/srcmorph-cli/src/test/java/net/ladenthin/srcmorph/cli/ExamplesConfigBindingTest.java @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2026 Bernard Ladenthin +// +// SPDX-License-Identifier: Apache-2.0 +package net.ladenthin.srcmorph.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import net.ladenthin.srcmorph.cli.configuration.CConfiguration; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +/** + * Round-trips every shipped {@code examples/config_*.{json,yaml}} fixture (the public, documented + * configuration examples referenced from the root and per-module {@code README.md} files) through + * the CLI's actual Jackson mappers ({@link Main#fromJson(String)} / {@link Main#fromYaml(String)}) + * and asserts a minimally sane {@link CConfiguration}/{@link SrcMorphConfiguration} object graph. + * + *

      Unlike {@code configuration.ConfigBindingTest} (which pins the exact object graph of one + * ad-hoc private fixture pair), this test is a coverage sweep over the public examples + * directory: every {@code config_*.json} and {@code config_*.yaml} file under the repository-root + * {@code examples/} directory must parse without error under the CLI's strict + * ({@code FAIL_ON_UNKNOWN_PROPERTIES}) mappers, so an example can never silently drift out of sync + * with {@link SrcMorphConfiguration}'s actual field set. Maven runs tests with the working directory + * set to the module's own base directory ({@code srcmorph-cli/}), so the examples directory is + * addressed relative to that as {@code ../examples}.

      + */ +public class ExamplesConfigBindingTest { + + /** + * The repository-root {@code examples/} directory, addressed relative to this module's own base + * directory (Maven Surefire's working directory during a test run). + */ + private static final Path EXAMPLES_DIRECTORY = Path.of("../examples"); + + /** + * Builds one {@link DynamicTest} per {@code config_*.json}/{@code config_*.yaml} file found under + * {@link #EXAMPLES_DIRECTORY}, each asserting that the file parses cleanly and yields a sane + * configuration object graph. + * + * @return one dynamic test per example configuration file + * @throws IOException if {@link #EXAMPLES_DIRECTORY} cannot be listed + */ + @TestFactory + public List everyExampleConfig_bindsSuccessfully() throws IOException { + final List exampleConfigs = listExampleConfigFiles(); + assertThat( + "expected at least one example config fixture under " + EXAMPLES_DIRECTORY, + exampleConfigs.size(), + greaterThan(0)); + + final List tests = new ArrayList<>(exampleConfigs.size()); + for (final Path exampleConfig : exampleConfigs) { + tests.add(DynamicTest.dynamicTest( + exampleConfig.getFileName().toString(), () -> assertBindsSuccessfully(exampleConfig))); + } + return tests; + } + + private static List listExampleConfigFiles() throws IOException { + final List found = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(EXAMPLES_DIRECTORY, "config_*.{json,yaml}")) { + for (final Path candidate : stream) { + found.add(candidate); + } + } + return found; + } + + private static void assertBindsSuccessfully(final Path exampleConfig) throws IOException { + final String fileName = exampleConfig.getFileName().toString(); + final String content = Main.readString(exampleConfig); + + final CConfiguration configuration = + fileName.endsWith(".yaml") ? Main.fromYaml(content) : Main.fromJson(content); + + assertThat(fileName + ": command must be set", configuration.command, is(notNullValue())); + + final SrcMorphConfiguration srcMorph = configuration.srcMorph; + assertThat(fileName + ": srcMorph must be set", srcMorph, is(notNullValue())); + assertThat(fileName + ": baseDirectory must be set", srcMorph.getBaseDirectory(), is(notNullValue())); + assertThat( + fileName + ": generationProvider must be the safe 'mock' default", + srcMorph.getGenerationProvider(), + is(equalTo("mock"))); + + // Every shipped example configures at least the fallback routing rule, so the run has + // something to do (a config with an empty fails GenerateEngine/ + // CalibrateEngine at run time, so an example silently missing it would be useless). + assertThat( + fileName + ": fieldGenerations must be configured", srcMorph.getFieldGenerations(), is(notNullValue())); + assertThat( + fileName + ": fieldGenerations must not be empty", + srcMorph.getFieldGenerations().size(), + greaterThan(0)); + } +} diff --git a/srcmorph/README.md b/srcmorph/README.md new file mode 100644 index 0000000..3d8ff99 --- /dev/null +++ b/srcmorph/README.md @@ -0,0 +1,147 @@ +# srcmorph (core library) + +`net.ladenthin:srcmorph` is the framework-free Java API behind the +[srcmorph](../README.md) project: a prompt-driven source-tree transformer built on local llama.cpp +(GGUF) models. It has **no dependency on the Maven Plugin API** (enforced by an ArchUnit rule — +see `CoreArchitectureTest#coreIsMavenFree`), so it can be embedded in any JVM tool: a Maven plugin (see +[`../llamacpp-ai-index-maven-plugin/`](../llamacpp-ai-index-maven-plugin/README.md)), a standalone CLI +(see [`../srcmorph-cli/`](../srcmorph-cli/README.md)), a Gradle plugin, a test harness, or plain code. + +Package root: `net.ladenthin.srcmorph`. + +## The shared configuration object: `SrcMorphConfiguration` + +`net.ladenthin.srcmorph.config.SrcMorphConfiguration` is one mutable JavaBean holding everything a +run needs. It is the single object every binding surface populates the same way — Maven plexus +reflection (structural XML binding, no annotations needed), a Jackson `ObjectMapper`/`YAMLMapper` +(the CLI), or plain Java setter calls. **Field names intentionally mirror the Maven plugin's +`@Parameter` names**, so a JSON/YAML key reads identically to the matching `` XML +element. + +Major field groups: + +| Group | Fields | +|---|---| +| Source selection | `baseDirectory`, `subtrees`, `excludes`, `fileExtensions`, `minFileSizeBytes`/`maxFileSizeBytes` | +| Output | `outputDirectory`, `force`, `planOnly` | +| Routing & prompts | `promptDefinitions` (`List`), `aiDefinitions` (`List`), `fieldGenerations` (`List` — the routing rules), `factDefinitions` | +| AI backend | `generationProvider` (`"mock"` or `"llamacpp-jni"`), `llamaLibraryPath`, plus the `llama*` fallback params (`llamaModelPath`/`llamaContextSize`/`llamaMaxOutputTokens`/`llamaTemperature`/`llamaThreads`), used only when `fieldGenerations` is empty | +| Header metadata | `pluginVersion`, `aiVersion`, `projectName` | + +Every other `config/` class is a plain, Maven-annotation-free JavaBean too: `AiModelDefinition` (a +named, complete set of GGUF sampling parameters), `AiPromptDefinition` (a named prompt template — +its `template` string is the system instructions, used verbatim; `AiPromptSupport` automatically +appends the file/package name and the source text as a separate user message, so a template needs no +placeholder), `AiFieldGenerationConfig` (one routing rule: a +`condition` tree, `priority`, and either a route, a `skip`, or the explicit `fallback`), +`AiCondition`/`AiConditionGroup`/`AiRangeCondition` (the composable and/or/not condition tree over +extensions/size/lines/modified-time/path-glob), `AiFactCounter`/`AiFactDefinition` (deterministic +regex-count "facts" prepended to a generated body), `AiOversizeStrategy` (`fail`/`sample`/ +`mapReduce`/`deterministic` — what a rule does when a file is larger than its model's context +window), `AiCalibration` (per-machine measured throughput, pasted onto an `AiModelDefinition`). + +## The four engines + +Each engine is constructed from one `SrcMorphConfiguration` and owns its own AI provider lifecycle +(try-with-resources; one model resident in memory at a time). All four throw the checked +`net.ladenthin.srcmorph.engine.SrcMorphException` on misconfiguration (an invalid rule set, an +unmatched file with no fallback, an oversized file with `onOversize=fail`, a bad prompt/model +reference) and let a plain `java.io.IOException` propagate for genuine I/O failures — callers (the +Maven plugin's mojos, the CLI's `Main`) translate these into whatever their own surface expects. + +| Engine | Purpose | Result | +|---|---|---| +| `GenerateEngine` | Phase 1: plans the whole run (which model + prompt each file gets, or skip/unmatched, and whether it fits its routed model's context window), fails fast on an unmatched file or a hard oversize failure, stops after the plan when `planOnly` is set, otherwise loads each distinct routed model once and indexes its files. | `GenerateResult` (`planOnly()`, `written()`, `unchanged()`, `skipped()`) | +| `AggregatePackagesEngine` | Phase 2: aggregates every package beneath the output directory into a `package.ai.md` per directory. | `int` — package index files written or refreshed | +| `AggregateProjectEngine` | Phase 3: harvests every package's one-line lead into a single `project.ai.md` table of contents. Fully deterministic by default (no model call); when `fieldGenerations` has at least one entry, its **first** entry is used for one extra AI call that writes a short `#### Overview` paragraph. | `int` — `1` if written/refreshed, `0` otherwise | +| `CalibrateEngine` | Loads each distinct model a `GenerateEngine` run would load, measures its prefill/decode throughput via a couple of representative generations, and returns a paste-ready report. | `CalibrationReport` (`measurements()`, `renderXml()` — a paste-ready `` XML block per model) | + +**A design nuance worth knowing if you drive more than one engine off the same +`SrcMorphConfiguration`** (as the CLI's `All`/`Calibrate` commands do): `GenerateEngine` *routes* each +file to exactly one matching `fieldGenerations` rule (by `condition` + `priority`, with the explicit +`fallback` catching the rest). `AggregatePackagesEngine` and the optional project overview do **not** +route by condition — `AggregatePackagesEngine` applies **every** entry in `fieldGenerations` to every +package in list order (the last entry's output wins), and `AggregateProjectEngine`'s optional overview +uses only `fieldGenerations.get(0)`. A single fallback-only rule (no condition needed) behaves +identically across all three phases; a multi-rule routing table is meaningful for `GenerateEngine` +but you should reason explicitly about what happens when the same list feeds package/project +aggregation. See `srcmorph-cli/README.md`'s config-file reference for the CLI's own take on this. + +## Use as a library + +```java +import java.io.File; +import java.util.Collections; +import net.ladenthin.srcmorph.config.AiFieldGenerationConfig; +import net.ladenthin.srcmorph.config.AiModelDefinition; +import net.ladenthin.srcmorph.config.SrcMorphConfiguration; +import net.ladenthin.srcmorph.engine.GenerateEngine; +import net.ladenthin.srcmorph.engine.GenerateResult; +import net.ladenthin.srcmorph.engine.SrcMorphException; +import net.ladenthin.srcmorph.prompt.AiPromptDefinition; + +SrcMorphConfiguration config = new SrcMorphConfiguration(); +config.setBaseDirectory(new File(".")); +config.setOutputDirectory(new File("target/srcmorph-ai")); +config.setGenerationProvider("mock"); // or "llamacpp-jni" with a real GGUF model below + +AiPromptDefinition prompt = new AiPromptDefinition(); +prompt.setKey("file-body"); +prompt.setTemplate("Summarize this file as structured markdown."); +config.setPromptDefinitions(Collections.singletonList(prompt)); + +AiModelDefinition model = new AiModelDefinition(); +model.setKey("coder"); +model.setModelPath("/path/to/model.gguf"); // unused by the mock provider +config.setAiDefinitions(Collections.singletonList(model)); + +AiFieldGenerationConfig rule = new AiFieldGenerationConfig(); +rule.setPromptKey("file-body"); +rule.setAiDefinitionKey("coder"); +rule.setFallback(true); +config.setFieldGenerations(Collections.singletonList(rule)); + +try { + GenerateResult result = new GenerateEngine(config).execute(); + System.out.println("written=" + result.written() + " unchanged=" + result.unchanged()); +} catch (SrcMorphException | java.io.IOException e) { + // handle: misconfiguration vs. I/O failure +} +``` + +## Provider abstraction + +`net.ladenthin.srcmorph.provider.AiGenerationProvider` (a `Closeable`) abstracts the AI backend. +`AiGenerationProviderFactory` looks up an implementation by name: + +- `"mock"` (default) — `MockAiGenerationProvider`, deterministic canned responses; no model, no JNI, + no native library required. Used throughout every module's test suite. +- `"llamacpp-jni"` — `LlamaCppJniAiGenerationProvider`, backed by + [`net.ladenthin:llama`](https://central.sonatype.com/artifact/net.ladenthin/llama) (the llama.cpp + JNI binding). This is the **only** package in this library that touches the JNI binding + (`CoreArchitectureTest#jniConfinedToProvider`). + +## Package layout + +``` +net.ladenthin.srcmorph/ +├── config/ mutable JavaBeans; SrcMorphConfiguration is the shared root object +├── engine/ GenerateEngine, AggregatePackagesEngine, AggregateProjectEngine, CalibrateEngine, +│ SrcMorphException, GenerateResult, CalibrationReport, EngineSupport +├── indexer/ the walk/plan/write orchestration engines delegate to +├── document/ the .ai.md model + codecs (AiMdDocument, AiMdHeader, AiMdDocumentCodec, ...) +├── prompt/ prompt template lookup + preparation (trimming to a model's context window) +├── provider/ the AI backend abstraction (see above) +└── support/ foundation helpers with no dependency on anything above them +``` + +See [`../CLAUDE.md`](../CLAUDE.md) for the full architecture rules (`CoreArchitectureTest`), the +layered-package dependency direction, and the PIT mutation-testing scope (currently 47 classes at +100%). + +## Testing + +Every test in this module is model-free by default (`MockAiGenerationProvider`); a handful of +real-model tests are gated on `src/test/resources/SmolLM2-135M-Instruct-Q3_K_M.gguf` and self-skip +when the native library is unavailable. See the root [`TEST_WRITING_GUIDE.md`](../TEST_WRITING_GUIDE.md) +for conventions and [`../CLAUDE.md`](../CLAUDE.md) for how to run a single test class. From ede41154ab51def02f45eadf4b58303b908fa75e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 16:31:15 +0000 Subject: [PATCH 6/9] Migration step 8: adapt CI to the 3-module reactor + prove release readiness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit publish.yml assumed the pre-reactor single-module layout in several places; adapt it to the srcmorph / srcmorph-cli / llamacpp-ai-index-maven-plugin reactor: - build job uploads jars from all three modules (incl. the CLI's jar-with-dependencies fat jar), not just the plugin's. - crash-dump upload globs are repo-wide (**/hs_err_pid*.log etc.) since a forked surefire JVM can crash in any module's own target/ tree. - PIT step scoped to `-pl srcmorph -am` (the only module with a pitest-maven execution); report glob/upload path moved to srcmorph/target/pit-reports/. - vmlens job scoped to `-pl llamacpp-ai-index-maven-plugin -am`, where VmlensInterleavingSmokeTest and the vmlens profile actually live (not relocated during the core extraction). Also adds -Dsurefire.failIfNoSpecifiedTests=false: -am pulls srcmorph into the build, and the pre-existing -DfailIfNoTests=false does not suppress the "-Dtest pattern matched nothing" failure that then occurs there (verified locally — this was a latent bug the reactor split introduced). - jdeps step prints a package graph per module. - Coveralls/Codecov point at srcmorph's jacoco report (the module with the bulk of the test suite and the only PIT gate), matching the single-primary-module precedent already used by the sibling java-llama.cpp reactor; documented as a gap for srcmorph-cli/plugin coverage in TODO.md. - signed-artifact collection steps (snapshot + release) now cp jars/.asc from all three module target/ directories. Verified locally (no deploy/tag/push): - `mvn -q clean verify` — BUILD SUCCESS. - `mvn -P release verify -DskipTests -Dgpg.skip=true` — BUILD SUCCESS for all 4 reactor projects; every module produced its expected jar/sources/ javadoc (+ CLI fat jar, + plugin's plugin.xml), no .asc files (gpg skipped, as intended). - publish.yml YAML re-validated after edits. TODO.md/CHANGELOG.md updated to reflect the CI adaptation; the actual 1.1.0 tag + `mvn -P release deploy` is left for the user to trigger. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- .github/workflows/publish.yml | 94 ++++++++++++++++++++++++++++------- CHANGELOG.md | 4 ++ TODO.md | 24 +++++++-- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2341636..451f711 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -223,7 +223,13 @@ jobs: continue-on-error: true run: | mvn -B --no-transfer-progress -DskipTests -Denforcer.skip=true compile - echo "=== internal package dependency graph (jdeps, bytecode) ===" + # Three reactor modules each ship their own module-info.java / target/classes tree + # (srcmorph, srcmorph-cli, llamacpp-ai-index-maven-plugin) — print each separately. + echo "=== internal package dependency graph (jdeps, bytecode) — srcmorph ===" + jdeps -verbose:package srcmorph/target/classes | grep 'net.ladenthin.srcmorph' || true + echo "=== internal package dependency graph (jdeps, bytecode) — srcmorph-cli ===" + jdeps -verbose:package srcmorph-cli/target/classes | grep 'net.ladenthin.srcmorph' || true + echo "=== internal package dependency graph (jdeps, bytecode) — llamacpp-ai-index-maven-plugin ===" jdeps -verbose:package llamacpp-ai-index-maven-plugin/target/classes | grep 'net.ladenthin.maven.llamacpp.aiindex' || true build: @@ -240,7 +246,15 @@ jobs: - name: Build run: mvn --batch-mode --no-transfer-progress -DskipTests package - uses: actions/upload-artifact@v7 - with: { name: plugin-jars, path: llamacpp-ai-index-maven-plugin/target/*.jar } + with: + name: plugin-jars + # All three reactor modules now produce their own jars: srcmorph (core library), + # srcmorph-cli (includes the jar-with-dependencies fat jar — the CLI's deliverable), + # and llamacpp-ai-index-maven-plugin (the Maven plugin, unchanged coordinates). + path: | + srcmorph/target/*.jar + srcmorph-cli/target/*.jar + llamacpp-ai-index-maven-plugin/target/*.jar test: name: Test @@ -256,11 +270,20 @@ jobs: - name: Memory before tests run: free -h - name: Test + # Reactor-wide (no -pl): runs every module's tests. The `jcstress` profile is only + # declared in llamacpp-ai-index-maven-plugin/pom.xml, so it activates there and is a + # silent no-op for srcmorph/srcmorph-cli (neither declares a profile with that id). run: mvn -e --batch-mode --no-transfer-progress -P jcstress verify - uses: actions/upload-artifact@v7 with: name: jacoco-report - path: llamacpp-ai-index-maven-plugin/target/site/jacoco/jacoco.xml + # srcmorph carries the bulk of the test suite (and the only PIT gate); its report is + # the one forwarded to Coveralls/Codecov in the `report` job below, matching the + # single-primary-module precedent already used by the sibling java-llama.cpp reactor + # (which reports only its `llama` core module, not an aggregate across all modules). + # srcmorph-cli / llamacpp-ai-index-maven-plugin jacoco reports are not currently + # aggregated or uploaded (documented gap, see TODO.md). + path: srcmorph/target/site/jacoco/jacoco.xml if-no-files-found: ignore - name: Memory after tests if: always() @@ -270,21 +293,29 @@ jobs: uses: actions/upload-artifact@v7 with: name: crash-dumps-test + # Repo-wide glob: a forked surefire JVM crash can happen in any of the three module + # working directories (srcmorph / srcmorph-cli / llamacpp-ai-index-maven-plugin), each + # with its own target/ tree. path: | - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/hs_err_pid*.log - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/*.hprof - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/target/surefire-reports/*.dump - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/target/surefire-reports/*.dumpstream - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/target/surefire-reports/*.txt - ${{ github.workspace }}/llamacpp-ai-index-maven-plugin/target/surefire-reports/TEST-*.xml + ${{ github.workspace }}/**/hs_err_pid*.log + ${{ github.workspace }}/**/*.hprof + ${{ github.workspace }}/**/target/surefire-reports/*.dump + ${{ github.workspace }}/**/target/surefire-reports/*.dumpstream + ${{ github.workspace }}/**/target/surefire-reports/*.txt + ${{ github.workspace }}/**/target/surefire-reports/TEST-*.xml if-no-files-found: ignore - name: Run PIT mutation tests - run: mvn --batch-mode --no-transfer-progress test-compile org.pitest:pitest-maven:mutationCoverage + # Scoped to srcmorph: it is the only reactor module with a pitest-maven execution + # (srcmorph-cli and llamacpp-ai-index-maven-plugin each document, in their own pom, why + # they have no PIT gate yet — see TODO.md). `-am` builds srcmorph's own reactor + # dependencies (none today) before running; without `-pl` this goal would otherwise be + # invoked (harmlessly, but pointlessly) against every reactor module. + run: mvn --batch-mode --no-transfer-progress -pl srcmorph -am test-compile org.pitest:pitest-maven:mutationCoverage - name: Extract PIT survivors if: always() run: | echo "=== PIT Survived Mutations ===" - for html_file in $(find llamacpp-ai-index-maven-plugin/target/pit-reports -name "*.html" -type f 2>/dev/null | sort); do + for html_file in $(find srcmorph/target/pit-reports -name "*.html" -type f 2>/dev/null | sort); do if grep -q "SURVIVED" "$html_file"; then echo "Found survivors in $html_file:" grep -B 2 -A 3 "SURVIVED" "$html_file" @@ -293,7 +324,7 @@ jobs: done - uses: actions/upload-artifact@v7 if: always() - with: { name: pit-reports, path: llamacpp-ai-index-maven-plugin/target/pit-reports/ } + with: { name: pit-reports, path: srcmorph/target/pit-reports/ } vmlens: name: Test (vmlens interleavings) @@ -304,9 +335,20 @@ jobs: - uses: actions/setup-java@v5 with: { java-version: '21', distribution: temurin, cache: maven } - name: Test under vmlens (one class — staged scope) + # VmlensInterleavingSmokeTest and the `vmlens` profile both live in + # llamacpp-ai-index-maven-plugin/pom.xml (they were not relocated during the core + # extraction — see CLAUDE.md) — scope directly to that module rather than the whole + # reactor. `-am` is required (not just an optimization): srcmorph is a reactor-local + # dependency, never published, so without `-am` this module's compile would fail trying + # to resolve net.ladenthin:srcmorph:1.1.0-SNAPSHOT from a remote repo. That in turn means + # the `test` phase also runs for srcmorph, where -Dtest=VmlensInterleavingSmokeTest + # matches no class — `-DfailIfNoTests=false` does NOT suppress that (it only covers "no + # tests at all"); the actual guard for "the -Dtest pattern matched nothing" is + # `-Dsurefire.failIfNoSpecifiedTests=false` (verified locally: omitting it fails the + # build on srcmorph with "No tests matching pattern ... were executed!"). run: >- - mvn --batch-mode --no-transfer-progress -Pvmlens test - -Dtest=VmlensInterleavingSmokeTest -DfailIfNoTests=false + mvn --batch-mode --no-transfer-progress -pl llamacpp-ai-index-maven-plugin -am -Pvmlens test + -Dtest=VmlensInterleavingSmokeTest -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=false - uses: actions/upload-artifact@v7 if: always() with: @@ -324,22 +366,26 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-java@v5 with: { java-version: '21', distribution: temurin } + # Only srcmorph's jacoco report is uploaded by the `test` job (see the comment there) — + # it carries the bulk of the test suite and the only PIT gate. Coveralls/Codecov are + # pointed at it directly rather than an aggregated multi-module report, matching the + # single-primary-module precedent already used by the sibling java-llama.cpp reactor. - uses: actions/download-artifact@v8 - with: { name: jacoco-report, path: llamacpp-ai-index-maven-plugin/target/site/jacoco/ } + with: { name: jacoco-report, path: srcmorph/target/site/jacoco/ } continue-on-error: true - uses: advanced-security/maven-dependency-submission-action@v5 - name: Coveralls uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - file: llamacpp-ai-index-maven-plugin/target/site/jacoco/jacoco.xml + file: srcmorph/target/site/jacoco/jacoco.xml format: jacoco continue-on-error: true - name: Codecov uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} - files: llamacpp-ai-index-maven-plugin/target/site/jacoco/jacoco.xml + files: srcmorph/target/site/jacoco/jacoco.xml continue-on-error: true check-snapshot: @@ -402,12 +448,17 @@ jobs: MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # Runs even when the deploy step failed: a Central publish-poll timeout reds the # job *after* the bundle was uploaded (and typically published server-side), while - # the signed jars + .asc files already exist in llamacpp-ai-index-maven-plugin/target/ (signing happens at + # the signed jars + .asc files already exist in each module's target/ (signing happens at # verify). Collecting on failure lets the github-snapshot job still attach them. - name: Collect signed artifacts if: ${{ !cancelled() }} + # All three reactor modules produce their own jars/asc files (root pom has no jar). run: | mkdir -p signed-snapshot-assets + cp srcmorph/target/*.jar signed-snapshot-assets/ 2>/dev/null || true + cp srcmorph/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true + cp srcmorph-cli/target/*.jar signed-snapshot-assets/ 2>/dev/null || true + cp srcmorph-cli/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true cp llamacpp-ai-index-maven-plugin/target/*.jar signed-snapshot-assets/ 2>/dev/null || true cp llamacpp-ai-index-maven-plugin/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true - uses: actions/upload-artifact@v7 @@ -478,12 +529,17 @@ jobs: MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # Runs even when the deploy step failed: a Central publish-poll timeout reds the # job *after* the bundle was uploaded (and typically published server-side), while - # the signed jars + .asc files already exist in llamacpp-ai-index-maven-plugin/target/ (signing happens at + # the signed jars + .asc files already exist in each module's target/ (signing happens at # verify). Collecting on failure lets the github-release job still attach them. - name: Collect signed artifacts if: ${{ !cancelled() }} + # All three reactor modules produce their own jars/asc files (root pom has no jar). run: | mkdir -p signed-release-assets + cp srcmorph/target/*.jar signed-release-assets/ 2>/dev/null || true + cp srcmorph/target/*.jar.asc signed-release-assets/ 2>/dev/null || true + cp srcmorph-cli/target/*.jar signed-release-assets/ 2>/dev/null || true + cp srcmorph-cli/target/*.jar.asc signed-release-assets/ 2>/dev/null || true cp llamacpp-ai-index-maven-plugin/target/*.jar signed-release-assets/ 2>/dev/null || true cp llamacpp-ai-index-maven-plugin/target/*.jar.asc signed-release-assets/ 2>/dev/null || true - uses: actions/upload-artifact@v7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 88163c1..45eb5c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ The release procedure (prompt template and step-by-step instructions) lives in [ - Logging in the extracted core/CLI layers moved from a constructor-injected Maven `Log` to `org.slf4j.Logger` (see the `1.0.x` entries below for the indexer-layer half of this change, already shipped before the reactor split). +- `.github/workflows/publish.yml` adapted to the 3-module reactor: per-module jar upload/release + globs, a repo-wide crash-dump glob, the PIT step scoped to `srcmorph` (the only module with a + mutation-testing gate), the `vmlens` job scoped to `llamacpp-ai-index-maven-plugin` (where its + test actually lives), and Coveralls/Codecov pointed at `srcmorph`'s jacoco report. ### Notes - **This release does not change the Maven plugin's own coordinates, package, goal prefix, or diff --git a/TODO.md b/TODO.md index dcf47dc..05560fc 100644 --- a/TODO.md +++ b/TODO.md @@ -8,11 +8,25 @@ recorded in git history and `crossrepostatus.md`, not here. ## Open - **Migration step 8 — CI full pass + first reactor release.** `.github/workflows/publish.yml` - still reflects the pre-reactor single-module layout in places (report globs, artifact paths, - coverage aggregation). Adapt it to the 3-module reactor (`srcmorph` / `srcmorph-cli` / - `llamacpp-ai-index-maven-plugin`), then cut the first real `1.1.0` release under the plugin's - **current** (pre-rename) coordinates via `mvn -P release deploy`. This is the gate the user asked - for before step 9 ("if all is working stat I can safely do the final rename"). + is now adapted to the 3-module reactor: the `build` job uploads jars from all three modules + (`srcmorph` / `srcmorph-cli`, including its `jar-with-dependencies` fat jar / `llamacpp-ai-index-maven-plugin`); + crash-dump globs are repo-wide (`**/hs_err_pid*.log` etc., since a forked surefire JVM can crash + in any module's own working directory); the PIT step is scoped to `-pl srcmorph -am` (the only + module with a `pitest-maven` execution) with its report glob at `srcmorph/target/pit-reports/**`; + the `vmlens` job is scoped to `-pl llamacpp-ai-index-maven-plugin -am` (where + `VmlensInterleavingSmokeTest` and the `vmlens` profile actually live — not relocated during the + core extraction) and additionally passes `-Dsurefire.failIfNoSpecifiedTests=false` (the + `-DfailIfNoTests=false` flag alone does not suppress the "-Dtest pattern matched nothing" + failure that `-am` now triggers in the upstream `srcmorph` module); jdeps prints a graph per + module; and Coveralls/Codecov are pointed at `srcmorph`'s jacoco report only (the + single-primary-module precedent already used by the sibling java-llama.cpp reactor — `srcmorph-cli` + and the plugin module's own coverage is not currently aggregated or uploaded). `mvn -q clean + verify` and a `-P release verify -DskipTests -Dgpg.skip=true` dry run (package/sources/javadoc + jars for all three modules + the CLI fat jar, no `.asc` files since signing was skipped) both + pass locally. **Still open: actually cutting the first real `1.1.0` release** (tag + `mvn -P + release deploy` with real credentials) — that action was deliberately left to the user, not + performed as part of this CI-adaptation step. This is the gate the user asked for before step 9 + ("if all is working stat I can safely do the final rename"). - **Migration step 9 — plugin rename + Maven Central relocation (deferred, do only after step 8 ships a real release).** Rename `llamacpp-ai-index-maven-plugin`'s own coordinates to From a0da53aa1a3d59d6e2909acc62d11a086da9a793 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 18:32:00 +0000 Subject: [PATCH 7/9] Migration step 9 (final): rename Maven plugin to srcmorph-maven-plugin + relocation stub Renames the plugin module net.ladenthin:llamacpp-ai-index-maven-plugin -> net.ladenthin:srcmorph-maven-plugin: goal prefix ai-index -> srcmorph, package net.ladenthin.maven.llamacpp.aiindex(.mojo) -> net.ladenthin.maven.srcmorph(.mojo), and every @Parameter property aiIndex.* -> srcmorph.* across all 5 mojos. The reactor's own stays 1.1.0-SNAPSHOT (unchanged, per explicit instruction) and the new plugin coordinates inherit it as usual. Adds a new, independent 4th reactor module at the vacated llamacpp-ai-index-maven-plugin/ path: a minimal relocation-stub POM (no , no dependencies, no source) pinned at net.ladenthin:llamacpp-ai-index-maven-plugin:1.0.4 with a pointing at net.ladenthin:srcmorph-maven-plugin:1.1.0, so existing consumers of the old coordinates are transparently redirected once both are published. Documents, in the stub's own pom.xml, in the root pom.xml module comment, in CLAUDE.md, and in TODO.md, that a reactor-wide `mvn versions:set` must exclude this module (-Dexcludes=net.ladenthin:llamacpp-ai-index-maven-plugin) so its frozen 1.0.4 is never accidentally bumped. Updates CI (.github/workflows/publish.yml), the srcmorph-selftest profile (renamed from ai-index-selftest), spotbugs-exclude.xml, module-info.java, and every cross-module doc (README.md, CLAUDE.md, CHANGELOG.md, TODO.md, CONTRIBUTING.md, TEST_WRITING_GUIDE.md, CODE_WRITING_GUIDE.md, REUSE.toml, .gitignore, srcmorph/srcmorph-cli READMEs and Javadoc) to match the new coordinates/package/properties, while leaving historical docs/ benchmark write-ups and the relocation stub's own old-coordinate literals untouched. Verified: reactor-wide `mvn clean verify` and `-P release verify -DskipTests -Dgpg.skip=true` both green (5 projects incl. the trivial pom-only stub); regenerated plugin.xml confirms goalPrefix=srcmorph and every parameter's property reads srcmorph.*; the renamed srcmorph-selftest profile run with the mock provider produced real .ai.md output via the new srcmorph:generate goal; the relocation stub installs standalone with its relocation content intact; publish.yml re-validated as parseable YAML. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_016DByQrKSWzzpBt4iSRcmRP --- .github/workflows/publish.yml | 34 +- .gitignore | 12 +- CHANGELOG.md | 33 +- CLAUDE.md | 109 +- CODE_WRITING_GUIDE.md | 2 +- CONTRIBUTING.md | 6 +- README.md | 49 +- REUSE.toml | 2 +- TEST_WRITING_GUIDE.md | 8 +- TODO.md | 43 +- llamacpp-ai-index-maven-plugin/pom.xml | 2281 +---------------- pom.xml | 20 +- srcmorph-cli/README.md | 4 +- .../README.md | 47 +- srcmorph-maven-plugin/pom.xml | 2265 ++++++++++++++++ .../spotbugs-exclude.xml | 20 +- .../src/main/java/module-info.java | 10 +- .../srcmorph}/mojo/AbstractAiIndexMojo.java | 20 +- .../srcmorph}/mojo/AggregatePackagesMojo.java | 12 +- .../srcmorph}/mojo/AggregateProjectMojo.java | 12 +- .../maven/srcmorph}/mojo/CalibrateMojo.java | 10 +- .../maven/srcmorph}/mojo/GenerateMojo.java | 22 +- .../maven/srcmorph}/mojo/package-info.java | 4 +- .../maven/srcmorph}/package-info.java | 2 +- .../src/site/ai/empty.md | 0 .../srcmorph}/PluginArchitectureTest.java | 26 +- .../jcstress/AiGenerationKindRace.java | 2 +- .../srcmorph}/mojo/MojoPhaseSkipTest.java | 2 +- .../vmlens/VmlensInterleavingSmokeTest.java | 2 +- srcmorph/README.md | 2 +- srcmorph/pom.xml | 14 +- srcmorph/src/main/java/module-info.java | 4 +- .../srcmorph/config/AiCalibration.java | 2 +- .../srcmorph/config/AiModelDefinition.java | 2 +- .../config/SrcMorphConfiguration.java | 4 +- .../srcmorph/engine/CalibrationReport.java | 2 +- .../srcmorph/indexer/AiCalibrationRunner.java | 2 +- .../provider/AiGenerationProvider.java | 2 +- .../provider/AiGenerationTimings.java | 2 +- 39 files changed, 2618 insertions(+), 2477 deletions(-) rename {llamacpp-ai-index-maven-plugin => srcmorph-maven-plugin}/README.md (95%) create mode 100644 srcmorph-maven-plugin/pom.xml rename {llamacpp-ai-index-maven-plugin => srcmorph-maven-plugin}/spotbugs-exclude.xml (90%) rename {llamacpp-ai-index-maven-plugin => srcmorph-maven-plugin}/src/main/java/module-info.java (90%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/AbstractAiIndexMojo.java (93%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/AggregatePackagesMojo.java (89%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/AggregateProjectMojo.java (92%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/CalibrateMojo.java (91%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/GenerateMojo.java (89%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/mojo/package-info.java (54%) rename {llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph}/package-info.java (96%) rename {llamacpp-ai-index-maven-plugin => srcmorph-maven-plugin}/src/site/ai/empty.md (100%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph}/PluginArchitectureTest.java (87%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph}/jcstress/AiGenerationKindRace.java (95%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph}/mojo/MojoPhaseSkipTest.java (98%) rename {llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex => srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph}/vmlens/VmlensInterleavingSmokeTest.java (97%) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 451f711..8084f2b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -224,13 +224,13 @@ jobs: run: | mvn -B --no-transfer-progress -DskipTests -Denforcer.skip=true compile # Three reactor modules each ship their own module-info.java / target/classes tree - # (srcmorph, srcmorph-cli, llamacpp-ai-index-maven-plugin) — print each separately. + # (srcmorph, srcmorph-cli, srcmorph-maven-plugin) — print each separately. echo "=== internal package dependency graph (jdeps, bytecode) — srcmorph ===" jdeps -verbose:package srcmorph/target/classes | grep 'net.ladenthin.srcmorph' || true echo "=== internal package dependency graph (jdeps, bytecode) — srcmorph-cli ===" jdeps -verbose:package srcmorph-cli/target/classes | grep 'net.ladenthin.srcmorph' || true - echo "=== internal package dependency graph (jdeps, bytecode) — llamacpp-ai-index-maven-plugin ===" - jdeps -verbose:package llamacpp-ai-index-maven-plugin/target/classes | grep 'net.ladenthin.maven.llamacpp.aiindex' || true + echo "=== internal package dependency graph (jdeps, bytecode) — srcmorph-maven-plugin ===" + jdeps -verbose:package srcmorph-maven-plugin/target/classes | grep 'net.ladenthin.maven.srcmorph' || true build: name: Build @@ -250,11 +250,13 @@ jobs: name: plugin-jars # All three reactor modules now produce their own jars: srcmorph (core library), # srcmorph-cli (includes the jar-with-dependencies fat jar — the CLI's deliverable), - # and llamacpp-ai-index-maven-plugin (the Maven plugin, unchanged coordinates). + # and srcmorph-maven-plugin (the Maven plugin, renamed from llamacpp-ai-index-maven-plugin; + # the retired coordinates now live only in the pom-only relocation-stub module, which + # produces no jar). path: | srcmorph/target/*.jar srcmorph-cli/target/*.jar - llamacpp-ai-index-maven-plugin/target/*.jar + srcmorph-maven-plugin/target/*.jar test: name: Test @@ -271,7 +273,7 @@ jobs: run: free -h - name: Test # Reactor-wide (no -pl): runs every module's tests. The `jcstress` profile is only - # declared in llamacpp-ai-index-maven-plugin/pom.xml, so it activates there and is a + # declared in srcmorph-maven-plugin/pom.xml, so it activates there and is a # silent no-op for srcmorph/srcmorph-cli (neither declares a profile with that id). run: mvn -e --batch-mode --no-transfer-progress -P jcstress verify - uses: actions/upload-artifact@v7 @@ -281,7 +283,7 @@ jobs: # the one forwarded to Coveralls/Codecov in the `report` job below, matching the # single-primary-module precedent already used by the sibling java-llama.cpp reactor # (which reports only its `llama` core module, not an aggregate across all modules). - # srcmorph-cli / llamacpp-ai-index-maven-plugin jacoco reports are not currently + # srcmorph-cli / srcmorph-maven-plugin jacoco reports are not currently # aggregated or uploaded (documented gap, see TODO.md). path: srcmorph/target/site/jacoco/jacoco.xml if-no-files-found: ignore @@ -294,7 +296,7 @@ jobs: with: name: crash-dumps-test # Repo-wide glob: a forked surefire JVM crash can happen in any of the three module - # working directories (srcmorph / srcmorph-cli / llamacpp-ai-index-maven-plugin), each + # working directories (srcmorph / srcmorph-cli / srcmorph-maven-plugin), each # with its own target/ tree. path: | ${{ github.workspace }}/**/hs_err_pid*.log @@ -306,7 +308,7 @@ jobs: if-no-files-found: ignore - name: Run PIT mutation tests # Scoped to srcmorph: it is the only reactor module with a pitest-maven execution - # (srcmorph-cli and llamacpp-ai-index-maven-plugin each document, in their own pom, why + # (srcmorph-cli and srcmorph-maven-plugin each document, in their own pom, why # they have no PIT gate yet — see TODO.md). `-am` builds srcmorph's own reactor # dependencies (none today) before running; without `-pl` this goal would otherwise be # invoked (harmlessly, but pointlessly) against every reactor module. @@ -336,7 +338,7 @@ jobs: with: { java-version: '21', distribution: temurin, cache: maven } - name: Test under vmlens (one class — staged scope) # VmlensInterleavingSmokeTest and the `vmlens` profile both live in - # llamacpp-ai-index-maven-plugin/pom.xml (they were not relocated during the core + # srcmorph-maven-plugin/pom.xml (they were not relocated during the core # extraction — see CLAUDE.md) — scope directly to that module rather than the whole # reactor. `-am` is required (not just an optimization): srcmorph is a reactor-local # dependency, never published, so without `-am` this module's compile would fail trying @@ -347,13 +349,13 @@ jobs: # `-Dsurefire.failIfNoSpecifiedTests=false` (verified locally: omitting it fails the # build on srcmorph with "No tests matching pattern ... were executed!"). run: >- - mvn --batch-mode --no-transfer-progress -pl llamacpp-ai-index-maven-plugin -am -Pvmlens test + mvn --batch-mode --no-transfer-progress -pl srcmorph-maven-plugin -am -Pvmlens test -Dtest=VmlensInterleavingSmokeTest -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=false - uses: actions/upload-artifact@v7 if: always() with: name: vmlens-report - path: llamacpp-ai-index-maven-plugin/target/vmlens-report/ + path: srcmorph-maven-plugin/target/vmlens-report/ if-no-files-found: ignore report: @@ -459,8 +461,8 @@ jobs: cp srcmorph/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true cp srcmorph-cli/target/*.jar signed-snapshot-assets/ 2>/dev/null || true cp srcmorph-cli/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true - cp llamacpp-ai-index-maven-plugin/target/*.jar signed-snapshot-assets/ 2>/dev/null || true - cp llamacpp-ai-index-maven-plugin/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true + cp srcmorph-maven-plugin/target/*.jar signed-snapshot-assets/ 2>/dev/null || true + cp srcmorph-maven-plugin/target/*.jar.asc signed-snapshot-assets/ 2>/dev/null || true - uses: actions/upload-artifact@v7 if: ${{ !cancelled() }} with: @@ -540,8 +542,8 @@ jobs: cp srcmorph/target/*.jar.asc signed-release-assets/ 2>/dev/null || true cp srcmorph-cli/target/*.jar signed-release-assets/ 2>/dev/null || true cp srcmorph-cli/target/*.jar.asc signed-release-assets/ 2>/dev/null || true - cp llamacpp-ai-index-maven-plugin/target/*.jar signed-release-assets/ 2>/dev/null || true - cp llamacpp-ai-index-maven-plugin/target/*.jar.asc signed-release-assets/ 2>/dev/null || true + cp srcmorph-maven-plugin/target/*.jar signed-release-assets/ 2>/dev/null || true + cp srcmorph-maven-plugin/target/*.jar.asc signed-release-assets/ 2>/dev/null || true - uses: actions/upload-artifact@v7 if: ${{ !cancelled() }} with: diff --git a/.gitignore b/.gitignore index 4556a94..23ff8f3 100644 --- a/.gitignore +++ b/.gitignore @@ -33,17 +33,17 @@ replay_pid* *.hprof # jcstress / jqwik test outputs (generated at the repo root pre-reactor; each module's own -# basedir post-reactor, e.g. llamacpp-ai-index-maven-plugin/.jqwik-database) +# basedir post-reactor, e.g. srcmorph-maven-plugin/.jqwik-database) **/.jqwik-database # Benchmark/experiment scratch output dirs (not part of the build) -llamacpp-ai-index-maven-plugin/src/siteGusto/ -llamacpp-ai-index-maven-plugin/src/siteMistral3/ -llamacpp-ai-index-maven-plugin/src/siteQwenBRUTAL/ +srcmorph-maven-plugin/src/siteGusto/ +srcmorph-maven-plugin/src/siteMistral3/ +srcmorph-maven-plugin/src/siteQwenBRUTAL/ # IDE config .idea/ # Generated AI-index output from the self-index run (keep the tracked placeholder) -/llamacpp-ai-index-maven-plugin/src/site/ai/* -!/llamacpp-ai-index-maven-plugin/src/site/ai/empty.md +/srcmorph-maven-plugin/src/site/ai/* +!/srcmorph-maven-plugin/src/site/ai/empty.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 45eb5c8..73a35bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,16 @@ The release procedure (prompt template and step-by-step instructions) lives in [ Maven reactor under a new parent, `net.ladenthin:srcmorph-parent` — `srcmorph` (new core library, `net.ladenthin:srcmorph`, framework-free — no Maven Plugin API dependency), `srcmorph-cli` (new standalone CLI, `net.ladenthin:srcmorph-cli`, driven by a single JSON/YAML configuration file, - ships as a `java -jar`-ready fat jar), and `llamacpp-ai-index-maven-plugin` (the original plugin, - now a thin wrapper depending on `srcmorph`). All three (plus the parent pom) release together at - one shared version. + ships as a `java -jar`-ready fat jar), and `srcmorph-maven-plugin` (the original plugin, now a + thin wrapper depending on `srcmorph`). All three (plus the parent pom) release together at one + shared version. +- **Plugin renamed** from `net.ladenthin:llamacpp-ai-index-maven-plugin` to + `net.ladenthin:srcmorph-maven-plugin` in this same release (goal prefix `ai-index` → `srcmorph`; + package `net.ladenthin.maven.llamacpp.aiindex.mojo` → `net.ladenthin.maven.srcmorph.mojo`; every + `@Parameter` property `aiIndex.*` → `srcmorph.*`). A new, independently-versioned relocation-stub + module/POM (`net.ladenthin:llamacpp-ai-index-maven-plugin:1.0.4`, no source, no dependencies, only + ``) keeps the old coordinates resolvable on Maven Central, + redirecting to `net.ladenthin:srcmorph-maven-plugin:1.1.0`. - New engine layer in `srcmorph` (`GenerateEngine`, `AggregatePackagesEngine`, `AggregateProjectEngine`, `CalibrateEngine`) extracted from what used to be each mojo's `execute()` body, plus a new shared root configuration object, @@ -35,19 +42,21 @@ The release procedure (prompt template and step-by-step instructions) lives in [ - Logging in the extracted core/CLI layers moved from a constructor-injected Maven `Log` to `org.slf4j.Logger` (see the `1.0.x` entries below for the indexer-layer half of this change, already shipped before the reactor split). -- `.github/workflows/publish.yml` adapted to the 3-module reactor: per-module jar upload/release +- `.github/workflows/publish.yml` adapted to the 4-module reactor: per-module jar upload/release globs, a repo-wide crash-dump glob, the PIT step scoped to `srcmorph` (the only module with a - mutation-testing gate), the `vmlens` job scoped to `llamacpp-ai-index-maven-plugin` (where its + mutation-testing gate), the `vmlens` job scoped to `srcmorph-maven-plugin` (where its test actually lives), and Coveralls/Codecov pointed at `srcmorph`'s jacoco report. ### Notes -- **This release does not change the Maven plugin's own coordinates, package, goal prefix, or - `@Parameter` property names.** `net.ladenthin:llamacpp-ai-index-maven-plugin`, package - `net.ladenthin.maven.llamacpp.aiindex`, goal prefix `ai-index`, and every `aiIndex.*` property are - unchanged — existing consumers of the plugin see no breaking change in this release. A - **future** release will rename the plugin to `net.ladenthin:srcmorph-maven-plugin` with a Maven - Central relocation POM for the old coordinates; that rename is a deliberately separate, later step - (tracked in `TODO.md`) and has not happened yet. +- **This release renames the Maven plugin's coordinates, package, goal prefix, and `@Parameter` + property names.** `net.ladenthin:llamacpp-ai-index-maven-plugin` → `net.ladenthin:srcmorph-maven-plugin`; + package `net.ladenthin.maven.llamacpp.aiindex.mojo` → `net.ladenthin.maven.srcmorph.mojo`; goal + prefix `ai-index` → `srcmorph`; every `aiIndex.*` property → `srcmorph.*`. Existing consumers of + the old coordinates are not broken: a new, independently-versioned relocation-stub artifact + (`net.ladenthin:llamacpp-ai-index-maven-plugin:1.0.4`, POM-only, no source/dependencies) is + published with a `` pointing at + `net.ladenthin:srcmorph-maven-plugin:1.1.0`, so Maven transparently redirects any build still + declaring the old artifactId. ## [1.0.2] - 2026-07-02 diff --git a/CLAUDE.md b/CLAUDE.md index 1670822..00fbae9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,21 +12,25 @@ layered output — per-file, then per-package, then per-project. Today it emits Markdown summaries for AI-assisted code navigation; the same rule-routed pipeline is generic enough to eventually emit wikis, architecture docs, diagrams, or source-to-source transformations. -**This repository is mid-migration.** It started as a single Maven plugin -(`net.ladenthin:llamacpp-ai-index-maven-plugin`) and is being restructured into a 3-module Maven -reactor: a framework-free core library, a standalone CLI, and the original plugin (now a thin -wrapper around the library). The migration is deliberately staged so every intermediate commit stays -green; **the plugin's own Maven coordinates, package, and property names have NOT changed yet** — -that rename (to `net.ladenthin:srcmorph-maven-plugin`) is a separate, deferred final step (tracked in -`TODO.md`), done only once the rest of the reactor has shipped at least one real release. Do not -assume the rename has happened, and do not invent `srcmorph.*` Maven `@Parameter` property names — -the plugin still uses `aiIndex.*` (see the plugin module's own section below). +**This repository completed its migration to a 4-module Maven reactor.** It started as a single +Maven plugin (`net.ladenthin:llamacpp-ai-index-maven-plugin`) and was restructured into: a +framework-free core library, a standalone CLI, the original plugin (now a thin wrapper around the +library, **renamed** to `net.ladenthin:srcmorph-maven-plugin`), and a tiny relocation-stub module +that keeps the old `net.ladenthin:llamacpp-ai-index-maven-plugin` coordinates alive on Maven +Central purely as a `` pointer. **The plugin rename is done**: +coordinates, package, goal prefix, and every `@Parameter` property changed in this step — do not +write `aiIndex.*` properties, the `ai-index` goal prefix, or the +`net.ladenthin.maven.llamacpp.aiindex` package in new documentation or code; use `srcmorph.*`, +`srcmorph`, and `net.ladenthin.maven.srcmorph.mojo` instead (see the plugin module's own section +below). Actually publishing the `1.1.0` reactor release and the `1.0.4` relocation stub to Maven +Central remains a separate, later action by the user. - **Group ID:** `net.ladenthin` - **Java:** target bytecode 1.8 (production code), Java 21 test sources, built with JDK 21 - **License:** Apache 2.0 - **Author:** Bernard Ladenthin (Copyright 2026) -- **Reactor version:** `1.1.0-SNAPSHOT` (single shared version across all reactor modules) +- **Reactor version:** `1.1.0-SNAPSHOT` (single shared version across `srcmorph`, `srcmorph-cli`, + and `srcmorph-maven-plugin`; the relocation stub below is version-pinned independently) --- @@ -48,16 +52,27 @@ llamacpp-ai-index-maven-plugin/ (repo root; reactor parent) │ └── src/main/java/net/ladenthin/srcmorph/cli/ │ ├── Main.java BitcoinAddressFinder cli/Main.java pattern │ └── configuration/ CConfiguration + CCommand (BAF public-field style) -├── llamacpp-ai-index-maven-plugin/ Maven plugin net.ladenthin:llamacpp-ai-index-maven-plugin -│ └── src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/ (5 mojos; UNCHANGED coordinates/package) +├── srcmorph-maven-plugin/ Maven plugin net.ladenthin:srcmorph-maven-plugin, goalPrefix srcmorph +│ └── src/main/java/net/ladenthin/maven/srcmorph/mojo/ (5 mojos; renamed package/properties) +├── llamacpp-ai-index-maven-plugin/ RELOCATION STUB net.ladenthin:llamacpp-ai-index-maven-plugin:1.0.4 +│ └── pom.xml only — no source, no ├── examples/ config_*.json/.yaml + run_*.sh/.bat + logbackConfiguration.xml ├── docs/ RELEASE.md + the ai-index model-benchmark writeups -└── .github/workflows/ CI (still keyed to the pre-reactor layout in places; step 8) +└── .github/workflows/ CI adapted to the 4-module reactor ``` -Every module inherits its `` from the parent (`net.ladenthin:srcmorph-parent`), so they ship -in lockstep by construction. Bump the version reactor-wide with -`mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false` from the repo root. +Every module except the relocation stub inherits its `` from the parent +(`net.ladenthin:srcmorph-parent`), so `srcmorph`/`srcmorph-cli`/`srcmorph-maven-plugin` ship in +lockstep by construction. Bump their version reactor-wide with +`mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false` from the repo root — but the relocation +stub (`llamacpp-ai-index-maven-plugin/`) has **no ``** and is version-pinned independently +at `1.0.4`; because it is still listed in the root ``, a plain `versions:set` run from the +root walks it too and would overwrite that pin unless excluded: + +```bash +mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false \ + -Dexcludes=net.ladenthin:llamacpp-ai-index-maven-plugin +``` ### `srcmorph` — the core library @@ -155,14 +170,18 @@ CLI driven by a single JSON or YAML configuration file: public, documented examples — through the same strict mappers), `CliEndToEndTest` (drives the `All` and `Plan` commands against the mock provider end to end, no forked process). -### `llamacpp-ai-index-maven-plugin` — the Maven plugin (pre-rename coordinates) +### `srcmorph-maven-plugin` — the Maven plugin (renamed; formerly `llamacpp-ai-index-maven-plugin`) -**Unchanged in this migration step**: coordinates `net.ladenthin:llamacpp-ai-index-maven-plugin`, -package `net.ladenthin.maven.llamacpp.aiindex`, goal prefix `ai-index`, every `@Parameter` property -still spelled `aiIndex.*` (e.g. `aiIndex.skip`, `aiIndex.file.skip`, `aiIndex.planOnly`, -`aiIndex.generationProvider`, `aiIndex.llama.modelPath`). Only the plugin's *contents* shrank: the -former ~65-class single module now depends on `net.ladenthin:srcmorph` (compile scope) for everything -except the 5 mojo classes themselves. +**Renamed in the final migration step**: coordinates `net.ladenthin:srcmorph-maven-plugin` (was +`net.ladenthin:llamacpp-ai-index-maven-plugin`), package `net.ladenthin.maven.srcmorph.mojo` (was +`net.ladenthin.maven.llamacpp.aiindex.mojo`), goal prefix `srcmorph` (was `ai-index`), every +`@Parameter` property now spelled `srcmorph.*` (e.g. `srcmorph.skip`, `srcmorph.file.skip`, +`srcmorph.planOnly`, `srcmorph.generationProvider`, `srcmorph.llama.modelPath` — was `aiIndex.*`). +The old coordinates are kept alive only via the separate relocation-stub module (see "Repository +layout" above and its own paragraph below) — never describe the plugin using the old +coordinates/package/properties in new documentation or code. The plugin's *contents* stay thin: it +depends on `net.ladenthin:srcmorph` (compile scope) for everything except the 5 mojo classes +themselves. - **`AbstractAiIndexMojo`** — shared `@Parameter` fields + `buildConfiguration()`, which maps them onto a new `SrcMorphConfiguration` for the matching engine to run. Concrete mojos @@ -170,20 +189,31 @@ except the 5 mojo classes themselves. goal-specific `@Parameter`s (e.g. `skipFile`/`skipPackage`/`skipProject`, `planOnly`, `fileExtensions`, `excludes`, `factDefinitions`), build the configuration, and delegate the whole run to one `net.ladenthin.srcmorph.engine.*` engine — mojos are now thin (≤ ~30 lines of actual logic each) and - translate a caught `SrcMorphException`/`IOException` into a `MojoExecutionException`. + translate a caught `SrcMorphException`/`IOException` into a `MojoExecutionException`. The class + itself keeps its historical name (`AbstractAiIndexMojo`) even though the package/goal-prefix/ + properties around it were renamed — only the Maven-facing surface (coordinates, package, goal + prefix, `@Parameter` property strings) was in scope for the rename. - **Skip flags stay mojo-side** (`skip`, `skipFile`, `skipPackage`, `skipProject`) — a Maven lifecycle concern, not part of `SrcMorphConfiguration`; an engine built from a configuration always executes when asked. See `MojoPhaseSkipTest`. -- **A future release will relocate this plugin** to `net.ladenthin:srcmorph-maven-plugin` (goal prefix - `srcmorph`, package `net.ladenthin.maven.srcmorph.mojo`, properties `srcmorph.*`) with a Maven Central - relocation POM for the old coordinates. **That has not happened yet** — do not write plugin - documentation, XML examples, or property names as if it had (see `TODO.md` for the tracked step). - **Architecture rules** (`PluginArchitectureTest`): Maven-annotation confinement to `mojo`, every mojo extends `AbstractMojo`, plus this module's slice of the shared conventions. - **jcstress** (`jcstress/AiGenerationKindRace.java`) and **vmlens** (`vmlens/VmlensInterleavingSmokeTest.java`) tests/profiles currently live in this module, not in `srcmorph` — they were not relocated during the core extraction. -- Full goal/parameter reference: `llamacpp-ai-index-maven-plugin/README.md`. +- Full goal/parameter reference: `srcmorph-maven-plugin/README.md`. + +### `llamacpp-ai-index-maven-plugin` — the relocation stub (retired coordinates) + +A separate, minimal 4th reactor module — **not** a renamed copy of the plugin above, and not a +child of `srcmorph-parent` (no `` at all). Its entire `pom.xml` is `groupId` + +`artifactId` (`llamacpp-ai-index-maven-plugin`) + a version pinned independently at `1.0.4` + +`` pointing at `net.ladenthin:srcmorph-maven-plugin:1.1.0`. +No source, no tests, no dependencies. Its sole purpose is so a consumer still declaring the old +Maven coordinates gets redirected by Maven to the renamed plugin once both are published. Because +it is still listed in the root `` for aggregation, a reactor-wide `mvn versions:set` run +from the repo root must exclude it explicitly — see "Repository layout" above for the exact +command; the same warning is repeated in the stub's own `pom.xml` comment and in `TODO.md`. --- @@ -194,13 +224,14 @@ except the 5 mojo classes themselves. ```bash mvn compile # Compiles every module (Java + generates nothing native; pure Java reactor) mvn test # Runs every module's tests -mvn package # Builds all four artifacts (parent pom + 3 jars, incl. the CLI's fat jar) -mvn install # Installs all four into ~/.m2 (needed before iterating on a single module — see below) +mvn package # Builds all five reactor projects: parent pom + 3 jars (incl. the CLI's fat + # jar) + the relocation-stub pom (trivial — no source, packaging=pom) +mvn install # Installs all five into ~/.m2 (needed before iterating on a single module — see below) ``` ### Iterating on one module -Maven resolves inter-module dependencies (`llamacpp-ai-index-maven-plugin` and `srcmorph-cli` both +Maven resolves inter-module dependencies (`srcmorph-maven-plugin` and `srcmorph-cli` both depend on `net.ladenthin:srcmorph`) via the local repository, not in-reactor classes, unless you use `-pl`/`-am`: @@ -210,7 +241,7 @@ mvn -pl srcmorph -am install -DskipTests # Then work on just one module: mvn -pl srcmorph-cli test -mvn -pl llamacpp-ai-index-maven-plugin test +mvn -pl srcmorph-maven-plugin test ``` ### Offline / restricted-network environments @@ -223,7 +254,7 @@ mvn package -o -DskipTests ### Run the self-test profile (plugin module only) ```bash -mvn -pl llamacpp-ai-index-maven-plugin ai-index:generate -P ai-index-selftest +mvn -pl srcmorph-maven-plugin srcmorph:generate -P srcmorph-selftest ``` ### Run the CLI @@ -248,8 +279,8 @@ is unavailable. - `srcmorph` — the bulk of the test suite (framework-free logic); see that module's section above. - `srcmorph-cli` — `MainTest`, `ConfigBindingTest`, `ExamplesConfigBindingTest`, `CliArchitectureTest`, `CliEndToEndTest`. -- `llamacpp-ai-index-maven-plugin` — `PluginArchitectureTest`, `MojoPhaseSkipTest`, plus the jcstress/ - vmlens tests noted above. +- `srcmorph-maven-plugin` — `PluginArchitectureTest`, `MojoPhaseSkipTest`, plus the jcstress/ + vmlens tests noted above. (The `llamacpp-ai-index-maven-plugin` relocation stub has no tests.) See `TEST_WRITING_GUIDE.md` (repo root, applies to every module) for full conventions. @@ -327,8 +358,8 @@ assume it has already been updated. | `ch.qos.logback:logback-classic` | 1.5.37 (converged in the parent) | `srcmorph-cli` (runtime binding) | | `com.fasterxml.jackson.core:jackson-databind` | pinned in parent | `srcmorph-cli` (JSON config) | | `com.fasterxml.jackson.dataformat:jackson-dataformat-yaml` | pinned in parent | `srcmorph-cli` (YAML config) | -| `org.apache.maven:maven-plugin-api` | 3.9.16 | `llamacpp-ai-index-maven-plugin` (provided) | -| `org.apache.maven.plugin-tools:maven-plugin-annotations` | 3.15.2 | `llamacpp-ai-index-maven-plugin` (provided) | +| `org.apache.maven:maven-plugin-api` | 3.9.16 | `srcmorph-maven-plugin` (provided) | +| `org.apache.maven.plugin-tools:maven-plugin-annotations` | 3.15.2 | `srcmorph-maven-plugin` (provided) | Test-only (every module): `org.junit.jupiter:junit-jupiter`, `org.hamcrest:hamcrest`, `com.tngtech.archunit:archunit-junit5`. `srcmorph` additionally uses jqwik (pinned ≤ 1.9.3 — see the @@ -387,7 +418,7 @@ See [`../workspace/policies/javadoc-conventions.md`](../workspace/policies/javad See [`../workspace/policies/spotbugs-suppressions.md`](../workspace/policies/spotbugs-suppressions.md). Each module has its own `spotbugs-exclude.xml` (`srcmorph/spotbugs-exclude.xml`, -`srcmorph-cli/spotbugs-exclude.xml`, `llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml`). +`srcmorph-cli/spotbugs-exclude.xml`, `srcmorph-maven-plugin/spotbugs-exclude.xml`). ## Spotless Formatting diff --git a/CODE_WRITING_GUIDE.md b/CODE_WRITING_GUIDE.md index 9a31ec0..ab53f8c 100644 --- a/CODE_WRITING_GUIDE.md +++ b/CODE_WRITING_GUIDE.md @@ -1,4 +1,4 @@ -# Code Writing Guide — llamacpp-ai-index-maven-plugin (Plugin-Specific Supplement) +# Code Writing Guide — srcmorph (reactor-wide Supplement) > **Canonical workspace rules** for production sources live in > [`../workspace/guides/src/CODE_WRITING_GUIDE-8.md`](../workspace/guides/src/CODE_WRITING_GUIDE-8.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1dbcf0..d446106 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,13 +30,13 @@ mvn package -DskipTests mvn install # Run the plugin against itself (self-test; requires a local GGUF model) -mvn clean install -Pai-index-selftest +mvn clean install -Psrcmorph-selftest # Run with native llama.cpp JNI tests enabled -mvn clean install -Pai-index-selftest -DrunNativeLlamaTests=true +mvn clean install -Psrcmorph-selftest -DrunNativeLlamaTests=true # Skip AI generation during build -mvn clean install -DaiIndex.skip=true +mvn clean install -Dsrcmorph.skip=true ``` ### Offline / restricted-network environments diff --git a/README.md b/README.md index 153f5ca..6221bc2 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,15 @@ code navigation; the same rule-routed pipeline (composable file-matching conditi model + prompt, oversize handling) is generic enough to eventually emit wikis, architecture docs, diagrams, or even source-to-source transformations. -> **This repository is mid-migration.** It began life as a single Maven plugin and is being -> restructured into a small reactor of three artifacts — see "Module overview" below. The **Maven -> plugin keeps its original coordinates and property names in this release**; a later, separately -> tagged release will relocate it to `net.ladenthin:srcmorph-maven-plugin` with a Maven Central -> relocation POM, so existing consumers are never broken out from under them. See -> [`CLAUDE.md`](CLAUDE.md) and [`TODO.md`](TODO.md) for the full migration status. +> **Migration note.** This repository was restructured from a single Maven plugin into a reactor of +> four artifacts — see "Module overview" below. As part of that migration the Maven plugin was +> **renamed** from `net.ladenthin:llamacpp-ai-index-maven-plugin` to +> `net.ladenthin:srcmorph-maven-plugin` (goal prefix `srcmorph`, properties `srcmorph.*`). Existing +> consumers who still declare the old coordinates are **not** broken: the old artifactId is +> published as a tiny relocation-stub POM (``) that redirects +> Maven to the new coordinates automatically — no consumer action required, though updating your own +> POM to the new coordinates directly is recommended. See [`CLAUDE.md`](CLAUDE.md) and +> [`TODO.md`](TODO.md) for the full migration status. ## Module overview @@ -89,20 +92,22 @@ diagrams, or even source-to-source transformations. |---|---|---| | [`srcmorph/`](srcmorph/README.md) | `net.ladenthin:srcmorph` | The core library: a framework-free Java API (no Maven dependency) — the shared `SrcMorphConfiguration` plus the `generate`/`aggregate-packages`/`aggregate-project`/`calibrate` engines. Use this to embed srcmorph in your own tooling. | | [`srcmorph-cli/`](srcmorph-cli/README.md) | `net.ladenthin:srcmorph-cli` | A standalone CLI driven by one JSON or YAML configuration file (ships as a `java -jar`-ready fat jar). No Maven project required. | -| [`llamacpp-ai-index-maven-plugin/`](llamacpp-ai-index-maven-plugin/README.md) | `net.ladenthin:llamacpp-ai-index-maven-plugin` | The original Maven plugin, now a thin wrapper around `srcmorph`. **Coordinates/goal-prefix/properties unchanged in this release** — see the note above. | +| [`srcmorph-maven-plugin/`](srcmorph-maven-plugin/README.md) | `net.ladenthin:srcmorph-maven-plugin` | The Maven plugin, a thin wrapper around `srcmorph` — **renamed** from `net.ladenthin:llamacpp-ai-index-maven-plugin` (goal prefix `srcmorph`, was `ai-index`; properties `srcmorph.*`, were `aiIndex.*`). | +| [`llamacpp-ai-index-maven-plugin/`](llamacpp-ai-index-maven-plugin/pom.xml) | `net.ladenthin:llamacpp-ai-index-maven-plugin` (pinned at `1.0.4`) | A tiny relocation-stub POM — no source, no dependencies — that redirects Maven Central consumers of the old plugin coordinates to `srcmorph-maven-plugin`. | -All three (plus the reactor parent pom) are released together from this one repository at the same -version. +The first three modules (plus the reactor parent pom) are released together from this one +repository at the same version; the relocation stub is versioned and published independently (see +`CLAUDE.md`). ## Quickstart -### 1. Maven plugin (current coordinates) +### 1. Maven plugin ```xml net.ladenthin - llamacpp-ai-index-maven-plugin - 1.0.2 + srcmorph-maven-plugin + 1.1.0 mock @@ -129,8 +134,24 @@ version. ``` -Run with `mvn ai-index:generate`. Full goal/parameter reference, routing rules, GPU acceleration, and -model recommendations: [`llamacpp-ai-index-maven-plugin/README.md`](llamacpp-ai-index-maven-plugin/README.md). +Run with `mvn srcmorph:generate`. Full goal/parameter reference, routing rules, GPU acceleration, and +model recommendations: [`srcmorph-maven-plugin/README.md`](srcmorph-maven-plugin/README.md). + +
      +Deprecated: old net.ladenthin:llamacpp-ai-index-maven-plugin coordinates (auto-relocates) + +```xml + + net.ladenthin + llamacpp-ai-index-maven-plugin + 1.0.4 + + +``` + +
      ### 2. Standalone CLI diff --git a/REUSE.toml b/REUSE.toml index b82ae1e..85b73b0 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -7,7 +7,7 @@ SPDX-PackageDownloadLocation = "https://github.com/bernardladenthin/llamacpp-ai- path = [ "**.md", "CITATION.cff", - "llamacpp-ai-index-maven-plugin/src/site/ai/empty.md", + "srcmorph-maven-plugin/src/site/ai/empty.md", "docs/RELEASE.md", ".github/ISSUE_TEMPLATE/bug_report.md", ".github/ISSUE_TEMPLATE/feature_request.md", diff --git a/TEST_WRITING_GUIDE.md b/TEST_WRITING_GUIDE.md index 6b03890..ee07b66 100644 --- a/TEST_WRITING_GUIDE.md +++ b/TEST_WRITING_GUIDE.md @@ -1,4 +1,4 @@ -# Unit Test Writing Guide — llamacpp-ai-index-maven-plugin (Plugin-Specific Supplement) +# Unit Test Writing Guide — srcmorph (reactor-wide Supplement) > **Canonical workspace rules** for test sources live in > [`../workspace/guides/test/TEST_WRITING_GUIDE-8.md`](../workspace/guides/test/TEST_WRITING_GUIDE-8.md) @@ -39,7 +39,7 @@ Every test file **must** start with the formatter-off block enclosing the Apache * */ // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex; +package net.ladenthin.maven.srcmorph; ``` - The `// @formatter:off` / `// @formatter:on` pair wraps **only** the license block. @@ -337,7 +337,7 @@ public class AiMdHeaderCodecTest { ``` For sources shared across multiple test classes, reference a fully-qualified method: -`@MethodSource("net.ladenthin.maven.llamacpp.aiindex.CommonDataProvider#nodeTypes")`. +`@MethodSource("net.ladenthin.maven.srcmorph.CommonDataProvider#nodeTypes")`. --- @@ -504,7 +504,7 @@ The goal is to **minimize the diff** to only lines that actually need changing. * */ // @formatter:on -package net.ladenthin.maven.llamacpp.aiindex; +package net.ladenthin.maven.srcmorph; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; diff --git a/TODO.md b/TODO.md index 05560fc..7fdb6fc 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -# TODO — srcmorph (llamacpp-ai-index-maven-plugin reactor) +# TODO — srcmorph reactor (Maven plugin now `srcmorph-maven-plugin`, formerly `llamacpp-ai-index-maven-plugin`) Open work items for this repo. Cross-cutting tracking lives in [`../workspace/crossrepostatus.md`](../workspace/crossrepostatus.md); items here are @@ -8,12 +8,14 @@ recorded in git history and `crossrepostatus.md`, not here. ## Open - **Migration step 8 — CI full pass + first reactor release.** `.github/workflows/publish.yml` - is now adapted to the 3-module reactor: the `build` job uploads jars from all three modules - (`srcmorph` / `srcmorph-cli`, including its `jar-with-dependencies` fat jar / `llamacpp-ai-index-maven-plugin`); + is now adapted to the 3-module reactor (module names as of that step; the plugin module has + since been renamed `srcmorph-maven-plugin` in step 9 below, and CI was updated to match): the + `build` job uploads jars from all three modules + (`srcmorph` / `srcmorph-cli`, including its `jar-with-dependencies` fat jar / the plugin module); crash-dump globs are repo-wide (`**/hs_err_pid*.log` etc., since a forked surefire JVM can crash in any module's own working directory); the PIT step is scoped to `-pl srcmorph -am` (the only module with a `pitest-maven` execution) with its report glob at `srcmorph/target/pit-reports/**`; - the `vmlens` job is scoped to `-pl llamacpp-ai-index-maven-plugin -am` (where + the `vmlens` job is scoped to `-pl srcmorph-maven-plugin -am` (where `VmlensInterleavingSmokeTest` and the `vmlens` profile actually live — not relocated during the core extraction) and additionally passes `-Dsurefire.failIfNoSpecifiedTests=false` (the `-DfailIfNoTests=false` flag alone does not suppress the "-Dtest pattern matched nothing" @@ -28,21 +30,34 @@ recorded in git history and `crossrepostatus.md`, not here. performed as part of this CI-adaptation step. This is the gate the user asked for before step 9 ("if all is working stat I can safely do the final rename"). -- **Migration step 9 — plugin rename + Maven Central relocation (deferred, do only after step 8 - ships a real release).** Rename `llamacpp-ai-index-maven-plugin`'s own coordinates to - `net.ladenthin:srcmorph-maven-plugin` (goal prefix `srcmorph`, package - `net.ladenthin.maven.srcmorph.mojo`, properties `aiIndex.*` → `srcmorph.*`), and add a - relocation-stub module/POM for the old `net.ladenthin:llamacpp-ai-index-maven-plugin` coordinates - (only `groupId`/`artifactId`/`version` + `` — no source, no - dependencies) so existing consumers resolving the old artifact get redirected. This is the last, - isolated step of the migration; do not do it early, and do not describe it as already done in any - documentation until it actually ships. +- **Migration step 9 — plugin rename + Maven Central relocation. DONE (structurally; publishing is + still the user's own later action).** The former `llamacpp-ai-index-maven-plugin` module was + renamed to `srcmorph-maven-plugin` (`net.ladenthin:srcmorph-maven-plugin`, goal prefix `srcmorph`, + package `net.ladenthin.maven.srcmorph.mojo`, properties `aiIndex.*` → `srcmorph.*`), and a new, + independent relocation-stub module `llamacpp-ai-index-maven-plugin/` (pom-only, no ``, + pinned at version `1.0.4`, only `` pointing at + `net.ladenthin:srcmorph-maven-plugin:1.1.0`) was added back to the root `` list so + existing consumers resolving the old coordinates get redirected once it is actually published. + This is the last, isolated step of the migration in terms of code/POM structure — actually + publishing both the `1.1.0` reactor release and the `1.0.4` relocation stub to Maven Central is + still the user's own action (this task never ran `mvn deploy` or signed anything). + + **Caveat — exclude the stub from reactor-wide version bumps.** Because the relocation stub is + listed in the root ``, a `mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false` run + from the repo root walks every module reachable from that list — including the stub — and would + overwrite its frozen `1.0.4` unless explicitly excluded: + ```bash + mvn versions:set -DnewVersion=X -DgenerateBackupPoms=false \ + -Dexcludes=net.ladenthin:llamacpp-ai-index-maven-plugin + ``` + The same warning is documented in the stub's own `pom.xml` comment and in the root `CLAUDE.md` + reactor-layout section. - **PIT mutation-testing gate for `srcmorph-cli` and the plugin module.** Only `srcmorph` (`srcmorph/pom.xml`) is PIT-gated today — `100` over an explicit `` list of 47 classes across config/document/engine/indexer/prompt/ provider/support, all killed at 100%. Neither `srcmorph-cli` nor - `llamacpp-ai-index-maven-plugin` has a `pitest-maven` execution of its own yet (both poms document + `srcmorph-maven-plugin` has a `pitest-maven` execution of its own yet (both poms document this explicitly with a comment at the spot a PIT plugin block would go). For the CLI: the pure helpers worth mutation-gating are the config copy/round-trip (`Main#copyWithPlanOnlyForced`) and command dispatch; `Main`'s I/O-heavy entry points (`main`, `loadConfiguration`) are better served by diff --git a/llamacpp-ai-index-maven-plugin/pom.xml b/llamacpp-ai-index-maven-plugin/pom.xml index 9b2ad01..e9d4bc3 100644 --- a/llamacpp-ai-index-maven-plugin/pom.xml +++ b/llamacpp-ai-index-maven-plugin/pom.xml @@ -4,2262 +4,47 @@ SPDX-FileCopyrightText: 2026 Bernard Ladenthin SPDX-License-Identifier: Apache-2.0 --> + 4.0.0 - - net.ladenthin - srcmorph-parent - 1.1.0-SNAPSHOT - ../pom.xml - - + net.ladenthin llamacpp-ai-index-maven-plugin - maven-plugin - - llamacpp-ai-index-maven-plugin - Free Maven plugin for hierarchical AI-readable indexing and summarization of source code projects using llama.cpp-compatible local models. - - - bernardladenthin - 8 - 21 - UTF-8 - 3.15.2 - 3.9.16 - - - gpt-oss-20B-mxfp4 - - -1 - - -1 - - - 6.1.2 - 3.0 - 1.37 - 0.16 - 1.2.28 - 2.50.0 - 0.13.7 - 1.0.0 - 4.2.1 - - 1.4.2 - 4.10.2.0 - 1.18.46 - 7.7.4 - 1.14.0 - 3.8.0 - 2.94.0 - - ${project.basedir}/src/site/ai - - - true - - - - ${git.commit.time} - - + 1.0.4 + pom - + llamacpp-ai-index-maven-plugin (relocated) + Relocated. This artifact moved to net.ladenthin:srcmorph-maven-plugin; see the distributionManagement/relocation below. - - - org.projectlombok - lombok - ${lombok.version} - provided - - - org.jspecify - jspecify - ${jspecify.version} - - - org.checkerframework - checker-qual - ${checker.version} - - - + + net.ladenthin - srcmorph - ${project.version} - - - - - org.slf4j - slf4j-api - - - - org.apache.maven - maven-plugin-api - ${maven.version} - provided - - - - org.apache.maven.plugin-tools - maven-plugin-annotations - ${maven.plugin.tools.version} - provided - - - - org.junit.jupiter - junit-jupiter - ${junit.version} - test - - - - org.hamcrest - hamcrest - ${hamcrest.version} - test - - - - com.tngtech.archunit - archunit-junit5 - ${archunit.version} - test - - - org.openjdk.jmh - jmh-core - ${jmh.version} - test - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - test - - - org.openjdk.jcstress - jcstress-core - ${jcstress.version} - test - - - - com.vmlens - api - ${vmlens.version} - test - - - - - - - - com.diffplug.spotless - spotless-maven-plugin - ${spotless.version} - - - com.github.spotbugs - spotbugs-maven-plugin - ${spotbugs.version} - - - com.vmlens - vmlens-maven-plugin - ${vmlens.version} - - - io.github.git-commit-id - git-commit-id-maven-plugin - 10.0.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.15.0 - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.6.3 - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.8 - - - org.apache.maven.plugins - maven-jar-plugin - 3.5.0 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-plugin-plugin - ${maven.plugin.tools.version} - - - org.apache.maven.plugins - maven-source-plugin - 3.4.0 - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.6 - - - @{argLine} -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:ErrorFile=hs_err_pid%p.log - - - **/VmlensInterleavingSmokeTest.java - - - false - - - - org.codehaus.mojo - exec-maven-plugin - 3.6.3 - - - org.jacoco - jacoco-maven-plugin - 0.8.15 - - - org.pitest - pitest-maven - 1.25.7 - - - org.sonatype.central - central-publishing-maven-plugin - 0.11.0 - - - - - - io.github.git-commit-id - git-commit-id-maven-plugin - - - get-git-properties - - revision - - initialize - - - - yyyy-MM-dd'T'HH:mm:ss'Z' - UTC - false - false - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce-maven - - enforce - - - - - [3.6.3,) - - - [1.8,) - - - - - - commons-logging:commons-logging - - log4j:log4j - - org.hamcrest:hamcrest-core - org.hamcrest:hamcrest-library - org.hamcrest:hamcrest-all - - junit:junit - junit:junit-dep - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${maven.compiler.release} - ${maven.compiler.testRelease} - true - true - - - -Xlint:all,-serial,-options,-classfile,-processing - -Werror - - -processor - lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor,org.checkerframework.checker.nullness.NullnessChecker - - -AskipDefs=^net\.ladenthin\.llamacpp_ai_index_maven_plugin\..*$ - -XDaddTypeAnnotationsToSymbol=true - -XDcompilePolicy=simple - --should-stop=ifError=FLOW - - -Xplugin:ErrorProne -XepExcludedPaths:.*/generated-sources/.* -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:ExcludedFieldAnnotations=org.apache.maven.plugins.annotations.Parameter,org.apache.maven.plugins.annotations.Component -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:CheckOptionalEmptiness=true -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true -XepOpt:NullAway:AcknowledgeAndroidRecent=true -XepOpt:NullAway:AssertsEnabled=true -Xep:BoxedPrimitiveEquality:ERROR -Xep:EqualsHashCode:ERROR -Xep:EqualsIncompatibleType:ERROR -Xep:IdentityBinaryExpression:ERROR -Xep:SelfAssignment:ERROR -Xep:SelfComparison:ERROR -Xep:SelfEquals:ERROR -Xep:DeadException:ERROR -Xep:FormatString:ERROR -Xep:InvalidPatternSyntax:ERROR -Xep:OptionalEquality:ERROR -Xep:ImpossibleNullComparison:ERROR - - - - org.projectlombok - lombok - ${lombok.version} - - - com.google.errorprone - error_prone_core - ${errorprone.version} - - - com.uber.nullaway - nullaway - ${nullaway.version} - - - org.checkerframework - checker - ${checker.version} - - - - - - default-compile - - - - module-info.java - - - - - module-info-compile - compile - - compile - - - - 9 - - module-info.java - - - - - - - default-testCompile - - - false - - -XDaddTypeAnnotationsToSymbol=true - -XDcompilePolicy=simple - --should-stop=ifError=FLOW - -Xplugin:ErrorProne -Xep:NullAway:OFF -Xep:GuardedBy:OFF -Xep:IdentityBinaryExpression:OFF - - - - org.openjdk.jcstress - jcstress-core - ${jcstress.version} - - - org.openjdk.jmh - jmh-generator-annprocess - ${jmh.version} - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - verify - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - ${maven.compiler.release} - true - true - all - - **/HelpMojo.java - - - - - attach-javadocs - - jar - - - - - - - org.apache.maven.plugins - maven-plugin-plugin - - ai-index - - - - default-descriptor - - descriptor - - - - help-goal - - helpmojo - - - - - - - org.jacoco - jacoco-maven-plugin - - - prepare-agent - - prepare-agent - - - - report - test - - report - - - - - - com.diffplug.spotless - spotless-maven-plugin - - - - src/main/java/**/*.java - src/test/java/**/*.java - - - ${palantir-java-format.version} - - - - - - - - - spotless-check - verify - - check - - - - - - com.github.spotbugs - spotbugs-maven-plugin - - Max - Low - true - false - spotbugs-exclude.xml - - - com.mebigfatguy.fb-contrib - fb-contrib - ${fb-contrib.version} - - - com.h3xstream.findsecbugs - findsecbugs-plugin - ${findsecbugs.version} - - - - - - spotbugs-check - verify - - check - - - - - - org.codehaus.mojo - exec-maven-plugin - - org.openjdk.jmh.Main - test - - - - - - - - - - - ai-index-selftest - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - - - ${ai.index.output.directory} - - - ../srcmorph/src/main/java/net/ladenthin/srcmorph/config - ../srcmorph/src/main/java/net/ladenthin/srcmorph/provider - - - - - **/package-info.java - **/module-info.java - - - llamacpp-jni - - - - - - - Mistral-7B-Instruct-v0.3-Q4_K_M-C16k - X:/Modelle/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf - 16384 - 2048 - 1.0 - 8 - 4 - true - 1.1 - - - - Meta-Llama-3.1-8B-Instruct-Q5_K_M-C32k - X:/Modelle/Meta-Llama-3.1-8B-Instruct-Q5_K_M.gguf - 32768 - 1536 - 1.0 - 8 - 4 - true - 1.1 - - - - Codestral-22B-v0.1-Q4_K_M-C32k - X:/Modelle/Codestral-22B-v0.1-Q4_K_M.gguf - 32768 - 1536 - 1.0 - 8 - 4 - true - 1.1 - - - - Codestral-22B-v0.1-Q6_K-C32k - X:/Modelle/Codestral-22B-v0.1-Q6_K.gguf - 32768 - 1536 - 1.0 - 8 - 4 - true - 1.1 - - - - Qwen2.5-Coder-7B-Instruct-Q5_K_M-C16k - X:/Modelle/qwen2.5-coder-7b-instruct-q5_k_m.gguf - 16384 - 2048 - 1.0 - 8 - 4 - true - 1.1 - - - - Ministral-3-8B-Instruct-2512-Q4_K_M-C32k - X:/Modelle/Ministral-3-8B-Instruct-2512-Q4_K_M.gguf - 32768 - 2048 - 0.1 - 8 - 4 - true - 1.1 - - - - Ministral-3-14B-Instruct-2512-Q4_K_M-C32k - X:/Modelle/Ministral-3-14B-Instruct-2512-Q4_K_M.gguf - 32768 - 2048 - 0.1 - 8 - 4 - true - 1.1 - - - - Ministral-3-8B-Instruct-2512-Q5_K_M-C32k - X:/Modelle/Ministral-3-8B-Instruct-2512-Q5_K_M.gguf - 32768 - 2048 - 0.1 - 8 - 4 - true - 1.1 - - - - Ministral-3-14B-Instruct-2512-Q5_K_M-C32k - X:/Modelle/Ministral-3-14B-Instruct-2512-Q5_K_M.gguf - 32768 - 2048 - 0.1 - 8 - 4 - true - 1.1 - - - - Gemma-4-26B-A4B-it-UD-Q4_K_M-C32k - X:/Modelle/gemma-4-26B-A4B-it-UD-Q4_K_M.gguf - 32768 - 8192 - 1.0 - 8 - 4 - true - 0.95 - 64 - 1.1 - false - - <end_of_turn> - - - - - Gemma-4-12B-it-Q4_K_M-C32k - X:/Modelle/gemma-4-12B-it-Q4_K_M.gguf - 32768 - 8192 - 1.0 - 8 - 4 - true - 0.95 - 64 - 1.1 - false - - <end_of_turn> - - - - - Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q4_K_M-C32k - X:/Modelle/Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf - 32768 - 8192 - 1.0 - 8 - 4 - true - 0.95 - 64 - 1.1 - false - - <end_of_turn> - - - - - Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q6_K_P-C32k - X:/Modelle/Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q6_K_P.gguf - 32768 - 8192 - 1.0 - 8 - 4 - true - 0.95 - 64 - 1.1 - false - - <end_of_turn> - - - - - Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL-C64k - X:/Modelle/Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL.gguf - - 16384 - 1536 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - ${ai.cachePrompt} - - - - Qwen2.5-Coder-14B-Instruct-Q4_K_M-C32k - X:/Modelle/Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf - 32768 - 2048 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - - - - Qwen3-4B-Instruct-2507-Q5_K_M-C32k - X:/Modelle/Qwen3-4B-Instruct-2507-Q5_K_M.gguf - 32768 - 2048 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - - - - Granite-4.0-H-Tiny-Q4_K_M-C128k - X:/Modelle/ibm-granite_granite-4.0-h-tiny-Q4_K_M.gguf - 131072 - 2048 - 0.2 - 8 - 4 - true - 1.1 - - - - Granite-4.0-H-Small-Q4_K_M-C128k - X:/Modelle/ibm-granite_granite-4.0-h-small-Q4_K_M.gguf - 131072 - 2048 - 0.2 - 8 - 4 - true - 1.1 - - - - EXP-Qwen3.5-4B - X:/Modelle/Qwen3.5-4B-Q4_K_M.gguf - 16384 - 1536 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - false - - - EXP-Qwen2.5-Coder-7B - X:/Modelle/qwen2.5-coder-7b-instruct-q5_k_m.gguf - 16384 - 1536 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - - - - gpt-oss-20B-c16k - X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf - 16384 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 2048 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-c48k - X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf - 49152 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 4096 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-c96k - X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - - - gpt-oss-20B-mxfp4 - X:/Modelle/gpt-oss-20b-mxfp4.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-Q4_K_M - X:/Modelle/gpt-oss-20b-Q4_K_M.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-Q5_K_M - X:/Modelle/gpt-oss-20b-Q5_K_M.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-Q8_0 - X:/Modelle/gpt-oss-20b-Q8_0.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - gpt-oss-20B-F16 - X:/Modelle/gpt-oss-20b-F16.gguf - 98304 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 6144 - 0.7 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - low - - - - - granite-4.0-h-tiny-bigwindow - X:/Modelle/granite-4.0-h-tiny-Q4_K_M.gguf - 393216 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 4096 - 0.2 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - - - - - - granite-4.0-h-1b-fastchunk - X:/Modelle/granite-4.0-h-1b-Q4_K_M.gguf - 16384 - ${ai.gpuLayers} - ${ai.mainGpu} - ${ai.devices} - 1536 - 0.2 - 8 - 3 - true - 1.0 - 0 - 0.05 - 1.0 - - - - - EXP-Qwen3-Coder-30B - X:/Modelle/Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL.gguf - 16384 - 1536 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - - - - EXP-DeepSeek-Coder-V2-Lite - X:/Modelle/DeepSeek-Coder-V2-Lite-Instruct-Q4_K_M.gguf - 16384 - 1536 - 0.3 - 8 - 4 - true - 0.9 - 40 - 1.05 - - - EXP-Seed-Coder-8B - X:/Modelle/Seed-Coder-8B-Instruct-Q4_K_M.gguf - 16384 - 1536 - 0.3 - 8 - 4 - true - 0.9 - 40 - 1.05 - - - EXP-Granite-4.0-H-Tiny - X:/Modelle/granite-4.0-h-tiny-Q4_K_M.gguf - 16384 - 1536 - 0.0 - 8 - 4 - true - 1.0 - 0 - 1.0 - - - - EXP-Granite-4.0-H-Small - X:/Modelle/granite-4.0-h-small-UD-Q4_K_XL.gguf - 16384 - 1536 - 0.0 - 8 - 4 - true - 1.0 - 0 - 1.0 - - - EXP-Qwen3-4B-Instruct-2507 - X:/Modelle/Qwen3-4B-Instruct-2507-Q4_K_M.gguf - 16384 - 1536 - 0.7 - 8 - 4 - true - 0.8 - 20 - 1.05 - - - - - - - file-body-java - - - - file-body-sql - - - - file-body-fallback - - - - package-body - - - - project-body - - - - - file-body-java-v2 - - - - package-body-v2 - - - - - - - - - - ai-generate - generate-resources - - generate - - - - - .java - .sql - - - - - java-facts - - (?m)^\s*(?:public\s+|final\s+|abstract\s+|sealed\s+|non-sealed\s+|static\s+)*(?:class|interface|enum|record)\s+\w - \bpublic\b - \b(?:TODO|FIXME)\b - @Override\b - - (?m)^[ \t]*(?:(?:public|private|protected|static|final|abstract|default|synchronized|native|strictfp)[ \t]+)*(?:<[^>]+>[ \t]*)?(?!(?:if|for|while|switch|catch|return|new|else|do|try|synchronized|assert|throw)\b)[A-Za-z_$][\w$.]*(?:<[^;{}=]*>)?(?:\[\])*[ \t]+([A-Za-z_$]\w*)[ \t]*\([^;{]*\)(?:[ \t]*throws[ \t][\w$., \t]+)?[ \t]*[{;] - (?m)^[ \t]*(?:(?:public|private|protected)[ \t]+)?([A-Z][A-Za-z0-9_$]*)[ \t]*\([^;{]*\)(?:[ \t]*throws[ \t][\w$., \t]+)?[ \t]*\{ - (?m)^[ \t]*(?:(?:public|private|protected|static|transient|volatile|final)[ \t]+)*(?:public|private|protected|static|transient|volatile)[ \t]+(?:(?:public|private|protected|static|transient|volatile|final)[ \t]+)*(?!class\b|interface\b|enum\b|record\b|void\b|new\b)[A-Za-z_$][\w$.]*(?:<[^;{}=]*>)?(?:\[\])*[ \t]+([A-Za-z_$]\w*(?:[ \t]*,[ \t]*[A-Za-z_$]\w*)*)[ \t]*(?:=[^;]*)?; - - - - sql-facts - - (?im)^\s*INSERT\s+INTO\b - (?im)^\s*CREATE\s+TABLE\b - (?im)^\s*CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\b - (?im)^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\b - (?im)^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\b - - - - - - - huge-java - file-body-java - granite-4.0-h-1b-fastchunk - 200 - - mapReduce - 6 - java-facts - - - - - .java - - - 1000000 - - - - - - - huge-sql - file-body-sql - granite-4.0-h-1b-fastchunk - 200 - mapReduce - 6 - - sql-facts - - - - - .sql - - - 1000000 - - - - - - - big-window-java - file-body-java - granite-4.0-h-tiny-bigwindow - 100 - - - - - - .java - - - 275000 - - - - - - - big-window-sql - file-body-sql - granite-4.0-h-tiny-bigwindow - 100 - - - - - .sql - - - 275000 - - - - - - - java - file-body-java - ${ai.model} - - java-facts - - .java - - - - sql - file-body-sql - ${ai.model} - sql-facts - - .sql - - - - - fallback - file-body-fallback - ${ai.model} - true - - - - - - - ai-aggregate-packages - process-resources - - aggregate-packages - - - - - package-body - ${ai.model} - - - - - - - - ai-aggregate-project - prepare-package - - aggregate-project - - - - - project-body - ${ai.model} - - - - - - - - - - - - vmlens - - - - com.vmlens - vmlens-maven-plugin - - - - **/VmlensInterleavingSmokeTest.java - - - - - vmlens-test - - test - - - - - - - - - jcstress - - - - org.codehaus.mojo - exec-maven-plugin - - - jcstress - test - exec - - ${java.home}/bin/java - test - - -classpath - - org.openjdk.jcstress.Main - -v - -m - default - - - - - - - - - + srcmorph-maven-plugin + 1.1.0 + + diff --git a/pom.xml b/pom.xml index 9aa9007..0d8e986 100644 --- a/pom.xml +++ b/pom.xml @@ -65,15 +65,25 @@ SPDX-License-Identifier: Apache-2.0 srcmorph srcmorph-cli + srcmorph-maven-plugin llamacpp-ai-index-maven-plugin diff --git a/srcmorph-cli/README.md b/srcmorph-cli/README.md index d17abf8..29874ca 100644 --- a/srcmorph-cli/README.md +++ b/srcmorph-cli/README.md @@ -4,7 +4,7 @@ [srcmorph](../README.md): a single `java -jar` invocation driven by one JSON or YAML configuration file, with no Maven project or build required. It wraps the [`srcmorph`](../srcmorph/README.md) core library's engines exactly the way the -[Maven plugin](../llamacpp-ai-index-maven-plugin/README.md) does, following the +[Maven plugin](../srcmorph-maven-plugin/README.md) does, following the [BitcoinAddressFinder](https://github.com/bernardladenthin/BitcoinAddressFinder) `cli.Main` pattern. ## Running it @@ -46,7 +46,7 @@ public class CConfiguration { `promptDefinitions`, `aiDefinitions`, `fieldGenerations`, `factDefinitions`, the `llama*` fallback parameters, `force`, `planOnly`, `projectName`. If you already know the plugin's XML shape, you already know this shape — see - [`../llamacpp-ai-index-maven-plugin/README.md`](../llamacpp-ai-index-maven-plugin/README.md)'s + [`../srcmorph-maven-plugin/README.md`](../srcmorph-maven-plugin/README.md)'s "Configuration" section for the full field-by-field reference (routing-rule conditions, oversize strategies, per-model sampling parameters, etc.), which applies verbatim here. diff --git a/llamacpp-ai-index-maven-plugin/README.md b/srcmorph-maven-plugin/README.md similarity index 95% rename from llamacpp-ai-index-maven-plugin/README.md rename to srcmorph-maven-plugin/README.md index b94bdd9..2bb2864 100644 --- a/llamacpp-ai-index-maven-plugin/README.md +++ b/srcmorph-maven-plugin/README.md @@ -1,4 +1,4 @@ -# llamacpp-ai-index-maven-plugin +# srcmorph-maven-plugin A Maven plugin for generating hierarchical, AI-readable documentation of source code projects using local llama.cpp-compatible models. It creates structured `.ai.md` files per source file and @@ -6,11 +6,14 @@ aggregates them into package-level (and project-level) summaries for fast semant retrieval. > This module is one of three in the [srcmorph](../README.md) Maven reactor — see the root README -> for the product overview, badges, and build status. **This plugin's Maven coordinates, package, -> and property names are unchanged** (`net.ladenthin:llamacpp-ai-index-maven-plugin`, goal prefix -> `ai-index`, properties `aiIndex.*`); a future release will relocate it to -> `net.ladenthin:srcmorph-maven-plugin`, but that has not happened yet. Internally, the plugin now -> depends on [`net.ladenthin:srcmorph`](../srcmorph/README.md) (a sibling reactor module) for all of +> for the product overview, badges, and build status. **This plugin was renamed** from +> `net.ladenthin:llamacpp-ai-index-maven-plugin` to `net.ladenthin:srcmorph-maven-plugin` (goal +> prefix `srcmorph`, properties `srcmorph.*`) as part of the reactor migration's final step. +> Consumers still declaring the old coordinates are transparently redirected: the old artifactId is +> now published only as a tiny relocation-stub POM +> (`../llamacpp-ai-index-maven-plugin/pom.xml`, ``) pointing here +> — see the root README's migration note. Internally, the plugin depends on +> [`net.ladenthin:srcmorph`](../srcmorph/README.md) (a sibling reactor module) for all of > its engine/indexing logic — the 5 Mojo classes in this module are thin wrappers that map their > `@Parameter` fields onto a shared `SrcMorphConfiguration` and delegate to one of `srcmorph`'s > `engine.*` classes. That split is an internal implementation detail; nothing below changes for a @@ -95,7 +98,7 @@ classpath): ```xml net.ladenthin - llamacpp-ai-index-maven-plugin + srcmorph-maven-plugin @@ -207,7 +210,7 @@ The plugin is configured from three building blocks, declared on the plugin insi
      @@ -370,17 +373,17 @@ model's window; `` caps how many chunks `mapReduce` summarizes. Previ running a model: ``` -mvn ai-index:generate -DaiIndex.planOnly=true +mvn srcmorph:generate -Dsrcmorph.planOnly=true ``` ## Usage Run AI index generation: ``` -mvn clean install -Pai-index-selftest +mvn clean install -Psrcmorph-selftest ``` With native llama tests: ``` -mvn clean install -Pai-index-selftest -DrunNativeLlamaTests=true +mvn clean install -Psrcmorph-selftest -DrunNativeLlamaTests=true ``` ## Plugin Configuration Run-level parameters (set in ``): @@ -395,11 +398,11 @@ Run-level parameters (set in ``): - `skip` — global switch: skip **every** phase (default: `false`) - Per-phase switches — turn any of the three phases on/off independently (each default `false`). Named after the three index levels (`file` / `package` / `project`, the `x` node types): - - `aiIndex.file.skip` — skip the **file** phase (the `generate` goal) - - `aiIndex.package.skip` — skip the **package** phase (the `aggregate-packages` goal) - - `aiIndex.project.skip` — skip the **project** phase (the `aggregate-project` goal) + - `srcmorph.file.skip` — skip the **file** phase (the `generate` goal) + - `srcmorph.package.skip` — skip the **package** phase (the `aggregate-packages` goal) + - `srcmorph.project.skip` — skip the **project** phase (the `aggregate-project` goal) - So `-DaiIndex.package.skip=true` runs file + project only, `-DaiIndex.skip=true` runs + So `-Dsrcmorph.package.skip=true` runs file + project only, `-Dsrcmorph.skip=true` runs nothing, and the defaults run all three. - `aiDefinitions` / `promptDefinitions` — named models / prompt templates, referenced by key - `fieldGenerations` — per goal: which `promptKey` runs with which `aiDefinitionKey` (**required**) @@ -538,7 +541,7 @@ GPU `jllama.dll` from it — no library path to manage: ```xml net.ladenthin - llamacpp-ai-index-maven-plugin + srcmorph-maven-plugin ... @@ -552,7 +555,7 @@ GPU `jllama.dll` from it — no library path to manage: ``` ``` -mvn ai-index:generate -Dai.gpuLayers=20 # + the GPU runtime on PATH (see below) +mvn srcmorph:generate -Dai.gpuLayers=20 # + the GPU runtime on PATH (see below) ``` **Alternative — runtime library override (no POM change).** Point `net.ladenthin.llama.lib.path` at a @@ -560,7 +563,7 @@ folder holding the GPU `jllama.dll` (extracted once from the classifier jar); it bundled native: ``` -mvn ai-index:generate -Dnet.ladenthin.llama.lib.path=C:\path\to\gpu-native -Dai.gpuLayers=20 +mvn srcmorph:generate -Dnet.ladenthin.llama.lib.path=C:\path\to\gpu-native -Dai.gpuLayers=20 ``` In both cases: @@ -595,7 +598,7 @@ mvn clean install ``` Skip AI generation: ``` -mvn clean install -DaiIndex.skip=true +mvn clean install -Dsrcmorph.skip=true ``` ### Contributors: do not upgrade jqwik past 1.9.3 diff --git a/srcmorph-maven-plugin/pom.xml b/srcmorph-maven-plugin/pom.xml new file mode 100644 index 0000000..6d06916 --- /dev/null +++ b/srcmorph-maven-plugin/pom.xml @@ -0,0 +1,2265 @@ + + + + 4.0.0 + + + net.ladenthin + srcmorph-parent + 1.1.0-SNAPSHOT + ../pom.xml + + + srcmorph-maven-plugin + maven-plugin + + srcmorph-maven-plugin + Free Maven plugin for hierarchical AI-readable indexing and summarization of source code projects using llama.cpp-compatible local models. Formerly published as net.ladenthin:llamacpp-ai-index-maven-plugin (see the relocation stub of that name). + + + bernardladenthin + 8 + 21 + UTF-8 + 3.15.2 + 3.9.16 + + + gpt-oss-20B-mxfp4 + + -1 + + -1 + + + 6.1.2 + 3.0 + 1.37 + 0.16 + 1.2.28 + 2.50.0 + 0.13.7 + 1.0.0 + 4.2.1 + + 1.4.2 + 4.10.2.0 + 1.18.46 + 7.7.4 + 1.14.0 + 3.8.0 + 2.94.0 + + ${project.basedir}/src/site/ai + + + true + + + + ${git.commit.time} + + + + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.jspecify + jspecify + ${jspecify.version} + + + org.checkerframework + checker-qual + ${checker.version} + + + + net.ladenthin + srcmorph + ${project.version} + + + + + org.slf4j + slf4j-api + + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven.plugin.tools.version} + provided + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + + com.tngtech.archunit + archunit-junit5 + ${archunit.version} + test + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + org.openjdk.jcstress + jcstress-core + ${jcstress.version} + test + + + + com.vmlens + api + ${vmlens.version} + test + + + + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + com.vmlens + vmlens-maven-plugin + ${vmlens.version} + + + io.github.git-commit-id + git-commit-id-maven-plugin + 10.0.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + + org.apache.maven.plugins + maven-jar-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.12.0 + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven.plugin.tools.version} + + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + @{argLine} -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:ErrorFile=hs_err_pid%p.log + + + **/VmlensInterleavingSmokeTest.java + + + false + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + org.pitest + pitest-maven + 1.25.7 + + + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + get-git-properties + + revision + + initialize + + + + yyyy-MM-dd'T'HH:mm:ss'Z' + UTC + false + false + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + [3.6.3,) + + + [1.8,) + + + + + + commons-logging:commons-logging + + log4j:log4j + + org.hamcrest:hamcrest-core + org.hamcrest:hamcrest-library + org.hamcrest:hamcrest-all + + junit:junit + junit:junit-dep + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.release} + ${maven.compiler.testRelease} + true + true + + + -Xlint:all,-serial,-options,-classfile,-processing + -Werror + + -processor + lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor,org.checkerframework.checker.nullness.NullnessChecker + + -AskipDefs=^net\.ladenthin\.srcmorph_maven_plugin\..*$ + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + + -Xplugin:ErrorProne -XepExcludedPaths:.*/generated-sources/.* -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:ExcludedFieldAnnotations=org.apache.maven.plugins.annotations.Parameter,org.apache.maven.plugins.annotations.Component -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:CheckOptionalEmptiness=true -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true -XepOpt:NullAway:AcknowledgeAndroidRecent=true -XepOpt:NullAway:AssertsEnabled=true -Xep:BoxedPrimitiveEquality:ERROR -Xep:EqualsHashCode:ERROR -Xep:EqualsIncompatibleType:ERROR -Xep:IdentityBinaryExpression:ERROR -Xep:SelfAssignment:ERROR -Xep:SelfComparison:ERROR -Xep:SelfEquals:ERROR -Xep:DeadException:ERROR -Xep:FormatString:ERROR -Xep:InvalidPatternSyntax:ERROR -Xep:OptionalEquality:ERROR -Xep:ImpossibleNullComparison:ERROR + + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + com.uber.nullaway + nullaway + ${nullaway.version} + + + org.checkerframework + checker + ${checker.version} + + + + + + default-compile + + + + module-info.java + + + + + module-info-compile + compile + + compile + + + + 9 + + module-info.java + + + + + + + default-testCompile + + + false + + -XDaddTypeAnnotationsToSymbol=true + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -Xep:NullAway:OFF -Xep:GuardedBy:OFF -Xep:IdentityBinaryExpression:OFF + + + + org.openjdk.jcstress + jcstress-core + ${jcstress.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${maven.compiler.release} + true + true + all + + **/HelpMojo.java + + + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + + srcmorph + + + + default-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + report + test + + report + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + ${palantir-java-format.version} + + + + + + + + + spotless-check + verify + + check + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + Max + Low + true + false + spotbugs-exclude.xml + + + com.mebigfatguy.fb-contrib + fb-contrib + ${fb-contrib.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + + spotbugs-check + verify + + check + + + + + + org.codehaus.mojo + exec-maven-plugin + + org.openjdk.jmh.Main + test + + + + + + + + + + + srcmorph-selftest + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + ${ai.index.output.directory} + + + ../srcmorph/src/main/java/net/ladenthin/srcmorph/config + ../srcmorph/src/main/java/net/ladenthin/srcmorph/provider + + + + + **/package-info.java + **/module-info.java + + + llamacpp-jni + + + + + + + Mistral-7B-Instruct-v0.3-Q4_K_M-C16k + X:/Modelle/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf + 16384 + 2048 + 1.0 + 8 + 4 + true + 1.1 + + + + Meta-Llama-3.1-8B-Instruct-Q5_K_M-C32k + X:/Modelle/Meta-Llama-3.1-8B-Instruct-Q5_K_M.gguf + 32768 + 1536 + 1.0 + 8 + 4 + true + 1.1 + + + + Codestral-22B-v0.1-Q4_K_M-C32k + X:/Modelle/Codestral-22B-v0.1-Q4_K_M.gguf + 32768 + 1536 + 1.0 + 8 + 4 + true + 1.1 + + + + Codestral-22B-v0.1-Q6_K-C32k + X:/Modelle/Codestral-22B-v0.1-Q6_K.gguf + 32768 + 1536 + 1.0 + 8 + 4 + true + 1.1 + + + + Qwen2.5-Coder-7B-Instruct-Q5_K_M-C16k + X:/Modelle/qwen2.5-coder-7b-instruct-q5_k_m.gguf + 16384 + 2048 + 1.0 + 8 + 4 + true + 1.1 + + + + Ministral-3-8B-Instruct-2512-Q4_K_M-C32k + X:/Modelle/Ministral-3-8B-Instruct-2512-Q4_K_M.gguf + 32768 + 2048 + 0.1 + 8 + 4 + true + 1.1 + + + + Ministral-3-14B-Instruct-2512-Q4_K_M-C32k + X:/Modelle/Ministral-3-14B-Instruct-2512-Q4_K_M.gguf + 32768 + 2048 + 0.1 + 8 + 4 + true + 1.1 + + + + Ministral-3-8B-Instruct-2512-Q5_K_M-C32k + X:/Modelle/Ministral-3-8B-Instruct-2512-Q5_K_M.gguf + 32768 + 2048 + 0.1 + 8 + 4 + true + 1.1 + + + + Ministral-3-14B-Instruct-2512-Q5_K_M-C32k + X:/Modelle/Ministral-3-14B-Instruct-2512-Q5_K_M.gguf + 32768 + 2048 + 0.1 + 8 + 4 + true + 1.1 + + + + Gemma-4-26B-A4B-it-UD-Q4_K_M-C32k + X:/Modelle/gemma-4-26B-A4B-it-UD-Q4_K_M.gguf + 32768 + 8192 + 1.0 + 8 + 4 + true + 0.95 + 64 + 1.1 + false + + <end_of_turn> + + + + + Gemma-4-12B-it-Q4_K_M-C32k + X:/Modelle/gemma-4-12B-it-Q4_K_M.gguf + 32768 + 8192 + 1.0 + 8 + 4 + true + 0.95 + 64 + 1.1 + false + + <end_of_turn> + + + + + Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q4_K_M-C32k + X:/Modelle/Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf + 32768 + 8192 + 1.0 + 8 + 4 + true + 0.95 + 64 + 1.1 + false + + <end_of_turn> + + + + + Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q6_K_P-C32k + X:/Modelle/Gemma-4-E4B-Uncensored-HauhauCS-Aggressive-Q6_K_P.gguf + 32768 + 8192 + 1.0 + 8 + 4 + true + 0.95 + 64 + 1.1 + false + + <end_of_turn> + + + + + Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL-C64k + X:/Modelle/Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL.gguf + + 16384 + 1536 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + ${ai.cachePrompt} + + + + Qwen2.5-Coder-14B-Instruct-Q4_K_M-C32k + X:/Modelle/Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf + 32768 + 2048 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + + + + Qwen3-4B-Instruct-2507-Q5_K_M-C32k + X:/Modelle/Qwen3-4B-Instruct-2507-Q5_K_M.gguf + 32768 + 2048 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + + + + Granite-4.0-H-Tiny-Q4_K_M-C128k + X:/Modelle/ibm-granite_granite-4.0-h-tiny-Q4_K_M.gguf + 131072 + 2048 + 0.2 + 8 + 4 + true + 1.1 + + + + Granite-4.0-H-Small-Q4_K_M-C128k + X:/Modelle/ibm-granite_granite-4.0-h-small-Q4_K_M.gguf + 131072 + 2048 + 0.2 + 8 + 4 + true + 1.1 + + + + EXP-Qwen3.5-4B + X:/Modelle/Qwen3.5-4B-Q4_K_M.gguf + 16384 + 1536 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + false + + + EXP-Qwen2.5-Coder-7B + X:/Modelle/qwen2.5-coder-7b-instruct-q5_k_m.gguf + 16384 + 1536 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + + + + gpt-oss-20B-c16k + X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf + 16384 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 2048 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-c48k + X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf + 49152 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 4096 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-c96k + X:/Modelle/gpt-oss-20b-UD-Q4_K_XL.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + + + gpt-oss-20B-mxfp4 + X:/Modelle/gpt-oss-20b-mxfp4.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-Q4_K_M + X:/Modelle/gpt-oss-20b-Q4_K_M.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-Q5_K_M + X:/Modelle/gpt-oss-20b-Q5_K_M.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-Q8_0 + X:/Modelle/gpt-oss-20b-Q8_0.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + gpt-oss-20B-F16 + X:/Modelle/gpt-oss-20b-F16.gguf + 98304 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 6144 + 0.7 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + low + + + + + granite-4.0-h-tiny-bigwindow + X:/Modelle/granite-4.0-h-tiny-Q4_K_M.gguf + 393216 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 4096 + 0.2 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + + + + + + granite-4.0-h-1b-fastchunk + X:/Modelle/granite-4.0-h-1b-Q4_K_M.gguf + 16384 + ${ai.gpuLayers} + ${ai.mainGpu} + ${ai.devices} + 1536 + 0.2 + 8 + 3 + true + 1.0 + 0 + 0.05 + 1.0 + + + + + EXP-Qwen3-Coder-30B + X:/Modelle/Qwen3-Coder-30B-A3B-Instruct-UD-Q4_K_XL.gguf + 16384 + 1536 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + + + + EXP-DeepSeek-Coder-V2-Lite + X:/Modelle/DeepSeek-Coder-V2-Lite-Instruct-Q4_K_M.gguf + 16384 + 1536 + 0.3 + 8 + 4 + true + 0.9 + 40 + 1.05 + + + EXP-Seed-Coder-8B + X:/Modelle/Seed-Coder-8B-Instruct-Q4_K_M.gguf + 16384 + 1536 + 0.3 + 8 + 4 + true + 0.9 + 40 + 1.05 + + + EXP-Granite-4.0-H-Tiny + X:/Modelle/granite-4.0-h-tiny-Q4_K_M.gguf + 16384 + 1536 + 0.0 + 8 + 4 + true + 1.0 + 0 + 1.0 + + + + EXP-Granite-4.0-H-Small + X:/Modelle/granite-4.0-h-small-UD-Q4_K_XL.gguf + 16384 + 1536 + 0.0 + 8 + 4 + true + 1.0 + 0 + 1.0 + + + EXP-Qwen3-4B-Instruct-2507 + X:/Modelle/Qwen3-4B-Instruct-2507-Q4_K_M.gguf + 16384 + 1536 + 0.7 + 8 + 4 + true + 0.8 + 20 + 1.05 + + + + + + + file-body-java + + + + file-body-sql + + + + file-body-fallback + + + + package-body + + + + project-body + + + + + file-body-java-v2 + + + + package-body-v2 + + + + + + + + + + ai-generate + generate-resources + + generate + + + + + .java + .sql + + + + + java-facts + + (?m)^\s*(?:public\s+|final\s+|abstract\s+|sealed\s+|non-sealed\s+|static\s+)*(?:class|interface|enum|record)\s+\w + \bpublic\b + \b(?:TODO|FIXME)\b + @Override\b + + (?m)^[ \t]*(?:(?:public|private|protected|static|final|abstract|default|synchronized|native|strictfp)[ \t]+)*(?:<[^>]+>[ \t]*)?(?!(?:if|for|while|switch|catch|return|new|else|do|try|synchronized|assert|throw)\b)[A-Za-z_$][\w$.]*(?:<[^;{}=]*>)?(?:\[\])*[ \t]+([A-Za-z_$]\w*)[ \t]*\([^;{]*\)(?:[ \t]*throws[ \t][\w$., \t]+)?[ \t]*[{;] + (?m)^[ \t]*(?:(?:public|private|protected)[ \t]+)?([A-Z][A-Za-z0-9_$]*)[ \t]*\([^;{]*\)(?:[ \t]*throws[ \t][\w$., \t]+)?[ \t]*\{ + (?m)^[ \t]*(?:(?:public|private|protected|static|transient|volatile|final)[ \t]+)*(?:public|private|protected|static|transient|volatile)[ \t]+(?:(?:public|private|protected|static|transient|volatile|final)[ \t]+)*(?!class\b|interface\b|enum\b|record\b|void\b|new\b)[A-Za-z_$][\w$.]*(?:<[^;{}=]*>)?(?:\[\])*[ \t]+([A-Za-z_$]\w*(?:[ \t]*,[ \t]*[A-Za-z_$]\w*)*)[ \t]*(?:=[^;]*)?; + + + + sql-facts + + (?im)^\s*INSERT\s+INTO\b + (?im)^\s*CREATE\s+TABLE\b + (?im)^\s*CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\b + (?im)^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\b + (?im)^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\b + + + + + + + huge-java + file-body-java + granite-4.0-h-1b-fastchunk + 200 + + mapReduce + 6 + java-facts + + + + + .java + + + 1000000 + + + + + + + huge-sql + file-body-sql + granite-4.0-h-1b-fastchunk + 200 + mapReduce + 6 + + sql-facts + + + + + .sql + + + 1000000 + + + + + + + big-window-java + file-body-java + granite-4.0-h-tiny-bigwindow + 100 + + + + + + .java + + + 275000 + + + + + + + big-window-sql + file-body-sql + granite-4.0-h-tiny-bigwindow + 100 + + + + + .sql + + + 275000 + + + + + + + java + file-body-java + ${ai.model} + + java-facts + + .java + + + + sql + file-body-sql + ${ai.model} + sql-facts + + .sql + + + + + fallback + file-body-fallback + ${ai.model} + true + + + + + + + ai-aggregate-packages + process-resources + + aggregate-packages + + + + + package-body + ${ai.model} + + + + + + + + ai-aggregate-project + prepare-package + + aggregate-project + + + + + project-body + ${ai.model} + + + + + + + + + + + + vmlens + + + + com.vmlens + vmlens-maven-plugin + + + + **/VmlensInterleavingSmokeTest.java + + + + + vmlens-test + + test + + + + + + + + + jcstress + + + + org.codehaus.mojo + exec-maven-plugin + + + jcstress + test + exec + + ${java.home}/bin/java + test + + -classpath + + org.openjdk.jcstress.Main + -v + -m + default + + + + + + + + + + diff --git a/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml b/srcmorph-maven-plugin/spotbugs-exclude.xml similarity index 90% rename from llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml rename to srcmorph-maven-plugin/spotbugs-exclude.xml index 3c9c221..d000788 100644 --- a/llamacpp-ai-index-maven-plugin/spotbugs-exclude.xml +++ b/srcmorph-maven-plugin/spotbugs-exclude.xml @@ -37,7 +37,7 @@ SPDX-License-Identifier: Apache-2.0 static would require a generator change we don't own. --> - + @@ -81,10 +81,10 @@ SPDX-License-Identifier: Apache-2.0 --> - - - - + + + + @@ -114,11 +114,11 @@ SPDX-License-Identifier: Apache-2.0 --> - - - - - + + + + + diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java b/srcmorph-maven-plugin/src/main/java/module-info.java similarity index 90% rename from llamacpp-ai-index-maven-plugin/src/main/java/module-info.java rename to srcmorph-maven-plugin/src/main/java/module-info.java index a18381c..94f795b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/module-info.java +++ b/srcmorph-maven-plugin/src/main/java/module-info.java @@ -3,13 +3,13 @@ // SPDX-License-Identifier: Apache-2.0 /** - * JPMS module descriptor for the llamacpp-ai-index Maven plugin. + * JPMS module descriptor for the srcmorph Maven plugin. * - *

      The module exports the single public package {@code net.ladenthin.maven.llamacpp.aiindex.mojo} + *

      The module exports the single public package {@code net.ladenthin.maven.srcmorph.mojo} * that holds the Mojo entry points. The configuration POJOs, document codec, indexers, prompt * support, and AI generation provider abstraction now live in the {@code net.ladenthin.srcmorph} * module (the extracted core library), which this module {@code requires}. The auto-generated - * {@code net.ladenthin.llamacpp_ai_index_maven_plugin.HelpMojo} package is deliberately + * {@code net.ladenthin.srcmorph_maven_plugin.HelpMojo} package is deliberately * not exported: Maven loads the plugin via its own classpath classloader and * never consults the module descriptor for Mojo discovery, so the {@code HelpMojo} class * remains reachable as an ordinary classpath type.

      @@ -37,7 +37,7 @@ * the JAR root.

      */ @org.jspecify.annotations.NullMarked -module net.ladenthin.maven.llamacpp.aiindex { +module net.ladenthin.maven.srcmorph { requires static org.jspecify; // Lombok is `provided` scope: only used at compile time to generate equals/hashCode/toString. @@ -48,5 +48,5 @@ // The extracted core library: config/document/indexer/prompt/provider/support now live there. requires net.ladenthin.srcmorph; - exports net.ladenthin.maven.llamacpp.aiindex.mojo; + exports net.ladenthin.maven.srcmorph.mojo; } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AbstractAiIndexMojo.java similarity index 93% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AbstractAiIndexMojo.java index 65c799b..d0a86db 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AbstractAiIndexMojo.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AbstractAiIndexMojo.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import java.io.File; import java.util.List; @@ -50,7 +50,7 @@ protected AbstractAiIndexMojo() { * Directory into which all {@code .ai.md} files are written. * Defaults to {@code ${project.basedir}/src/site/ai}. */ - @Parameter(property = "aiIndex.outputDirectory", defaultValue = "${project.basedir}/src/site/ai") + @Parameter(property = "srcmorph.outputDirectory", defaultValue = "${project.basedir}/src/site/ai") protected File outputDirectory; /** @@ -58,21 +58,21 @@ protected AbstractAiIndexMojo() { * aggregate-packages, aggregate-project) skips and returns immediately. To toggle a single * phase instead, use that goal's own {@code skip} flag (see {@link #isPhaseSkipped()}). */ - @Parameter(property = "aiIndex.skip", defaultValue = "false") + @Parameter(property = "srcmorph.skip", defaultValue = "false") protected boolean skip; /** * When {@code true}, regenerates AI fields even when they already have a value. * When {@code false}, only missing or changed entries are processed. */ - @Parameter(property = "aiIndex.force", defaultValue = "false") + @Parameter(property = "srcmorph.force", defaultValue = "false") protected boolean force; /** * Source subdirectory paths (relative to {@code basedir}) to restrict processing. * When empty, all discovered source roots are used. */ - @Parameter(property = "aiIndex.subtrees") + @Parameter(property = "srcmorph.subtrees") protected List subtrees; /** @@ -81,7 +81,7 @@ protected AbstractAiIndexMojo() { * * @see net.ladenthin.srcmorph.provider.AiGenerationProviderFactory */ - @Parameter(property = "aiIndex.generationProvider", defaultValue = "mock") + @Parameter(property = "srcmorph.generationProvider", defaultValue = "mock") protected String generationProvider; /** Prompt template definitions referenced by field generation configurations. */ @@ -107,19 +107,19 @@ protected AbstractAiIndexMojo() { * Optional native library path passed to the llama.cpp JNI provider. * Leave unset to use the bundled native library. */ - @Parameter(property = "aiIndex.llama.libraryPath") + @Parameter(property = "srcmorph.llama.libraryPath") protected String llamaLibraryPath; /** Path to the GGUF model file used by the llama.cpp JNI provider. */ - @Parameter(property = "aiIndex.llama.modelPath") + @Parameter(property = "srcmorph.llama.modelPath") protected String llamaModelPath; /** Maximum number of output tokens the model may generate per request. */ - @Parameter(property = "aiIndex.llama.maxOutputTokens", defaultValue = "128") + @Parameter(property = "srcmorph.llama.maxOutputTokens", defaultValue = "128") protected int llamaMaxOutputTokens; /** Sampling temperature for the llama.cpp model (lower = more deterministic). */ - @Parameter(property = "aiIndex.llama.temperature", defaultValue = "0.15") + @Parameter(property = "srcmorph.llama.temperature", defaultValue = "0.15") protected float llamaTemperature; // ------------------------------------------------------------------------- diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregatePackagesMojo.java similarity index 89% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregatePackagesMojo.java index 50f0d75..380aa6b 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregatePackagesMojo.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregatePackagesMojo.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import java.io.IOException; import java.nio.file.Path; @@ -14,7 +14,7 @@ import org.apache.maven.plugins.annotations.Parameter; /** - * Maven goal {@code ai-index:aggregate-packages}: aggregates per-package + * Maven goal {@code srcmorph:aggregate-packages}: aggregates per-package * {@code .ai.md} index files and fills in their AI-generated summary and keyword fields. * *

      Thin wrapper: builds a {@link SrcMorphConfiguration} from its {@code @Parameter} fields and @@ -37,21 +37,21 @@ public AggregatePackagesMojo() { * Phase switch for the package phase (the {@code aggregate-packages} goal): when * {@code true}, only this phase is skipped. The global {@link #skip} still skips every phase. */ - @Parameter(property = "aiIndex.package.skip", defaultValue = "false") + @Parameter(property = "srcmorph.package.skip", defaultValue = "false") private boolean skipPackage; @Parameter(defaultValue = "${project.version}", readonly = true) private String pluginVersion; - @Parameter(property = "aiIndex.aiVersion", defaultValue = "0.0.0") + @Parameter(property = "srcmorph.aiVersion", defaultValue = "0.0.0") private String aiVersion; /** llama.cpp context window size; smaller default suits the fast aggregate pass. */ - @Parameter(property = "aiIndex.llama.contextSize", defaultValue = "2048") + @Parameter(property = "srcmorph.llama.contextSize", defaultValue = "2048") private int llamaContextSize; /** CPU threads for llama.cpp inference during package aggregation. */ - @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") + @Parameter(property = "srcmorph.llama.threads", defaultValue = "2") private int llamaThreads; @Override diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregateProjectMojo.java similarity index 92% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregateProjectMojo.java index d825212..2aaf1eb 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/AggregateProjectMojo.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/AggregateProjectMojo.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import java.io.IOException; import java.nio.file.Path; @@ -14,7 +14,7 @@ import org.apache.maven.plugins.annotations.Parameter; /** - * Maven goal {@code ai-index:aggregate-project}: aggregates every per-package + * Maven goal {@code srcmorph:aggregate-project}: aggregates every per-package * {@code package.ai.md} into a single project-level {@code project.ai.md} — the top of the * three-level index. * @@ -56,7 +56,7 @@ public AggregateProjectMojo() { * Phase switch for the project phase (the {@code aggregate-project} goal): when * {@code true}, only this phase is skipped. The global {@link #skip} still skips every phase. */ - @Parameter(property = "aiIndex.project.skip", defaultValue = "false") + @Parameter(property = "srcmorph.project.skip", defaultValue = "false") private boolean skipProject; /** Plugin version recorded in the project index header. */ @@ -64,7 +64,7 @@ public AggregateProjectMojo() { private String pluginVersion; /** AI summarisation logic version recorded in the project index header. */ - @Parameter(property = "aiIndex.aiVersion", defaultValue = "0.0.0") + @Parameter(property = "srcmorph.aiVersion", defaultValue = "0.0.0") private String aiVersion; /** Maven project name recorded as the project index title. */ @@ -75,11 +75,11 @@ public AggregateProjectMojo() { * llama.cpp context window size used only as the fallback when no {@code } is * configured; with an overview field generation present, the size comes from its AI definition. */ - @Parameter(property = "aiIndex.llama.contextSize", defaultValue = "4096") + @Parameter(property = "srcmorph.llama.contextSize", defaultValue = "4096") private int llamaContextSize; /** CPU threads for llama.cpp inference, used only on the no-field-generation fallback path. */ - @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") + @Parameter(property = "srcmorph.llama.threads", defaultValue = "2") private int llamaThreads; @Override diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/CalibrateMojo.java similarity index 91% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/CalibrateMojo.java index 8f29ac9..eedd4b6 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/CalibrateMojo.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/CalibrateMojo.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import net.ladenthin.srcmorph.engine.CalibrateEngine; import net.ladenthin.srcmorph.engine.CalibrationReport; @@ -11,7 +11,7 @@ import org.apache.maven.plugins.annotations.Parameter; /** - * Maven goal {@code ai-index:calibrate}: a preflight + timing pass that sits between {@code planOnly} (no + * Maven goal {@code srcmorph:calibrate}: a preflight + timing pass that sits between {@code planOnly} (no * model loaded) and a real {@code generate} run. * *

      For each distinct model a run would load (the {@code aiDefinitionKey}s referenced by @@ -35,11 +35,11 @@ public CalibrateMojo() { } /** llama.cpp context window size (unused by calibrate, which uses each model's own config). */ - @Parameter(property = "aiIndex.llama.contextSize", defaultValue = "2048") + @Parameter(property = "srcmorph.llama.contextSize", defaultValue = "2048") private int llamaContextSize; /** CPU threads (unused by calibrate, which uses each model's own config). */ - @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") + @Parameter(property = "srcmorph.llama.threads", defaultValue = "2") private int llamaThreads; @Override @@ -55,7 +55,7 @@ protected int getLlamaThreads() { @Override protected boolean isPhaseSkipped() { // calibrate is a manual diagnostic goal, not one of the file/package/project phases; only the - // global aiIndex.skip disables it. + // global srcmorph.skip disables it. return false; } diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/GenerateMojo.java similarity index 89% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/GenerateMojo.java index eee736d..6972e9d 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/GenerateMojo.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/GenerateMojo.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import java.io.IOException; import java.nio.file.Path; @@ -16,7 +16,7 @@ import org.apache.maven.plugins.annotations.Parameter; /** - * Maven goal {@code ai-index:generate}: indexes source files and fills in their + * Maven goal {@code srcmorph:generate}: indexes source files and fills in their * AI-generated summary and keyword fields. * *

      Thin wrapper: builds a {@link SrcMorphConfiguration} from its {@code @Parameter} fields and @@ -39,16 +39,16 @@ public GenerateMojo() { * Phase switch for the file phase (the {@code generate} goal): when {@code true}, * only this phase is skipped. The global {@link #skip} still skips every phase. */ - @Parameter(property = "aiIndex.file.skip", defaultValue = "false") + @Parameter(property = "srcmorph.file.skip", defaultValue = "false") private boolean skipFile; @Parameter(defaultValue = "${project.version}", readonly = true) private String pluginVersion; - @Parameter(property = "aiIndex.aiVersion", defaultValue = "0.0.0") + @Parameter(property = "srcmorph.aiVersion", defaultValue = "0.0.0") private String aiVersion; - @Parameter(property = "aiIndex.fileExtensions") + @Parameter(property = "srcmorph.fileExtensions") private List fileExtensions; /** @@ -59,7 +59,7 @@ public GenerateMojo() { * * @see net.ladenthin.srcmorph.support.AiSourceExcludeFilter */ - @Parameter(property = "aiIndex.excludes") + @Parameter(property = "srcmorph.excludes") private List excludes; /** @@ -80,7 +80,7 @@ public GenerateMojo() { * every file exactly once. Use non-overlapping bands ({@code band2.min == band1.max}); make the last * band unbounded ({@code maxFileSizeBytes=0}) so files above all bands still get indexed. */ - @Parameter(property = "aiIndex.file.minSizeBytes", defaultValue = "0") + @Parameter(property = "srcmorph.file.minSizeBytes", defaultValue = "0") private long minFileSizeBytes; /** @@ -88,15 +88,15 @@ public GenerateMojo() { * {@code 0} (default) disables the upper bound (unlimited). See {@link #minFileSizeBytes} for the * size-tiering pattern. */ - @Parameter(property = "aiIndex.file.maxSizeBytes", defaultValue = "0") + @Parameter(property = "srcmorph.file.maxSizeBytes", defaultValue = "0") private long maxFileSizeBytes; /** llama.cpp context window size; smaller default suits the fast generate pass. */ - @Parameter(property = "aiIndex.llama.contextSize", defaultValue = "2048") + @Parameter(property = "srcmorph.llama.contextSize", defaultValue = "2048") private int llamaContextSize; /** CPU threads for llama.cpp inference during the generate pass. */ - @Parameter(property = "aiIndex.llama.threads", defaultValue = "2") + @Parameter(property = "srcmorph.llama.threads", defaultValue = "2") private int llamaThreads; /** @@ -104,7 +104,7 @@ public GenerateMojo() { * prompt) and then stop — no model is loaded and nothing is generated. Useful to verify routing * before a long run. */ - @Parameter(property = "aiIndex.planOnly", defaultValue = "false") + @Parameter(property = "srcmorph.planOnly", defaultValue = "false") private boolean planOnly; @Override diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/package-info.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/package-info.java similarity index 54% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/package-info.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/package-info.java index 8aa9f7b..64af843 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/mojo/package-info.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/mojo/package-info.java @@ -3,6 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 /** - * Maven goal entry points (the {@code ai-index} mojos). + * Maven goal entry points (the {@code srcmorph} mojos). */ -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; diff --git a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/package-info.java b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/package-info.java similarity index 96% rename from llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/package-info.java rename to srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/package-info.java index eacf45f..0305bfa 100644 --- a/llamacpp-ai-index-maven-plugin/src/main/java/net/ladenthin/maven/llamacpp/aiindex/package-info.java +++ b/srcmorph-maven-plugin/src/main/java/net/ladenthin/maven/srcmorph/package-info.java @@ -20,4 +20,4 @@ * unsuppressible {@code unknown enum constant ElementType.MODULE} * classfile-read warning that javac emits otherwise. */ -package net.ladenthin.maven.llamacpp.aiindex; +package net.ladenthin.maven.srcmorph; diff --git a/llamacpp-ai-index-maven-plugin/src/site/ai/empty.md b/srcmorph-maven-plugin/src/site/ai/empty.md similarity index 100% rename from llamacpp-ai-index-maven-plugin/src/site/ai/empty.md rename to srcmorph-maven-plugin/src/site/ai/empty.md diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/PluginArchitectureTest.java similarity index 87% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java rename to srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/PluginArchitectureTest.java index 19a967e..aa1ba44 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/PluginArchitectureTest.java +++ b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/PluginArchitectureTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex; +package net.ladenthin.maven.srcmorph; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -16,14 +16,14 @@ import org.slf4j.Logger; /** - * Architecture rules for the llamacpp-ai-index Maven plugin module itself — the {@code mojo} + * Architecture rules for the srcmorph Maven plugin module itself — the {@code mojo} * package (Mojo entry points) — after the {@code config}/{@code document}/{@code indexer}/ * {@code prompt}/{@code provider}/{@code support} packages were extracted into the sibling * {@code srcmorph} core library. The rules that governed those layers (the internal layered * architecture, {@code nonMojoIsMavenFree}, {@code jniConfinedToProvider}, etc.) now live in that * module's own {@code CoreArchitectureTest}; only mojo-relevant rules remain here. */ -@AnalyzeClasses(packages = "net.ladenthin.maven.llamacpp.aiindex", importOptions = ImportOption.DoNotIncludeTests.class) +@AnalyzeClasses(packages = "net.ladenthin.maven.srcmorph", importOptions = ImportOption.DoNotIncludeTests.class) public class PluginArchitectureTest { /** @@ -32,7 +32,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noJavaUtilLogging = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .dependOnClassesThat() .resideInAPackage("java.util.logging.."); @@ -43,7 +43,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noTestFrameworksInProduction = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .dependOnClassesThat() .resideInAnyPackage("org.junit..", "net.jqwik..", "com.tngtech.archunit.."); @@ -56,7 +56,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noSystemOutOrErrInProduction = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .accessField(System.class, "out") .orShould() @@ -69,7 +69,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noInternalJdkImports = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .dependOnClassesThat() .resideInAnyPackage("sun..", "com.sun..", "jdk.internal.."); @@ -78,7 +78,7 @@ public class PluginArchitectureTest { * No package cycles between the layered sub-packages. */ @ArchTest - static final ArchRule noPackageCycles = slices().matching("net.ladenthin.maven.llamacpp.aiindex.(*)..") + static final ArchRule noPackageCycles = slices().matching("net.ladenthin.maven.srcmorph.(*)..") .should() .beFreeOfCycles() .allowEmptyShould(true); @@ -93,7 +93,7 @@ public class PluginArchitectureTest { static final ArchRule layeredArchitecture = layeredArchitecture() .consideringOnlyDependenciesInLayers() .layer("Mojo") - .definedBy("net.ladenthin.maven.llamacpp.aiindex.mojo..") + .definedBy("net.ladenthin.maven.srcmorph.mojo..") .whereLayer("Mojo") .mayNotBeAccessedByAnyLayer(); @@ -121,7 +121,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noSystemExit = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .callMethod(System.class, "exit", int.class) .allowEmptyShould(true); @@ -134,7 +134,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noNewRandom = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .callConstructor(Random.class) .orShould() @@ -149,7 +149,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule noThreadSleep = noClasses() .that() - .resideInAPackage("net.ladenthin.maven.llamacpp.aiindex..") + .resideInAPackage("net.ladenthin.maven.srcmorph..") .should() .callMethod(Thread.class, "sleep", long.class) .orShould() @@ -165,7 +165,7 @@ public class PluginArchitectureTest { @ArchTest static final ArchRule mavenMojoAnnotationsConfinedToMojo = noClasses() .that() - .resideOutsideOfPackage("net.ladenthin.maven.llamacpp.aiindex.mojo..") + .resideOutsideOfPackage("net.ladenthin.maven.srcmorph.mojo..") .should() .dependOnClassesThat() .resideInAPackage("org.apache.maven.plugins.annotations..") diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/jcstress/AiGenerationKindRace.java similarity index 95% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java rename to srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/jcstress/AiGenerationKindRace.java index 3391515..4c0ff33 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/jcstress/AiGenerationKindRace.java +++ b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/jcstress/AiGenerationKindRace.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.jcstress; +package net.ladenthin.maven.srcmorph.jcstress; import net.ladenthin.srcmorph.config.AiGenerationKind; import org.openjdk.jcstress.annotations.Actor; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/mojo/MojoPhaseSkipTest.java b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/mojo/MojoPhaseSkipTest.java similarity index 98% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/mojo/MojoPhaseSkipTest.java rename to srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/mojo/MojoPhaseSkipTest.java index a7d35f1..d3a1c02 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/mojo/MojoPhaseSkipTest.java +++ b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/mojo/MojoPhaseSkipTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.mojo; +package net.ladenthin.maven.srcmorph.mojo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/vmlens/VmlensInterleavingSmokeTest.java b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/vmlens/VmlensInterleavingSmokeTest.java similarity index 97% rename from llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/vmlens/VmlensInterleavingSmokeTest.java rename to srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/vmlens/VmlensInterleavingSmokeTest.java index 22577ed..adcd02d 100644 --- a/llamacpp-ai-index-maven-plugin/src/test/java/net/ladenthin/maven/llamacpp/aiindex/vmlens/VmlensInterleavingSmokeTest.java +++ b/srcmorph-maven-plugin/src/test/java/net/ladenthin/maven/srcmorph/vmlens/VmlensInterleavingSmokeTest.java @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Bernard Ladenthin // // SPDX-License-Identifier: Apache-2.0 -package net.ladenthin.maven.llamacpp.aiindex.vmlens; +package net.ladenthin.maven.srcmorph.vmlens; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; diff --git a/srcmorph/README.md b/srcmorph/README.md index 3d8ff99..5cbe5a0 100644 --- a/srcmorph/README.md +++ b/srcmorph/README.md @@ -4,7 +4,7 @@ [srcmorph](../README.md) project: a prompt-driven source-tree transformer built on local llama.cpp (GGUF) models. It has **no dependency on the Maven Plugin API** (enforced by an ArchUnit rule — see `CoreArchitectureTest#coreIsMavenFree`), so it can be embedded in any JVM tool: a Maven plugin (see -[`../llamacpp-ai-index-maven-plugin/`](../llamacpp-ai-index-maven-plugin/README.md)), a standalone CLI +[`../srcmorph-maven-plugin/`](../srcmorph-maven-plugin/README.md)), a standalone CLI (see [`../srcmorph-cli/`](../srcmorph-cli/README.md)), a Gradle plugin, a test harness, or plain code. Package root: `net.ladenthin.srcmorph`. diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml index c4e78d7..9bb03f7 100644 --- a/srcmorph/pom.xml +++ b/srcmorph/pom.xml @@ -21,7 +21,7 @@ SPDX-License-Identifier: Apache-2.0 jar srcmorph - Core library extracted from llamacpp-ai-index-maven-plugin: routing/configuration model, the .ai.md document codec, the file/package/project indexers, prompt templating support, and the AI generation provider abstraction (incl. the llama.cpp JNI binding). Framework-free (no Maven plugin API); the llamacpp-ai-index-maven-plugin module supplies only the Mojo entry points on top of this library. + Core library extracted from llamacpp-ai-index-maven-plugin: routing/configuration model, the .ai.md document codec, the file/package/project indexers, prompt templating support, and the AI generation provider abstraction (incl. the llama.cpp JNI binding). Framework-free (no Maven plugin API); the srcmorph-maven-plugin module supplies only the Mojo entry points on top of this library. bernardladenthin @@ -131,7 +131,7 @@ SPDX-License-Identifier: Apache-2.0 @@ -405,7 +405,7 @@ SPDX-License-Identifier: Apache-2.0 false @@ -573,7 +573,7 @@ SPDX-License-Identifier: Apache-2.0 AggregateProjectEngine, CalibrateEngine. These are the orchestration classes that replaced the mojo execute() bodies - they own AiGenerationProvider try-with-resources lifecycles, multi-step control flow, and logging, the same - shape the sibling llamacpp-ai-index-maven-plugin module's own pom.xml comment + shape the sibling srcmorph-maven-plugin module's own pom.xml comment says mojos are not PIT-gated for ("Mojos are typically integration-tested via Maven invoker/executor rather than unit-mutation-tested"). They are instead covered by ordinary JUnit tests in engine/*Test.java (planOnly/unmatched/ @@ -641,14 +641,14 @@ SPDX-License-Identifier: Apache-2.0 diff --git a/srcmorph/src/main/java/module-info.java b/srcmorph/src/main/java/module-info.java index 6849132..e66c833 100644 --- a/srcmorph/src/main/java/module-info.java +++ b/srcmorph/src/main/java/module-info.java @@ -12,8 +12,8 @@ * {@code .ai.md} document codec, the per-phase orchestration engines, the file/package/project * indexers, the prompt templating support, the AI generation provider abstraction (incl. the * llama.cpp JNI binding), and the shared stateless helpers. The Maven plugin module - * ({@code net.ladenthin.maven.llamacpp.aiindex}, still in its own reactor module) depends on - * this module and supplies only the {@code mojo} entry points, which now delegate to the + * ({@code net.ladenthin.maven.srcmorph}, in its own reactor module {@code srcmorph-maven-plugin}) + * depends on this module and supplies only the {@code mojo} entry points, which now delegate to the * {@code engine} package.

      * *

      JSpecify {@code @NullMarked} is declared at the module level here so that no source diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java index 156cb6d..b165602 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiCalibration.java @@ -6,7 +6,7 @@ import lombok.ToString; /** - * Per-model, per-machine timing calibration measured by the {@code ai-index:calibrate} goal and pasted + * Per-model, per-machine timing calibration measured by the {@code srcmorph:calibrate} goal and pasted * onto an {@code } via {@code }. It makes the plan's time estimate reflect the * actual hardware (GPU/CPU, quant, context) instead of the built-in reference-CPU coefficients. * diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java index e4d206c..8ace934 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/AiModelDefinition.java @@ -67,7 +67,7 @@ public AiModelDefinition() { private @Nullable List drySequenceBreakers; private @Nullable List stopStrings; - /** Optional per-machine timing calibration ({@code }), measured by {@code ai-index:calibrate}. */ + /** Optional per-machine timing calibration ({@code }), measured by {@code srcmorph:calibrate}. */ private @Nullable AiCalibration calibration; /** diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java index de76290..122586f 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/config/SrcMorphConfiguration.java @@ -11,14 +11,14 @@ /** * Root mutable JavaBean holding every parameter a {@code srcmorph} run needs, independent of how it is - * bound: today from Maven {@code @Parameter} fields (the {@code llamacpp-ai-index-maven-plugin} module's + * bound: today from Maven {@code @Parameter} fields (the {@code srcmorph-maven-plugin} module's * mojos each build one of these from their own annotated fields), tomorrow from a JSON/YAML config file * (a future CLI) or from plain Java code constructing one directly. * *

      Field names intentionally mirror today's Maven {@code @Parameter} field names (e.g. * {@link #outputDirectory}, {@link #fieldGenerations}, {@link #llamaContextSize}) so that a future * JSON/YAML config's keys read the same as the existing plugin XML — this class is the single shared - * config object bindable from every surface. See {@code net.ladenthin.maven.llamacpp.aiindex.mojo}'s + * config object bindable from every surface. See {@code net.ladenthin.maven.srcmorph.mojo}'s * mojo classes (in the sibling plugin module) for the exact provenance of every field.

      * *

      Deliberately excluded: the per-goal {@code skip}/{@code skipFile}/{@code skipPackage}/ diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java index 81044ec..b1ad648 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/engine/CalibrationReport.java @@ -16,7 +16,7 @@ * routed model, in calibration order. * *

      {@link #renderXml()} is the pure, paste-ready {@code } XML block renderer — the same - * text the {@code ai-index:calibrate} goal has always printed, extracted here so it is unit-testable + * text the {@code srcmorph:calibrate} goal has always printed, extracted here so it is unit-testable * without a Maven {@code Log} and reusable by any future caller (e.g. a CLI).

      */ @ToString diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java index f7727a7..b6b57c1 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/indexer/AiCalibrationRunner.java @@ -14,7 +14,7 @@ import net.ladenthin.srcmorph.provider.AiGenerationTimings; /** - * Measures one model's per-machine timing for the {@code ai-index:calibrate} goal: it triggers the load, + * Measures one model's per-machine timing for the {@code srcmorph:calibrate} goal: it triggers the load, * runs two representative generations (mid- and near-window sized), and reads the model's own measured * prefill/decode throughput. Lives in the indexer layer because it builds requests and drives the * provider; the mojo stays thin (orchestrate + format). diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java index 12e20b9..69a608b 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationProvider.java @@ -23,7 +23,7 @@ public interface AiGenerationProvider extends AutoCloseable { /** * Generates text and returns it together with the model's measured timing, for the - * {@code ai-index:calibrate} goal. The default implementation delegates to {@link #generate} and + * {@code srcmorph:calibrate} goal. The default implementation delegates to {@link #generate} and * reports no timings (rates {@code 0}); providers that expose timings override this. * * @param request the generation request diff --git a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java index c8dedb7..d33084d 100644 --- a/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java +++ b/srcmorph/src/main/java/net/ladenthin/srcmorph/provider/AiGenerationTimings.java @@ -9,7 +9,7 @@ /** * A generated text plus the model's measured timing for that generation, used by the - * {@code ai-index:calibrate} goal to derive per-machine throughput. Prefill = prompt processing; decode = + * {@code srcmorph:calibrate} goal to derive per-machine throughput. Prefill = prompt processing; decode = * answer generation. Rates of {@code 0} mean the provider did not report timings (e.g. the mock provider * or a binding that omits them). * From 87f4f7bc0e9303ed03e2349388b7ff87cb15126a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Jul 2026 20:12:53 +0000 Subject: [PATCH 8/9] Bump reactor version from 1.1.0-SNAPSHOT to 1.1.0 for release Flips in the parent pom and every child that inherits it (srcmorph, srcmorph-cli, srcmorph-maven-plugin) via `mvn versions:set -DnewVersion=1.1.0 -Dexcludes=net.ladenthin:llamacpp-ai-index-maven-plugin`, leaving the relocation stub pinned at its own independent 1.0.4. Also corrects now-stale 1.1.0-SNAPSHOT mentions in README.md, CLAUDE.md, and the example launcher scripts' illustrative jar-name comments. publish-release only fires on a v* tag via manual workflow_dispatch, but the poms must already carry the release version before that tag is cut. --- CLAUDE.md | 6 +++--- README.md | 2 +- examples/run_all.bat | 2 +- examples/run_all.sh | 2 +- examples/run_calibrate.bat | 2 +- examples/run_calibrate.sh | 2 +- examples/run_generate.bat | 2 +- examples/run_generate.sh | 2 +- examples/run_plan.bat | 2 +- examples/run_plan.sh | 2 +- llamacpp-ai-index-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- srcmorph-cli/pom.xml | 4 ++-- srcmorph-maven-plugin/pom.xml | 4 ++-- srcmorph/pom.xml | 4 ++-- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 00fbae9..9193d2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ Central remains a separate, later action by the user. - **Java:** target bytecode 1.8 (production code), Java 21 test sources, built with JDK 21 - **License:** Apache 2.0 - **Author:** Bernard Ladenthin (Copyright 2026) -- **Reactor version:** `1.1.0-SNAPSHOT` (single shared version across `srcmorph`, `srcmorph-cli`, +- **Reactor version:** `1.1.0` (single shared version across `srcmorph`, `srcmorph-cli`, and `srcmorph-maven-plugin`; the relocation stub below is version-pinned independently) --- @@ -38,7 +38,7 @@ Central remains a separate, later action by the user. ``` llamacpp-ai-index-maven-plugin/ (repo root; reactor parent) -├── pom.xml net.ladenthin:srcmorph-parent:1.1.0-SNAPSHOT (packaging=pom) +├── pom.xml net.ladenthin:srcmorph-parent:1.1.0 (packaging=pom) │ shared build plugins + dependencyManagement + release profile ├── srcmorph/ CORE LIBRARY net.ladenthin:srcmorph (Java 8, Maven-API-free) │ └── src/main/java/net/ladenthin/srcmorph/ @@ -261,7 +261,7 @@ mvn -pl srcmorph-maven-plugin srcmorph:generate -P srcmorph-selftest ```bash mvn -pl srcmorph-cli package -java -jar srcmorph-cli/target/srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar examples/config_All.json +java -jar srcmorph-cli/target/srcmorph-cli-1.1.0-jar-with-dependencies.jar examples/config_All.json ``` See `examples/` (repo root) for ready-to-run `config_*.json`/`.yaml` + paired `run_*.sh`/`.bat` diff --git a/README.md b/README.md index 6221bc2..9c41029 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Snapshots are published to the Central Snapshots repository on every push to `ma https://central.sonatype.com/repository/maven-snapshots/net/ladenthin/ ``` -Current development version: `1.1.0-SNAPSHOT`. +Current reactor version: `1.1.0`. ## A Note on History diff --git a/examples/run_all.bat b/examples/run_all.bat index dfdae77..1e1ea3a 100644 --- a/examples/run_all.bat +++ b/examples/run_all.bat @@ -8,7 +8,7 @@ rem AggregatePackages, AggregateProject). Uses the mock provider, so no GGUF mod rem point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. rem rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. setlocal cd /d "%~dp0" diff --git a/examples/run_all.sh b/examples/run_all.sh index ee00b8c..9078d3c 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -8,7 +8,7 @@ # point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. # # The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so the # glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. set -euo pipefail cd "$(dirname "$0")" diff --git a/examples/run_calibrate.bat b/examples/run_calibrate.bat index 37a019e..5b3db0a 100644 --- a/examples/run_calibrate.bat +++ b/examples/run_calibrate.bat @@ -9,7 +9,7 @@ rem this is a no-op smoke check; swap generationProvider to "llamacpp-jni" and s rem modelPath to calibrate an actual GGUF model on this machine. rem rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. setlocal cd /d "%~dp0" diff --git a/examples/run_calibrate.sh b/examples/run_calibrate.sh index 10ec18d..e0fd30c 100755 --- a/examples/run_calibrate.sh +++ b/examples/run_calibrate.sh @@ -9,7 +9,7 @@ # calibrate an actual GGUF model on this machine. # # The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so the # glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. set -euo pipefail cd "$(dirname "$0")" diff --git a/examples/run_generate.bat b/examples/run_generate.bat index 0dbb769..22c6a91 100644 --- a/examples/run_generate.bat +++ b/examples/run_generate.bat @@ -8,7 +8,7 @@ rem and fill in their AI-generated summary bodies). Uses the mock provider, so n rem required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. rem rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. setlocal cd /d "%~dp0" diff --git a/examples/run_generate.sh b/examples/run_generate.sh index 7a884a2..3a251ac 100755 --- a/examples/run_generate.sh +++ b/examples/run_generate.sh @@ -8,7 +8,7 @@ # required - point generationProvider at "llamacpp-jni" and set a real modelPath to run a model. # # The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so the # glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. set -euo pipefail cd "$(dirname "$0")" diff --git a/examples/run_plan.bat b/examples/run_plan.bat index f66bc2f..a720abf 100644 --- a/examples/run_plan.bat +++ b/examples/run_plan.bat @@ -8,7 +8,7 @@ rem GenerateFileIndex phase with planOnly forced true by the Plan command) - no rem and nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). rem rem The fat jar's file name is version-qualified (e.g. -rem srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so +rem srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so rem the loop below picks whichever one was last built by "mvn package" in ..\srcmorph-cli. setlocal cd /d "%~dp0" diff --git a/examples/run_plan.sh b/examples/run_plan.sh index cbda0ac..42add78 100755 --- a/examples/run_plan.sh +++ b/examples/run_plan.sh @@ -8,7 +8,7 @@ # nothing is written. Safe to run with no GGUF model on disk (generationProvider is "mock"). # # The fat jar's file name is version-qualified (e.g. -# srcmorph-cli-1.1.0-SNAPSHOT-jar-with-dependencies.jar) and changes on every version bump, so the +# srcmorph-cli-1.1.0-jar-with-dependencies.jar) and changes on every version bump, so the # glob below picks whichever one was last built by `mvn package` in ../srcmorph-cli. set -euo pipefail cd "$(dirname "$0")" diff --git a/llamacpp-ai-index-maven-plugin/pom.xml b/llamacpp-ai-index-maven-plugin/pom.xml index e9d4bc3..6610829 100644 --- a/llamacpp-ai-index-maven-plugin/pom.xml +++ b/llamacpp-ai-index-maven-plugin/pom.xml @@ -14,7 +14,7 @@ SPDX-License-Identifier: Apache-2.0 looks. Its version is pinned independently of the reactor's own (currently - 1.1.0-SNAPSHOT in ../pom.xml) and must NOT be bumped by a reactor-wide version change: a + 1.1.0 in ../pom.xml) and must NOT be bumped by a reactor-wide version change: a `mvn versions:set -DnewVersion=X` run from the repo root walks every module reachable via the root list, which includes this one. Exclude it explicitly, e.g.: diff --git a/pom.xml b/pom.xml index 0d8e986..14fb334 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ SPDX-License-Identifier: Apache-2.0 net.ladenthin srcmorph-parent - 1.1.0-SNAPSHOT + 1.1.0 pom ${project.groupId}:${project.artifactId} diff --git a/srcmorph-cli/pom.xml b/srcmorph-cli/pom.xml index f504dfc..26c98f6 100644 --- a/srcmorph-cli/pom.xml +++ b/srcmorph-cli/pom.xml @@ -13,7 +13,7 @@ SPDX-License-Identifier: Apache-2.0 net.ladenthin srcmorph-parent - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml @@ -47,7 +47,7 @@ SPDX-License-Identifier: Apache-2.0 3.8.0 2.94.0 - ${git.commit.time} + 2026-07-15T20:07:58Z - ${git.commit.time} + 2026-07-15T20:07:58Z diff --git a/srcmorph/pom.xml b/srcmorph/pom.xml index 9bb03f7..e84218c 100644 --- a/srcmorph/pom.xml +++ b/srcmorph/pom.xml @@ -13,7 +13,7 @@ SPDX-License-Identifier: Apache-2.0 net.ladenthin srcmorph-parent - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml @@ -61,7 +61,7 @@ SPDX-License-Identifier: Apache-2.0 3.8.0 2.94.0 - ${git.commit.time} + 2026-07-15T20:07:58Z