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

Mutations

79

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

83

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

108

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

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()]
replaced return value with null for org/egothor/methodatlas/command/AiRuntimeBuilder::buildCache → KILLED

112

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

146

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

147

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

149

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

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)]
replaced return value with "" for org/egothor/methodatlas/command/AiRuntimeBuilder::resolveTaxonomyInfo → KILLED

152

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

Active mutators

Tests examined


Report generated by PIT 1.22.1