ScanOrchestrator.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.ArrayList;
9
import java.util.LinkedHashMap;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Optional;
13
import java.util.concurrent.ConcurrentHashMap;
14
import java.util.logging.Level;
15
import java.util.logging.Logger;
16
import java.util.stream.Collectors;
17
18
import org.egothor.methodatlas.AiCacheEntry;
19
import org.egothor.methodatlas.AiCacheStore;
20
import org.egothor.methodatlas.AiResultCache;
21
import org.egothor.methodatlas.emit.ClassificationOverride;
22
import org.egothor.methodatlas.CliConfig;
23
import org.egothor.methodatlas.emit.CompositeTestMethodSink;
24
import org.egothor.methodatlas.emit.TestMethodSink;
25
import org.egothor.methodatlas.ai.AiClassSuggestion;
26
import org.egothor.methodatlas.ai.AiMethodSuggestion;
27
import org.egothor.methodatlas.ai.AiOptions;
28
import org.egothor.methodatlas.ai.AiSuggestionEngine;
29
import org.egothor.methodatlas.ai.AiSuggestionException;
30
import org.egothor.methodatlas.ai.CredentialTriageVerdict;
31
import org.egothor.methodatlas.ai.PromptBuilder;
32
import org.egothor.methodatlas.ai.SuggestionLookup;
33
import org.egothor.methodatlas.api.DiscoveredMethod;
34
import org.egothor.methodatlas.api.TestDiscovery;
35
import org.egothor.methodatlas.api.TestDiscoveryConfig;
36
37
/**
38
 * Orchestrates the scan-and-emit loop that every command mode is built around.
39
 *
40
 * <p>
41
 * Each {@link Command} mode varies in three places — output format, per-record
42
 * sink behaviour, and whether records stream or buffer — but they all share
43
 * the same core sequence:
44
 * </p>
45
 * <ol>
46
 *   <li>Load all configured {@link TestDiscovery} providers.</li>
47
 *   <li>For each scan root, run every provider, merge their results, and
48
 *       group methods by class.</li>
49
 *   <li>For each class, optionally consult the AI engine through a layered
50
 *       cache + override lookup.</li>
51
 *   <li>Forward each method record to the supplied sink.</li>
52
 *   <li>Close all providers.</li>
53
 * </ol>
54
 *
55
 * <p>
56
 * This class owns that sequence. Commands compose it with a
57
 * {@link PluginLoader} (passed in at construction) and configure the
58
 * per-record sink, AI runtime, and content-hash policy at call time.
59
 * </p>
60
 *
61
 * <h2>API shape</h2>
62
 *
63
 * <p>
64
 * Two entry points serve the two common patterns:
65
 * </p>
66
 * <ul>
67
 *   <li>{@link #scan} manages the provider lifecycle internally; it is the
68
 *       right call for SARIF, JSON, and GitHub-annotation modes that buffer
69
 *       or emit unconditionally.</li>
70
 *   <li>{@link #runDiscovery} processes a single root against pre-loaded
71
 *       providers; it is the right call for CSV and plain-text modes that
72
 *       compute per-root metadata (such as the {@code source_root} column)
73
 *       before forwarding records.</li>
74
 * </ul>
75
 *
76
 * <p>
77
 * The apply-tags flow has its own shape: {@link #collectMethodsByFile}
78
 * groups discovered methods by source file (the caller owns the provider
79
 * lifecycle so it can read each provider's
80
 * {@link TestDiscovery#hadErrors()} afterwards), and
81
 * {@link #gatherAiSuggestionsForFile} resolves AI suggestions for one
82
 * file at a time.
83
 * </p>
84
 *
85
 * <h2>Thread safety</h2>
86
 *
87
 * <p>
88
 * This class is thread-safe. The injected {@link PluginLoader} is
89
 * thread-safe and {@link java.util.ServiceLoader} resolution is
90
 * idempotent; nothing else is shared between calls.
91
 * </p>
92
 *
93
 * <p>
94
 * When {@code parallelAi} is enabled (see
95
 * {@link #ScanOrchestrator(PluginLoader, java.util.Optional, boolean)}),
96
 * AI classification calls for different classes within one root are issued
97
 * concurrently via {@link java.util.stream.Stream#parallel()}.  All objects
98
 * read during that phase ({@link AiResultCache}, the classification override,
99
 * and the underlying {@link java.net.http.HttpClient}) are thread-safe.
100
 * Sink emission is always sequential, so output ordering matches discovery
101
 * order regardless of AI call completion order.
102
 * </p>
103
 *
104
 * @see PluginLoader
105
 * @see AiRuntime
106
 * @see Command
107
 * @since 1.0.0
108
 */
