stud-finder 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77b17fc211c036ac76d6508d2d37177a0aff6331cb7be4101bb56c8c17e0c61f
4
- data.tar.gz: d601cf1c93a7fa5d9bad190614f57375f2c02a4c4f07d00d6487f4d0ab7e4cf6
3
+ metadata.gz: faa8060a75346fc7a402adf203764c650d1596c2c6b3f4cb25571b3b21ecfa7a
4
+ data.tar.gz: 9c26eed3cb20c42d0155a730ba0ff70f9893a43493bf49d633aea4a0a2036a79
5
5
  SHA512:
6
- metadata.gz: '03438058c1234cd7740190d78807102e39a7b75def36ce914f276d0be9d7cfadc277ee6f5b6a04b7eabc031c7473316b65b0e2d1309a8f98d9e3d96b0559f405'
7
- data.tar.gz: 7f1f8bba0ed383a56dedd2f59efa10ff1f0c662b5b2649416efdd40bc4f66bc99424ca257cc321e3c32e6a77a49880bc93ee74bf2b078dbe29aeaa1f922c8081
6
+ metadata.gz: 863567f3e6dedd1808075bf9ae272cdb3f2e5b3b849b0eaa3b7b829a0db56873b757e6587d4fc82b6460fe6ab993407de24742fdde86d6bea6cb9809eb388ec0
7
+ data.tar.gz: a0a6d9b9fb0ad35bd88e22e26c24eb72957d22c46fd138e0e4c8b7da2bbcc885c5fb7f23bc58db547b826c754a478d72aa9b6cf0b3b04c81fa1801ef11fc903a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ 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
+
23
+ ## [0.4.0] - Unreleased
24
+
25
+ ### Changed
26
+
27
+ - BREAKING: Classification thresholds are now percentile-based, not score-value-based. `--trunk-threshold 85` and `--branch-threshold 50` now mean "top 15% and top 50% of files by composite score" — not "score value ≥ 0.85 / 0.50". This is a semantic change to the same flag values; the numbers you pass stay the same but their meaning inverts. Previously, trunk was unreachable at defaults (max observed composite score ~0.717 in real repos, zero trunks); now every repo of meaningful size produces some trunks and branches. Absolute floors (raw complexity ≥ 15, raw fan_in ≥ 25) still escalate to `branch` regardless of percentile.
28
+
8
29
  ## [0.3.0] - 2026-07-11
9
30
 
10
31
  ### Fixed
@@ -38,7 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
38
59
 
39
60
  ### Added
40
61
 
41
- - Initial `stud-finder` cut: a CLI that ranks files by five risk signals: fan-in (blast radius), fan-out, cyclomatic complexity, git churn, and test coverage.
62
+ - Initial RubyGems release: a CLI that ranks files by five risk signals: fan-in (blast radius), fan-out, cyclomatic complexity, git churn, and test coverage.
42
63
  - Temporal coupling analysis for identifying files that change together.
43
64
  - Diff mode for scoring only files changed in a pull request while preserving repo-relative scores.
44
65
  - Ruby and JavaScript/TypeScript support.
data/README.md CHANGED
@@ -9,12 +9,12 @@ $ bundle exec bin/stud-finder ./my-rails-app
9
9
 
10
10
  Ruby
11
11
  rank language file score evidence class new age_days escalation ...
