AiRuntimeBuilder.java

1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright 2026 Egothor
3
// Copyright 2026 Accenture
4
package org.egothor.methodatlas.command;
5
6
import java.io.IOException;
7
import java.nio.file.Path;
8
import java.util.Locale;
9
10
import org.egothor.methodatlas.AiResultCache;
11
import org.egothor.methodatlas.ai.AiOptions;
12
import org.egothor.methodatlas.ai.AiSuggestionEngine;
13
import org.egothor.methodatlas.ai.AiSuggestionEngineImpl;
14
import org.egothor.methodatlas.ai.AiSuggestionException;
15
import org.egothor.methodatlas.ai.TaxonomyVersion;
16
17
/**
18
 * Constructs the per-run AI infrastructure: the suggestion engine, the result
19
 * cache, and the human-readable taxonomy descriptor.
20
 *
21
 * <p>
22
 * The builder is stateless and may be shared across commands. It returns
23
 * {@code null} from {@link #buildEngine} when AI is disabled — the
24
 * orchestration layer relies on this sentinel rather than building a no-op
25
 * engine, because most commands change behaviour entirely when AI is off
26
 * (no extra columns, no confidence threshold, no taxonomy in metadata).
27
 * </p>
28
 *
29
 * <h2>Failure handling</h2>
30
 *
31
 * <p>
32
 * Engine construction can fail when an external provider cannot be reached
33
 * or the configured model is unknown. Such failures are wrapped in an
34
 * {@link IllegalStateException}, because they indicate a misconfigured run
35
 * rather than a recoverable I/O fault: the user must change configuration
36
 * before retrying.
37
 * </p>
38
 *
39
 * <p>
40
 * Cache loading reads an arbitrary CSV produced by a previous run; a
41
 * malformed file is reported as {@link IllegalArgumentException} so that
42
 * the offending path appears in the user-facing CLI error.
43
 * </p>
44
 *
45
 * @see AiRuntime
46
 * @see AiSuggestionEngine
47
 * @see AiResultCache
48
 * @since 1.0.0
49
 */
50
public final class AiRuntimeBuilder {
51
52
    /**
53
     * Creates a new builder. Stateless; safe to share across commands.
54
     */
55
    public AiRuntimeBuilder() {
56
        // Intentionally empty; AiRuntimeBuilder is stateless.
57
    }
58
59
    /**
60
     * Creates the AI suggestion engine for the current run, or returns
61
     * {@code null} when AI support is disabled.
62
     *
63
     * <p>
64
     * The orchestration layer treats {@code null} as a sentinel meaning
65
     * "skip every AI step": no per-class submission, no taxonomy resolution,
66
     * no confidence filtering. Returning a no-op engine would force every
67
     * call site to check a different way; returning {@code null} keeps the
68
     * decision in one place.
69
     * </p>
70
     *
71
     * @param aiOptions AI configuration for the current run; must not be
72
     *                  {@code null}
73
     * @return initialised AI engine, or {@code null} when {@code aiOptions}
74
     *         reports AI as disabled
75
     * @throws IllegalStateException if engine initialisation fails — typically
76
     *                               an unknown provider or unreachable
77
     *                               endpoint
78
     */
79
    public AiSuggestionEngine buildEngine(AiOptions aiOptions) {
80 2 1. buildEngine : removed conditional - replaced equality check with true → KILLED
2. buildEngine : removed conditional - replaced equality check with false → KILLED
        if (!aiOptions.enabled()) {
81
            return null;
82
        }
83
        try {
84 1 1. buildEngine : replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildEngine → KILLED
            return new AiSuggestionEngineImpl(aiOptions);
85
        } catch (AiSuggestionException e) {
86
            throw new IllegalStateException("Failed to initialize AI engine", e);
87
        }
88
    }
89
90
    /**
91
     * Loads the AI result cache from a previous MethodAtlas CSV output, or
92
     * returns the empty no-op cache when no cache file was configured.
93
     *
94
     * <p>
95
     * The empty no-op cache is a sentinel: every {@code lookup} returns
96
     * absent and {@code isActive()} returns {@code false}, which lets the
97
     * scan loop short-circuit cache work entirely without per-call null
98
     * checks.
99
     * </p>
100
     *
101
     * @param cacheFile path to a previous run's CSV output (must include the
102
     *                  {@code content_hash} column), or {@code null} to
103
     *                  disable caching
104
     * @return loaded cache, or the empty no-op cache; never {@code null}
105
     * @throws IllegalArgumentException if the cache file exists but cannot
106
     *                                  be read or parsed
107
     */
108
    public AiResultCache buildCache(Path cacheFile) {
109 2 1. buildCache : removed conditional - replaced equality check with false → KILLED
2. buildCache : removed conditional - replaced equality check with true → KILLED
        if (cacheFile == null) {
110 1 1. buildCache : replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildCache → KILLED
            return AiResultCache.empty();
111
        }
112
        try {
113 1 1. buildCache : replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildCache → KILLED
            return AiResultCache.load(cacheFile);
114
        } catch (IOException e) {
115
            throw new IllegalArgumentException("Cannot load AI cache file: " + cacheFile, e);
116
        }
117
    }
118
119
    /**
120
     * Produces a human-readable string identifying which taxonomy
121
     * configuration is in effect, for inclusion in scan-metadata output.
122
     *
123
     * <p>
124
     * Output forms:
125
     * </p>
126
     * <ul>
127
     *   <li>{@code "n/a (AI disabled)"} when no AI engine is active for the run</li>
128
     *   <li>{@code "file:<absolute path>"} when {@code aiOptions} names an external
129
     *       taxonomy file</li>
130
     *   <li>{@code "built-in/<mode>"} (for example {@code built-in/default} or
131
     *       {@code built-in/optimized}) when no external file was supplied</li>
132
     * </ul>
133
     *
134
     * <p>
135
     * The string is intended for human eyes — auditors checking which
136
     * taxonomy a particular scan run used. It is emitted by
137
     * {@code -emit-metadata} into the CSV preamble and into SARIF run
138
     * properties.
139
     * </p>
140
     *
141
     * @param aiOptions AI configuration for the current run; must not be
142
     *                  {@code null}
143
     * @param aiActive  whether an AI engine is active for this run
144
     * @return taxonomy descriptor string; never {@code null}
145
     */
146
    public String resolveTaxonomyInfo(AiOptions aiOptions, boolean aiActive) {
147 2 1. resolveTaxonomyInfo : removed conditional - replaced equality check with true → KILLED
2. resolveTaxonomyInfo : removed conditional - replaced equality check with false → KILLED
        if (!aiActive) {
148 1 1. resolveTaxonomyInfo : replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED
            return "n/a (AI disabled)";
149
        }
150 2 1. resolveTaxonomyInfo : removed conditional - replaced equality check with false → KILLED
2. resolveTaxonomyInfo : removed conditional - replaced equality check with true → KILLED
        if (aiOptions.taxonomyFile() != null) {
151 1 1. resolveTaxonomyInfo : replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED
            return "file:" + aiOptions.taxonomyFile().toAbsolutePath();
152
        }
153 1 1. resolveTaxonomyInfo : replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED
        return "built-in/" + aiOptions.taxonomyMode().name().toLowerCase(Locale.ROOT);
154
    }
155
156
    /**
157
     * Resolves the taxonomy version identifier stamped into each AI-classified
158
     * record ({@code ai_taxonomy_version}).
159
     *
160
     * <p>
161
     * Built-in taxonomies report their revision (for example
162
     * {@code built-in/default@1}); a custom {@code -ai-taxonomy} file reports
163
     * {@code sha256:<hex>} of its raw bytes, the same digest the reproducibility
164
     * receipt records as {@code taxonomyFileSha256}. See {@link TaxonomyVersion}.
165
     * </p>
166
     *
167
     * @param aiOptions AI configuration for the current run; must not be
168
     *                  {@code null}
169
     * @param aiActive  whether an AI engine is active for this run
170
     * @return version identifier, or {@code null} when no AI engine is active
171
     *         (no record carries AI output, so none is stamped)
172
     * @throws IOException if a configured taxonomy file cannot be read
173
     * @since 6.1.0
174
     */
175
    public String resolveTaxonomyVersion(AiOptions aiOptions, boolean aiActive) throws IOException {
176 3 1. resolveTaxonomyVersion : removed conditional - replaced equality check with false → KILLED
2. resolveTaxonomyVersion : replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyVersion → KILLED
3. resolveTaxonomyVersion : removed conditional - replaced equality check with true → KILLED
        return aiActive ? TaxonomyVersion.resolve(aiOptions) : null;
177
    }
178
}