109
// Class-level total complexity grows with every orchestrated mode; no single method exceeds the cap.
110
@SuppressWarnings("PMD.CyclomaticComplexity")
111
public final class ScanOrchestrator {
112
113
    private static final Logger LOG = Logger.getLogger(ScanOrchestrator.class.getName());
114
115
    private final PluginLoader pluginLoader;
116
117
    /**
118
     * Optional secondary sink that observes every record alongside the
119
     * command's primary sink. {@code null} means no fan-out.
120
     */
121
    private final TestMethodSink extraSink;
122
123
    /**
124
     * When {@code true}, AI classification calls for different classes are
125
     * issued concurrently via {@link java.util.stream.Stream#parallel()}
126
     * before records are emitted in the original discovery order.
127
     */
128
    private final boolean parallelAi;
129
130
    /**
131
     * Redaction service applied to the AI payload when {@code -redact-comments}
132
     * is set; never {@code null}.
133
     */
134
    private final CommentRedaction redaction;
135
136
    /**
137
     * Creates a new orchestrator with no extra sink and parallel AI disabled.
138
     *
139
     * @param pluginLoader plugin loader used by {@link #scan} and
140
     *                     {@link #collectMethodsByFile}; must not be
141
     *                     {@code null}
142
     */
143
    public ScanOrchestrator(PluginLoader pluginLoader) {
144
        this(pluginLoader, Optional.empty(), false);
145
    }
146
147
    /**
148
     * Creates a new orchestrator that fans out every record to {@code extraSink}
149
     * in addition to the command's primary sink.
150
     *
151
     * <p>
152
     * The extra sink, when present, is composed with the primary sink at the
153
     * {@link #runDiscovery} boundary so every command mode (SARIF, JSON, CSV,
154
     * GitHub annotations) sees the same fan-out automatically. When
155
     * {@code extraSink} is {@link Optional#empty()} the orchestrator behaves
156
     * identically to the legacy single-sink constructor.
157
     * </p>
158
     *
159
     * @param pluginLoader plugin loader used by {@link #scan} and
160
     *                     {@link #collectMethodsByFile}; must not be
161
     *                     {@code null}
162
     * @param extraSink    optional secondary sink invoked in addition to the
163
     *                     command-supplied primary sink; must not be
164
     *                     {@code null} (use {@link Optional#empty()})
165
     */
166
    public ScanOrchestrator(PluginLoader pluginLoader, Optional<TestMethodSink> extraSink) {
167
        this(pluginLoader, extraSink, false);
168
    }
169
170
    /**
171
     * Creates a new orchestrator with full configuration.
172
     *
173
     * @param pluginLoader plugin loader used by {@link #scan} and
174
     *                     {@link #collectMethodsByFile}; must not be
175
     *                     {@code null}
176
     * @param extraSink    optional secondary sink invoked in addition to the
177
     *                     command-supplied primary sink; must not be
178
     *                     {@code null} (use {@link Optional#empty()})
179
     * @param parallelAi   when {@code true}, AI classification calls are issued
180
     *                     concurrently; output is still emitted in discovery order
181
     */
182
    public ScanOrchestrator(PluginLoader pluginLoader, Optional<TestMethodSink> extraSink,
183
            boolean parallelAi) {
184
        this(pluginLoader, extraSink, parallelAi, CommentRedaction.none());
185
    }
186
187
    /**
188
     * Creates a new orchestrator with full configuration and a comment-redaction
189
     * service.
190
     *
191
     * @param pluginLoader plugin loader; must not be {@code null}
192
     * @param extraSink    optional secondary sink; must not be {@code null}
193
     * @param parallelAi   when {@code true}, AI classification calls are issued concurrently
194
     * @param redaction    redaction service consulted when {@code -redact-comments} is set;
195
     *                     must not be {@code null}
196
     */
197
    public ScanOrchestrator(PluginLoader pluginLoader, Optional<TestMethodSink> extraSink,
198
            boolean parallelAi, CommentRedaction redaction) {
199
        this.pluginLoader = pluginLoader;
200
        this.extraSink = extraSink.orElse(null);
201
        this.parallelAi = parallelAi;
202
        this.redaction = redaction;
203
    }
204
205
    /**
206
     * Returns the redaction service this orchestrator applies to AI payloads, so
207
     * other AI-sending components (credential triage) share the same fail-closed policy.
208
     *
209
     * @return the redaction service; never {@code null}
210
     */
211
    public CommentRedaction redaction() {
212 1 1. redaction : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::redaction → KILLED
        return redaction;
213
    }
214
215
    /**
216
     * Scans every configured root, forwarding each discovered test method to
217
     * {@code sink}. Loads and closes the {@link TestDiscovery} providers
218
     * internally so callers do not need to manage the lifecycle.
219
     *
220
     * <p>
221
     * When {@code secretCtx} is supplied, credential triage is folded into each
222
     * per-class AI call so the class source is sent to the provider once for both
223
     * classification and triage; pass {@code null} for classification only.
224
     * </p>
225
     *
226
     * @param roots           source roots to scan; must not be {@code null}
227
     * @param cliConfig       full parsed CLI configuration
228
     * @param discoveryConfig discovery configuration forwarded to providers
229
     * @param aiEngine        AI engine providing suggestions; may be
230
     *                        {@code null} when AI is disabled
231
     * @param sink            receiver of discovered test method records
232
     * @param override        human classification overrides
233
     * @param aiCache         AI result cache
234
     * @param secretCtx       credential-triage context, or {@code null} to disable
235
     *                        the fold (classification only)
236
     * @return {@code 0} if all files were processed successfully, {@code 1}
237
     *         if any file produced a parse or processing error
238
     * @throws IOException if traversing a file tree fails
239
     * @since 1.0.0
240
     */
241
    public int scan(List<Path> roots, CliConfig cliConfig, TestDiscoveryConfig discoveryConfig,
242
            AiSuggestionEngine aiEngine, TestMethodSink sink,
243
            ClassificationOverride override, AiResultCache aiCache,
244
            CredentialTriageContext secretCtx) throws IOException {
245
        List<TestDiscovery> providers = pluginLoader.loadProviders(discoveryConfig);
246
        boolean hadErrors = false;
247
        // Accumulates one cache entry per processed class across all roots; written
248
        // once at the end when -ai-cache-out is set.
249
        Map<String, AiCacheEntry> cacheAll =
250 2 1. scan : removed conditional - replaced equality check with true → KILLED
2. scan : removed conditional - replaced equality check with false → KILLED
                cliConfig.aiCacheOut() != null ? new LinkedHashMap<>() : null;
251
        try {
252
            for (Path root : roots) {
253
                DiscoveryResult result = runDiscoveryInternal(root, providers, cliConfig.aiOptions(),
254
                        aiEngine, sink, cliConfig.contentHash(), override, aiCache, secretCtx);
255 2 1. scan : removed conditional - replaced equality check with true → SURVIVED
2. scan : removed conditional - replaced equality check with false → SURVIVED
                if (result.hadErrors()) {
256
                    hadErrors = true;
257
                }
258 2 1. scan : removed conditional - replaced equality check with true → KILLED
2. scan : removed conditional - replaced equality check with false → KILLED
                if (cacheAll != null) {
259
                    cacheAll.putAll(result.cacheEntries());
260
                }
261
            }
262
        } finally {
263
            pluginLoader.closeAll(providers);
264
        }
265 2 1. scan : removed conditional - replaced equality check with true → KILLED
2. scan : removed conditional - replaced equality check with false → KILLED
        if (cacheAll != null) {
266
            AiCacheStore.write(cliConfig.aiCacheOut(), cacheAll.values());
267
        }
268 2 1. scan : removed conditional - replaced equality check with true → SURVIVED
2. scan : removed conditional - replaced equality check with false → SURVIVED
        return hadErrors ? 1 : 0;
269
    }
270
271
    /**
272
     * Runs all configured {@link TestDiscovery} providers on {@code root},
273
     * merges their results, orchestrates AI analysis per class, and forwards
274
     * each method record to {@code sink}.
275
     *
276
     * <p>
277
     * Providers are passed in pre-loaded; callers manage the lifecycle
278
     * (typically through {@link PluginLoader#closeAll(List)} in a
279
     * {@code finally} block) so that they can share one provider list across
280
     * multiple roots while still computing per-root metadata before each
281
     * call.
282
     * </p>
283
     *
284
     * <p>
285
     * When {@code secretCtx} is supplied, credential triage is folded into each
286
     * per-class AI call; pass {@code null} for classification only.
287
     * </p>
288
     *
289
     * @param root               directory to scan
290
     * @param providers          list of pre-configured discovery providers
291
     * @param aiOptions          AI configuration for the current run
292
     * @param aiEngine           AI engine, or {@code null} when AI is disabled
293
     * @param sink               receiver of discovered test method records
294
     * @param contentHashEnabled whether to include the class content hash in
295
     *                           emitted records
296
     * @param override           human classification overrides
297
     * @param aiCache            AI result cache
298
     * @param secretCtx          credential-triage context, or {@code null} to disable the fold
299
     * @return {@code true} if any provider encountered a parse or processing
300
     *         error
301
     * @throws IOException if traversing the file tree fails
302
     * @since 1.0.0
303
     */
304
    public boolean runDiscovery(Path root, List<TestDiscovery> providers,
305
            AiOptions aiOptions, AiSuggestionEngine aiEngine, TestMethodSink sink,
306
            boolean contentHashEnabled, ClassificationOverride override,
307
            AiResultCache aiCache, CredentialTriageContext secretCtx) throws IOException {
308 2 1. runDiscovery : replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::runDiscovery → KILLED
2. runDiscovery : replaced boolean return with false for org/egothor/methodatlas/command/ScanOrchestrator::runDiscovery → KILLED
        return runDiscoveryInternal(root, providers, aiOptions, aiEngine, sink, contentHashEnabled,
309
                override, aiCache, secretCtx).hadErrors();
310
    }
311
312
    /**
313
     * Body of {@link #runDiscovery(Path, List, AiOptions, AiSuggestionEngine, TestMethodSink,
314
     * boolean, ClassificationOverride, AiResultCache, CredentialTriageContext)} that also returns one
315
     * {@link AiCacheEntry} per processed class (cache hits preserved, misses freshly computed) so the
316
     * caller can persist the unified AI result cache.
317
     *
318
     * @return the error flag plus the per-class cache entries keyed by content hash
319
     */
320
    @SuppressWarnings("PMD.CloseResource") // providers are owned by the caller; this method does not close them
321
    private DiscoveryResult runDiscoveryInternal(Path root, List<TestDiscovery> providers,
322
            AiOptions aiOptions, AiSuggestionEngine aiEngine, TestMethodSink sink,
323
            boolean contentHashEnabled, ClassificationOverride override,
324
            AiResultCache aiCache, CredentialTriageContext secretCtx) throws IOException {
325
326
        TestMethodSink effectiveSink = wrapWithExtraSink(sink);
327
        List<DiscoveredMethod> methods = new ArrayList<>();
328
        boolean hadErrors = false;
329
        for (TestDiscovery provider : providers) {
330
            provider.discover(root).forEach(methods::add);
331 2 1. runDiscoveryInternal : removed conditional - replaced equality check with true → KILLED
2. runDiscoveryInternal : removed conditional - replaced equality check with false → KILLED
            if (provider.hadErrors()) {
332
                hadErrors = true;
333
            }
334
        }
335
336
        Map<String, List<DiscoveredMethod>> byClass = methods.stream()
337
                .collect(Collectors.groupingBy(DiscoveredMethod::fqcn,
338
                        LinkedHashMap::new, Collectors.toList()));
339
340
        AiRuntime ai = new AiRuntime(aiOptions, aiEngine, override, aiCache);
341
        // The prompt-catalogue signature gates cache reuse and tags written entries;
342
        // only meaningful when AI is enabled.
343 2 1. runDiscoveryInternal : removed conditional - replaced equality check with false → SURVIVED
2. runDiscoveryInternal : removed conditional - replaced equality check with true → KILLED
        String promptSignature = aiEngine == null ? null : AiCacheKey.signature(aiOptions);
344
345
        // Fail-closed pre-pass: redact every class first so an unredactable file aborts
346
        // the run before the first provider call.
347 2 1. runDiscoveryInternal : removed conditional - replaced equality check with false → KILLED
2. runDiscoveryInternal : removed conditional - replaced equality check with true → KILLED
        Map<String, String> redacted = redactClasses(byClass, aiOptions, aiEngine != null);
348
349
        Map<String, ClassResolution> resolutions = resolveAll(
350
                byClass, redacted, ai, promptSignature, contentHashEnabled, secretCtx);
351
352
        Map<String, AiCacheEntry> cacheEntries = new LinkedHashMap<>();
353
        for (Map.Entry<String, List<DiscoveredMethod>> entry : byClass.entrySet()) {
354
            emitClass(entry, resolutions.get(entry.getKey()), promptSignature, effectiveSink, cacheEntries, root);
355
        }
356
357 1 1. runDiscoveryInternal : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::runDiscoveryInternal → KILLED
        return new DiscoveryResult(hadErrors, cacheEntries);
358
    }
359
360
    /**
361
     * Resolves AI suggestions for all classes, sequentially or in parallel depending on
362
     * {@link #parallelAi}. Returns a map keyed by FQCN; keyed in the same order as
363
     * {@code byClass} when sequential, unordered when parallel (callers must iterate
364
     * {@code byClass} for emission order).
365
     *
366
     * @param byClass         methods grouped by FQCN
367
     * @param redacted        redacted class sources by FQCN (empty when redaction is off)
368
     * @param ai              AI runtime (engine, override, cache)
369
     * @param promptSignature current prompt-catalogue signature, or {@code null}
370
     * @param contentHashEnabled whether content hashes should be computed
371
     * @param secretCtx       credential-triage context, or {@code null}
372
     * @return per-FQCN resolution results; never {@code null}
373
     */
374
    private Map<String, ClassResolution> resolveAll(
375
            Map<String, List<DiscoveredMethod>> byClass, Map<String, String> redacted, AiRuntime ai,
376
            String promptSignature, boolean contentHashEnabled, CredentialTriageContext secretCtx) {
377 2 1. resolveAll : removed conditional - replaced equality check with true → SURVIVED
2. resolveAll : removed conditional - replaced equality check with false → SURVIVED
        if (parallelAi) {
378 1 1. resolveAll : Replaced integer multiplication with division → NO_COVERAGE
            Map<String, ClassResolution> result = new ConcurrentHashMap<>(byClass.size() * 2);
379
            byClass.entrySet().parallelStream().forEach(entry ->
380
                    result.put(entry.getKey(),
381
                            resolveClass(entry, redacted, ai, promptSignature, contentHashEnabled, secretCtx)));
382 1 1. resolveAll : replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::resolveAll → NO_COVERAGE
            return result;
383
        }
384 1 1. resolveAll : Replaced integer multiplication with division → SURVIVED
        Map<String, ClassResolution> result = new LinkedHashMap<>(byClass.size() * 2);
385
        for (Map.Entry<String, List<DiscoveredMethod>> entry : byClass.entrySet()) {
386
            result.put(entry.getKey(), resolveClass(entry, redacted, ai, promptSignature, contentHashEnabled, secretCtx));
387
        }
388 1 1. resolveAll : replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::resolveAll → KILLED
        return result;
389
    }
390
391
    /**
392
     * Resolves the AI suggestion for one class without touching the sink.
393
     * All objects read here ({@link AiResultCache}, {@link org.egothor.methodatlas.emit.ClassificationOverride},
394
     * and the underlying {@link java.net.http.HttpClient}) are thread-safe, so this
395
     * method may be called from a parallel stream.
396
     *
397
     * @param entry           one class's discovered methods, keyed by FQCN
398
     * @param redacted        redacted class sources by FQCN; a class present here is hashed and
399
     *                        sent in this form instead of the source on disk
400
     * @param ai              AI runtime (engine, override, cache)
401
     * @param promptSignature current prompt-catalogue signature, or {@code null}
402
     * @param contentHashEnabled whether content hashes should be computed
403
     * @param secretCtx       credential-triage context, or {@code null}
404
     * @return the resolution result for this class; never {@code null}
405
     */
406
    private static ClassResolution resolveClass(Map.Entry<String, List<DiscoveredMethod>> entry,
407
            Map<String, String> redacted, AiRuntime ai, String promptSignature, boolean contentHashEnabled,
408
            CredentialTriageContext secretCtx) {
409
        String fqcn = entry.getKey();
410
        List<DiscoveredMethod> classMethods = entry.getValue();
411
412
        // groupingBy never produces an empty value list, so the first method is always
413
        // present; it is the representative carrying the class-level fields (source
414
        // content and file stem are identical across a class's methods).
415
        DiscoveredMethod representative = classMethods.get(0);
416 2 1. resolveClass : removed conditional - replaced equality check with false → KILLED
2. resolveClass : removed conditional - replaced equality check with true → KILLED
        String classSource = redacted.containsKey(fqcn) ? redacted.get(fqcn)
417
                : representative.sourceContent().get().orElse(null);
418
419
        // The content hash (computed on the redacted text when redaction is on, so it reflects
420
        // what was sent) is needed to read the cache, to emit the hash column, and to
421
        // key a written cache entry — so compute it whenever any of those apply.
422 6 1. resolveClass : removed conditional - replaced equality check with true → SURVIVED
2. resolveClass : removed conditional - replaced equality check with true → SURVIVED
3. resolveClass : removed conditional - replaced equality check with false → SURVIVED
4. resolveClass : removed conditional - replaced equality check with true → SURVIVED
5. resolveClass : removed conditional - replaced equality check with false → SURVIVED
6. resolveClass : removed conditional - replaced equality check with false → KILLED
        String lookupHash = (contentHashEnabled || ai.cache().isActive() || ai.engine() != null)
423 2 1. resolveClass : removed conditional - replaced equality check with false → SURVIVED
2. resolveClass : removed conditional - replaced equality check with true → KILLED
                && classSource != null
424
                ? ContentHasher.hashClass(classSource) : null;
425 2 1. resolveClass : removed conditional - replaced equality check with true → SURVIVED
2. resolveClass : removed conditional - replaced equality check with false → KILLED
        String outputHash = contentHashEnabled ? lookupHash : null;
426
427
        String fileStem = representative.fileStem();
428
        List<String> methodNames = classMethods.stream().map(DiscoveredMethod::method).toList();
429
        List<PromptBuilder.TargetMethod> targetMethods = classMethods.stream()
430
                .map(ScanOrchestrator::toTargetMethod)
431
                .toList();
432
433
        Resolved resolved = resolveSuggestionLookup(fileStem, fqcn, classSource, methodNames,
434
                targetMethods, ai, lookupHash, promptSignature, secretCtx);
435 1 1. resolveClass : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveClass → KILLED
        return new ClassResolution(resolved, lookupHash, outputHash);
436
    }
437
438
    /**
439
     * Emits one class's records to the sink and records the cache entry when applicable.
440
     * Must be called sequentially (in discovery order) to preserve output determinism.
441
     *
442
     * @param entry           one class's discovered methods, keyed by FQCN
443
     * @param resolution      pre-computed resolution for this class
444
     * @param promptSignature current prompt-catalogue signature, or {@code null}
445
     * @param effectiveSink   sink receiving the per-method records
446
     * @param cacheEntries    accumulator for cache entries, keyed by content hash
447
     * @param root            scan root the methods were discovered under; used to derive each
448
     *                        method's root-relative source path for location-bearing sinks
449
     * @throws IOException if the sink fails to record a method
450
     */
451
    private static void emitClass(Map.Entry<String, List<DiscoveredMethod>> entry,
452
            ClassResolution resolution, String promptSignature,
453
            TestMethodSink effectiveSink, Map<String, AiCacheEntry> cacheEntries,
454
            Path root) throws IOException {
455
        SuggestionLookup suggestions = resolution.resolved().lookup();
456
        String outputHash = resolution.outputHash();
457
458
        for (DiscoveredMethod m : entry.getValue()) {
459
            effectiveSink.recordWithSourcePath(m.fqcn(), m.method(), m.beginLine(), m.loc(), outputHash,
460
                    m.tags(), m.displayName(),
461
                    suggestions.find(m.method()).orElse(null), SourcePaths.relativeSourcePath(root, m.filePath()));
462
        }
463
464
        String lookupHash = resolution.lookupHash();
465
        AiClassSuggestion cacheable = resolution.resolved().cacheable();
466 4 1. emitClass : removed conditional - replaced equality check with true → SURVIVED
2. emitClass : removed conditional - replaced equality check with true → SURVIVED
3. emitClass : removed conditional - replaced equality check with false → KILLED
4. emitClass : removed conditional - replaced equality check with false → KILLED
        if (cacheable != null && lookupHash != null) {
467
            cacheEntries.put(lookupHash, new AiCacheEntry(lookupHash, promptSignature, cacheable));
468
        }
469
    }
470
471
    /**
472
     * Wraps {@code primary} with {@link CompositeTestMethodSink} when an
473
     * extra sink was supplied to the constructor; returns {@code primary}
474
     * unchanged otherwise.
475
     *
476
     * @param primary command-supplied primary sink
477
     * @return effective sink to feed during this discovery pass
478
     */
479
    private TestMethodSink wrapWithExtraSink(TestMethodSink primary) {
480 2 1. wrapWithExtraSink : removed conditional - replaced equality check with false → KILLED
2. wrapWithExtraSink : removed conditional - replaced equality check with true → KILLED
        if (extraSink == null) {
481 1 1. wrapWithExtraSink : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::wrapWithExtraSink → KILLED
            return primary;
482
        }
483 1 1. wrapWithExtraSink : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::wrapWithExtraSink → KILLED
        return new CompositeTestMethodSink(primary, extraSink);
484
    }
485
486
    /**
487
     * Collects all discovered methods from every configured root, keyed by
488
     * source-file path. Methods whose {@link DiscoveredMethod#filePath()} is
489
     * {@code null} are silently skipped.
490
     *
491
     * <p>
492
     * Providers are passed in pre-loaded so the caller can read each
493
     * provider's {@link TestDiscovery#hadErrors()} after the call and decide
494
     * how to propagate the exit code; the caller also owns closing them.
495
     * </p>
496
     *
497
     * @param roots     scan roots; must not be {@code null}
498
     * @param providers configured and already-loaded discovery providers;
499
     *                  must not be {@code null}
500
     * @return mutable map from source-file path to the methods found in that
501
     *         file; insertion order matches discovery order
502
     * @throws IOException if directory traversal fails for any root
503
     */
504
    @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops",
505
            "PMD.CloseResource"}) // providers are owned by the caller
