Skip to content

feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2)#141

Merged
Wolfvin merged 2 commits into
mainfrom
feat/issue-57-cicd-baseline-strict
Jul 3, 2026
Merged

feat(ci): baseline diff + strict mode + diff-scan flags (closes #57 phase 1+2)#141
Wolfvin merged 2 commits into
mainfrom
feat/issue-57-cicd-baseline-strict

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Issue #57 — CI/CD integration suite, Phase 1 + Phase 2

Closes #57 (partial — Phase 1 + Phase 2 only).

Scope of this PR

Issue #57 has 7 phases. This PR implements Phase 1 (baseline + diff scan) and Phase 2 (strict mode + thresholds). Phases 3–7 (codelens ci command, manifest tests, golden snapshots, git hooks, agent benchmark) will be separate PRs.

Phase 1 — baseline + diff scan

New files:

  • scripts/git_integration.py — thin wrapper over git_aware._run_git with CI/CD-specific helpers: list_staged_files, list_working_tree_changes, list_diff_vs, resolve_baseline_sha (resolution order: explicit arg → $CODELENS_BASELINE_SHA env → HEAD~1), detect_ci_environment (GitHub/GitLab/Jenkins/Bitbucket/CircleCI).
  • scripts/baseline_diff.py — finding identity hash (rule_id|file|line|severity) (deliberately excludes message text and column for stability across parser updates), diff_findings() classifying into new/preexisting/resolved, save_baseline()/load_baseline() persisting to .codelens/baseline_<SHA>.json, filter_to_changed_files().

Modified:

  • scripts/commands/check.py — new flags:
    • --baseline-commit <SHA> — only NEW findings fail the gate; preexisting ones are surfaced in output but don't fail
    • --save-baseline — persist current findings as the baseline for a future --baseline-commit run
    • --diff-scan — restrict to working-tree changes (vs HEAD)
    • --staged — restrict to staged files (git diff --cached)
    • --diff-vs <ref> — restrict to files changed vs ref (branch/tag/SHA)
  • scripts/formatters/sarif.pyto_sarif() / format_sarif() accept automation_guid param, emitted as SARIF automationDetails.guid so CI dashboards (GitHub code scanning, SonarQube) can group related runs.

Phase 2 — strict mode + thresholds

New file:

  • scripts/exit_policy.pyExitDecision dataclass + evaluate_exit_policy() implementing:
    • --strict → threshold = low (any finding fails)
    • --error → threshold = high
    • --severity-threshold <level> → explicit
    • --max-findings N → cap (0 = no cap)
    • Priority: explicit threshold > strict > error
    • Pure function — does NOT call sys.exit; the existing codelens.py exit-on-gate-failed dispatcher handles it via the gate field.

Modified:

  • scripts/commands/check.py--strict / --error / --severity-threshold flags wired in. When none of the new flags are set, the legacy --severity / --max-findings / --health-min gate runs unchanged (backward-compat for existing CI configs).

Acceptance criteria

Phase 1+2 subset:

  • codelens check --baseline-commit <SHA> reports only new findings
  • codelens check --strict --error exits non-zero on any high-severity finding
  • SARIF automationDetails.guid emitted when --baseline-commit is set
  • --diff-scan / --staged / --diff-vs narrow to git-changed files

Phase 3+ acceptance criteria (out of scope):

  • codelens ci auto-detects GitHub Actions and uploads SARIF — Phase 3 (separate PR)
  • codelens install-ci generates workflow file — Phase 3 (separate PR)
  • Manifest test suite runs 50+ cases — Phase 4 (separate PR)
  • Rule quality harness catches regression — Phase 5 (separate PR)

Tests

File Tests Coverage
tests/test_baseline_diff.py 33 identity stability, diff classification, save/load round-trip, filter_to_changed_files
tests/test_exit_policy.py 32 strict/error/threshold modes, max-findings cap, priority resolution, ExitDecision shape
tests/test_git_integration.py 21 staged/working-tree/diff-vs against real temp git repos, CI env detection, non-git graceful failure
tests/test_check_ci_flags.py 13 end-to-end codelens check with new flags, real git repos, SARIF guid emission
Total new 99 all green

Test results

tests/test_baseline_diff.py:     33 passed
tests/test_exit_policy.py:       32 passed
tests/test_git_integration.py:   21 passed
tests/test_check_ci_flags.py:    13 passed

Full suite (excluding test_integration.py per CONTEXT.md,
and tree-sitter-dependent tests that require optional deps):
  1294 passed, 90 skipped, 4 pre-existing failures

The 4 pre-existing failures are unrelated to this PR (verified via git stash + rerun on unmodified main):

  • test_codelensignore::test_actual_target_dir_is_ignored
  • test_universal_grammar_loader::test_auto_installs_when_env_var_set
  • test_universal_grammar_loader::test_returns_language_after_successful_install
  • test_universal_grammar_loader::test_available_languages_returns_list

All four fail identically on unmodified main (caused by missing optional tree_sitter dependency and an unrelated target/ ignore-rule quirk).

Design notes

  • Finding identity excludes message and column. Per issue [FEATURE] CI/CD integration suite — baseline scan, diff scan, strict mode, manifest tests, golden snapshots #57 worker consensus (UBS [ARCH] Replace flat registry with true graph data model (nodes + edges) #7): identity = hash of (rule_id, file, line, severity). This means parser updates that change message wording or shift a column by 1–2 chars will NOT re-flag the same issue as "new" against the baseline.
  • Baselines stored in .codelens/baseline_<SHA>.json. Human-readable JSON so they can be committed to a repo or attached as CI artifacts. The SHA in the filename makes it easy to find the right baseline for a given PR base.
  • $CODELENS_BASELINE_SHA env var. Phase 3 (codelens ci command) will set this automatically from the PR base SHA. For now users can set it manually in their CI workflow.
  • Pure exit_policy. The decision logic is a pure function — no sys.exit side effect. This makes it trivially testable and reusable by Phase 3's codelens ci orchestrator.
  • Backward-compat preserved. Running codelens check with none of the new flags behaves identically to before (legacy severity/max-findings gate runs unchanged).

@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.

@Wolfvin Wolfvin force-pushed the feat/issue-57-cicd-baseline-strict branch from 78a5a8f to 6c88d5e Compare July 3, 2026 03:09
Wolfvin added a commit that referenced this pull request Jul 3, 2026
Two fixes for PR #141 per issue #171:

1. Add missing @WHO/@WHAT/@PART/@entry file headers to the 3 new files
   introduced by PR #141:
   - scripts/baseline_diff.py
   - scripts/exit_policy.py
   - scripts/git_integration.py
   (issue #171 explicitly mentions baseline_diff.py; exit_policy.py and
   git_integration.py have the same gap — fixed for consistency since
   CONTRIBUTING.md mandates the header on all new files.)

2. Restore the `-f` shortcut conflict guard in scripts/codelens.py that
   was removed by PR #153 (graphml, issue #59 Phase 3). The guard checks
   `existing_option_strings` before adding `-f` to a subparser, so
   commands like `affected` (issue #62) that use `-f` for `--filter`
   don't conflict with the global `--format -f` shortcut.

   This bug made `codelens --help` and every CLI invocation crash with
   `argparse.ArgumentError: argument --format/-f: conflicting option
   string: -f`. It was a pre-existing regression on main (introduced
   when PR #153 was merged) that blocked PR #141's tests from running.

   The fix restores the pre-#153 logic:
   ```python
   if "format" not in existing_dests:
       format_args = ["--format"]
       if "-f" not in existing_option_strings:
           format_args.append("-f")
       sub.add_argument(*format_args, choices=[...], ...)
   ```

Verified:
- `codelens --command-count` works (returns 75)
- `codelens --help` works
- 99 PR #141 tests pass (test_baseline_diff + test_check_ci_flags +
  test_exit_policy + test_git_integration)
- 196 passed regression (test_cli + test_formatters + test_command_count +
  test_command_registry + 4 PR #141 test files)
- 135 passed (test_graphml_formatter + test_diff_scope + test_secrets_gitleaks
  — confirms the format change doesn't regress graphml/diff-base/gitleaks)
- sync_command_count.py --check: all docs in sync (count=75)

Findings (per pre-flight SKILL.md — flag to BOS):
- The `-f` conflict bug was introduced by PR #153 (issue #59 Phase 3,
  graphml export). PR #153 simplified the format-arg logic and
  accidentally dropped the `existing_option_strings` check. This fix
  restores the check while keeping the `graphml` choice. No regression
  to graphml functionality (test_graphml_formatter.py: 35 passed).
- PR #141 was already rebased on main; no merge conflicts. The rebase +
  these fixes make PR #141 mergeable.
@Wolfvin Wolfvin force-pushed the feat/issue-57-cicd-baseline-strict branch from 6c88d5e to 23487af Compare July 3, 2026 03:29
@Wolfvin Wolfvin merged commit 27465ee into main Jul 3, 2026
@Wolfvin Wolfvin deleted the feat/issue-57-cicd-baseline-strict branch July 3, 2026 03:30
Wolfvin added 2 commits July 3, 2026 10:30
…hase 1+2)

Issue #57 — Phase 1 (baseline + diff scan) + Phase 2 (strict mode).

Phase 1 — baseline + diff scan:
- New scripts/git_integration.py: thin wrapper over git_aware._run_git
  with CI/CD-specific helpers (list_staged_files, list_working_tree_changes,
  list_diff_vs, resolve_baseline_sha, detect_ci_environment).
- New scripts/baseline_diff.py: finding identity hash
  (rule_id|file|line|severity), diff_findings() that classifies findings
  into new/preexisting/resolved, save/load_baseline() persisting to
  .codelens/baseline_<SHA>.json, filter_to_changed_files().
- check.py: new --baseline-commit, --save-baseline, --diff-scan,
  --staged, --diff-vs flags. When --baseline-commit is set the gate
  operates on NEW findings only (preexisting ones are surfaced but
  don't fail). --save-baseline persists the current findings as the
  baseline for a future run.
- formatters/sarif.py: to_sarif() and format_sarif() now accept
  automation_guid (issue #57 spec) — emitted as SARIF
  automationDetails.guid so CI dashboards can group related runs.

Phase 2 — strict mode + thresholds:
- New scripts/exit_policy.py: ExitDecision dataclass +
  evaluate_exit_policy() implementing --strict (threshold=low),
  --error (threshold=high), --severity-threshold <level>,
  --max-findings N. Pure function — does not call sys.exit; the
  caller (codelens.py main dispatcher) handles the exit code via
  the existing 'gate' field.
- check.py: --strict / --error / --severity-threshold flags wired
  in. When none of the new flags are set, the legacy
  --severity/--max-findings gate runs unchanged (backward-compat
  for existing CI configs).

Acceptance criteria satisfied (Phase 1+2 subset):
- [x] codelens check --baseline-commit <SHA> reports only new findings
- [x] codelens check --strict --error exits non-zero on high-severity
- [x] SARIF automationDetails.guid emitted when --baseline-commit set
- [x] --diff-scan / --staged / --diff-vs narrow to git-changed files

Out of scope for this PR (will be separate PRs):
- Phase 3: 'codelens ci' orchestration command + 'codelens install-ci'
- Phase 4: manifest-driven test suite
- Phase 5: rule quality harness + golden snapshots
- Phase 6: git sync hooks
- Phase 7: agent benchmark harness

Tests:
- tests/test_baseline_diff.py — 33 unit tests (identity, diff, persistence)
- tests/test_exit_policy.py — 32 unit tests (strict/error/threshold/max-findings)
- tests/test_git_integration.py — 21 unit tests (staged/working-tree/diff-vs,
  CI env detection, non-git graceful failure)
- tests/test_check_ci_flags.py — 13 integration tests (end-to-end
  codelens check with new flags, real git repos)
- Total new: 99 tests, all green

Full test suite (excluding test_integration.py per CONTEXT.md and
tree-sitter-dependent tests that require optional deps):
  1294 passed, 90 skipped, 4 pre-existing failures
  (test_codelensignore::test_actual_target_dir_is_ignored +
   3× test_universal_grammar_loader — verified via git stash to fail
   identically on unmodified main, unrelated to this PR).
Two fixes for PR #141 per issue #171:

1. Add missing @WHO/@WHAT/@PART/@entry file headers to the 3 new files
   introduced by PR #141:
   - scripts/baseline_diff.py
   - scripts/exit_policy.py
   - scripts/git_integration.py
   (issue #171 explicitly mentions baseline_diff.py; exit_policy.py and
   git_integration.py have the same gap — fixed for consistency since
   CONTRIBUTING.md mandates the header on all new files.)

2. Restore the `-f` shortcut conflict guard in scripts/codelens.py that
   was removed by PR #153 (graphml, issue #59 Phase 3). The guard checks
   `existing_option_strings` before adding `-f` to a subparser, so
   commands like `affected` (issue #62) that use `-f` for `--filter`
   don't conflict with the global `--format -f` shortcut.

   This bug made `codelens --help` and every CLI invocation crash with
   `argparse.ArgumentError: argument --format/-f: conflicting option
   string: -f`. It was a pre-existing regression on main (introduced
   when PR #153 was merged) that blocked PR #141's tests from running.

   The fix restores the pre-#153 logic:
   ```python
   if "format" not in existing_dests:
       format_args = ["--format"]
       if "-f" not in existing_option_strings:
           format_args.append("-f")
       sub.add_argument(*format_args, choices=[...], ...)
   ```

Verified:
- `codelens --command-count` works (returns 75)
- `codelens --help` works
- 99 PR #141 tests pass (test_baseline_diff + test_check_ci_flags +
  test_exit_policy + test_git_integration)
- 196 passed regression (test_cli + test_formatters + test_command_count +
  test_command_registry + 4 PR #141 test files)
- 135 passed (test_graphml_formatter + test_diff_scope + test_secrets_gitleaks
  — confirms the format change doesn't regress graphml/diff-base/gitleaks)
- sync_command_count.py --check: all docs in sync (count=75)

Findings (per pre-flight SKILL.md — flag to BOS):
- The `-f` conflict bug was introduced by PR #153 (issue #59 Phase 3,
  graphml export). PR #153 simplified the format-arg logic and
  accidentally dropped the `existing_option_strings` check. This fix
  restores the check while keeping the `graphml` choice. No regression
  to graphml functionality (test_graphml_formatter.py: 35 passed).
- PR #141 was already rebased on main; no merge conflicts. The rebase +
  these fixes make PR #141 mergeable.
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] CI/CD integration suite — baseline scan, diff scan, strict mode, manifest tests, golden snapshots

1 participant