MethodAtlas supports five report modes — CSV (default), plain text, SARIF, GitHub Actions annotations, and JSON — plus a write-back mode (-apply-tags) that modifies source files directly instead of emitting a report.
All report modes produce one record per discovered test method.
CSV mode is the default. It produces a header row followed by one data row per test method.
fqcn,method,loc,tags,display_name
com.acme.tests.SampleOneTest,alpha,8,fast;crypto,
com.acme.tests.SampleOneTest,beta,6,param,
com.acme.tests.SampleOneTest,gamma,4,nested1;nested2,
com.acme.other.AnotherTest,delta,3,,
Multiple JUnit @Tag values are joined with ;. An empty tags field means the method has no source-level tags. The display_name field contains any @DisplayName annotation value declared on the method; it is empty when the annotation is absent.
Pass -content-hash to append a SHA-256 fingerprint column immediately after tags:
fqcn,method,loc,tags,display_name,content_hash
com.acme.tests.SampleOneTest,alpha,8,fast;crypto,,3a7f9b2e...
com.acme.tests.SampleOneTest,beta,6,param,,3a7f9b2e...
com.acme.other.AnotherTest,delta,3,,,f1c04a8d...
The hash is a 64-character lowercase hexadecimal string (SHA-256). It is computed from the AST text of the enclosing class, so all test methods in the same class share the same value. The hash changes only when the class body changes, not when unrelated files in the same package change.
fqcn,method,loc,tags,display_name,ai_security_relevant,ai_display_name,ai_tags,ai_reason,ai_interaction_score
com.acme.tests.SampleOneTest,alpha,8,fast;crypto,,true,"SECURITY: crypto - validates encrypted happy path",security;crypto,The test exercises a crypto-related security property.,0.0
com.acme.tests.SampleOneTest,beta,6,param,,false,,,,0.2
Fields ai_display_name, ai_tags, and ai_reason are empty for non-security-relevant methods. ai_interaction_score is always present when AI is enabled — see Interaction Score for its meaning and use in CI gates.
When -content-hash is combined with -ai, the content_hash column appears between display_name and ai_security_relevant:
fqcn,method,loc,tags,display_name,content_hash,ai_security_relevant,ai_display_name,ai_tags,ai_reason,ai_interaction_score
fqcn,method,loc,tags,display_name,ai_security_relevant,ai_display_name,ai_tags,ai_reason,ai_interaction_score,ai_confidence
com.acme.tests.SampleOneTest,alpha,8,fast;crypto,,true,"SECURITY: crypto - validates encrypted happy path",security;crypto,The test exercises a crypto-related security property.,0.0,0.9
com.acme.tests.SampleOneTest,beta,6,param,,false,,,,0.2,0.0
ai_confidence is 0.0 for methods classified as not security-relevant. ai_interaction_score is always present when AI is enabled.
Pass -drift-detect alongside -ai to append a tag_ai_drift column at the end of each row:
fqcn,method,loc,tags,display_name,ai_security_relevant,ai_display_name,ai_tags,ai_reason,ai_interaction_score,tag_ai_drift
com.acme.tests.SampleOneTest,alpha,8,security;crypto,,true,"SECURITY: crypto - ...",security;crypto,The test exercises...,0.0,none
com.acme.tests.SampleOneTest,beta,6,,,true,"SECURITY: auth - ...",security;auth,Verifies auth...,0.1,ai-only
com.acme.tests.SampleOneTest,gamma,4,security,,,,,0.0,tag-only
tag_ai_drift value |
Meaning |
|---|---|
none |
Source annotation and AI classification agree |
ai-only |
AI classifies as security-relevant; no @Tag("security") in source |
tag-only |
@Tag("security") present in source; AI does not classify as security-relevant |
When -ai-confidence is also set, ai_confidence appears between ai_interaction_score and tag_ai_drift.
Pass -emit-source-root when scanning multiple roots where the same fully qualified class name can appear under different source trees. The flag appends a source_root column immediately after display_name (before content_hash and AI columns):
fqcn,method,loc,tags,display_name,source_root
com.acme.auth.AuthTest,testLogin,12,security,,module-a/src/test/java/
com.acme.auth.AuthTest,testLogout,8,,,module-b/src/test/java/
The column value is the CWD-relative path of the scan root with a trailing /. When a scan root is the current working directory itself, the column is empty. The column is absent from the header and all rows when the flag is not set, so downstream scripts that do not need it are unaffected.
The flag can be combined with all other column flags. When combined with -content-hash and -ai:
fqcn,method,loc,tags,display_name,source_root,content_hash,ai_security_relevant,...
See Multi-root and monorepo scanning for a detailed walkthrough and a CI pipeline example.
Pass -emit-metadata to prepend # key: value comment lines before the CSV header:
# tool_version: 1.2.0
# scan_timestamp: 2025-04-09T10:15:30Z
# taxonomy: built-in/default
fqcn,method,loc,tags,...
Standard CSV parsers treat #-prefixed lines as comments and skip them. The lines help historical output files remain interpretable when compared over time.
Enable plain mode with -plain:
./methodatlas -plain /path/to/projectPlain mode renders one human-readable line per method:
com.acme.tests.SampleOneTest, alpha, LOC=8, TAGS=fast;crypto, DISPLAY=-
com.acme.tests.SampleOneTest, beta, LOC=6, TAGS=param, DISPLAY=-
com.acme.tests.SampleOneTest, gamma, LOC=4, TAGS=nested1;nested2, DISPLAY=-
com.acme.other.AnotherTest, delta, LOC=3, TAGS=-, DISPLAY=-
TAGS=- is printed when a method has no source-level JUnit tags. DISPLAY=- is printed when the method has no @DisplayName annotation; when the annotation is present its value is printed verbatim.
When -content-hash is also passed, a HASH=<value> token is appended after DISPLAY:
com.acme.tests.SampleOneTest, alpha, LOC=8, TAGS=fast;crypto, DISPLAY=-, HASH=3a7f9b2e...
com.acme.tests.SampleOneTest, beta, LOC=6, TAGS=param, DISPLAY=-, HASH=3a7f9b2e...
com.acme.other.AnotherTest, delta, LOC=3, TAGS=-, DISPLAY=-, HASH=f1c04a8d...
com.acme.tests.SampleOneTest, alpha, LOC=8, TAGS=fast;crypto, DISPLAY=-, AI_SECURITY=true, AI_DISPLAY=SECURITY: crypto - validates encrypted happy path, AI_TAGS=security;crypto, AI_REASON=The test exercises a crypto-related security property., AI_INTERACTION_SCORE=0.0
com.acme.tests.SampleOneTest, beta, LOC=6, TAGS=param, DISPLAY=-, AI_SECURITY=false, AI_DISPLAY=-, AI_TAGS=-, AI_REASON=-, AI_INTERACTION_SCORE=0.2
Absent AI values are printed as - in plain mode. AI_INTERACTION_SCORE is always present when AI is enabled.
When -ai-confidence is also passed, an AI_CONFIDENCE token is appended after AI_INTERACTION_SCORE:
com.acme.tests.SampleOneTest, alpha, LOC=8, TAGS=fast;crypto, DISPLAY=-, AI_SECURITY=true, AI_DISPLAY=SECURITY: crypto - validates encrypted happy path, AI_TAGS=security;crypto, AI_REASON=The test exercises a crypto-related security property., AI_INTERACTION_SCORE=0.0, AI_CONFIDENCE=0.9
When -emit-source-root is passed, a SRCROOT= token is appended after DISPLAY (before HASH when that flag is also set):
com.acme.auth.AuthTest, testLogin, LOC=12, TAGS=security, DISPLAY=-, SRCROOT=module-a/src/test/java/
com.acme.auth.AuthTest, testLogout, LOC=8, TAGS=-, DISPLAY=-, SRCROOT=module-b/src/test/java/
When the scan root is the current working directory itself, SRCROOT=- is printed.
Enable SARIF mode with -sarif:
./methodatlas -sarif /path/to/project
./methodatlas -ai -sarif /path/to/projectMethodAtlas buffers all discovered test methods and, after the scan completes, emits a single SARIF 2.1.0 JSON document to standard output. The document contains one SARIF run with one result per test method.
The SARIF level field distinguishes security-relevant methods from ordinary test methods:
| Level | Condition |
|---|---|
note |
The AI classified the method as security-relevant, or the method carries @DisplayName("") |
none |
All other test methods (no AI, or AI returned securityRelevant=false) |
Each SARIF result carries a message.text field that stands alone as a complete, human-readable annotation. The design goal is that an operator should be able to act on a finding without leaving the SARIF viewer or opening the raw JSON.
SARIF results have two parallel channels for data:
message.text— the visible annotation shown in every SARIF-aware tool (GitHub Code Scanning, Azure DevOps, SonarQube, IDE plugins, and custom viewers alike).propertiesbag — structured key/value metadata attached to each result. Whether this bag is surfaced in the UI depends entirely on the consuming tool.
GitHub Code Scanning does not display the properties bag. When a SARIF file is uploaded to GitHub, the Security tab shows only the message.text field as the inline annotation on the code. The properties values (interaction score, confidence, tags, reason) are stored in GitHub's database and accessible via the API, but they are not shown in the finding panel that an operator reviews during triage.
Because of this, MethodAtlas embeds the interaction score and, when -ai-confidence is active, the confidence percentage directly in the message.text by default. An operator reviewing findings in GitHub sees the score immediately in the annotation without needing to download and inspect the raw SARIF JSON.
For SARIF viewers that do surface the properties bag (custom tooling, enterprise SAST dashboards, etc.), the scores would appear twice — once in the message and once in the structured properties. In that case, pass -sarif-omit-scores to suppress the inline embedding and keep the message clean. See -sarif-omit-scores for details.
Security-relevant method (security/<tag> or security-test) — states the AI's suggested @DisplayName and @Tag values, the reasoning, and the interaction score. When -ai-confidence is active the confidence percentage follows. When the score is ≥ 0.8 an additional sentence points to the security-test/placebo finding.
Default (no -ai-confidence):
AI suggests: @DisplayName("SECURITY: auth - verify API key absence") @Tag("security") @Tag("auth"). Reason: The test verifies that requests without a valid API key are rejected with 401. Interaction score: 0.12.
With -ai-confidence and a low interaction score:
AI suggests: @DisplayName("SECURITY: auth - verify API key absence") @Tag("security") @Tag("auth"). Reason: The test verifies that requests without a valid API key are rejected with 401. Interaction score: 0.12. Confidence: 88%.
With -ai-confidence and a high interaction score (≥ 0.8):
AI suggests: @DisplayName("SECURITY: auth - verify API key absence") @Tag("security") @Tag("auth"). Reason: The test verifies that requests without a valid API key are rejected with 401. Interaction score: 0.90. Confidence: 88%. Assertions primarily verify method calls, not actual outcomes. See the security-test/placebo finding for remediation guidance.
With -sarif-omit-scores (scores suppressed from message):
AI suggests: @DisplayName("SECURITY: auth - verify API key absence") @Tag("security") @Tag("auth"). Reason: The test verifies that requests without a valid API key are rejected with 401.
security-test/placebo — a separate result emitted alongside the primary security finding when ai_interaction_score >= 0.8. The message always states the actual score and the threshold it was compared against, because those values are the core content of this finding, not supplementary context. The interaction score is never suppressed from this message even when -sarif-omit-scores is active.
Interaction score: 0.90 (threshold: 0.8). This security test only verifies that methods were called, not what values they returned or what state they produced. Tests that do not assert outcomes cannot catch regressions in security-critical logic. Add assertions on return values, thrown exceptions, or observable state changes.
annotation/empty-display-name — names the class and method, explains the audit impact, and states the corrective action:
@DisplayName("") on com.acme.util.HelperTest.testHelper is explicitly empty — the test will appear unnamed in CI reports and audit evidence packages. Replace with a meaningful description, e.g. @DisplayName("Verifies that ...").
Rules are derived automatically from the AI tags present in the results:
| Rule ID | Level | Meaning |
|---|---|---|
test-method |
none |
Default rule for all non-security test methods |
security/<tag> |
note |
One rule per specific security category (e.g. security/auth, security/crypto) |
security-test |
note |
Security-relevant method carrying only the umbrella security tag |
annotation/empty-display-name |
note |
Method carries @DisplayName("") — an empty display name that causes the test to appear unnamed in reports, obscuring the audit trail |
security-test/placebo |
warning |
Security-relevant method whose ai_interaction_score is ≥ 0.8 — the test asserts only method calls, not return values or state; may not catch outcome regressions |
The annotation/empty-display-name rule applies to any test method (security-relevant or not) that declares @DisplayName(""). Because an empty display name hides tests from report views, it is treated as a low-severity quality finding that affects auditability.
The security-test/placebo rule identifies security tests that are at risk of being ineffective. A security test with ai_interaction_score >= 0.8 has assertions that primarily verify whether methods were called (e.g. Mockito verify(), spy call counts) rather than what values they returned or what state they produced. Such tests may give false confidence: the code under test could return wrong data or corrupt application state and the test would still pass. This finding is emitted as a second SARIF result alongside the primary security-relevant result, and uses level warning and security-severity: 6.0 (Medium) so that GitHub Code Scanning surfaces it distinctly from informational security-test inventory entries.
Each result carries both a physical and a logical location:
- Physical location — artifact URI derived from the scan root and the FQCN, producing a path relative to the repository root (e.g.
src/test/java/com/acme/LoginTest.java). MethodAtlas computes this by combining the scan root path (relative to the current working directory) with the FQCN-derived path, using the same algorithm as the GitHub Annotations emitter. GitHub Code Scanning uses this path to resolve the inline annotation position in the PR diff. - Logical location — the fully qualified method name (e.g.
com.acme.LoginTest.testLoginWithValidCredentials) with kindmember
AI enrichment fields are stored in the result properties object when AI is enabled:
| Property | Description |
|---|---|
loc |
Inclusive line count of the method declaration |
contentHash |
SHA-256 fingerprint of the enclosing class (64-char lowercase hex), or omitted when -content-hash was not passed |
sourceTags |
Semicolon-separated JUnit @Tag values from the source, or omitted when none |
aiSecurityRelevant |
Boolean AI classification, or omitted when AI is disabled |
aiDisplayName |
Suggested @DisplayName text, or omitted |
aiTags |
Semicolon-separated security taxonomy tags, or omitted |
aiReason |
Explanatory rationale from the AI, or omitted |
aiInteractionScore |
Interaction score 0.0–1.0; present whenever AI is enabled |
aiConfidence |
Confidence score 0.0–1.0, or omitted when -ai-confidence was not passed |
Properties with null or absent values are omitted from the JSON output entirely.
The properties bag is the machine-readable counterpart of the result message. It exposes every AI-enrichment field in a structured form suitable for API queries, custom dashboards, and policy automation. Whether a given SARIF viewer surfaces this bag in its UI is tool-dependent. See Result messages — Why scores appear in the message text by default for an explanation of how MethodAtlas handles this difference and how to control it.
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "MethodAtlas",
"version": "1.3.0",
"rules": [
{
"id": "test-method",
"name": "TestMethod",
"shortDescription": { "text": "JUnit test method" },
"properties": { "tags": ["test"] },
"help": { "text": "MethodAtlas inventories all JUnit test methods found in the scanned source tree. No action required." }
},
{
"id": "security/auth",
"name": "SecurityAuth",
"shortDescription": { "text": "Security test: auth" },
"properties": { "tags": ["security", "auth"] },
"help": { "text": "MethodAtlas detected this test method as security-relevant via AI analysis. Review the suggested @DisplayName and @Tag values in the result message. If correct, apply them by running: ./methodatlas -ai -apply-tags SOURCE_ROOT." }
}
]
}
},
"results": [
{
"ruleId": "test-method",
"level": "none",
"message": { "text": "com.acme.tests.SampleOneTest.testCountItems" },
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/test/java/com/acme/tests/SampleOneTest.java"
},
"region": { "startLine": 14 }
},
"logicalLocations": [
{
"fullyQualifiedName": "com.acme.tests.SampleOneTest.testCountItems",
"kind": "member"
}
]
}
],
"properties": {
"loc": 3,
"contentHash": "3a7f9b2e4c1d8f5a0e2b6c9d7f4a1e8b3c5d2f7a0b4e9c6d1f8a3b5e2c7d4f9"
}
},
{
"ruleId": "security/auth",
"level": "note",
"message": { "text": "AI suggests: @DisplayName(\"SECURITY: auth - validates session token after login\") @Tag(\"security\") @Tag(\"auth\"). Reason: Verifies that a valid session token is issued after successful login." },
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/test/java/com/acme/security/LoginTest.java"
},
"region": { "startLine": 22 }
},
"logicalLocations": [
{
"fullyQualifiedName": "com.acme.security.LoginTest.testLoginWithValidCredentials",
"kind": "member"
}
]
}
],
"properties": {
"loc": 8,
"aiSecurityRelevant": true,
"aiDisplayName": "SECURITY: auth - validates session token after login",
"aiTags": "security;auth",
"aiReason": "Verifies that a valid session token is issued after successful login.",
"aiConfidence": 0.95
}
}
]
}
]
}SARIF is natively supported by many static-analysis platforms and IDEs:
- GitHub Advanced Security — upload via the
upload-sarifaction to surface findings in the Security tab - VS Code — the SARIF Viewer extension renders results inline
- Azure DevOps — the
PublishBuildArtifacts+ SARIF viewer extension pipeline tasks - SonarQube — import via the generic issue import format after conversion; MethodAtlas does not ship a SARIF-to-SonarQube converter — community-maintained converters are available via the SonarQube community forum
Enable with -github-annotations:
./methodatlas -ai -github-annotations src/test/javaInstead of CSV or JSON, MethodAtlas emits GitHub Actions workflow commands to standard output.
Each line is one of two command forms:
| Command | Condition |
|---|---|
::warning file=…,line=…,title=…::… |
Security-relevant method with ai_interaction_score >= 0.8 — the test only verifies method calls, not outcomes (potential placebo test) |
::notice file=…,line=…,title=…::… |
Security-relevant method with interaction score below threshold, OR any method carrying @DisplayName("") |
The @DisplayName("") notice is emitted for every method that declares an explicitly empty display name, regardless of whether AI enrichment is enabled or whether the method is security-relevant. An empty display name causes the test to appear unnamed in CI and coverage reports, which obscures the audit trail.
Example output:
::notice file=src/test/java/com/acme/auth/LoginTest.java,line=22,title=Security test: auth::SECURITY: auth - validates session token after login
::warning file=src/test/java/com/acme/auth/SessionTest.java,line=45,title=Placebo security test::SECURITY: auth - session invalidation (interaction-only)
::notice file=src/test/java/com/acme/util/HelperTest.java,line=10,title=@DisplayName("") on com.acme.util.HelperTest#testHelper::@DisplayName("") declares an empty display name — the test will appear unnamed in reports, obscuring the audit trail
GitHub renders these as inline annotations on the PR diff. No GitHub Advanced Security licence is required — ::notice/::warning are standard GitHub Actions features available on all plan tiers.
File paths in the annotations are derived from the scan root and the class FQCN, producing paths such as src/test/java/com/acme/auth/LoginTest.java that GitHub resolves to the correct inline position for standard Maven/Gradle source layouts.
See docs/cli/github-actions.md for a complete workflow example, and docs/cli-reference.md#-github-annotations for the full flag description.
Enable JSON mode with -json:
./methodatlas -json /path/to/project
./methodatlas -ai -ai-confidence -json /path/to/testsMethodAtlas buffers all discovered test methods and, after the scan completes, serializes a single flat JSON array to standard output with pretty-printing. The format is especially useful for downstream tooling (dashboards, scripts, APIs) that prefers structured JSON over CSV parsing.
| Aspect | CSV | JSON |
|---|---|---|
tags / ai_tags |
Semicolon-separated string | JSON array of strings |
Numeric fields (loc, scores) |
Plain text | JSON numbers |
ai_security_relevant |
true/false string |
JSON boolean |
| Optional columns | Present with blank value when flag off | Absent entirely when flag off |
| Output timing | Incremental (line-by-line) | Buffered (full array after scan) |
[
{
"fqcn": "com.acme.tests.SampleOneTest",
"method": "alpha",
"loc": 8,
"tags": ["fast", "crypto"]
},
{
"fqcn": "com.acme.tests.SampleOneTest",
"method": "beta",
"loc": 6,
"tags": []
}
]When -ai is enabled, each record gains ai_security_relevant, ai_display_name, ai_tags, ai_reason, and ai_interaction_score. Fields absent for non-security-relevant methods (such as ai_display_name and ai_reason) are omitted from the JSON object rather than set to null.
[
{
"fqcn": "com.acme.tests.SampleOneTest",
"method": "alpha",
"loc": 8,
"tags": ["fast", "crypto"],
"ai_security_relevant": true,
"ai_display_name": "SECURITY: crypto - validates encrypted happy path",
"ai_tags": ["security", "crypto"],
"ai_reason": "The test exercises a crypto-related security property.",
"ai_interaction_score": 0.0
},
{
"fqcn": "com.acme.tests.SampleOneTest",
"method": "beta",
"loc": 6,
"tags": [],
"ai_security_relevant": false,
"ai_interaction_score": 0.2
}
]Adding -ai-confidence appends an ai_confidence field to each record:
[
{
"fqcn": "com.acme.tests.SampleOneTest",
"method": "alpha",
"loc": 8,
"tags": ["fast", "crypto"],
"ai_security_relevant": true,
"ai_display_name": "SECURITY: crypto - validates encrypted happy path",
"ai_tags": ["security", "crypto"],
"ai_reason": "The test exercises a crypto-related security property.",
"ai_interaction_score": 0.0,
"ai_confidence": 0.9
}
]| Field | Type | When present |
|---|---|---|
fqcn |
string | Always |
method |
string | Always |
loc |
number | Always |
tags |
array of strings | Always |
display_name |
string | When @DisplayName is present on the method |
source_root |
string | When -emit-source-root is passed |
content_hash |
string | When -content-hash is passed |
ai_security_relevant |
boolean | When -ai is enabled |
ai_display_name |
string | When AI enabled and method is security-relevant |
ai_tags |
array of strings | When AI enabled and method is security-relevant |
ai_reason |
string | When AI enabled and method has a reason |
ai_interaction_score |
number | When -ai is enabled |
ai_confidence |
number | When -ai-confidence is passed |
tag_ai_drift |
string | When -drift-detect is passed |
All filtering flags apply in JSON mode: -security-only drops non-security records, -min-confidence drops records below the confidence threshold, and -drift-detect adds the tag_ai_drift field.
-apply-tags is not a report mode — it modifies source files in place instead of emitting output. When combined with -ai (or -manual-consume), MethodAtlas writes AI-generated @DisplayName and @Tag annotations directly into the scanned test source files, then prints a summary to standard output.
./methodatlas -ai -apply-tags /path/to/testsBefore:
@Test
void testLoginWithValidCredentials() { ... }After:
@Test
@DisplayName("SECURITY: auth - validates session token after login")
@Tag("security")
@Tag("auth")
void testLoginWithValidCredentials() { ... }Only security-relevant methods are annotated. Existing @DisplayName or @Tag annotations are never overwritten or duplicated. Required imports (org.junit.jupiter.api.DisplayName, org.junit.jupiter.api.Tag) are added automatically when at least one annotation of that type is inserted. Unrelated formatting is preserved through lexical-preserving pretty printing.
See Source Write-back for the complete workflow, including how to combine it with the manual AI workflow.
| Situation | Recommended mode |
|---|---|
| Feeding output into a spreadsheet or data pipeline | CSV (default) |
| Quick visual inspection in a terminal | Plain (-plain) |
| Archiving scan results with provenance metadata | CSV + -emit-metadata |
| Filtering high-confidence security findings | CSV or JSON + -ai-confidence -min-confidence |
| Incremental scanning or change detection | CSV or SARIF + -content-hash |
| Integrating with a SAST platform or IDE | SARIF (-sarif) |
| Feeding output into a dashboard, API, or custom script | JSON (-json) |
| Annotating source files with AI-suggested tags | -apply-tags |