506
    public Map<Path, List<DiscoveredMethod>> collectMethodsByFile(
507
            List<Path> roots, List<TestDiscovery> providers) throws IOException {
508
        Map<Path, List<DiscoveredMethod>> byFile = new LinkedHashMap<>();
509
        for (Path root : roots) {
510
            for (TestDiscovery provider : providers) {
511
                provider.discover(root).forEach(m -> {
512 2 1. lambda$collectMethodsByFile$2 : removed conditional - replaced equality check with true → SURVIVED
2. lambda$collectMethodsByFile$2 : removed conditional - replaced equality check with false → KILLED
                    if (m.filePath() != null) {
513 1 1. lambda$collectMethodsByFile$1 : replaced return value with Collections.emptyList for org/egothor/methodatlas/command/ScanOrchestrator::lambda$collectMethodsByFile$1 → KILLED
                        byFile.computeIfAbsent(m.filePath(), k -> new ArrayList<>()).add(m);
514
                    }
515
                });
516
            }
517
        }
518 1 1. collectMethodsByFile : replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::collectMethodsByFile → KILLED
        return byFile;
519
    }
520
521
    /**
522
     * Resolves AI security-classification suggestions for every class in
523
     * {@code byClass} and populates {@code tagsToApply} and
524
     * {@code displayNames} with the results for methods that are
525
     * security-relevant.
526
     *
527
     * <p>
528
     * A display-name suggestion is only placed into {@code displayNames}
529
     * when the discovered method has no existing {@code @DisplayName} in
530
     * source (i.e. {@link DiscoveredMethod#displayName()} returns
531
     * {@code null}). This prevents AI-generated names from overwriting
532
     * manually authored ones.
533
     * </p>
534
     *
535
     * @param byClass      discovered methods grouped by FQCN for one source file
536
     * @param ai           AI runtime carrying the engine, override, and cache
537
     * @param aiCache      AI result cache used to compute the content-hash lookup key
538
     * @param tagsToApply  output accumulator: method name to tag values to write
539
     * @param displayNames output accumulator: method name to display name to write
540
     * @since 1.0.0
541
     */