12
- 1 ruby app/models/proficiency.rb 0.7304 1.0000 branch false 842 ...
13
- 2 ruby app/services/payment_service.rb 0.6890 1.0000 branch false 611 ...
14
- 3 ruby app/controllers/orders_ctlr.rb 0.5721 0.6667 branch false 520 ...
12
+ 1 ruby app/models/proficiency.rb 0.7304 1.0000 trunk false 842 ...
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 1.0000 branch false 520 ...
15
15
  ```
16
16
 
17
- *Scores and evidence are illustrative. Row 3 has no coverage data (evidence capped at `0.6667 = (1.0 + 1.0 + 0.0) / 3`); rows 1–2 have full coverage (evidence `1.0000`). Real runs on well-distributed repos typically top out around 0.70–0.75 composite score — see Classification.*
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
 
@@ -117,15 +117,19 @@ Result is clamped to `[0.0, 1.0]` and rounded to four decimal places.
117
117
 
118
118
  ## Classification
119
119
 
120
- Every row is labelled with a `class`:
120
+ Files are classified into three tiers based on the **percentile rank of their composite score** within the repo:
121
121
 
122
- - **trunk** — composite score `trunk_threshold / 100` (default `trunk_threshold: 85`, so score ≥ 0.85). Load-bearing. High review bar.
123
- - **branch** — composite score `branch_threshold / 100` and below the trunk cutoff (default `branch_threshold: 50`, so 0.50 ≤ score < 0.85). Meaningful coupling.
124
- - **leaf** — score below the branch cutoff. Isolated. Move fast here.
122
+ - **trunk** — top 15% by composite score (default `--trunk-threshold 85`). Load-bearing. High review bar, change with care.
123
+ - **branch** — top 50% but below top 15% (default `--branch-threshold 50`). Meaningful coupling.
124
+ - **leaf** — everything below the 50th percentile. Isolated. Move fast here.
125
125
 
126
- `--trunk-threshold` and `--branch-threshold` take integer values 1–99 that set the composite-score cutoff (a threshold of 85 means score 0.85). Classification is driven by the full composite, not by fan-in percentile a file with modest fan-in but very high complexity + churn can still classify as `trunk`.
126
+ This means every repo of meaningful size has trunks: a file at score 0.55 is trunk if the rest of the repo scores below it. The absolute floors below provide a safety net for tiny repos.
127
127
 
128
- Because `score` is a weighted sum of percentile-ranked signals, not itself a percentile, a threshold of 85 does not mean "top 15% of files." Well-distributed projects rarely have any file scoring above 0.85; in tighter distributions the cutoffs may need tuning per-project.
128
+ **Absolute floors:** raw complexity 15 or raw fan-in 25 escalates a `leaf` to `branch` regardless of percentile. This ensures that structurally dangerous files in tiny repos (where percentile spread is minimal) still receive elevated attention.
129
+
130
+ **Tiny repos:** In repos with very few files and uniform scores, the percentile spread may place all files at the same score_pct (0.0), resulting in 0 trunks. The absolute floor is the escape hatch for dangerous files in this case.
131
+
132
+ Note: `--trunk-threshold 85` and `--branch-threshold 50` now mean "top 15% / top 50% of files by composite score" — not "score value ≥ 0.85 / 0.50". This is a BREAKING semantic change from 0.3.0. Trunk was previously unreachable at defaults (max observed composite score ~0.717 in real repos); this change restores the guarantee that some files are always classified trunk-tier relative to their codebase.
129
133
 
130
134
  ### Absolute floors
131
135
 
@@ -143,7 +147,7 @@ A file is considered new when its first commit is within `--new-file-days` days
143
147
 
144
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.
145
149
 
146
- **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 auto-disables both newness rules and emits `shallow_clone_newness_disabled` in `warnings` so classifications match `--no-newness` instead of misclassifying mature files as new.
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`.
147
151
 
148
152
  ---
149
153
 
@@ -151,9 +155,9 @@ A stronger rule runs first: if a new file depends on a structurally `trunk` file
151
155
 
152
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?
153
157
 
154
- Evidence combines file age, commit count, and whether coverage data was explicitly provided. 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.
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.
155
159
 
156
- In shallow clones (git fetch-depth < full history), evidence is `null` on every row because file-metadata history is unavailable. The `shallow_clone_newness_disabled` warning also fires. See the CI note above.
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.
157
161
 
158
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.
159
163
 
@@ -163,7 +167,8 @@ In shallow clones (git fetch-depth < full history), evidence is `null` on every
163
167
 
164
168
  `analysis.warnings` (available in JSON output) surfaces conditions the run detected that a consumer should know about:
165
169
 
