Skip to content

feat(agent-ux): confidence scores per finding + schema_version in JSON output (closes #5)#148

Merged
Wolfvin merged 1 commit into
mainfrom
feat/issue-5-confidence-schema-version
Jul 3, 2026
Merged

feat(agent-ux): confidence scores per finding + schema_version in JSON output (closes #5)#148
Wolfvin merged 1 commit into
mainfrom
feat/issue-5-confidence-schema-version

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Closes #5 (subset: sub-features #1 + #5)

Summary

Two agent-UX features from issue #5, implemented as a non-breaking enhancement so existing consumers keep working. Sub-features #2 (delta output), #3 (dependency graph), #4 (partial failures) are deferred to follow-up — they are larger and benefit from this foundation landing first.

What changed

1. Confidence scores per finding (sub-feature #1)

New file: scripts/confidence.py

  • score_finding(engine, category, finding) → float in [0.0, 1.0]
  • Category-driven base scores with modifier adjustments:
    • Dead-code: unreachable=0.95, unused_vars=0.70, zombie_css=0.50, unused_exports=0.45, registry_dead=0.75
    • Secrets: pattern_match=0.90, env_exposed=0.85, entropy=0.65
  • Modifiers: test files (-0.15..-0.30), config files (-0.10), library API (-0.20), downgraded (-0.05..-0.10), after=return (+0.03), after=throw (-0.03)
  • enrich_findings(engine, payload) enriches both dead-code (category-keyed dict) and secrets (flat list) payload shapes
  • confidence_distribution(findings) buckets into high/medium/low/unscored for quick reliability summaries
  • Unknown engines/categories default to 0.50 (neutral) so missing scores never break the pipeline

Modified: scripts/deadcode_engine.py

  • detect_dead_code() calls enrich_findings("dead_code", payload) before returning — every finding now has a confidence field
  • Defensive import: if confidence module is unavailable, engine still returns a valid payload

Modified: scripts/secrets_engine.py

  • detect_secrets() calls enrich_findings("secrets", payload) before returning — every finding now has a confidence field
  • Same defensive import pattern

2. Schema versioning (sub-feature #5)

  • SCHEMA_VERSION = "8.2" in scripts/confidence.py (single source of truth)
  • stamp_schema_version(payload) adds the field idempotently (preserves existing value)
  • formatters._normalize_to_ai() stamps every AI-normalized output (ok / error / non-dict paths)
  • formatters.format_output() also stamps raw JSON output (on a copy, so caller's dict is not mutated)
  • Downstream agents can read schema_version to detect breaking changes and migrate gracefully

Example output

Before:

{
  "status": "ok",
  "findings": [
    {"type": "pattern_match", "file": "config.js", "line": 5, "category": "api_key"}
  ]
}

After:

{
  "status": "ok",
  "schema_version": "8.2",
  "findings": [
    {"type": "pattern_match", "file": "config.js", "line": 5, "category": "api_key", "confidence": 0.9}
  ]
}

Test results

  • 54 new tests in tests/test_confidence.py pass:
    • TestSchemaVersion (5) — stamping idempotency, non-dict handling
    • TestScoreFinding (13) — base scores, modifiers, range safety, fail-safe
    • TestEnrichFinding (6) — idempotency, secrets vs dead-code field resolution
    • TestEnrichFindings (6) — both payload shapes, idempotency, preservation
    • TestConfidenceDistribution (6) — bucketing, boundaries, non-dict handling
    • TestDeadCodeEngineIntegration (3) — real engine returns confidence fields
    • TestSecretsEngineIntegration (3) — real engine returns confidence fields
    • TestFormatterIntegration (7) — schema_version in AI/JSON/markdown paths
    • TestEndToEndSchemaVersion (4) — full formatter pipeline
  • No regressions: 442 passed, 33 skipped, 0 failed in safe-to-run test subset.
  • Pre-existing failures in test_universal_grammar_loader (tree-sitter-zig env) and test_codelensignore are unchanged by this PR (verified via git stash).

Non-breaking

  • New confidence field is additive — existing consumers ignore unknown fields.
  • New schema_version field is additive — same.
  • No command count change, no MCP tool count change. sync_command_count.py --check passes.
  • Defensive imports mean engines still work if confidence.py is missing.

Deferred to follow-up

These benefit from the confidence + schema_version foundation landing first.

…N output (closes #5)

Two agent-UX features from issue #5, implemented as a non-breaking
enhancement so existing consumers keep working:

1. Confidence scores (sub-feature #1)
   - New scripts/confidence.py: category-driven scoring with modifier
     adjustments. Dead-code categories: unreachable=0.95, unused_vars=0.70,
     zombie_css=0.50, unused_exports=0.45, registry_dead=0.75. Secrets
     categories: pattern_match=0.90, env_exposed=0.85, entropy=0.65.
     Modifiers: test files -0.15..-0.30, config files -0.10, library API
     -0.20, downgraded -0.05..-0.10, after=return +0.03, after=throw -0.03.
   - deadcode_engine.detect_dead_code() enriches every finding with a
     'confidence' field via enrich_findings('dead_code', payload).
   - secrets_engine.detect_secrets() does the same for secrets findings.
   - Defensive import: if confidence module is unavailable, engines still
     return valid payloads (fail-safe).
   - confidence_distribution() helper buckets findings into high/medium/
     low/unscored for quick reliability summaries.

2. Schema versioning (sub-feature #5)
   - SCHEMA_VERSION = '8.2' in scripts/confidence.py (single source of truth).
   - formatters._normalize_to_ai() stamps schema_version on every AI-normalized
     output (ok path, error path, non-dict path).
   - formatters.format_output() also stamps raw JSON output (on a copy, so
     the caller's dict is not mutated).
   - Idempotent: if schema_version is already present, it is preserved.
   - Downstream agents can read this field to detect breaking changes and
     migrate gracefully.

Tests: 54 new tests in tests/test_confidence.py covering score_finding,
enrich_finding, enrich_findings, confidence_distribution, schema_version
stamping, dead-code/secrets engine integration, and formatter integration
(AI/JSON/markdown paths). No regressions in safe-to-run test subset
(442 passed, 33 skipped, 0 failed). Pre-existing failures in
test_universal_grammar_loader (tree-sitter-zig env) and test_codelensignore
are unchanged by this PR.

Sub-features #2 (delta output), #3 (dependency graph), #4 (partial failures)
deferred to follow-up issues — they are larger and benefit from the
confidence + schema_version foundation landing first.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Wolfvin Wolfvin merged commit 9042a51 into main Jul 3, 2026
4 of 11 checks passed
Wolfvin added a commit that referenced this pull request Jul 3, 2026
…#159)

Upgrade `codelens secrets` to use gitleaks as the primary backend when
available (600+ maintained rules, entropy scoring, structured JSON output).
Falls back to the existing regex scanner when gitleaks is not installed
or `--no-gitleaks` is set. Gitleaks is an opt-in upgrade, never a hard
dependency.

New files:
- scripts/gitleaks_backend.py — self-contained gitleaks integration (454 lines)
  - _gitleaks_available() — detects binary via `gitleaks version`
  - _run_gitleaks() — invokes `gitleaks detect --no-git --report-format json`,
    writes to temp file, parses JSON, handles both list and {Results:[...]}
    schemas (older gitleaks versions), cleans up temp file
  - _normalize_gitleaks_findings() — maps gitleaks JSON fields to CodeLens
    finding schema (RuleID→type, File→file, StartLine→line, Secret→masked
    match/value, Tags→tags, Fingerprint→fingerprint)
  - _infer_severity() — heuristic severity mapping (aws-access-key/private-key/
    github-pat/stripe-secret/etc → critical; 'critical'/'high'/'medium'/'low'
    in rule ID or tags → matching severity; default → high)
  - _mask_secret() — first 4 chars + *** (matches secrets_engine convention)
  - scan_with_gitleaks() — public entry point; returns CodeLens-shaped result
    dict with backend='gitleaks', or None if gitleaks unavailable
  - GitleaksError — caught by caller, falls back to regex with warning
- tests/test_secrets_gitleaks.py — 56 tests (543 lines)
  - Detection (mocked subprocess: available, not-found, timeout, nonzero,
    unexpected exception)
  - Masking (long, short, empty, 4-char, 5-char)
  - Severity inference (aws/private-key/github-pat/stripe → critical;
    tags with high/medium/low; default → high)
  - Normalization (basic, masking, abs→rel paths, tags as string, empty,
    non-dict entries, missing fields)
  - Stats/risk/recommendations (counts, files_with_findings, critical→high
    →medium→low risk cascade, recommendation content)
  - _run_gitleaks (mocked: JSON parse, empty file, missing file, nonzero
    exit, timeout, invalid JSON, {Results:[...]} schema)
  - scan_with_gitleaks (full integration mocked: result dict shape,
    severity filter, nonexistent workspace)
  - CLI integration (subprocess): --no-gitleaks in help, regex backend +
    install hint when gitleaks absent, --no-gitleaks suppresses hint,
    stats.backend field set

Modified files:
- scripts/commands/secrets.py (73 lines):
  - Add --no-gitleaks flag (force regex backend)
  - execute() tries gitleaks first (unless --no-gitleaks); on gitleaks
    failure, falls back to regex with stderr warning (never crashes)
  - Tags result with backend='regex' or 'gitleaks'
  - When gitleaks unavailable, adds gitleaks_hint field with install URL
  - stats.backend also set so compact/ai formatters pick it up
- SKILL.md — add 'Gitleaks-Backed Secrets Scanner' to v8.2 capability pillars
  with install instructions

Design decisions:
- gitleaks logic in NEW module (gitleaks_backend.py), not in secrets_engine.py
  — avoids collision with PR #148 (which touches secrets_engine.py for
  confidence scores). commands/secrets.py is the wiring point.
- Gitleaks is OPT-IN: if not installed, regex runs unchanged with a hint.
  Never a hard dependency.
- --no-gitleaks forces regex even if gitleaks is available — useful for
  testing, offline environments, or when gitleaks produces unexpected results.
- --no-git flag passed to gitleaks (scan working tree, not git history) to
  match the regex backend's behavior. Git history scanning can be added
  later via a --git-history flag if needed.
- Normalized findings include 'backend' field so consumers can tell which
  scanner produced each finding (useful when mixing backends in future).

Verified:
- 56 new tests pass (test_secrets_gitleaks.py — all subprocess mocked)
- 109 passed regression check (test_secrets_engine + test_cli +
  test_command_count + test_command_registry — no regressions)
- Command count unchanged at 70 (same command, upgraded backend)
- End-to-end: --no-gitleaks in help, regex backend + hint when gitleaks
  absent, --no-gitleaks suppresses hint, stats.backend set

Findings (per pre-flight SKILL.md — flag to BOS):
- PR #148 (issue #5, open) touches scripts/secrets_engine.py (+16 lines,
  adding confidence scores). This PR touches scripts/commands/secrets.py
  (different file). No collision. Both can merge independently. The
  gitleaks backend's normalized findings include a 'confidence' field
  position in the schema — PR #148's confidence.py module can score
  gitleaks findings the same way as regex findings if desired.
Wolfvin added a commit that referenced this pull request Jul 3, 2026
…#159)

Upgrade `codelens secrets` to use gitleaks as the primary backend when
available (600+ maintained rules, entropy scoring, structured JSON output).
Falls back to the existing regex scanner when gitleaks is not installed
or `--no-gitleaks` is set. Gitleaks is an opt-in upgrade, never a hard
dependency.

New files:
- scripts/gitleaks_backend.py — self-contained gitleaks integration (454 lines)
  - _gitleaks_available() — detects binary via `gitleaks version`
  - _run_gitleaks() — invokes `gitleaks detect --no-git --report-format json`,
    writes to temp file, parses JSON, handles both list and {Results:[...]}
    schemas (older gitleaks versions), cleans up temp file
  - _normalize_gitleaks_findings() — maps gitleaks JSON fields to CodeLens
    finding schema (RuleID→type, File→file, StartLine→line, Secret→masked
    match/value, Tags→tags, Fingerprint→fingerprint)
  - _infer_severity() — heuristic severity mapping (aws-access-key/private-key/
    github-pat/stripe-secret/etc → critical; 'critical'/'high'/'medium'/'low'
    in rule ID or tags → matching severity; default → high)
  - _mask_secret() — first 4 chars + *** (matches secrets_engine convention)
  - scan_with_gitleaks() — public entry point; returns CodeLens-shaped result
    dict with backend='gitleaks', or None if gitleaks unavailable
  - GitleaksError — caught by caller, falls back to regex with warning
- tests/test_secrets_gitleaks.py — 56 tests (543 lines)
  - Detection (mocked subprocess: available, not-found, timeout, nonzero,
    unexpected exception)
  - Masking (long, short, empty, 4-char, 5-char)
  - Severity inference (aws/private-key/github-pat/stripe → critical;
    tags with high/medium/low; default → high)
  - Normalization (basic, masking, abs→rel paths, tags as string, empty,
    non-dict entries, missing fields)
  - Stats/risk/recommendations (counts, files_with_findings, critical→high
    →medium→low risk cascade, recommendation content)
  - _run_gitleaks (mocked: JSON parse, empty file, missing file, nonzero
    exit, timeout, invalid JSON, {Results:[...]} schema)
  - scan_with_gitleaks (full integration mocked: result dict shape,
    severity filter, nonexistent workspace)
  - CLI integration (subprocess): --no-gitleaks in help, regex backend +
    install hint when gitleaks absent, --no-gitleaks suppresses hint,
    stats.backend field set

Modified files:
- scripts/commands/secrets.py (73 lines):
  - Add --no-gitleaks flag (force regex backend)
  - execute() tries gitleaks first (unless --no-gitleaks); on gitleaks
    failure, falls back to regex with stderr warning (never crashes)
  - Tags result with backend='regex' or 'gitleaks'
  - When gitleaks unavailable, adds gitleaks_hint field with install URL
  - stats.backend also set so compact/ai formatters pick it up
- SKILL.md — add 'Gitleaks-Backed Secrets Scanner' to v8.2 capability pillars
  with install instructions

Design decisions:
- gitleaks logic in NEW module (gitleaks_backend.py), not in secrets_engine.py
  — avoids collision with PR #148 (which touches secrets_engine.py for
  confidence scores). commands/secrets.py is the wiring point.
- Gitleaks is OPT-IN: if not installed, regex runs unchanged with a hint.
  Never a hard dependency.
- --no-gitleaks forces regex even if gitleaks is available — useful for
  testing, offline environments, or when gitleaks produces unexpected results.
- --no-git flag passed to gitleaks (scan working tree, not git history) to
  match the regex backend's behavior. Git history scanning can be added
  later via a --git-history flag if needed.
- Normalized findings include 'backend' field so consumers can tell which
  scanner produced each finding (useful when mixing backends in future).

Verified:
- 56 new tests pass (test_secrets_gitleaks.py — all subprocess mocked)
- 109 passed regression check (test_secrets_engine + test_cli +
  test_command_count + test_command_registry — no regressions)
- Command count unchanged at 70 (same command, upgraded backend)
- End-to-end: --no-gitleaks in help, regex backend + hint when gitleaks
  absent, --no-gitleaks suppresses hint, stats.backend set

Findings (per pre-flight SKILL.md — flag to BOS):
- PR #148 (issue #5, open) touches scripts/secrets_engine.py (+16 lines,
  adding confidence scores). This PR touches scripts/commands/secrets.py
  (different file). No collision. Both can merge independently. The
  gitleaks backend's normalized findings include a 'confidence' field
  position in the schema — PR #148's confidence.py module can score
  gitleaks findings the same way as regex findings if desired.
@Wolfvin Wolfvin deleted the feat/issue-5-confidence-schema-version branch July 3, 2026 02:14
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.

Agent UX: Confidence scores, delta output, and dependency graph between findings

1 participant