542
    public void gatherAiSuggestionsForFile(Map<String, List<DiscoveredMethod>> byClass,
543
            AiRuntime ai, AiResultCache aiCache,
544
            Map<String, List<String>> tagsToApply, Map<String, String> displayNames) {
545 2 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → SURVIVED
        String promptSignature = ai.engine() == null ? null : AiCacheKey.signatureUnchecked(ai.options());
546 2 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
        Map<String, String> redacted = redactClasses(byClass, ai.options(), ai.engine() != null);
547
        for (Map.Entry<String, List<DiscoveredMethod>> classEntry : byClass.entrySet()) {
548
            String fqcn = classEntry.getKey();
549
            List<DiscoveredMethod> classMethods = classEntry.getValue();
550
551
            // groupingBy never produces an empty value list; the first method is the
552
            // representative carrying the class-level fields (source content and file
553
            // stem are identical across a class's methods).
554
            DiscoveredMethod representative = classMethods.get(0);
555 2 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → KILLED
            String classSource = redacted.containsKey(fqcn) ? redacted.get(fqcn)
556
                    : representative.sourceContent().get().orElse(null);
557 4 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → NO_COVERAGE
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → SURVIVED
3. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
4. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → NO_COVERAGE
            String lookupHash = aiCache.isActive() && classSource != null
558
                    ? ContentHasher.hashClass(classSource) : null;
559
            String fileStem = representative.fileStem();
560
            List<String> methodNames = classMethods.stream().map(DiscoveredMethod::method).toList();
561
            List<PromptBuilder.TargetMethod> targetMethods = classMethods.stream()
562
                    .map(ScanOrchestrator::toTargetMethod).toList();
563
564
            SuggestionLookup suggestions = resolveSuggestionLookup(
565
                    fileStem, fqcn, classSource, methodNames, targetMethods, ai, lookupHash,
566
                    promptSignature, null).lookup();
567
568
            for (DiscoveredMethod m : classMethods) {
569
                AiMethodSuggestion suggestion = suggestions.find(m.method()).orElse(null);
570 4 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → KILLED
3. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → KILLED
4. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
                if (suggestion == null || !suggestion.securityRelevant()) {
571
                    continue;
572
                }
573 4 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → KILLED
3. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
4. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
                if (suggestion.displayName() != null && !suggestion.displayName().isBlank()
574 2 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → KILLED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
                        && m.displayName() == null) {
575
                    displayNames.putIfAbsent(m.method(), suggestion.displayName());
576
                }
577 4 1. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
2. gatherAiSuggestionsForFile : removed conditional - replaced equality check with true → SURVIVED
3. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
4. gatherAiSuggestionsForFile : removed conditional - replaced equality check with false → KILLED
                if (suggestion.tags() != null && !suggestion.tags().isEmpty()) {
578
                    tagsToApply.putIfAbsent(m.method(), suggestion.tags());
579
                }
580
            }
581
        }
582
    }
583
584
    /**
585
     * Verifies up front that every class of every given file can be redacted, so an
586
     * unredactable file aborts a multi-file run (apply-tags) before the first provider
587
     * call. A no-op unless {@code -redact-comments} is set and an AI engine is active.
588
     *
589
     * @param methodsPerFile discovered methods of each file that would reach the AI provider
590
     * @param ai             AI runtime carrying the options and engine
591
     * @throws RedactionAbortedException if any class cannot be redacted
592
     */
593
    public void requireRedactable(java.util.Collection<List<DiscoveredMethod>> methodsPerFile, AiRuntime ai) {
594
        for (List<DiscoveredMethod> methods : methodsPerFile) {
595
            Map<String, List<DiscoveredMethod>> byClass = methods.stream()
596
                    .collect(Collectors.groupingBy(DiscoveredMethod::fqcn,
597
                            LinkedHashMap::new, Collectors.toList()));
598 2 1. requireRedactable : removed conditional - replaced equality check with true → SURVIVED
2. requireRedactable : removed conditional - replaced equality check with false → KILLED
            redactClasses(byClass, ai.options(), ai.engine() != null);
599
        }
600
    }
601
602
    /**
603
     * Wraps a {@link TestMethodSink} so that only records that pass all
604
     * active filters are forwarded to {@code delegate}.
605
     *
606
     * <p>
607
     * Two independent filters are supported and composed in order:
608
     * </p>
609
     * <ol>
610
     *   <li><b>Security-only filter</b> — when {@code securityOnly} is
611
     *       {@code true}, records whose {@link AiMethodSuggestion} is
612
     *       {@code null} or has {@code securityRelevant=false} are dropped.</li>
613
     *   <li><b>Confidence threshold filter</b> — when {@code confidenceEnabled}
614
     *       is {@code true} <em>and</em> {@code minConfidence > 0.0}, records
615
     *       whose {@link AiMethodSuggestion} is {@code null} or has a
616
     *       {@link AiMethodSuggestion#confidence()} below {@code minConfidence}
617
     *       are dropped. This filter is a no-op when {@code confidenceEnabled}
618
     *       is {@code false} because the confidence field is always
619
     *       {@code 0.0} when confidence scoring was not requested.</li>
620
     * </ol>
621
     *
622
     * <p>
623
     * When neither filter is active the original {@code delegate} is
624
     * returned unchanged (zero overhead).
625
     * </p>
626
     *
627
     * @param delegate          the underlying sink to forward matching
628
     *                          records to
629
     * @param securityOnly      whether to enable the security-relevance filter
630
     * @param minConfidence     minimum confidence score (inclusive) required
631
     *                          to pass the confidence filter; {@code 0.0}
632
     *                          disables it
633
     * @param confidenceEnabled whether confidence scoring was requested;
634
     *                          must be {@code true} for the confidence
635
     *                          filter to activate
636
     * @return filtered sink, or {@code delegate} unchanged when all filters
637
     *         are off
638
     * @since 1.0.0
639
     */
640
    public TestMethodSink filterSink(TestMethodSink delegate, boolean securityOnly,
641
            double minConfidence, boolean confidenceEnabled) {
642
        TestMethodSink sink = delegate;
643 2 1. filterSink : removed conditional - replaced equality check with false → KILLED
2. filterSink : removed conditional - replaced equality check with true → KILLED
        if (securityOnly) {
644 5 1. lambda$filterSink$3 : removed conditional - replaced equality check with true → KILLED
2. lambda$filterSink$3 : removed conditional - replaced equality check with false → KILLED
3. lambda$filterSink$3 : removed conditional - replaced equality check with true → KILLED
4. lambda$filterSink$3 : removed conditional - replaced equality check with false → KILLED
5. lambda$filterSink$3 : replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::lambda$filterSink$3 → KILLED
            sink = new FilteringSink(sink, s -> s != null && s.securityRelevant());
645
        }
646 5 1. filterSink : removed conditional - replaced comparison check with true → SURVIVED
2. filterSink : changed conditional boundary → SURVIVED
3. filterSink : removed conditional - replaced equality check with false → KILLED
4. filterSink : removed conditional - replaced equality check with true → KILLED
5. filterSink : removed conditional - replaced comparison check with false → KILLED
        if (confidenceEnabled && minConfidence > 0.0) {
647
            final double threshold = minConfidence;
648 6 1. lambda$filterSink$4 : changed conditional boundary → SURVIVED
2. lambda$filterSink$4 : removed conditional - replaced equality check with true → KILLED
3. lambda$filterSink$4 : removed conditional - replaced equality check with false → KILLED
4. lambda$filterSink$4 : replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::lambda$filterSink$4 → KILLED
5. lambda$filterSink$4 : removed conditional - replaced comparison check with false → KILLED
6. lambda$filterSink$4 : removed conditional - replaced comparison check with true → KILLED
            sink = new FilteringSink(sink, s -> s != null && s.confidence() >= threshold);
649
        }
650 1 1. filterSink : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::filterSink → KILLED
        return sink;
651
    }
652
653
    /**
654
     * Forwards only records whose AI suggestion satisfies a predicate, preserving the
655
     * real source path so location-bearing downstream sinks (SARIF, GitHub annotations)
656
     * still receive it.
657
     */
