stud-finder 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +7 -6
- data/SIGNALS.md +1 -1
- data/lib/stud_finder/cli.rb +25 -2
- data/lib/stud_finder/newness.rb +8 -2
- data/lib/stud_finder/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: faa8060a75346fc7a402adf203764c650d1596c2c6b3f4cb25571b3b21ecfa7a
|
|
4
|
+
data.tar.gz: 9c26eed3cb20c42d0155a730ba0ff70f9893a43493bf49d633aea4a0a2036a79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 863567f3e6dedd1808075bf9ae272cdb3f2e5b3b849b0eaa3b7b829a0db56873b757e6587d4fc82b6460fe6ab993407de24742fdde86d6bea6cb9809eb388ec0
|
|
7
|
+
data.tar.gz: a0a6d9b9fb0ad35bd88e22e26c24eb72957d22c46fd138e0e4c8b7da2bbcc885c5fb7f23bc58db547b826c754a478d72aa9b6cf0b3b04c81fa1801ef11fc903a
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- BREAKING: Evidence formula changed — coverage is now a bonus, not a required third signal. The new formula is `max(history_only, with_coverage)` where `history_only = (age + commits) / 2`. Mature files without a coverage report now produce `evidence: 1.0` instead of the old ceiling of `0.6667`. Files with coverage data can only equal or exceed the history-only baseline, never fall below it.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Shallow-clone evidence null in CI: when Stud Finder detects a depth-1 clone (GitHub Actions default), it now automatically runs `git fetch --unshallow` (45 second timeout) before computing newness metadata. Repos that used the default `actions/checkout` without `fetch-depth: 0` now get real evidence values instead of `null` on every row.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `--no-auto-unshallow` flag: opt out of the automatic `git fetch --unshallow` on shallow clones. When set, Stud Finder behaves as before — shallow clone disables newness and emits only `shallow_clone_newness_disabled`.
|
|
21
|
+
- `shallow_clone_unshallow_failed` warning code: fires when auto-unshallow was attempted but failed (network error, timeout, or non-zero exit). Appears alongside `shallow_clone_newness_disabled` so consumers can distinguish "unshallow was never tried" from "unshallow was tried and failed".
|
|
22
|
+
|
|
8
23
|
## [0.4.0] - Unreleased
|
|
9
24
|
|
|
10
25
|
### Changed
|
data/README.md
CHANGED
|
@@ -11,10 +11,10 @@ Ruby
|
|
|
11
11
|
rank language file score evidence class new age_days escalation ...
|
|
12
12
|
1 ruby app/models/proficiency.rb 0.7304 1.0000 trunk false 842 ...
|
|
13
13
|
2 ruby app/services/payment_service.rb 0.6890 1.0000 trunk false 611 ...
|
|
14
|
-
3 ruby app/controllers/orders_ctlr.rb 0.5721
|
|
14
|
+
3 ruby app/controllers/orders_ctlr.rb 0.5721 1.0000 branch false 520 ...
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
*Scores and evidence are illustrative. Row 3 has no coverage data (evidence
|
|
17
|
+
*Scores and evidence are illustrative. Row 3 has full history but no coverage data (evidence `1.0000` — coverage is a bonus, not a requirement); rows 1–2 have full coverage (evidence `1.0000`). The `class` column reflects percentile rank of composite score within the repo — see Classification.*
|
|
18
18
|
|
|
19
19
|
The full table adds `fan_in`, `fan_out`, `instability`, `complexity`, `churn_commits`, `churn_lines`, `churn_pct`, `loc`, `loc_pct`, `max_coupling`, `max_coupling_partner`, `coupling_partners`, `coupling_pct`, and `coverage`. Use `--output json` for machine-readable output including a `warnings` section and full `meta`.
|
|
20
20
|
|
|
@@ -147,7 +147,7 @@ A file is considered new when its first commit is within `--new-file-days` days
|
|
|
147
147
|
|
|
148
148
|
A stronger rule runs first: if a new file depends on a structurally `trunk` file through its fan-out edges, it escalates to `trunk` with `escalation=trunk_adjacent`. This highlights new code consuming critical interfaces, where contract-violation risk is highest. Use `--no-newness` to disable both newness rules.
|
|
149
149
|
|
|
150
|
-
**CI usage:** newness rules require full git history. In GitHub Actions, set `fetch-depth: 0` before running Stud Finder. If Stud Finder detects a shallow clone
|
|
150
|
+
**CI usage:** newness rules require full git history. In GitHub Actions, set `fetch-depth: 0` before running Stud Finder. If Stud Finder detects a shallow clone it automatically runs `git fetch --unshallow` (45 second timeout) to recover full history before computing metadata. If the fetch fails (network error, timeout, offline runner), Stud Finder falls back to disabled newness and emits both `shallow_clone_newness_disabled` and `shallow_clone_unshallow_failed` in `warnings`. Pass `--no-auto-unshallow` to skip the fetch attempt entirely — on shallow clones only `shallow_clone_newness_disabled` fires and classifications match `--no-newness`.
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
@@ -155,9 +155,9 @@ A stronger rule runs first: if a new file depends on a structurally `trunk` file
|
|
|
155
155
|
|
|
156
156
|
Every row carries an `evidence` value (0.0–1.0) alongside `score`. Score is the weighted signal composite. Evidence is a metadata confidence: how much history + coverage-data backing does that score have?
|
|
157
157
|
|
|
158
|
-
Evidence combines file age
|
|
158
|
+
Evidence combines file age and commit count as its base, with coverage data as a bonus signal. The formula is `max(history_only, with_coverage)`, where `history_only = (age + commits) / 2` and `with_coverage = (age + commits + 1.0) / 3`. A mature file with full history always reaches `1.0` regardless of whether a coverage report is present — coverage can only raise evidence above the history baseline, never suppress it. A high score with low evidence means "structural signals concentrated risk here, but we're not certain because the file is young or the history is thin." A high score with high evidence is a strong claim.
|
|
159
159
|
|
|
160
|
-
In shallow clones (
|
|
160
|
+
In shallow clones where auto-unshallow fails (or `--no-auto-unshallow` is set), evidence is `null` on every row because file-metadata history is unavailable. See the CI note above.
|
|
161
161
|
|
|
162
162
|
**Gate consumers should threshold `class` for verdicts and `evidence` for confidence, not raw `score` alone.** Output is sorted by `(class_rank, score)` so trunks group above branches above leaves, and `--top N` no longer drops newness-escalated `trunk_adjacent` files behind high-score branches.
|
|
163
163
|
|
|
@@ -167,7 +167,8 @@ In shallow clones (git fetch-depth < full history), evidence is `null` on every
|
|
|
167
167
|
|
|
168
168
|
`analysis.warnings` (available in JSON output) surfaces conditions the run detected that a consumer should know about:
|
|
169
169
|
|
|
170
|
-
- **`shallow_clone_newness_disabled`** — shallow git clone detected; newness rules auto-disabled.
|
|
170
|
+
- **`shallow_clone_newness_disabled`** — shallow git clone detected; newness rules auto-disabled (auto-unshallow also failed, or `--no-auto-unshallow` was passed).
|
|
171
|
+
- **`shallow_clone_unshallow_failed`** — `git fetch --unshallow` was attempted but failed (network error or timeout); evidence is unavailable. Use `fetch-depth: 0` in CI or pass `--no-auto-unshallow` to suppress the attempt.
|
|
171
172
|
- **`insufficient_dispersion_<signal>`** — every file in the codebase has the same non-zero raw value for `<signal>`, so its percentile-ranked contribution collapsed to `0.0`. The score is unchanged; the warning flags that the signal is silently uninformative rather than genuinely absent. One per affected signal: `fan_in`, `fan_out`, `complexity`, `churn`, `coverage`, `interaction`, `coupling`.
|
|
172
173
|
- Language-specific warnings such as `js_depcruise_no_config` when the JS pipeline had to fall back.
|
|
173
174
|
|
data/SIGNALS.md
CHANGED
|
@@ -89,7 +89,7 @@ Three pieces of output serve three purposes. Confusing them causes gate consumer
|
|
|
89
89
|
|
|
90
90
|
- **`score`** (0.0–1.0, four decimals) — the weighted composite. This is **evidence about the file's structural risk**, nothing more. Higher score means the signals concentrated more risk on this file.
|
|
91
91
|
- **`class`** (`leaf` / `branch` / `trunk`) — the decision label. Driven by the composite-score PERCENTILE across the codebase against configurable thresholds (defaults: `branch` at top 50%, `trunk` at top 15%). This guarantees that some files are always trunk-tier relative to their repo — a file scoring 0.55 can be trunk if the rest of the codebase scores below it. This is what a gate should threshold on for verdicts.
|
|
92
|
-
- **`evidence`** (0.0–1.0) — a metadata confidence value based on file age, commit count, and
|
|
92
|
+
- **`evidence`** (0.0–1.0) — a metadata confidence value based on file age, commit count, and optionally coverage presence. The formula is `max(history_only, with_coverage)`, where `history_only = (age + commits) / 2` and `with_coverage = (age + commits + 1.0) / 3` (only included when coverage data is present). Coverage is a bonus: a mature file with full history always reaches `1.0` even without a coverage report. A high score with low evidence means "structural signals concentrated risk here, but we're not certain because the file is young or the history is thin." Gates should threshold `evidence` for confidence, not raw `score`.
|
|
93
93
|
|
|
94
94
|
### Absolute floors
|
|
95
95
|
|
data/lib/stud_finder/cli.rb
CHANGED
|
@@ -63,6 +63,7 @@ module StudFinder
|
|
|
63
63
|
newness: true,
|
|
64
64
|
new_file_days: StudFinder::Newness::DEFAULT_DAYS,
|
|
65
65
|
new_file_min_commits: StudFinder::Newness::DEFAULT_MIN_COMMITS,
|
|
66
|
+
auto_unshallow: true,
|
|
66
67
|
cli_warnings: []
|
|
67
68
|
}.freeze
|
|
68
69
|
|
|
@@ -259,6 +260,9 @@ module StudFinder
|
|
|
259
260
|
opts.on('--no-newness', 'Disable new-file classification rules') do
|
|
260
261
|
@options[:newness] = false
|
|
261
262
|
end
|
|
263
|
+
opts.on('--no-auto-unshallow', 'Disable automatic git fetch --unshallow on shallow clones') do
|
|
264
|
+
@options[:auto_unshallow] = false
|
|
265
|
+
end
|
|
262
266
|
opts.on('--verbose', 'Print suppressed per-file warnings to stderr') do
|
|
263
267
|
@options[:verbose] = true
|
|
264
268
|
end
|
|
@@ -511,8 +515,12 @@ module StudFinder
|
|
|
511
515
|
|
|
512
516
|
def apply_newness(files, edges, rows, coverage_result = nil)
|
|
513
517
|
metadata = if @options[:newness] && Newness.shallow_repository?(@repo_path)
|
|
514
|
-
@options[:
|
|
515
|
-
|
|
518
|
+
if @options[:auto_unshallow]
|
|
519
|
+
attempt_unshallow(files)
|
|
520
|
+
else
|
|
521
|
+
@options[:cli_warnings] << Newness::SHALLOW_CLONE_WARNING
|
|
522
|
+
Newness.disabled_metadata(files)
|
|
523
|
+
end
|
|
516
524
|
elsif @options[:newness]
|
|
517
525
|
Newness.new(repo_path: @repo_path, files: files, days: @options[:new_file_days],
|
|
518
526
|
min_commits: @options[:new_file_min_commits]).call
|
|
@@ -522,6 +530,21 @@ module StudFinder
|
|
|
522
530
|
Newness.apply(rows: rows, edges: edges, metadata: metadata, coverage: coverage_result)
|
|
523
531
|
end
|
|
524
532
|
|
|
533
|
+
def attempt_unshallow(files)
|
|
534
|
+
require 'timeout'
|
|
535
|
+
Timeout.timeout(45) do
|
|
536
|
+
_stdout, _stderr, status = Open3.capture3('git', 'fetch', '--unshallow', chdir: @repo_path)
|
|
537
|
+
raise 'unshallow failed' unless status.success?
|
|
538
|
+
|
|
539
|
+
Newness.new(repo_path: @repo_path, files: files, days: @options[:new_file_days],
|
|
540
|
+
min_commits: @options[:new_file_min_commits]).call
|
|
541
|
+
end
|
|
542
|
+
rescue StandardError
|
|
543
|
+
@options[:cli_warnings] << Newness::SHALLOW_CLONE_WARNING
|
|
544
|
+
@options[:cli_warnings] << Newness::UNSHALLOW_FAILED_WARNING
|
|
545
|
+
Newness.disabled_metadata(files)
|
|
546
|
+
end
|
|
547
|
+
|
|
525
548
|
def loc_percentiles_by_language(files, loc, language_by_file)
|
|
526
549
|
files.group_by { |file| language_by_file.fetch(file) }.values.reduce({}) do |pct, language_files|
|
|
527
550
|
pct.merge(Normalizer.percentile_rank(loc, language_files))
|
data/lib/stud_finder/newness.rb
CHANGED
|
@@ -14,6 +14,10 @@ module StudFinder
|
|
|
14
14
|
code: 'shallow_clone_newness_disabled',
|
|
15
15
|
message: 'shallow git clone detected; newness rules disabled (use fetch-depth: 0 in CI for full newness behavior)'
|
|
16
16
|
}.freeze
|
|
17
|
+
UNSHALLOW_FAILED_WARNING = {
|
|
18
|
+
code: 'shallow_clone_unshallow_failed',
|
|
19
|
+
message: 'auto-unshallow failed; evidence unavailable (use fetch-depth: 0 in CI or pass --no-auto-unshallow)'
|
|
20
|
+
}.freeze
|
|
17
21
|
|
|
18
22
|
History = Struct.new(:first_commit_epoch, :total_commits, keyword_init: true)
|
|
19
23
|
|
|
@@ -104,9 +108,11 @@ module StudFinder
|
|
|
104
108
|
|
|
105
109
|
age_component = [metadata.fetch(:age_days, 0) / 30.0, 1.0].min
|
|
106
110
|
commits_component = [metadata.fetch(:total_commits, 0) / 3.0, 1.0].min
|
|
107
|
-
coverage_component = explicit_coverage ? 1.0 : 0.0
|
|
108
111
|
|
|
109
|
-
(
|
|
112
|
+
history_only = (age_component + commits_component) / 2.0
|
|
113
|
+
with_coverage = ((age_component + commits_component + 1.0) / 3.0) if explicit_coverage
|
|
114
|
+
|
|
115
|
+
[history_only, with_coverage].compact.max.round(4)
|
|
110
116
|
end
|
|
111
117
|
|
|
112
118
|
def self.git_shallow_file?(repo_path)
|
data/lib/stud_finder/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stud-finder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- bazfer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: csv
|