Skip to content

fix(chronometer): resolve git history for subdirectory scans - #2977

Merged
squid-protocol merged 2 commits into
mainfrom
fix/2976-chronometer-subdir-git
Sep 12, 2026
Merged

fix(chronometer): resolve git history for subdirectory scans#2977
squid-protocol merged 2 commits into
mainfrom
fix/2976-chronometer-subdir-git

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Closes #2976

Chronometer._initialize_history_scan gated git on (self.root / ".git").exists() — true only at a repository root. Any subdirectory scan (galaxyscope repo/src, every keyword-rosetta corpus folder) silently lost the entire temporal pipeline to the mtime fallback + TEMPORAL COLLAPSE guard: neutral (stability 50, churn 0) on every file. This is why the rosetta corpus reads risk_stability flat 50 / risk_churn 0 in all 46 languages.

The fix (one layer, chronometer only)

  1. Gate on git rev-parse --is-inside-work-tree — resolves upward from any subdir; worktree .git file pointers pass; non-git dirs keep the exact fallback.
  2. --relative -- . on the history stream — from a subdir, git log --name-only emits repo-relative paths while git ls-files emits cwd-relative ones; the maps would be keyed differently from both the denominator and the scanner's rel_path. Both flags are byte-identical no-ops at the repo root.
  3. Coverage floor: the 50% early-exit stopped a 6-file corpus folder after 3 files; ≤200 tracked files now get full coverage (a tiny subtree's year-log parses in ms — the guard only ever protected monorepos). Monorepo math unchanged.
  4. Explicit determinism switch: GITGALAXY_DISABLE_GIT_HISTORY=1 env (or CHRONOMETER_CONFIG.DISABLE_GIT_HISTORY) forces the neutral fallback. The golden-crucible test sets it — it scans a subdirectory of a pinned checkout whose git history is not part of the measured structure, and previously got neutrality only by accident of the broken root check.

Verification

  • Chronometer probe on keyword-rosetta/data/python: before — collapse warning, 0 files mapped, is_git_tracked: False; after — 6/6 files mapped, real commits/authors/mtimes. Neutral mode reproduces pre-fix values exactly (span pegged to 1y, churn 0).
  • pytest -k "chrono or temporal or churn or stability": 10 passed.
  • Golden crucible byte-identical on both legs (full_precision + zero_dependency venvs): 1 passed each.
  • Corpus effect is regen-only on keyword-rosetta (churn/stability are never-gated commit-age context there); the corpus PR follows engine-first.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JaGQfW7WGbGXg54iyBg23j

The git gate was `(self.root / ".git").exists()`, and `.git` only exists
at the repository root -- any scan rooted at a SUBDIRECTORY of a repo
(galaxyscope path/to/repo/src, every keyword-rosetta corpus folder)
silently lost the whole temporal pipeline: the mtime fallback fired, a
fresh checkout's uniform mtimes tripped the TEMPORAL COLLAPSE guard, and
every file read the neutral (stability 50, churn 0).

- Gate on `git rev-parse --is-inside-work-tree` instead: resolves upward
  from any subdirectory; a linked worktree's `.git` FILE pointer passes
  identically; a non-git directory lands in the OS-walk fallback exactly
  as before.
- The history stream gains `--relative -- .`: from a subdirectory,
  `git log --name-only` emits repo-relative paths while `git ls-files`
  emits cwd-relative ones, so the churn/mtime maps would have been keyed
  differently from both the tracked-files denominator and the scanner's
  scan-root-relative rel_path. Both flags are byte-identical no-ops at
  the repository root.
- The 50% early-exit stopped a 6-file corpus folder after mapping 3
  files; full coverage at or below 200 tracked files, monorepo math
  unchanged above.
- Determinism-sensitive harnesses opt out EXPLICITLY now: the
  golden-crucible test sets GITGALAXY_DISABLE_GIT_HISTORY=1 (config key
  CHRONOMETER_CONFIG.DISABLE_GIT_HISTORY also honoured) -- before this
  fix it got temporal neutrality by accident of the broken root check.
  Both golden legs verified byte-identical (full_precision and
  zero_dependency venvs, 1 passed each).

Closes #2976

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaGQfW7WGbGXg54iyBg23j
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

The dead-key audit requires a read key to be written somewhere:
DISABLE_GIT_HISTORY joins CHRONOMETER_CONFIG's defaults (False), and the
GITGALAXY_DISABLE_GIT_HISTORY env var joins the audit ALLOWLIST on the
GITGALAXY_LICENSE_KEY precedent. ruff format reflows the gate condition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaGQfW7WGbGXg54iyBg23j
@squid-protocol
squid-protocol marked this pull request as ready for review September 12, 2026 00:52
@squid-protocol
squid-protocol merged commit f2f2bdf into main Sep 12, 2026
30 checks passed
@squid-protocol
squid-protocol deleted the fix/2976-chronometer-subdir-git branch September 12, 2026 00:52
squid-protocol added a commit that referenced this pull request Sep 12, 2026
…l fix (#2980)

The README and validation.md consistency sections update to the post-#2977/
kr#130 scoreboard: 56 chartable metrics (54 >=80% in band), open-defect
share still 0/2632. Both gain the expansion-on-demand proof point: the
corpus's first security-lens plant (one identical secret, every language)
read a uniform risk_secrets_risk across all 44 lens-covered languages in a
single screened change, with the two data-format exceptions and the
measured length dependence ledgered and filed the same day (#2978, #2979);
validation.md also notes the commit-age context group now reads measured
git history (#2976).


Claude-Session: https://claude.ai/code/session_01JaGQfW7WGbGXg54iyBg23j

Co-authored-by: Claude Opus 4.8 <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.

chronometer: subdirectory scans lose all temporal data — the git gate checks (root/.git).exists()

1 participant