658
    private static final class FilteringSink implements TestMethodSink {
659
        private final TestMethodSink next;
660
        private final java.util.function.Predicate<AiMethodSuggestion> accept;
661
662
        /* default */ FilteringSink(TestMethodSink next, java.util.function.Predicate<AiMethodSuggestion> accept) {
663
            this.next = next;
664
            this.accept = accept;
665
        }
666
667
        @Override
668
        @SuppressWarnings("PMD.UseObjectForClearerAPI")
669
        public void record(String fqcn, String method, int beginLine, int loc, String contentHash,
670
                List<String> tags, String displayName, AiMethodSuggestion suggestion) {
671 2 1. record : removed conditional - replaced equality check with false → KILLED
2. record : removed conditional - replaced equality check with true → KILLED
            if (accept.test(suggestion)) {
672
                next.record(fqcn, method, beginLine, loc, contentHash, tags, displayName, suggestion);
673
            }
674
        }
675
676
        @Override
677
        @SuppressWarnings("PMD.UseObjectForClearerAPI")
678
        public void recordWithSourcePath(String fqcn, String method, int beginLine, int loc, String contentHash,
679
                List<String> tags, String displayName, AiMethodSuggestion suggestion, String sourcePath) {
680 2 1. recordWithSourcePath : removed conditional - replaced equality check with true → KILLED
2. recordWithSourcePath : removed conditional - replaced equality check with false → KILLED
            if (accept.test(suggestion)) {
681
                next.recordWithSourcePath(fqcn, method, beginLine, loc, contentHash, tags, displayName, suggestion,
682
                        sourcePath);
683
            }
684
        }
685
    }
686
687
    // -------------------------------------------------------------------------
688
    // Static utilities
689
    // -------------------------------------------------------------------------
690
691
    /**
692
     * Converts a single discovered test method into a prompt target descriptor.
693
     *
694
     * <p>
695
     * Exposed publicly because {@link ManualPrepareCommand} also needs to
696
     * build prompt-target lists from discovered methods when writing manual
697
     * work files; the conversion logic must stay aligned across both call
698
     * sites to keep the prompt format consistent.
699
     * </p>
700
     *
701
     * @param m discovered test method; must not be {@code null}
702
     * @return corresponding prompt target descriptor; never {@code null}
703
     * @see PromptBuilder.TargetMethod
704
     */
705
    public static PromptBuilder.TargetMethod toTargetMethod(DiscoveredMethod m) {
706 1 1. toTargetMethod : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::toTargetMethod → KILLED
        return new PromptBuilder.TargetMethod(
707
                m.method(),
708 3 1. toTargetMethod : changed conditional boundary → KILLED
2. toTargetMethod : removed conditional - replaced comparison check with true → KILLED
3. toTargetMethod : removed conditional - replaced comparison check with false → KILLED
                m.beginLine() > 0 ? m.beginLine() : null,
709 3 1. toTargetMethod : changed conditional boundary → KILLED
2. toTargetMethod : removed conditional - replaced comparison check with true → KILLED
3. toTargetMethod : removed conditional - replaced comparison check with false → KILLED
                m.endLine() > 0 ? m.endLine() : null);
710
    }
711
712
    // -------------------------------------------------------------------------
713
    // Private helpers
714
    // -------------------------------------------------------------------------
715
716
    /**
717
     * Redacts the representative source of every class when {@code -redact-comments} is
718
     * active, sequentially, before any provider call. Sources are transformed in memory;
719
     * the files on disk (and therefore source write-back) are never touched.
720
     *
721
     * @param byClass  discovered methods grouped by FQCN
722
     * @param options  AI options of the run
723
     * @param aiActive whether an AI engine will receive class source
724
     * @return redacted source by FQCN; empty when redaction is off or nothing is sent
725
     * @throws RedactionAbortedException if any class cannot be redacted
726
     */
727
    private Map<String, String> redactClasses(Map<String, List<DiscoveredMethod>> byClass,
728
            AiOptions options, boolean aiActive) {
729 6 1. redactClasses : removed conditional - replaced equality check with true → SURVIVED
2. redactClasses : removed conditional - replaced equality check with false → KILLED
3. redactClasses : removed conditional - replaced equality check with false → KILLED
4. redactClasses : removed conditional - replaced equality check with true → KILLED
5. redactClasses : removed conditional - replaced equality check with true → KILLED
6. redactClasses : removed conditional - replaced equality check with false → KILLED
        if (!aiActive || options == null || !options.redactComments()) {
730
            return Map.of();
731
        }
732 1 1. redactClasses : Replaced integer multiplication with division → SURVIVED
        Map<String, String> redacted = new LinkedHashMap<>(byClass.size() * 2);
733
        for (Map.Entry<String, List<DiscoveredMethod>> entry : byClass.entrySet()) {
734
            DiscoveredMethod representative = entry.getValue().get(0);
735
            String source = representative.sourceContent().get().orElse(null);
736 2 1. redactClasses : removed conditional - replaced equality check with true → SURVIVED
2. redactClasses : removed conditional - replaced equality check with false → KILLED
            if (source != null) {
737
                redacted.put(entry.getKey(), redaction.redactOrThrow(representative.filePath(), source));
738
            }
739
        }
740 1 1. redactClasses : replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::redactClasses → KILLED
        return redacted;
741
    }
742
743
    /**
744
     * Resolves the AI answer for one class, consulting the signature-gated cache
745
     * before any provider call and serving cached credential verdicts when present.
746
     *
747
     * @param promptSignature the current run's prompt-catalogue signature, or
748
     *                        {@code null} when AI is disabled
749
     * @return the override-applied lookup plus the raw answer to cache (or
750
     *         {@code null} when nothing should be cached)
751
     */
