MethodAtlasApp.java

1
package org.egothor.methodatlas;
2
3
import java.io.FilterOutputStream;
4
import java.io.IOException;
5
import java.io.OutputStream;
6
import java.io.OutputStreamWriter;
7
import java.io.PrintWriter;
8
import java.nio.charset.StandardCharsets;
9
import java.nio.file.Files;
10
import java.nio.file.Path;
11
import java.util.logging.Level;
12
import java.util.logging.Logger;
13
14
import org.egothor.methodatlas.ai.ManualConsumeEngine;
15
import org.egothor.methodatlas.ai.AiSuggestionEngine;
16
import org.egothor.methodatlas.api.TestDiscoveryConfig;
17
import org.egothor.methodatlas.command.AiRuntimeBuilder;
18
import org.egothor.methodatlas.command.ApplyTagsCommand;
19
20
import org.egothor.methodatlas.emit.ClassificationOverride;
21
import org.egothor.methodatlas.emit.OutputMode;
22
import org.egothor.methodatlas.command.ApplyTagsFromCsvCommand;
23
import org.egothor.methodatlas.command.CheckPromptsCommand;
24
import org.egothor.methodatlas.command.DiffCommand;
25
import org.egothor.methodatlas.command.GitHubAnnotationsCommand;
26
import org.egothor.methodatlas.command.JsonCommand;
27
import org.egothor.methodatlas.command.ManualPrepareCommand;
28
import org.egothor.methodatlas.command.OverrideLoader;
29
import org.egothor.methodatlas.command.PluginLoader;
30
import org.egothor.methodatlas.command.SarifCommand;
31
import org.egothor.methodatlas.command.ScanCommand;
32
import org.egothor.methodatlas.command.ScanOrchestrator;
33
import org.egothor.methodatlas.coverage.CoverageFacade;
34
import org.egothor.methodatlas.receipt.ReceiptFacade;
35
import org.egothor.methodatlas.evidence.EvidenceFramework;
36
import org.egothor.methodatlas.evidence.EvidencePackCommand;
37
import org.egothor.methodatlas.evidence.EvidencePackOptions;
38
import org.egothor.methodatlas.evidence.GenSigningKeyCommand;
39
40
/**
41
 * Command-line entry point that parses the arguments and routes the invocation
42
 * to the matching {@link org.egothor.methodatlas.command.Command} or utility
43
 * mode.
44
 *
45
 * <p>
46
 * This class owns no discovery or parsing logic of its own. It parses arguments
47
 * into a {@link CliConfig} (via {@link CliArgs}), selects the operating mode,
48
 * builds the collaborators that mode needs, and delegates execution. Test
49
 * discovery is performed by the language plugins on the classpath (loaded
50
 * through {@link java.util.ServiceLoader}); AI enrichment, output emission, and
51
 * the evidence-pack, coverage, and receipt features are each handled by their
52
 * own command or facade.
53
 * </p>
54
 *
55
 * <h2>Source-Derived Metadata</h2>
56
 *
57
 * <p>
58
 * For each discovered test method, the application reports:
59
 * </p>
60
 * <ul>
61
 * <li>fully qualified class name</li>
62
 * <li>method name</li>
63
 * <li>inclusive line count of the method declaration</li>
64
 * <li>JUnit {@code @Tag} values declared on the method</li>
65
 * </ul>
66
 *
67
 * <h2>AI Enrichment</h2>
68
 *
69
 * <p>
70
 * When AI support is enabled, the application submits each discovered test
71
 * class to an {@link org.egothor.methodatlas.ai.AiSuggestionEngine} and merges
72
 * the returned method-level suggestions into the emitted output.
73
 * </p>
74
 *
75
 * <h2>Manual AI Workflow</h2>
76
 *
77
 * <p>
78
 * Operators who cannot access an AI API directly can use the two-phase manual
79
 * workflow:
80
 * </p>
81
 * <ol>
82
 * <li><b>Prepare phase</b> ({@code -manual-prepare}): the application scans
83
 * test sources and writes one work file per class to the specified directory.
84
 * Each work file contains operator instructions and the full AI prompt (with
85
 * class source embedded). No CSV output is produced in this phase.</li>
86
 * <li><b>Consume phase</b> ({@code -manual-consume}): the application reads
87
 * operator-saved AI response files ({@code <stem>.response.txt}) from the
88
 * response directory and produces the final enriched CSV. Classes whose
89
 * response file is absent receive empty AI columns.</li>
90
 * </ol>
91
 *
92
 * <h2>Supported Command-Line Options</h2>
93
 *
94
 * <ul>
95
 * <li>{@code -config <path>} — loads default values from a YAML configuration
96
 * file; command-line flags override YAML values</li>
97
 * <li>{@code -plain} — emits plain text output instead of CSV</li>
98
 * <li>{@code -sarif} — emits SARIF 2.1.0 JSON output</li>
99
 * <li>{@code -json} — emits a flat JSON array; each element carries the same
100
 * fields as CSV with {@code tags} and {@code ai_tags} as arrays and numeric
101
 * values as JSON numbers</li>
102
 * <li>{@code -ai} — enables AI-based enrichment</li>
103
 * <li>{@code -ai-provider <provider>} — selects the AI provider</li>
104
 * <li>{@code -ai-model <model>} — selects the provider-specific model</li>
105
 * <li>{@code -ai-base-url <url>} — overrides the provider base URL</li>
106
 * <li>{@code -ai-api-key <key>} — supplies the AI API key directly</li>
107
 * <li>{@code -ai-api-key-env <name>} — resolves the AI API key from an
108
 * environment variable</li>
109
 * <li>{@code -ai-taxonomy <path>} — loads taxonomy text from an external
110
 * file</li>
111
 * <li>{@code -ai-taxonomy-mode <mode>} — selects the built-in taxonomy
112
 * variant</li>
113
 * <li>{@code -ai-max-class-chars <count>} — limits class source size submitted
114
 * to AI</li>
115
 * <li>{@code -ai-timeout-sec <seconds>} — sets the AI request timeout</li>
116
 * <li>{@code -ai-max-retries <count>} — sets the retry limit for AI
117
 * operations</li>
118
 * <li>{@code -ai-confidence} — requests a confidence score for each AI
119
 * security classification; adds an {@code ai_confidence} column to the
120
 * output</li>
121
 * <li>{@code -min-confidence <threshold>} — silently drops methods whose AI
122
 * confidence score is below {@code threshold} (range {@code 0.0–1.0}); only
123
 * effective when {@code -ai-confidence} is also enabled</li>
124
 * <li>{@code -ai-cache <path>} — loads a previous scan CSV produced with
125
 * {@code -content-hash -ai} as an AI result cache; classes whose
126
 * {@code content_hash} matches a cache entry are classified without an API
127
 * call; changed and new classes are classified normally</li>
128
 * <li>{@code -file-suffix <suffix>} — matches source files by name suffix
129
 * (default: {@code Test.java}); may be repeated to match multiple patterns,
130
 * e.g. {@code -file-suffix Test.java -file-suffix IT.java}; the first
131
 * occurrence replaces the default</li>
132
 * <li>{@code -test-annotation <name>} — recognises methods annotated with
133
 * {@code name} as test methods; may be repeated; the first occurrence replaces
134
 * the JVM provider's default set (JUnit 5 {@code Test}, {@code ParameterizedTest},
135
 * {@code RepeatedTest}, {@code TestFactory}, {@code TestTemplate})</li>
136
 * <li>{@code -emit-metadata} — emits {@code # key: value} comment lines
137
 * before the header row describing the tool version, scan timestamp, and
138
 * taxonomy configuration</li>
139
 * <li>{@code -apply-tags} — instead of emitting a report, writes
140
 * AI-generated {@code @DisplayName} and {@code @Tag} annotations back to
141
 * the scanned source files; requires AI enrichment to be enabled</li>
142
 * <li>{@code -content-hash} — includes a SHA-256 fingerprint of each class
143
 * source as a {@code content_hash} column in CSV/plain output and as a SARIF
144
 * property; useful for detecting which classes changed between scans</li>
145
 * <li>{@code -security-only} — suppresses non-security methods from the output;
146
 * only methods whose AI classification (or override) has
147
 * {@code securityRelevant=true} are emitted; requires AI enrichment or a
148
 * classification override file to have any effect</li>
149
 * <li>{@code -manual-prepare <workdir> <responsedir>} — runs the manual AI
150
 * prepare phase, writing work files to {@code workdir} and empty response stubs
151
 * to {@code responsedir}; the two paths may be identical</li>
152
 * <li>{@code -manual-consume <workdir> <responsedir>} — runs the manual AI
153
 * consume phase, reading response files from {@code responsedir} and emitting
154
 * the final enriched CSV</li>
155
 * <li>{@code -diff <before.csv> <after.csv>} — compares two MethodAtlas scan
156
 * outputs and emits a delta report showing added, removed, and modified test
157
 * methods; all other flags are ignored when {@code -diff} is present</li>
158
 * <li>{@code -apply-tags-from-csv <path>} — instead of emitting a report,
159
 * applies the annotation decisions recorded in the reviewed CSV back to the
160
 * source files; the CSV is treated as a complete desired-state specification:
161
 * every test method's {@code @Tag} set and {@code @DisplayName} are driven
162
 * entirely by the corresponding CSV row</li>
163
 * <li>{@code -mismatch-limit <n>} — when used with {@code -apply-tags-from-csv},
164
 * aborts without modifying any source file if the number of mismatches between
165
 * the CSV and the current source tree reaches or exceeds {@code n}; {@code -1}
166
 * (the default) logs mismatches as warnings and proceeds</li>
167
 * <li>{@code -promote-ai} — <strong>risky, not recommended</strong> opt-in for
168
 * {@code -apply-tags-from-csv}: fills blank tags/display_name from the AI columns
169
 * (unvalidated AI output reaches source); off by default</li>
170
 * <li>{@code -help} / {@code --help} / {@code -h} — print usage and exit</li>
171
 * <li>{@code -emit-source-root} — adds a {@code source_root} column (CSV) and a
172
 * {@code SRCROOT=} token (plain text) identifying which scan root each record
173
 * came from; essential in multi-root projects where the same fully qualified
174
 * class name can appear under different source trees; no effect on SARIF or
175
 * GitHub Annotations output</li>
176
 * </ul>
177
 *
178
 * <p>
179
 * Any remaining non-option arguments are interpreted as root paths to scan. If
180
 * no scan path is supplied, the current working directory is scanned.
181
 * </p>
182
 *
183
 * @see org.egothor.methodatlas.ai.AiSuggestionEngine
184
 * @see org.egothor.methodatlas.api.SourcePatcher
185
 * @see org.egothor.methodatlas.emit.OutputEmitter
186
 * @see org.egothor.methodatlas.emit.SarifEmitter
187
 * @see org.egothor.methodatlas.command.Command
188
 * @see #main(String[])
189
 */