166
- - **`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.
167
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`.
168
173
  - Language-specific warnings such as `js_depcruise_no_config` when the JS pipeline had to fall back.
169
174
 
@@ -218,8 +223,8 @@ Each language gets its own ranking section in the output — Ruby and JS are not
218
223
  | `--weights WEIGHTS` | Custom weights as fractions, e.g. `fan_in:F,fan_out:O,complexity:C,churn:H,coverage:V[,interaction:I][,coupling:P]`. The five base keys (`fan_in`, `fan_out`, `complexity`, `churn`, `coverage`) are required. `interaction` and `coupling` are optional: when omitted, `interaction` defaults to `0.0` (custom weights opt-in) and `coupling` defaults to `0.05`. Each value must be in `[0.0, 1.0]`. When no coverage data is provided, `coverage` must be `0.0`. |
219
224
  | `--interaction-weight N` | Sugar flag for setting only the interaction weight. |
220
225
  | `--coupling-weight N` | Sugar flag for setting only the coupling weight. Bounds-checked `[0.0, 1.0]`. |
221
- | `--trunk-threshold N` | Composite-score threshold for trunk classification: score N/100 (integer 1–99, default: 85) |
222
- | `--branch-threshold N` | Composite-score threshold for branch classification: score N/100 (integer 1–99, default: 50) |
226
+ | `--trunk-threshold N` | composite-score percentile cutoff for trunk classification; top (100-N)% of files by score (default: 85) |
227
+ | `--branch-threshold N` | composite-score percentile cutoff for branch classification; top (100-N)% of files by score (default: 50) |
223
228
  | `--exclude PATTERN` | Exclude glob pattern (repeatable). `spec/` and `test/` excluded by default. |
224
229
  | `--top N` | Emit only the top N results |
225
230
  | `--diff-base REF` | Score the whole repo but emit only the files changed on `HEAD` vs the merge-base with `REF` (e.g. `origin/staging`). Ranks and scores stay relative to the full repo. Ideal for per-PR runs. |