752
    private static Resolved resolveSuggestionLookup(String fileStem, String fqcn,
753
            String classSource, List<String> methodNames, List<PromptBuilder.TargetMethod> targetMethods,
754
            AiRuntime ai, String contentHash, String promptSignature, CredentialTriageContext secretCtx) {
755 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with false → SURVIVED
2. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
        if (methodNames.isEmpty()) {
756 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → NO_COVERAGE
            return new Resolved(SuggestionLookup.from(null), null);
757
        }
758
759 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
2. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
        if (ai.engine() == null) {
760 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED
            return new Resolved(SuggestionLookup.from(ai.override().apply(fqcn, null, methodNames)), null);
761
        }
762
763
        List<PromptBuilder.CredentialCandidateRef> candidates =
764 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
2. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
                secretCtx == null ? List.of() : secretCtx.candidatesFor(fqcn);
765
766
        // Cache first, gated on the prompt-catalogue signature. A hit serves the
767
        // classification AND (when this run triages credentials) the verdicts —
768
        // from the same cached answer, with no provider call.
769
        AiClassSuggestion cached = ai.cache().classification(contentHash, promptSignature).orElse(null);
770 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
2. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
        if (cached != null) {
771 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
2. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
            AiClassSuggestion cacheable = candidates.isEmpty() ? cached
772
                    : serveOrTriageVerdicts(ai, fqcn, classSource, contentHash, promptSignature,
773
                            candidates, cached, secretCtx);
774 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED
            return new Resolved(SuggestionLookup.from(ai.override().apply(fqcn, cached, methodNames)), cacheable);
775
        }
776
777 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with false → SURVIVED
2. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
        if (classSource == null) {
778 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → NO_COVERAGE
            return new Resolved(SuggestionLookup.from(ai.override().apply(fqcn, null, methodNames)), null);
779
        }
780
781 5 1. resolveSuggestionLookup : removed conditional - replaced equality check with true → SURVIVED
2. resolveSuggestionLookup : changed conditional boundary → SURVIVED
3. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
4. resolveSuggestionLookup : removed conditional - replaced comparison check with true → KILLED
5. resolveSuggestionLookup : removed conditional - replaced comparison check with false → KILLED
        if (ai.options().enabled() && classSource.length() > ai.options().maxClassChars()) {
782
            if (LOG.isLoggable(Level.INFO)) {
783
                LOG.log(Level.INFO, "Skipping AI for {0}: class source too large ({1} chars)",
784
                        new Object[] { fqcn, classSource.length() });
785
            }
786 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED
            return new Resolved(SuggestionLookup.from(ai.override().apply(fqcn, null, methodNames)), null);
787
        }
788
789
        if (LOG.isLoggable(Level.INFO)) {
790
            LOG.log(Level.INFO, "Querying AI for {0} ({1} methods)", new Object[] { fqcn, targetMethods.size() });
791
        }
792
793
        try {
794
            // Fold credential triage into the single classification call when there
795
            // are candidates, so the class source is sent to the provider once.
796
            AiClassSuggestion aiClassSuggestion;
797 2 1. resolveSuggestionLookup : removed conditional - replaced equality check with true → KILLED
2. resolveSuggestionLookup : removed conditional - replaced equality check with false → KILLED
            if (candidates.isEmpty()) {
798
                aiClassSuggestion = ai.engine().suggestForClass(fileStem, fqcn, classSource, targetMethods);
799
            } else {
800
                aiClassSuggestion =
801
                        ai.engine().suggestForClass(fileStem, fqcn, classSource, targetMethods, candidates);
802
                secretCtx.recordVerdicts(fqcn, aiClassSuggestion.secrets());
803
            }
804 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED
            return new Resolved(
805
                    SuggestionLookup.from(ai.override().apply(fqcn, aiClassSuggestion, methodNames)),
806
                    aiClassSuggestion);
807
        } catch (AiSuggestionException e) {
808
            if (LOG.isLoggable(Level.WARNING)) {
809
                LOG.log(Level.WARNING, "AI suggestion failed for class " + fqcn, e);
810
            }
811 1 1. resolveSuggestionLookup : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED
            return new Resolved(SuggestionLookup.from(ai.override().apply(fqcn, null, methodNames)), null);
812
        }
813
    }
814
815
    /**
816
     * On a classification cache hit, supplies the credential verdicts: cached when
817
     * present for this signature, otherwise a one-off dedicated triage call (so a
818
     * class whose classification was cached without verdicts still gets scored).
819
     *
820
     * @return the cached suggestion augmented with the resolved verdicts, for re-caching
821
     */
822
    private static AiClassSuggestion serveOrTriageVerdicts(AiRuntime ai, String fqcn, String classSource,
823
            String contentHash, String promptSignature,
824
            List<PromptBuilder.CredentialCandidateRef> candidates, AiClassSuggestion cached,
825
            CredentialTriageContext secretCtx) {
826
        Optional<List<CredentialTriageVerdict>> cachedVerdicts =
827
                ai.cache().verdicts(contentHash, promptSignature);
828 2 1. serveOrTriageVerdicts : removed conditional - replaced equality check with false → KILLED
2. serveOrTriageVerdicts : removed conditional - replaced equality check with true → KILLED
        if (cachedVerdicts.isPresent()) {
829
            secretCtx.recordVerdicts(fqcn, cachedVerdicts.get());
830 1 1. serveOrTriageVerdicts : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → SURVIVED
            return withSecrets(cached, cachedVerdicts.get());
831
        }
832 2 1. serveOrTriageVerdicts : removed conditional - replaced equality check with false → SURVIVED
2. serveOrTriageVerdicts : removed conditional - replaced equality check with true → KILLED
        if (classSource == null) {
833 1 1. serveOrTriageVerdicts : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → NO_COVERAGE
            return cached;
834
        }
835
        try {
836
            List<CredentialTriageVerdict> verdicts = ai.engine().triageSecrets(fqcn, classSource, candidates);
837
            secretCtx.recordVerdicts(fqcn, verdicts);
838 1 1. serveOrTriageVerdicts : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → SURVIVED
            return withSecrets(cached, verdicts);
839
        } catch (AiSuggestionException e) {
840
            if (LOG.isLoggable(Level.WARNING)) {
841
                LOG.log(Level.WARNING, "Credential triage failed for cached class " + fqcn, e);
842
            }
843 1 1. serveOrTriageVerdicts : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → NO_COVERAGE
            return cached;
844
        }
845
    }
846
847
    /**
848
     * Returns a copy of {@code suggestion} with its credential verdicts replaced.
849
     *
850
     * @param suggestion the classification result to copy; never {@code null}
851
     * @param secrets    the verdicts to attach; may be {@code null}
852
     * @return a new suggestion carrying {@code secrets}
853
     */
854
    private static AiClassSuggestion withSecrets(AiClassSuggestion suggestion,
855
            List<CredentialTriageVerdict> secrets) {
856 1 1. withSecrets : replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::withSecrets → SURVIVED
        return new AiClassSuggestion(suggestion.className(), suggestion.classSecurityRelevant(),
857
                suggestion.classTags(), suggestion.classReason(), suggestion.methods(), secrets);
858
    }
859
860
    /**
861
     * The outcome of resolving one class: the override-applied per-method lookup, and
862
     * the raw AI answer to persist in the cache ({@code null} when nothing should be
863
     * cached — no AI, no methods, oversized source, or a failed call).
864
     *
865
     * @param lookup    per-method suggestion lookup fed to the sink; never {@code null}
866
     * @param cacheable the full AI answer to cache, or {@code null}
867
     */
868
    private record Resolved(SuggestionLookup lookup, AiClassSuggestion cacheable) {
869
    }
870
871
    /**
872
     * The result of resolving AI suggestions for one class, separated from sink
873
     * emission so that resolution can be parallelised while emission stays sequential.
874
     *
875
     * @param resolved    override-applied suggestion lookup and optional cache answer
876
     * @param lookupHash  SHA-256 of the class source, used to key the cache entry;
877
     *                    {@code null} when hashing was not requested or source was absent
878
     * @param outputHash  same hash for inclusion in emitted records, or {@code null}
879
     *                    when {@code -content-hash} is not enabled
880
     */
881
    private record ClassResolution(Resolved resolved, String lookupHash, String outputHash) {
882
    }
883
884
    /**
885
     * The result of scanning one root: whether any provider errored, and the unified
886
     * cache entries collected for the classes processed in that root.
887
     *
888
     * @param hadErrors    {@code true} if any provider reported a non-fatal error
889
     * @param cacheEntries cache entries keyed by content hash; never {@code null}
890
     */
891
    private record DiscoveryResult(boolean hadErrors, Map<String, AiCacheEntry> cacheEntries) {
892
    }
893
}

Mutations

212

1.1
Location : redaction
Killed by : org.egothor.methodatlas.command.CredentialDetectionRunnerRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.CredentialDetectionRunnerRedactionTest]/[method:separatePath_unredactableFileAbortsBeforeAnyProviderCall()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::redaction → KILLED

250

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

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

255

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

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

258

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

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

265

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

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

268

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

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

308

1.1
Location : runDiscovery
Killed by : org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest]/[method:scan_withoutRedact_providerReceivesOriginalSource(java.nio.file.Path)]
replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::runDiscovery → KILLED

2.2
Location : runDiscovery
Killed by : org.egothor.methodatlas.MethodAtlasAppScanCoverageTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppScanCoverageTest]/[method:csvMode_unparseableFile_returnsExitCode1(java.nio.file.Path)]
replaced boolean return with false for org/egothor/methodatlas/command/ScanOrchestrator::runDiscovery → KILLED