190
public final class MethodAtlasApp {
191
192
    private static final String FLAG_DIFF = "-diff";
193
194
    /** Logger for receipt-emission warnings; receipt failures never abort the scan. */
195
    private static final Logger LOG = Logger.getLogger(MethodAtlasApp.class.getName());
196
197
    /** Tool version fallback when the JAR manifest carries no Implementation-Version. */
198
    private static final String DEV_VERSION = "dev";
199
200
    /** Exit code returned when CLI validation rejects the requested invocation. */
201
    private static final int EXIT_BAD_ARGS = 2;
202
    /** Exit code used when an evidence-pack framework token cannot be parsed. */
203
    private static final int EXIT_BAD_FRAMEWORK = 2;
204
    /** Exit code returned when the run fails with an expected, reported error. */
205
    private static final int EXIT_RUN_ERROR = 1;
206
207
    /**
208
     * Prevents instantiation of this utility class.
209
     */
210
    private MethodAtlasApp() {
211
    }
212
213
    /**
214
     * Program entry point.
215
     *
216
     * <p>
217
     * Delegates all work to {@link #run(String[], PrintWriter)}. The expected
218
     * failure modes — an I/O error traversing a scan tree, a rejected option, or
219
     * an AI engine that cannot be built — are caught here, reported as a single
220
     * concise line on {@code System.err}, and turned into exit code
221
     * {@value #EXIT_RUN_ERROR}; the raw stack trace is not shown. Unexpected
222
     * runtime failures are deliberately left to propagate so genuine defects
223
     * still surface with a full trace. A non-zero status is also returned when a
224
     * source file could not be processed.
225
     * </p>
226
     *
227
     * @param args command-line arguments
228
     */
229
    public static void main(String[] args) {
230
        // Wrap System.out in a guarded stream whose close() only flushes.
231
        // This lets try-with-resources manage the PrintWriter (satisfying
232
        // SpotBugs CloseResource and PMD UseTryWithResources) without
233
        // permanently closing System.out (satisfying Error Prone's
234
        // ClosingStandardOutputStreams check).
235
        OutputStream guarded = new FilterOutputStream(System.out) {
236
            @Override
237
            public void close() throws IOException {
238 1 1. close : removed call to org/egothor/methodatlas/MethodAtlasApp$1::flush → NO_COVERAGE
                flush(); // flush but do NOT close System.out
239
            }
240
        };
241
        int exitCode;
242
        try (PrintWriter out = new PrintWriter(new OutputStreamWriter(guarded, StandardCharsets.UTF_8), true)) {
243
            exitCode = run(args, out);
244
        } catch (IOException | IllegalArgumentException | IllegalStateException e) {
245
            // Expected, actionable failures: report one concise line, no stack
246
            // trace. Unexpected RuntimeExceptions are not caught here so real
247
            // bugs still surface with a full trace.
248 2 1. main : removed conditional - replaced equality check with false → NO_COVERAGE
2. main : removed conditional - replaced equality check with true → NO_COVERAGE
            String detail = e.getMessage() != null ? e.getMessage() : e.toString();
249 1 1. main : removed call to java/io/PrintStream::println → NO_COVERAGE
            System.err.println("methodatlas: " + detail);
250
            exitCode = EXIT_RUN_ERROR;
251
        }
252 2 1. main : removed conditional - replaced equality check with false → NO_COVERAGE
2. main : removed conditional - replaced equality check with true → NO_COVERAGE
        if (exitCode != 0) {
253 1 1. main : removed call to java/lang/System::exit → NO_COVERAGE
            System.exit(exitCode);
254
        }
255
    }
256
257
    /**
258
     * Executes a full application run by routing to the appropriate
259
     * {@link org.egothor.methodatlas.command.Command} implementation.
260
     *
261
     * <p>
262
     * This method is the primary entry point for programmatic and test use. It
263
     * parses arguments, identifies the requested operating mode, constructs the
264
     * matching command object, and delegates execution to it.
265
     * </p>
266
     *
267
     * @param args command-line arguments
268
     * @param out  writer that receives all emitted output
269
     * @return {@code 0} if all files were processed successfully, {@code 1} if
270
     *         any file produced a parse or processing error
271
     * @throws IOException              if traversal of a configured file tree fails
272
     * @throws IllegalArgumentException if an option is unknown, if a required
273
     *                                  option value is missing, or if an option
274
     *                                  value cannot be parsed
275
     * @throws IllegalStateException    if AI support is enabled but the AI engine
276
     *                                  cannot be created successfully
277
     */
278
    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // DiffCommand is created inside the loop but returned immediately
279
    /* default */ static int run(String[] args, PrintWriter out) throws IOException {
280
        // -help is handled before argument parsing so it works even with no
281
        // other (or otherwise invalid) arguments and never trips the
282
        // "Unknown argument" path.
283
        for (String arg : args) {
284 6 1. run : removed conditional - replaced equality check with false → KILLED
2. run : removed conditional - replaced equality check with false → KILLED
3. run : removed conditional - replaced equality check with true → KILLED
4. run : removed conditional - replaced equality check with true → KILLED
5. run : removed conditional - replaced equality check with false → KILLED
6. run : removed conditional - replaced equality check with true → KILLED
            if ("-help".equals(arg) || "--help".equals(arg) || "-h".equals(arg)) {
285 1 1. run : removed call to org/egothor/methodatlas/Usage::print → KILLED
                Usage.print(out);
286
                return 0;
287
            }
288
        }
289
290
        // -diff and -gen-signing-key are utility modes handled before full
291
        // argument parsing; each owns its own small set of flags and never
292
        // participates in the scan pipeline, so all other flags are ignored.
293 3 1. run : removed conditional - replaced comparison check with false → KILLED
2. run : removed conditional - replaced comparison check with true → KILLED
3. run : changed conditional boundary → KILLED
        for (int i = 0; i < args.length; i++) {
294 2 1. run : removed conditional - replaced equality check with false → KILLED
2. run : removed conditional - replaced equality check with true → KILLED
            if (FLAG_DIFF.equals(args[i])) {
295 4 1. run : changed conditional boundary → SURVIVED
2. run : removed conditional - replaced comparison check with false → SURVIVED
3. run : Replaced integer addition with subtraction → SURVIVED
4. run : removed conditional - replaced comparison check with true → KILLED
                if (i + 2 >= args.length) {
296
                    throw new IllegalArgumentException(
297
                            "-diff requires two arguments: -diff <before.csv> <after.csv>");
298
                }
299 3 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → SURVIVED
2. run : Replaced integer addition with subtraction → KILLED
3. run : Replaced integer addition with subtraction → KILLED
                return new DiffCommand(Path.of(args[i + 1]), Path.of(args[i + 2])).execute(out);
300
            }
301 2 1. run : removed conditional - replaced equality check with false → KILLED
2. run : removed conditional - replaced equality check with true → KILLED
            if (GenSigningKeyCommand.FLAG_GEN_SIGNING_KEY.equals(args[i])) {
302 1 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → SURVIVED
                return GenSigningKeyCommand.run(args, out);
303
            }
304 2 1. run : removed conditional - replaced equality check with false → SURVIVED
2. run : removed conditional - replaced equality check with true → KILLED
            if (CheckPromptsCommand.FLAG_CHECK_PROMPTS.equals(args[i])) {
305 1 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → NO_COVERAGE
                return CheckPromptsCommand.fromArgs(args).execute(out);
306
            }
307
        }
308
309
        CliConfig cliConfig = CliArgs.parse(args);
310 2 1. run : removed conditional - replaced equality check with true → KILLED
2. run : removed conditional - replaced equality check with false → KILLED
        if (cliConfig == null) {
311
            // CliArgs already printed an actionable stderr message; signal
312
            // bad-args via the conventional exit code 2.
313 1 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED
            return EXIT_BAD_ARGS;
314
        }
315
316
        // Load the coverage mapping up-front so a malformed file aborts the run
317
        // before the scan starts, avoiding wasted work and ambiguous errors.
318
        CoverageFacade.Handle coverageHandle;
319
        try {
320
            coverageHandle = prepareCoverageHandle(cliConfig);
321
        } catch (IOException | IllegalArgumentException e) {
322 1 1. run : removed call to java/io/PrintStream::println → SURVIVED
            System.err.println("Error loading coverage mapping: " + e.getMessage());
323 1 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED
            return EXIT_BAD_ARGS;
324
        }
325
326
        // Establish the run identity once and place it in the thread-local
327
        // context so the JUL formatter (Item 20) can prepend the correlation
328
        // id to every log record emitted during this invocation. clear() in
329
        // the finally block keeps the thread-local from outliving the run
330
        // when MethodAtlas is invoked programmatically (the standard CLI
331
        // exits the JVM anyway).
332
        String version = MethodAtlasApp.class.getPackage().getImplementationVersion();
333
        ScanRun scanRun = ScanRun.create(version, cliConfig.toString());
334 1 1. run : removed call to org/egothor/methodatlas/ScanRunContext::set → SURVIVED
        ScanRunContext.set(scanRun);
335
        try {
336
            int exit = runWithScanRun(out, cliConfig, coverageHandle);
337 2 1. run : removed conditional - replaced equality check with false → KILLED
2. run : removed conditional - replaced equality check with true → KILLED
            if (cliConfig.emitReceipt()) {
338 1 1. run : removed call to org/egothor/methodatlas/MethodAtlasApp::emitReceipt → KILLED
                emitReceipt(cliConfig, version);
339
            }
340 2 1. run : removed conditional - replaced equality check with true → KILLED
2. run : removed conditional - replaced equality check with false → KILLED
            if (coverageHandle != null) {
341 1 1. run : removed call to org/egothor/methodatlas/MethodAtlasApp::writeCoverage → KILLED
                writeCoverage(cliConfig, version, coverageHandle);
342
            }
343 1 1. run : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED
            return exit;
344
        } finally {
345 1 1. run : removed call to org/egothor/methodatlas/ScanRunContext::clear → SURVIVED
            ScanRunContext.clear();
346
        }
347
    }
348
349
    /**
350
     * Loads the coverage mapping when {@code -emit-coverage} is active.
351
     *
352
     * @param cliConfig parsed CLI configuration
353
     * @return prepared handle or {@code null} when coverage mode is not active
354
     * @throws IOException              if the mapping file cannot be read
355
     * @throws IllegalArgumentException if the mapping file fails validation
356
     */
357
    private static CoverageFacade.Handle prepareCoverageHandle(CliConfig cliConfig)
358
            throws IOException {
359 2 1. prepareCoverageHandle : removed conditional - replaced equality check with false → KILLED
2. prepareCoverageHandle : removed conditional - replaced equality check with true → KILLED
        if (!cliConfig.emitCoverage()) {
360
            return null;
361
        }
362 1 1. prepareCoverageHandle : replaced return value with null for org/egothor/methodatlas/MethodAtlasApp::prepareCoverageHandle → KILLED
        return CoverageFacade.prepare(cliConfig.coverageMappingFile(), cliConfig.minConfidence());
363
    }
364
365
    /**
366
     * Writes the coverage report. Errors are logged and swallowed so a
367
     * coverage-write failure never demotes a successful scan.
368
     *
369
     * @param cliConfig parsed CLI configuration
370
     * @param version   resolved tool version
371
     * @param handle    prepared coverage handle; never {@code null}
372
     */
373
    private static void writeCoverage(CliConfig cliConfig, String version,
374
            CoverageFacade.Handle handle) {
375 2 1. writeCoverage : removed conditional - replaced equality check with true → SURVIVED
2. writeCoverage : removed conditional - replaced equality check with false → SURVIVED
        String toolVersion = version != null ? version : DEV_VERSION;
376 2 1. writeCoverage : removed conditional - replaced equality check with true → SURVIVED
2. writeCoverage : removed conditional - replaced equality check with false → KILLED
        Path target = cliConfig.coverageFile() != null
377
                ? cliConfig.coverageFile()
378
                : Path.of(CoverageFacade.DEFAULT_COVERAGE_FILENAME);
379
        try {
380 1 1. writeCoverage : removed call to org/egothor/methodatlas/coverage/CoverageFacade$Handle::write → KILLED
            handle.write(toolVersion, target);
381
        } catch (IOException e) {
382
            if (LOG.isLoggable(Level.WARNING)) {
383
                LOG.log(Level.WARNING,
384
                        "Could not write coverage report: {0}", e.getMessage());
385
            }
386
        }
387
    }
388
389
    /**
390
     * Writes a reproducibility receipt for the just-completed scan.
391
     *
392
     * <p>
393
     * Failures are logged at WARNING level and swallowed so a receipt-write
394
     * error never turns a successful scan into a non-zero exit code.
395
     * </p>
396
     *
397
     * @param cliConfig parsed CLI configuration whose
398
     *                  {@link CliConfig#emitReceipt()} is already known to be
399
     *                  {@code true}
400
     * @param version   tool version resolved from the JAR manifest; {@code null}
401
     *                  resolves to {@code "dev"}
402
     */
403
    private static void emitReceipt(CliConfig cliConfig, String version) {
404 2 1. emitReceipt : removed conditional - replaced equality check with false → SURVIVED
2. emitReceipt : removed conditional - replaced equality check with true → KILLED
        String toolVersion = version != null ? version : DEV_VERSION;
405
        String modeName = cliConfig.outputMode().name();
406
        try {
407 1 1. emitReceipt : removed call to org/egothor/methodatlas/receipt/ReceiptFacade::emit → KILLED
            ReceiptFacade.emit(cliConfig, toolVersion, modeName);
408
        } catch (IOException e) {
409
            if (LOG.isLoggable(Level.WARNING)) {
410
                LOG.log(Level.WARNING,
411
                        "Could not write reproducibility receipt: {0}", e.getMessage());
412
            }
413
        }
414
    }
415
416
    private static int runWithScanRun(PrintWriter out, CliConfig cliConfig,
417
            CoverageFacade.Handle coverageHandle) throws IOException {
418
        AiRuntimeBuilder aiRuntimeBuilder = new AiRuntimeBuilder();
419
        ClassificationOverride override = new OverrideLoader().load(cliConfig.overrideFile());
420
        AiResultCache aiCache = aiRuntimeBuilder.buildCache(cliConfig.aiCacheFile());
421
422
        TestDiscoveryConfig discoveryConfig =
423
                new TestDiscoveryConfig(cliConfig.fileSuffixes(), cliConfig.testMarkers(), cliConfig.properties());
424
425
        // One PluginLoader + one ScanOrchestrator are shared by every command in
426
        // this run; both are stateless and the providers they resolve are owned
427
        // (and closed) by the command that requested them. When -emit-coverage
428
        // is active the orchestrator carries the coverage sink as an extra
429
        // fan-out — every command mode sees the same fan-out automatically.
430
        PluginLoader pluginLoader = new PluginLoader();
431
        java.util.Optional<org.egothor.methodatlas.emit.TestMethodSink> extraSink =
432 2 1. runWithScanRun : removed conditional - replaced equality check with true → KILLED
2. runWithScanRun : removed conditional - replaced equality check with false → KILLED
                coverageHandle == null
433
                        ? java.util.Optional.empty()
434
                        : java.util.Optional.of(coverageHandle.asSink());
435
        ScanOrchestrator scanOrchestrator = new ScanOrchestrator(pluginLoader, extraSink);
436
437
        // Manual prepare phase: write AI prompt work files; no CSV output.
438 2 1. runWithScanRun : removed conditional - replaced equality check with false → KILLED
2. runWithScanRun : removed conditional - replaced equality check with true → KILLED
        if (cliConfig.manualMode() instanceof ManualMode.Prepare prepare) {
439 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED
            return new ManualPrepareCommand(prepare, cliConfig, discoveryConfig, pluginLoader).execute(out);
440
        }
441
442
        // Determine AI engine: manual consume reads from files; normal mode calls APIs.
443
        AiSuggestionEngine aiEngine;
444 2 1. runWithScanRun : removed conditional - replaced equality check with false → KILLED
2. runWithScanRun : removed conditional - replaced equality check with true → KILLED
        if (cliConfig.manualMode() instanceof ManualMode.Consume consume) {
445
            aiEngine = new ManualConsumeEngine(consume.responseDir());
446
        } else {
447
            aiEngine = aiRuntimeBuilder.buildEngine(cliConfig.aiOptions());
448
        }
449
450
        // Apply-tags-from-csv mode: apply reviewed CSV decisions to source files.
451 2 1. runWithScanRun : removed conditional - replaced equality check with true → KILLED
2. runWithScanRun : removed conditional - replaced equality check with false → KILLED
        if (cliConfig.applyTagsFromCsvFile() != null) {
452 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED
            return new ApplyTagsFromCsvCommand(cliConfig, discoveryConfig, pluginLoader).execute(out);
453
        }
454
455
        // Apply-tags mode: annotate source files; no report emitted.
456 2 1. runWithScanRun : removed conditional - replaced equality check with false → KILLED
2. runWithScanRun : removed conditional - replaced equality check with true → KILLED
        if (cliConfig.applyTags()) {
457 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
            return new ApplyTagsCommand(cliConfig, discoveryConfig, aiEngine, override, aiCache,
458
                    pluginLoader, scanOrchestrator).execute(out);
459
        }
460
461
        // Evidence-pack mode: produces a tamper-evident self-contained directory
462
        // (SARIF + CSV + manifest + optional ZeroEcho signature). Must precede the
463
        // SARIF/JSON/GitHub-annotation branches because those formats are
464
        // re-used internally; the dispatch decision is owned by this command.
465 2 1. runWithScanRun : removed conditional - replaced equality check with false → KILLED
2. runWithScanRun : removed conditional - replaced equality check with true → KILLED
        if (cliConfig.evidencePackFramework() != null) {
466 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED
            return dispatchEvidencePack(cliConfig, discoveryConfig, aiEngine, override, aiCache,
467
                    scanOrchestrator);
468
        }
469
470
        // SARIF mode: buffer all records; write JSON once after the scan completes.
471 2 1. runWithScanRun : removed conditional - replaced equality check with true → KILLED
2. runWithScanRun : removed conditional - replaced equality check with false → KILLED
        if (cliConfig.outputMode() == OutputMode.SARIF) {
472 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
            return new SarifCommand(cliConfig, discoveryConfig, aiEngine, override, aiCache,
473
                    scanOrchestrator).execute(out);
474
        }
475
476
        // JSON mode: buffer all records; write flat JSON array after scan completes.
477 2 1. runWithScanRun : removed conditional - replaced equality check with true → KILLED
2. runWithScanRun : removed conditional - replaced equality check with false → KILLED
        if (cliConfig.outputMode() == OutputMode.JSON) {
478 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
            return new JsonCommand(cliConfig, discoveryConfig, aiEngine, override, aiCache,
479
                    pluginLoader, scanOrchestrator).execute(out);
480
        }
481
482
        // GitHub Annotations mode: emit ::notice/::warning workflow commands.
483 2 1. runWithScanRun : removed conditional - replaced equality check with true → KILLED
2. runWithScanRun : removed conditional - replaced equality check with false → KILLED
        if (cliConfig.outputMode() == OutputMode.GITHUB_ANNOTATIONS) {
484 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
            return new GitHubAnnotationsCommand(cliConfig, discoveryConfig, aiEngine, override, aiCache,
485
                    scanOrchestrator).execute(out);
486
        }
487
488
        // CSV / PLAIN mode: emit incrementally (default).
489 1 1. runWithScanRun : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED
        return new ScanCommand(cliConfig, discoveryConfig, aiEngine, override, aiCache,
490
                pluginLoader, scanOrchestrator).execute(out);
491
    }
492
493
    /**
494
     * Parses the {@code -evidence-pack} framework token, assembles
495
     * {@link EvidencePackOptions}, runs the {@link EvidencePackCommand}, and
496
     * prints a one-line summary to {@code stderr}. The signing key is read from
497
     * a ZeroEcho keyring file (a plaintext {@code KeyringStore}); ZeroEcho
498
     * keyrings are not password-protected, so no password is collected.
499
     *
500
     * @param cliConfig         parsed CLI configuration
501
     * @param discoveryConfig   discovery configuration forwarded to providers
502
     * @param aiEngine          AI engine, or {@code null}
503
     * @param override          classification override
504
     * @param aiCache           AI result cache
505
     * @param scanOrchestrator  shared scan orchestrator
506
     * @return exit code propagated from {@link EvidencePackCommand#execute()}
507
     *         or {@value #EXIT_BAD_FRAMEWORK} when the framework token is invalid
508
     * @throws IOException if the underlying scan or I/O fails
509
     */
510
    private static int dispatchEvidencePack(CliConfig cliConfig, TestDiscoveryConfig discoveryConfig,
511
            AiSuggestionEngine aiEngine, ClassificationOverride override, AiResultCache aiCache,
512
            ScanOrchestrator scanOrchestrator) throws IOException {
513
        EvidenceFramework framework;
514
        try {
515
            framework = EvidenceFramework.parse(cliConfig.evidencePackFramework());
516
        } catch (IllegalArgumentException e) {
517 1 1. dispatchEvidencePack : removed call to java/io/PrintStream::println → SURVIVED
            System.err.println(e.getMessage());
518 1 1. dispatchEvidencePack : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::dispatchEvidencePack → KILLED
            return EXIT_BAD_FRAMEWORK;
519
        }
520
        EvidencePackOptions packOptions = new EvidencePackOptions(
521
                framework,
522
                cliConfig.evidencePackDir(),
523
                cliConfig.evidencePackOverwrite(),
524
                cliConfig.evidencePackKeyringFile(),
525
                cliConfig.evidencePackKeyringEnv(),
526
                cliConfig.evidencePackKeyAlias(),
527
                cliConfig.evidencePackSignAlgo());
528
        EvidencePackCommand command = new EvidencePackCommand(cliConfig, packOptions, discoveryConfig,
529
                aiEngine, override, aiCache, scanOrchestrator);
530
        int exit = command.execute();
531 1 1. dispatchEvidencePack : removed call to org/egothor/methodatlas/MethodAtlasApp::announcePack → SURVIVED
        announcePack(command, packOptions);
532 1 1. dispatchEvidencePack : replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::dispatchEvidencePack → SURVIVED
        return exit;
533
    }
534
535
    /**
536
     * Prints the one-line outcome banner that documents how the pack was
537
     * produced. Always written to {@code System.err} so the stdout stream
538
     * remains usable for piping.
539
     *
540
     * @param command     executed command, used to retrieve the absolute path
541
     * @param packOptions options driving the command
542
     */
543
    private static void announcePack(EvidencePackCommand command, EvidencePackOptions packOptions) {
544
        String absolute = command.outputDir().toString();
545 4 1. announcePack : removed conditional - replaced equality check with false → SURVIVED
2. announcePack : removed conditional - replaced equality check with true → SURVIVED
3. announcePack : removed conditional - replaced equality check with false → SURVIVED
4. announcePack : removed conditional - replaced equality check with true → SURVIVED
        if (packOptions.keyringFile() == null && packOptions.keyringEnv() == null) {
546 1 1. announcePack : removed call to java/io/PrintStream::println → SURVIVED
            System.err.println("evidence pack written to " + absolute
547
                    + " (unsigned — no keyring supplied)");
548
            return;
549
        }
550 2 1. announcePack : removed conditional - replaced equality check with true → SURVIVED
2. announcePack : removed conditional - replaced equality check with false → SURVIVED
        String algo = packOptions.signatureAlgorithm() != null
551
                ? packOptions.signatureAlgorithm() : "Ed25519 (from keyring)";
552
        Path signed = command.outputDir().resolve("manifest.sha256.signed");
553 2 1. announcePack : removed conditional - replaced equality check with true → SURVIVED
2. announcePack : removed conditional - replaced equality check with false → SURVIVED
        if (Files.exists(signed)) {
554 1 1. announcePack : removed call to java/io/PrintStream::println → SURVIVED
            System.err.println("evidence pack written to " + absolute
555
                    + " (signed: " + algo + ")");
556
        } else {
557 1 1. announcePack : removed call to java/io/PrintStream::println → SURVIVED
            System.err.println("evidence pack written to " + absolute
558
                    + " (WARNING: signing failed — check log)");
559
        }
560
    }
561
}

