Skip to content

feat(0.2): explain finding + suppress writer + --new-findings-only (Tracks 4.6/4.7/4.8)#140

Open
pmclSF wants to merge 1 commit intofeat/0.2-suppressionsfrom
feat/0.2-suppression-cli
Open

feat(0.2): explain finding + suppress writer + --new-findings-only (Tracks 4.6/4.7/4.8)#140
pmclSF wants to merge 1 commit intofeat/0.2-suppressionsfrom
feat/0.2-suppression-cli

Conversation

@pmclSF
Copy link
Copy Markdown
Owner

@pmclSF pmclSF commented May 2, 2026

Summary

Bundles Tracks 4.6, 4.7, and 4.8 from the 0.2.0 parity-gated release plan. With finding IDs (#138) and the suppression schema (#139) in flight, this PR makes the suppression workflow usable end-to-end.

Base branch: `feat/0.2-suppressions` (#139, which itself stacks on #138). Will rebase onto main after the chain merges.

Track 4.6 — `terrain explain `

`terrain explain` recognizes stable finding IDs and prints a finding-detail block: detector + severity + location + evidence + explanation + suggested action + the canonical `terrain suppress` invocation. A finding ID that parses but isn't in the snapshot returns a distinct exit-5 (not-found) message that distinguishes "stale ID after refactor" from "garbage input."

Track 4.7 — `terrain suppress --reason "..." [--expires] [--owner]`

New top-level Gate-pillar primitive. Validates the ID format, refuses duplicates (existing entry → usage error pointing at the existing reason), appends a YAML entry to `.terrain/suppressions.yaml`. Writes text rather than re-marshaling so user comments / ordering are preserved. Schema header auto-emitted on first call.

Track 4.8 — `--new-findings-only --baseline `

The "established repos with debt" adoption flow. `--fail-on critical` alone would brick CI on day one against existing findings; combining with `--new-findings-only --baseline old.json` makes the gate fire only on findings introduced AFTER the baseline. `--new-findings-only` without `--baseline` is rejected at usage-error level so the user gets a clear message rather than a silent no-op.

Refactor (small, helpful)

`runAnalyze` gets an `analyzeRunOpts` struct so the call site in `main.go` isn't a 17-positional-argument list. Future flag additions don't expand the signature.

Test plan

  • `go test ./cmd/terrain/ -run "TestRunSuppress|TestLooksLikeISODate"` — 7 tests green (creates new file, appends to existing, rejects duplicate, rejects bad ID, requires reason, rejects bad expiry shape, ISO validator)
  • `go test ./internal/engine/ -run "TestApplyNewFindingsOnly"` — 6 tests green (drops baseline matches, no-baseline warning, empty baseline, per-file signals, keeps signals without ID, nil-safe)
  • `go test ./...` full suite green
  • `go test ./internal/testdata/` golden + CLI suite green

Plan link

`/Users/pzachary/.claude/plans/kind-mapping-turing.md` (Tracks 4.6 / 4.7 / 4.8).

🤖 Generated with Claude Code

…/4.7/4.8)

Bundles the three remaining Track 4 deliverables into one PR. With
4.4 (finding IDs) and 4.5 (suppression model) already in flight,
this PR makes the suppression workflow actually usable end-to-end:

  Track 4.6 — terrain explain <finding-id>
    Extends `terrain explain` to recognize stable finding IDs (e.g.
    "weakAssertion@internal/auth/login.go:TestLogin#a1b2c3d4"). On a
    hit, prints a finding-detail block: detector + severity + location +
    evidence + explanation + suggested action + the canonical
    `terrain suppress <id> --reason "..."` invocation.

    A finding ID that parses but isn't in the snapshot returns a
    distinct exit-5 (not-found) message that distinguishes "stale ID
    after refactor" from "garbage input" — common adoption flow when
    a user keeps a CI link to a finding that has since moved.

    Implementation: lookupSignalByFindingID + renderFindingExplanation
    in cmd/terrain/cmd_explain.go.

  Track 4.7 — terrain suppress <finding-id> --reason "..." [--expires] [--owner]
    New top-level Gate-pillar primitive. Validates the ID format,
    refuses duplicates (existing entry → usage error pointing at the
    existing reason), appends a YAML entry to .terrain/suppressions.yaml.

    Writes text rather than re-marshaling the file so any comments /
    ordering the user added by hand are preserved. Schema header is
    auto-emitted on first call.

    --reason required (every suppression justifies itself, per Track
    4.5 schema). --expires optional but recommended; ISO YYYY-MM-DD
    shape validated up front. --owner optional free-text pointer.

    Implementation: cmd/terrain/cmd_suppress.go + 7 unit tests.

  Track 4.8 — terrain analyze --new-findings-only --baseline <path>
    Filters the snapshot to keep only signals whose FindingID is NOT
    present in the baseline. The "established repos with debt"
    adoption flow: `--fail-on critical` would brick CI on day one
    against existing high findings; combining with
    `--new-findings-only --baseline old.json` makes the gate fire
    only on findings introduced AFTER the baseline.

    Implementation: PipelineOptions.NewFindingsOnly +
    internal/engine/new_findings_only.go (applyNewFindingsOnly).
    Runs after suppression apply so the baseline comparison sees
    the user's intended-active signal set.

    No-baseline case: --new-findings-only is inert; logs a warning so
    the user notices the flag had no effect (better than silent
    success that masks the misconfiguration).

    Signals without FindingID (older / specialized emissions) are
    KEPT — over-report rather than under-report.

    Implementation: 6 unit tests including the "no-baseline" warning
    path, empty baseline, per-file signals, and signals without IDs.

Refactor: runAnalyze gets a `analyzeRunOpts` struct so the call site
in main.go isn't a 17-positional-argument list. The struct collapses
the existing args + adds SuppressionsPath + NewFindingsOnly. Future
flag additions stop expanding the call signature.

Validation in main.go: --new-findings-only requires --baseline; the
combination is rejected at usage-error level (exit 2) so the user
gets a clear message rather than a silent no-op.

Verification:
  go test ./cmd/terrain/ -run "TestRunSuppress|TestLooksLikeISODate" — 7 tests green
  go test ./internal/engine/ -run "TestApplyNewFindingsOnly" — 6 tests green
  go test ./... — full suite green
  go test ./internal/testdata/ — golden + CLI suite green

Plan link: /Users/pzachary/.claude/plans/kind-mapping-turing.md
(Tracks 4.6, 4.7, 4.8).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant