net.ladenthin:srcmorph-cli is a standalone command-line front end for
srcmorph: a single java -jar invocation driven by one JSON or YAML configuration
file, with no Maven project or build required. It wraps the
srcmorph core library's engines exactly the way the
Maven plugin does, following the
BitcoinAddressFinder cli.Main pattern.
java -jar srcmorph-cli-<version>-jar-with-dependencies.jar <path-to-config.json-or-.yaml>The fat jar (srcmorph-cli-<version>-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/ 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.
The root object is net.ladenthin.srcmorph.cli.configuration.CConfiguration — a plain, public-field
JavaBean (Jackson binds it directly, no getters/setters needed):
public class CConfiguration {
public CCommand command = CCommand.Plan;
public SrcMorphConfiguration srcMorph = new SrcMorphConfiguration();
}command— one of the sixCCommandvalues below. Defaults toPlan— the safe default: no command means no model load and nothing written.srcMorph— the samenet.ladenthin.srcmorph.config.SrcMorphConfigurationobject the Maven plugin's mojos build from their own@Parameterfields. Every key undersrcMorphin your JSON/YAML file reads identically to the matching Maven<configuration>XML element —baseDirectory,outputDirectory,subtrees,excludes,fileExtensions,generationProvider,promptDefinitions,aiDefinitions,fieldGenerations,factDefinitions, thellama*fallback parameters,force,planOnly,projectName. If you already know the plugin's XML shape, you already know this shape — see../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.
| 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 <calibration> XML block per model to standard output (CalibrateEngine). |
Unlike the Maven plugin, where each goal execution gets its own <fieldGenerations> 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:
GenerateFileIndexroutes: each file is matched to exactly one rule bycondition+priority(the explicitfallbackcatches anything unmatched).AggregatePackagesdoes not route by condition — it applies every entry infieldGenerationsto every package, in list order, and the last entry's output is kept.AggregateProject's optional AI overview paragraph uses onlyfieldGenerations[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), 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 for the engine-level
detail.
MainTest— unit tests for the extension-dispatched loader and the JSON/YAML mappers.configuration.ConfigBindingTest— round-trips a private ad-hocsrc/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 withSrcMorphConfiguration's field set.CliEndToEndTest— drivesMain#run()directly (no forked process) against themockprovider for theAllandPlancommands, asserting the expected.ai.mdtree lands on disk (or, forPlan, 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 outsideconfiguration, noSystem.exit, no dependency on the Maven Plugin API.
mvn -pl srcmorph-cli -am packageproduces srcmorph-cli/target/srcmorph-cli-<version>.jar (plain jar) and
srcmorph-cli/target/srcmorph-cli-<version>-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).