CredentialTriageContext.java

package org.egothor.methodatlas.command;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.egothor.methodatlas.ai.PromptBuilder;
import org.egothor.methodatlas.ai.CredentialTriageVerdict;

/**
 * Carries the per-class credential candidates into the scan loop and collects the
 * triage verdicts the AI returns, so classification and credential triage share a
 * single per-class provider call — the class source is transmitted only once.
 *
 * <p>
 * Populated up-front by the credential-detection pass (candidates keyed by fully
 * qualified class name, in finding order) and mutated during the single-threaded
 * discovery loop as each class is classified. Not thread-safe; the scan loop is
 * single-threaded.
 * </p>
 *
 * @since 4.1.0
 */
final class CredentialTriageContext {

    private final Map<String, List<PromptBuilder.CredentialCandidateRef>> candidatesByFqcn;
    private final Map<String, List<CredentialTriageVerdict>> verdictsByFqcn = new HashMap<>();

    /**
     * Creates a context for the supplied candidates.
     *
     * @param candidatesByFqcn candidate spans per class, in finding order; never {@code null}
     */
    /* default */ CredentialTriageContext(Map<String, List<PromptBuilder.CredentialCandidateRef>> candidatesByFqcn) {
        this.candidatesByFqcn = Map.copyOf(candidatesByFqcn);
    }

    /**
     * Returns the candidates for a class, or an empty list when none were detected.
     *
     * @param fqcn fully qualified class name
     * @return candidate spans for the class; never {@code null}
     */
    /* default */ List<PromptBuilder.CredentialCandidateRef> candidatesFor(String fqcn) {
        return candidatesByFqcn.getOrDefault(fqcn, List.of());
    }

    /**
     * Records the triage verdicts returned for a class.
     *
     * @param fqcn     fully qualified class name
     * @param verdicts verdicts from the folded AI call; ignored when {@code null}/empty
     */
    /* default */ void recordVerdicts(String fqcn, List<CredentialTriageVerdict> verdicts) {
        if (verdicts != null && !verdicts.isEmpty()) {
            verdictsByFqcn.put(fqcn, verdicts);
        }
    }

    /**
     * Returns the collected verdicts keyed by class.
     *
     * @return verdicts per class; never {@code null}
     */
    /* default */ Map<String, List<CredentialTriageVerdict>> verdictsByFqcn() {
        return verdictsByFqcn;
    }
}