Mutations

80

1.1
Location : buildEngine
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_emitsBuiltinDefaultTaxonomy(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : buildEngine
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:buildEngine_whenAiDisabled_returnsNullSentinel()]
removed conditional - replaced equality check with false → KILLED

84

1.1
Location : buildEngine
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_emitsBuiltinDefaultTaxonomy(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildEngine → KILLED

109

1.1
Location : buildCache
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:buildCache_whenNullPath_returnsInactiveEmptyCache()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : buildCache
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:buildCache_missingFile_throwsIllegalArgumentExceptionNamingPath(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

110

1.1
Location : buildCache
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:buildCache_whenNullPath_returnsInactiveEmptyCache()]
replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildCache → KILLED

113

1.1
Location : buildCache
Killed by : org.egothor.methodatlas.MethodAtlasAppAiTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppAiTest]/[method:aiCache_miss_engineCalledForUnknownHash(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildCache → KILLED

147

1.1
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyInfo_builtInDefault_returnsBuiltInWithMode()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyInfo_whenAiInactive_returnsDisabledMarker()]
removed conditional - replaced equality check with false → KILLED

148

1.1
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyInfo_whenAiInactive_returnsDisabledMarker()]
replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED

150

1.1
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_taxonomyFile_emitsFilePath(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyInfo_builtInDefault_returnsBuiltInWithMode()]
removed conditional - replaced equality check with true → KILLED

151

1.1
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:emitMetadata_aiEnabled_taxonomyFile_emitsFilePath(java.nio.file.Path)]
replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED

153

1.1
Location : resolveTaxonomyInfo
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyInfo_builtInDefault_returnsBuiltInWithMode()]
replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED

176

1.1
Location : resolveTaxonomyVersion
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyVersion_unreadableFile_throwsIoException(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : resolveTaxonomyVersion
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyVersion_whenAiInactive_returnsNull()]
replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyVersion → KILLED

3.3
Location : resolveTaxonomyVersion
Killed by : org.egothor.methodatlas.command.AiRuntimeBuilderTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.AiRuntimeBuilderTest]/[method:resolveTaxonomyVersion_whenAiInactive_returnsNull()]
removed conditional - replaced equality check with true → KILLED

Active mutators

Tests examined


Report generated by PIT 1.30.1 support