Mutations

238

1.1
Location : close
Killed by : none
removed call to org/egothor/methodatlas/MethodAtlasApp$1::flush → NO_COVERAGE

248

1.1
Location : main
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

2.2
Location : main
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

249

1.1
Location : main
Killed by : none
removed call to java/io/PrintStream::println → NO_COVERAGE

252

1.1
Location : main
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

2.2
Location : main
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

253

1.1
Location : main
Killed by : none
removed call to java/lang/System::exit → NO_COVERAGE

284

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

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

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

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

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

6.6
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppTest]/[method:help_longAndShortAliasesAccepted()]
removed conditional - replaced equality check with true → KILLED

285

1.1
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppTest]/[method:help_longAndShortAliasesAccepted()]
removed call to org/egothor/methodatlas/Usage::print → KILLED

293

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

2.2
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppTest]/[method:testAnnotation_customAnnotationIsRecognised(java.nio.file.Path)]
removed conditional - replaced comparison check with true → KILLED

3.3
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppTest]/[method:testAnnotation_customAnnotationIsRecognised(java.nio.file.Path)]
changed conditional boundary → KILLED

294

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

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

295

1.1
Location : run
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

2.2
Location : run
Killed by : none
removed conditional - replaced comparison check with false → SURVIVED Covering tests