data/SIGNALS.md CHANGED
@@ -88,8 +88,8 @@ Ties receive the same rank. Edge cases: a codebase with only one file gets `0.0`
88
88
  Three pieces of output serve three purposes. Confusing them causes gate consumers to threshold the wrong number.
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
- - **`class`** (`leaf` / `branch` / `trunk`) — the decision label. Driven by the composite score against configurable thresholds (defaults: `branch` at score ≥ 0.50, `trunk` at score 0.85). 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 whether coverage data was explicitly provided. 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`.
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 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
 
@@ -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
 
@@ -209,11 +210,13 @@ module StudFinder
209
210
  @options[:rails_inference] = false
210
211
  end
211
212
  opts.on('--trunk-threshold N', Integer,
212
- 'score cutoff for trunk classification, integer 1-99 (default: 85)') do |value|
213
+ 'score percentile cutoff for trunk classification, integer 1-99 ' \
214
+ '(default: 85 = top 15% of files by score)') do |value|
213
215
  @options[:trunk_threshold] = value
214
216
  end
215
217
  opts.on('--branch-threshold N', Integer,
216
- 'score cutoff for branch classification, integer 1-99 (default: 50)') do |value|
218
+ 'score percentile cutoff for branch classification, integer 1-99 ' \
219
+ '(default: 50 = top 50% of files by score)') do |value|
217
220
  @options[:branch_threshold] = value
218
221
  end
219
222
  opts.on('--exclude PATTERN', 'Exclude glob pattern (repeatable)') do |value|
@@ -257,6 +260,9 @@ module StudFinder
257
260
  opts.on('--no-newness', 'Disable new-file classification rules') do
258
261
  @options[:newness] = false
259
262
  end
263
+ opts.on('--no-auto-unshallow', 'Disable automatic git fetch --unshallow on shallow clones') do
264
+ @options[:auto_unshallow] = false
265
+ end
260
266
  opts.on('--verbose', 'Print suppressed per-file warnings to stderr') do
261
267
  @options[:verbose] = true
262
268
  end
@@ -509,8 +515,12 @@ module StudFinder
509
515
 
510
516
  def apply_newness(files, edges, rows, coverage_result = nil)
511
517
  metadata = if @options[:newness] && Newness.shallow_repository?(@repo_path)
512
- @options[:cli_warnings] << Newness::SHALLOW_CLONE_WARNING
513
- Newness.disabled_metadata(files)
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
514
524
  elsif @options[:newness]
515
525
  Newness.new(repo_path: @repo_path, files: files, days: @options[:new_file_days],
516
526
  min_commits: @options[:new_file_min_commits]).call
@@ -520,6 +530,21 @@ module StudFinder
520
530
  Newness.apply(rows: rows, edges: edges, metadata: metadata, coverage: coverage_result)
521
531
  end
522
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
+
523
548
  def loc_percentiles_by_language(files, loc, language_by_file)
524
549
  files.group_by { |file| language_by_file.fetch(file) }.values.reduce({}) do |pct, language_files|
525
550
  pct.merge(Normalizer.percentile_rank(loc, language_files))
@@ -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
- ((age_component + commits_component + coverage_component) / 3.0).round(4)
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)
@@ -52,9 +52,11 @@ module StudFinder
52
52
  }
53
53
  @warnings = insufficient_dispersion_warnings(pcts)
54
54
 
55
+ scores = @files.to_h { |file| [file, weighted_score(file, pcts)] }
56
+ score_pcts = Normalizer.percentile_rank(scores, @files)
57
+
55
58
  rows = @files.each_with_index.map do |file, index|
56
- score = weighted_score(file, pcts)
57
- [index, result_row(file, score, pcts)]
59
+ [index, result_row(file, scores.fetch(file), pcts, score_pcts.fetch(file))]
58
60
  end
59
61
 
60
62
  rows.sort_by { |index, row| [-row[:score], index] }
@@ -123,12 +125,12 @@ module StudFinder
123
125
  end
124
126
  end
125
127
 
126
- def result_row(file, score, pcts)
128
+ def result_row(file, score, pcts, score_pct)
127
129
  fi = @fan_in.fetch(file, 0).to_i
128
130
  fo = @fan_out.fetch(file, 0).to_i
129
131
  rounded_score = score.round(4)
130
132
  complexity = @complexity.fetch(file, 0).to_i
131
- floored_class, floor_escalation = floored_classification(classification(rounded_score), complexity, fi)
133
+ floored_class, floor_escalation = floored_classification(classification(score_pct), complexity, fi)
132
134
  {
133
135
  path: file,
134
136
  score: rounded_score,
@@ -242,9 +244,9 @@ module StudFinder
242
244
  end
243
245
  end
244
246
 
245
- def classification(score)
246
- return 'trunk' if score >= @trunk_threshold / 100.0
247
- return 'branch' if score >= @branch_threshold / 100.0
247
+ def classification(score_pct)
248
+ return 'trunk' if score_pct >= @trunk_threshold / 100.0
249
+ return 'branch' if score_pct >= @branch_threshold / 100.0
248
250
 
249
251
  'leaf'
250
252
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StudFinder
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bazfer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-11 00:00:00.000000000 Z
11
+ date: 2026-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: csv
@@ -180,7 +180,7 @@ metadata:
180
180
  changelog_uri: https://github.com/bazfer/stud-finder/blob/main/CHANGELOG.md
181
181
  bug_tracker_uri: https://github.com/bazfer/stud-finder/issues
182
182
  rubygems_mfa_required: 'true'
183
- post_install_message:
183
+ post_install_message:
184
184
  rdoc_options: []
185
185
  require_paths:
186
186
  - lib
@@ -195,8 +195,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
195
  - !ruby/object:Gem::Version
196
196
  version: '0'
197
197
  requirements: []
198
- rubygems_version: 3.5.22
199
- signing_key:
198
+ rubygems_version: 3.4.10
199
+ signing_key:
200
200
  specification_version: 4
201
201
  summary: Rank files by structural risk in Ruby and JavaScript/TypeScript codebases.
202
202
  test_files: []