331

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

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

343

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

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

347

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

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

357

1.1
Location : runDiscoveryInternal
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::runDiscoveryInternal → KILLED

377

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

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

378

1.1
Location : resolveAll
Killed by : none
Replaced integer multiplication with division → NO_COVERAGE

382

1.1
Location : resolveAll
Killed by : none
replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::resolveAll → NO_COVERAGE

384

1.1
Location : resolveAll
Killed by : none
Replaced integer multiplication with division → SURVIVED
Covering tests

388

1.1
Location : resolveAll
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::resolveAll → KILLED

416

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

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

422

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

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

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

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

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

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

423

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

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

425

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

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

435

1.1
Location : resolveClass
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveClass → KILLED

466

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

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

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

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

480

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

2.2
Location : wrapWithExtraSink
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

481

1.1
Location : wrapWithExtraSink
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::wrapWithExtraSink → KILLED

483

1.1
Location : wrapWithExtraSink
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/command/ScanOrchestrator::wrapWithExtraSink → KILLED

512

1.1
Location : lambda$collectMethodsByFile$2
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : lambda$collectMethodsByFile$2
Killed by : org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest]/[method:applyTags_withRedactionAndNoRedactorInjected_abortsBeforeProviderAndLeavesFileUntouched(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

513

1.1
Location : lambda$collectMethodsByFile$1
Killed by : org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest]/[method:applyTags_withRedactionAndNoRedactorInjected_abortsBeforeProviderAndLeavesFileUntouched(java.nio.file.Path)]
replaced return value with Collections.emptyList for org/egothor/methodatlas/command/ScanOrchestrator::lambda$collectMethodsByFile$1 → KILLED

518

1.1
Location : collectMethodsByFile
Killed by : org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.MethodAtlasAppRedactCommentsTest]/[method:applyTags_withRedactionAndNoRedactorInjected_abortsBeforeProviderAndLeavesFileUntouched(java.nio.file.Path)]
replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::collectMethodsByFile → KILLED

545

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

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

546

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

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

555

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

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

557

1.1
Location : gatherAiSuggestionsForFile
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

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

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

4.4
Location : gatherAiSuggestionsForFile
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

570

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

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

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

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

573

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

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

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

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

574

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

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

577

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

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

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

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

598

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

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

643

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

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

644

1.1
Location : lambda$filterSink$3
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_securityOnly_dropsNonSecurityRecords()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : lambda$filterSink$3
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_securityOnly_dropsNonSecurityRecords()]
removed conditional - replaced equality check with false → KILLED

3.3
Location : lambda$filterSink$3
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_securityOnly_dropsNonSecurityRecords()]
removed conditional - replaced equality check with true → KILLED

4.4
Location : lambda$filterSink$3
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_securityOnly_dropsNonSecurityRecords()]
removed conditional - replaced equality check with false → KILLED

5.5
Location : lambda$filterSink$3
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_securityOnly_dropsNonSecurityRecords()]
replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::lambda$filterSink$3 → KILLED

646

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

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

3.3
Location : filterSink
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED
Covering tests

4.4
Location : filterSink
Killed by : none
changed conditional boundary → SURVIVED Covering tests

5.5
Location : filterSink
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
removed conditional - replaced comparison check with false → KILLED

648

1.1
Location : lambda$filterSink$4
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : lambda$filterSink$4
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
removed conditional - replaced equality check with false → KILLED

3.3
Location : lambda$filterSink$4
Killed by : none
changed conditional boundary → SURVIVED
Covering tests

4.4
Location : lambda$filterSink$4
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
replaced boolean return with true for org/egothor/methodatlas/command/ScanOrchestrator::lambda$filterSink$4 → KILLED

5.5
Location : lambda$filterSink$4
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
removed conditional - replaced comparison check with false → KILLED

6.6
Location : lambda$filterSink$4
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_confidenceFilter_dropsLowConfidenceRecords()]
removed conditional - replaced comparison check with true → KILLED

650

1.1
Location : filterSink
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:filterSink_neitherFilterActive_returnsDelegateUnchanged()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::filterSink → KILLED

671

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

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

680

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

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

706

1.1
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_nonPositiveLineNumbers_collapseToNull()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::toTargetMethod → KILLED

708

1.1
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_nonPositiveLineNumbers_collapseToNull()]
changed conditional boundary → KILLED

2.2
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_nonPositiveLineNumbers_collapseToNull()]
removed conditional - replaced comparison check with true → KILLED

3.3
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_validBeginAndEndLines_preservesBoth()]
removed conditional - replaced comparison check with false → KILLED

709

1.1
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_nonPositiveLineNumbers_collapseToNull()]
changed conditional boundary → KILLED

2.2
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_nonPositiveLineNumbers_collapseToNull()]
removed conditional - replaced comparison check with true → KILLED

3.3
Location : toTargetMethod
Killed by : org.egothor.methodatlas.command.ScanOrchestratorTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorTest]/[method:toTargetMethod_validBeginAndEndLines_preservesBoth()]
removed conditional - replaced comparison check with false → KILLED

729

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

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

3.3
Location : redactClasses
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionOff_engineReceivesOriginalSourceAndHashIsOfOriginal()]
removed conditional - replaced equality check with false → KILLED

4.4
Location : redactClasses
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
removed conditional - replaced equality check with true → KILLED

5.5
Location : redactClasses
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:gatherAiSuggestions_unsupportedLanguage_abortsWithZeroProviderCalls()]
removed conditional - replaced equality check with true → KILLED

6.6
Location : redactClasses
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:gatherAiSuggestions_unsupportedLanguage_abortsWithZeroProviderCalls()]
removed conditional - replaced equality check with false → KILLED

732

1.1
Location : redactClasses
Killed by : none
Replaced integer multiplication with division → SURVIVED
Covering tests

736

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

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

740

1.1
Location : redactClasses
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:gatherAiSuggestions_sendsRedactedSource()]
replaced return value with Collections.emptyMap for org/egothor/methodatlas/command/ScanOrchestrator::redactClasses → KILLED

755

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

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

756

1.1
Location : resolveSuggestionLookup
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → NO_COVERAGE

759

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

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

760

1.1
Location : resolveSuggestionLookup
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:redactionRequestedButAiInactive_isNoOp()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED

764

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

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

770

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

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

771

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

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

774

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

777

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

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

778

1.1
Location : resolveSuggestionLookup
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → NO_COVERAGE

781

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

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

3.3
Location : resolveSuggestionLookup
Killed by : none
changed conditional boundary → SURVIVED Covering tests

4.4
Location : resolveSuggestionLookup
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:gatherAiSuggestions_sendsRedactedSource()]
removed conditional - replaced comparison check with true → KILLED

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

786

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

797

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

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

804

1.1
Location : resolveSuggestionLookup
Killed by : org.egothor.methodatlas.command.ScanOrchestratorRedactionTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.command.ScanOrchestratorRedactionTest]/[method:gatherAiSuggestions_sendsRedactedSource()]
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::resolveSuggestionLookup → KILLED

811

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

828

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

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

830

1.1
Location : serveOrTriageVerdicts
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → SURVIVED
Covering tests

832

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

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

833

1.1
Location : serveOrTriageVerdicts
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → NO_COVERAGE

838

1.1
Location : serveOrTriageVerdicts
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → SURVIVED
Covering tests

843

1.1
Location : serveOrTriageVerdicts
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::serveOrTriageVerdicts → NO_COVERAGE

856

1.1
Location : withSecrets
Killed by : none
replaced return value with null for org/egothor/methodatlas/command/ScanOrchestrator::withSecrets → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.30.1 support