3.3
Location : run
Killed by : none
Replaced integer addition with subtraction → SURVIVED Covering tests

4.4
Location : run
Killed by : org.egothor.methodatlas.DeltaReportTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.DeltaReportTest]/[method:app_diffFlag_producesReport(java.nio.file.Path)]
removed conditional - replaced comparison check with true → KILLED

299

1.1
Location : run
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → SURVIVED
Covering tests

2.2
Location : run
Killed by : org.egothor.methodatlas.DeltaReportTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.DeltaReportTest]/[method:app_diffFlag_producesReport(java.nio.file.Path)]
Replaced integer addition with subtraction → KILLED

3.3
Location : run
Killed by : org.egothor.methodatlas.DeltaReportTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.DeltaReportTest]/[method:app_diffFlag_producesReport(java.nio.file.Path)]
Replaced integer addition with subtraction → KILLED

301

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

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

302

1.1
Location : run
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → SURVIVED
Covering tests

304

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

2.2
Location : run
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

305

1.1
Location : run
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → NO_COVERAGE

310

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

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

313

1.1
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppCoverageTest]/[method:emitCoverage_withoutMapping_exitsWithCodeTwoAndPrintsHelpfulError(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED

322

1.1
Location : run
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

323

1.1
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppCoverageTest]/[method:emitCoverage_withMalformedMapping_exitsWithCodeTwo(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED

334

1.1
Location : run
Killed by : none
removed call to org/egothor/methodatlas/ScanRunContext::set → SURVIVED
Covering tests

337

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

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

338

1.1
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:defaultReceiptPath_isHonoured(java.nio.file.Path)]
removed call to org/egothor/methodatlas/MethodAtlasApp::emitReceipt → KILLED

340

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

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

341

1.1
Location : run
Killed by : org.egothor.methodatlas.MethodAtlasAppCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppCoverageTest]/[method:coveragePercent_isInClosedZeroToHundredRange(java.nio.file.Path)]
removed call to org/egothor/methodatlas/MethodAtlasApp::writeCoverage → KILLED

343

1.1
Location : run
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:unknownFrameworkExitsWithCodeTwo(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::run → KILLED

345

1.1
Location : run
Killed by : none
removed call to org/egothor/methodatlas/ScanRunContext::clear → SURVIVED
Covering tests

359

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

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

362

1.1
Location : prepareCoverageHandle
Killed by : org.egothor.methodatlas.MethodAtlasAppCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppCoverageTest]/[method:coveragePercent_isInClosedZeroToHundredRange(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/MethodAtlasApp::prepareCoverageHandle → KILLED

375

1.1
Location : writeCoverage
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : writeCoverage
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

376

1.1
Location : writeCoverage
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

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

380

1.1
Location : writeCoverage
Killed by : org.egothor.methodatlas.MethodAtlasAppCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppCoverageTest]/[method:coveragePercent_isInClosedZeroToHundredRange(java.nio.file.Path)]
removed call to org/egothor/methodatlas/coverage/CoverageFacade$Handle::write → KILLED

404

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

2.2
Location : emitReceipt
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

407

1.1
Location : emitReceipt
Killed by : org.egothor.methodatlas.MethodAtlasAppReceiptTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppReceiptTest]/[method:defaultReceiptPath_isHonoured(java.nio.file.Path)]
removed call to org/egothor/methodatlas/receipt/ReceiptFacade::emit → KILLED

432

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

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

438

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

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

439

1.1
Location : runWithScanRun
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:manualPrepare_unparseableFile_returnsExitCode1(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED

444

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

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

451

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

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

452

1.1
Location : runWithScanRun
Killed by : org.egothor.methodatlas.MethodAtlasAppApplyTagsFromCsvTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppApplyTagsFromCsvTest]/[method:applyTagsFromCsv_mismatchLimitReturnsOne(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED

456

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

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

457

1.1
Location : runWithScanRun
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
Covering tests

465

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

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

466

1.1
Location : runWithScanRun
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:unknownFrameworkExitsWithCodeTwo(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED

471

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

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

472

1.1
Location : runWithScanRun
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
Covering tests

477

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

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

478

1.1
Location : runWithScanRun
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
Covering tests

483

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

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

484

1.1
Location : runWithScanRun
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → SURVIVED
Covering tests

489

1.1
Location : runWithScanRun
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_unparseableFile_returnsExitCode1(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::runWithScanRun → KILLED

517

1.1
Location : dispatchEvidencePack
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

518

1.1
Location : dispatchEvidencePack
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:unknownFrameworkExitsWithCodeTwo(java.nio.file.Path)]
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::dispatchEvidencePack → KILLED

531

1.1
Location : dispatchEvidencePack
Killed by : none
removed call to org/egothor/methodatlas/MethodAtlasApp::announcePack → SURVIVED
Covering tests

532

1.1
Location : dispatchEvidencePack
Killed by : none
replaced int return with 0 for org/egothor/methodatlas/MethodAtlasApp::dispatchEvidencePack → SURVIVED
Covering tests

545

1.1
Location : announcePack
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : announcePack
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

3.3
Location : announcePack
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

4.4
Location : announcePack
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

546

1.1
Location : announcePack
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

550

1.1
Location : announcePack
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : announcePack
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

553

1.1
Location : announcePack
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : announcePack
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

554

1.1
Location : announcePack
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

557

1.1
Location : announcePack
Killed by : none
removed call to java/io/PrintStream::println → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.22.1