stud-finder 0.5.0 → 0.6.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: faa8060a75346fc7a402adf203764c650d1596c2c6b3f4cb25571b3b21ecfa7a
4
- data.tar.gz: 9c26eed3cb20c42d0155a730ba0ff70f9893a43493bf49d633aea4a0a2036a79
3
+ metadata.gz: 65ca1119e7b7a29b14a47547161d99ce047ca0bf5f49325e7393c01bc7fc56a6
4
+ data.tar.gz: 54674df0b9f1409062d37c85f364323d6edd4e76fbbcecfa31cb3257902cdb87
5
5
  SHA512:
6
- metadata.gz: 863567f3e6dedd1808075bf9ae272cdb3f2e5b3b849b0eaa3b7b829a0db56873b757e6587d4fc82b6460fe6ab993407de24742fdde86d6bea6cb9809eb388ec0
7
- data.tar.gz: a0a6d9b9fb0ad35bd88e22e26c24eb72957d22c46fd138e0e4c8b7da2bbcc885c5fb7f23bc58db547b826c754a478d72aa9b6cf0b3b04c81fa1801ef11fc903a
6
+ metadata.gz: 62f1e83fbe670e67b91d5640e120f19ab5a307f8cc887ea12bdf2103e80e6dd00f32fd4cc3415df195b4bb82a0ba3a6929f569957b011b00a2061121563c6ce6
7
+ data.tar.gz: 6f29ee2b1a9e575b49f3fb1978071059e1eb203b8a33ded65a973cd7924b8915e6d44a07e4b8ec9226cdb324d5bbdaa6b71ab266511d00314aaf4c7509cb42d7
data/CHANGELOG.md CHANGED
@@ -5,7 +5,24 @@ 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
8
+ ## [0.6.0] - Unreleased
9
+
10
+ ### Changed
11
+
12
+ - BREAKING: JSON output now reports `meta.schema_version: 1`, an integer schema marker that will bump on future breaking JSON changes.
13
+ - BREAKING: JSON `warnings` and `meta.warnings` are now normalized to objects shaped as `{code, message}`. Bare warning strings are no longer emitted.
14
+
15
+ ## [0.5.1] - Unreleased
16
+
17
+ ### Added
18
+
19
+ - Auto-unshallow now emits a stderr progress line before fetching, so users aren't surprised by the silent clone mutation. `--no-auto-unshallow` still skips the whole attempt.
20
+
21
+ ### Fixed
22
+
23
+ - Auto-unshallow no longer leaks the child `git fetch` process on timeout. `Open3.popen3` + explicit `TERM`/`KILL` reap replaces the prior `Open3.capture3` + `Timeout.timeout` pattern that unwound the parent stack while leaving the child orphaned.
24
+
25
+ ## [0.5.0] - 2026-07-12
9
26
 
10
27
  ### Changed
11
28
 
@@ -20,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
37
  - `--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
38
  - `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
39
 
23
- ## [0.4.0] - Unreleased
40
+ ## [0.4.0] - 2026-07-11
24
41
 
25
42
  ### Changed
26
43
 
data/README.md CHANGED
@@ -165,7 +165,7 @@ In shallow clones where auto-unshallow fails (or `--no-auto-unshallow` is set),
165
165
 
166
166
  ## Warnings
167
167
 
168
- `analysis.warnings` (available in JSON output) surfaces conditions the run detected that a consumer should know about:
168
+ `analysis.warnings` (available in JSON output) surfaces conditions the run detected that a consumer should know about. Starting in schema version `1`, every warning is an object with `code` and human-readable `message` fields; bare warning strings are no longer emitted.
169
169
 
170
170
  - **`shallow_clone_newness_disabled`** — shallow git clone detected; newness rules auto-disabled (auto-unshallow also failed, or `--no-auto-unshallow` was passed).
171
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.
@@ -245,7 +245,7 @@ Each language gets its own ranking section in the output — Ruby and JS are not
245
245
 
246
246
  - `table` — human-readable, aligned columns
247
247
  - `csv` — spreadsheet-friendly, pipe to a file
248
- - `json` — machine-readable with `meta`, `warnings`, `ruby`, `javascript` sections. `meta.formula` labels the active mode (`5-factor + coupling`, `5-factor`, `4-factor + coupling`, `4-factor`). `meta.weights` reports the normalized weights actually used (with `null` for signals that were unavailable).
248
+ - `json` — machine-readable with `meta`, `warnings`, `ruby`, `javascript` sections. `meta.schema_version` is the integer JSON schema version (`1` as of Stud Finder 0.6.0). `meta.formula` labels the active mode (`5-factor + coupling`, `5-factor`, `4-factor + coupling`, `4-factor`). `meta.weights` reports the normalized weights actually used (with `null` for signals that were unavailable).
249
249
  - `markdown` — drop directly into a PR comment or issue
250
250
 
251
251
  ---
@@ -9,6 +9,7 @@ require 'set'
9
9
  require 'time'
10
10
  require_relative 'churn'
11
11
  require_relative 'temporal_coupling'
12
+ require_relative 'warnings'
12
13
  require_relative 'complexity'
13
14
  require_relative 'diff'
14
15
  require_relative 'coverage/detector'
@@ -531,10 +532,14 @@ module StudFinder
531
532
  end
532
533
 
533
534
  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?
535
+ progress('auto-unshallow: fetching full history to compute evidence (use --no-auto-unshallow to skip)')
536
+ Open3.popen3('git', '-C', @repo_path, 'fetch', '--unshallow') do |_stdin, _stdout, _stderr, wait_thread|
537
+ unless wait_thread.join(45)
538
+ Process.kill('TERM', wait_thread.pid)
539
+ Process.kill('KILL', wait_thread.pid) unless wait_thread.join(5)
540
+ raise 'unshallow timed out'
541
+ end
542
+ raise 'unshallow failed' unless wait_thread.value.success?
538
543
 
539
544
  Newness.new(repo_path: @repo_path, files: files, days: @options[:new_file_days],
540
545
  min_commits: @options[:new_file_min_commits]).call
@@ -718,7 +723,7 @@ module StudFinder
718
723
  def emit_json(path, analysis, ruby_rows, javascript_rows)
719
724
  @stdout.puts JSON.generate(
720
725
  meta: json_meta(path, analysis),
721
- warnings: analysis.warnings,
726
+ warnings: Warnings.normalize(analysis.warnings),
722
727
  ruby: ruby_rows.map { |row| json_file(row) },
723
728
  javascript: javascript_rows.map { |row| json_file(row) }
724
729
  )
@@ -733,7 +738,8 @@ module StudFinder
733
738
  files_skipped: analysis.ruby.skipped_files.length + analysis.javascript.skipped_files.length,
734
739
  formula: json_formula(analysis),
735
740
  weights: json_weights(analysis.ruby.weights || analysis.javascript.weights),
736
- warnings: analysis.warnings
741
+ schema_version: 1,
742
+ warnings: Warnings.normalize(analysis.warnings)
737
743
  }
738
744
  meta[:filtered] = true if @options[:filter_set]
739
745
  meta[:diff_base] = @options[:diff_base] if @options[:diff_base]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StudFinder
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StudFinder
4
+ module Warnings
5
+ MESSAGES = {
6
+ 'coverage_flag_deprecated' => '--coverage is deprecated; use --ruby-coverage instead',
7
+ 'coverage_unavailable' => 'coverage data was not provided; coverage and interaction signals are unavailable',
8
+ 'coverage_partial' => 'coverage data did not include every scored file; missing files use uncovered risk',
9
+ 'diff_filter_empty' => 'diff filter matched no changed files; output rows are empty',
10
+ 'diff_no_scored_files' => 'diff matched no scored files; the PR may only touch unscorable files',
11
+ 'fan_in_rails_inference_failed' => 'Rails inference failed; inferred Rails references may be incomplete',
12
+ 'fan_in_reference_resolution_failed' => 'constant reference resolution failed; fan-in may be incomplete',
13
+ 'files_skipped' => 'one or more files were skipped during analysis',
14
+ 'git_error' => 'git command failed while computing temporal coupling; coupling is unavailable',
15
+ 'git_not_found' => 'git was not found in PATH while computing temporal coupling; coupling is unavailable',
16
+ 'js_depcruise_failed' => 'dependency-cruiser failed; JavaScript/TypeScript fan-in is unavailable',
17
+ 'js_depcruise_no_config' => 'dependency-cruiser config failed; retried with --no-config, so ' \
18
+ 'JavaScript/TypeScript fan-in may be undercounted',
19
+ 'js_depcruise_timeout' => 'dependency-cruiser timed out; JavaScript/TypeScript fan-in is unavailable',
20
+ 'js_eslint_failed' => 'ESLint failed for at least one batch; JavaScript/TypeScript complexity may be incomplete',
21
+ 'js_eslint_malformed' => 'ESLint produced malformed JSON for at least one batch; JavaScript/TypeScript ' \
22
+ 'complexity may be incomplete',
23
+ 'js_eslint_missing' => 'ESLint was not found; JavaScript/TypeScript complexity is unavailable',
24
+ 'js_eslint_timeout' => 'ESLint timed out for at least one batch; JavaScript/TypeScript complexity may be ' \
25
+ 'incomplete',
26
+ 'js_tools_missing' => 'Node.js or dependency-cruiser was not found; JavaScript/TypeScript fan-in is unavailable',
27
+ 'js_ts_parser_missing' => 'TypeScript files were present but @typescript-eslint/parser was not found; ' \
28
+ 'TypeScript complexity may be incomplete',
29
+ 'shallow_clone_newness_disabled' => 'shallow git clone detected; newness rules disabled (use fetch-depth: 0)',
30
+ 'shallow_clone_unshallow_failed' => 'auto-unshallow failed; evidence unavailable (use full git history)',
31
+ 'small_repo' => 'repo has fewer files than --min-files; results may be noisy',
32
+ 'temporal_coupling_bulk_commits_skipped' => 'one or more bulk commits were skipped while computing temporal ' \
33
+ 'coupling',
34
+ 'zero_churn_majority' => 'most files have zero churn in the selected window; churn may be less informative'
35
+ }.freeze
36
+
37
+ INSUFFICIENT_DISPERSION_MESSAGE = lambda do |signal|
38
+ "every file has the same non-zero raw #{signal} value, so its percentile-ranked contribution collapsed to 0.0"
39
+ end
40
+ private_constant :INSUFFICIENT_DISPERSION_MESSAGE
41
+
42
+ module_function
43
+
44
+ def normalize(items)
45
+ Array(items).map { |item| normalize_one(item) }.uniq { |warning| warning.fetch(:code) }
46
+ end
47
+
48
+ def normalize_one(item)
49
+ code, explicit_message = warning_parts(item)
50
+ { code: code, message: message_for(code, explicit_message, item) }
51
+ end
52
+
53
+ def message_for(code, explicit_message = nil, item = nil)
54
+ return explicit_message.to_s unless explicit_message.to_s.empty?
55
+
56
+ if code.start_with?('insufficient_dispersion_')
57
+ signal = code.delete_prefix('insufficient_dispersion_')
58
+ return INSUFFICIENT_DISPERSION_MESSAGE.call(signal)
59
+ end
60
+
61
+ if code == 'temporal_coupling_bulk_commits_skipped' && item.respond_to?(:fetch)
62
+ count = item.fetch(:count, nil) || item.fetch('count', nil)
63
+ max = item.fetch(:max_commit_files, nil) || item.fetch('max_commit_files', nil)
64
+ return "#{MESSAGES.fetch(code)} (#{count} skipped; max_commit_files=#{max})" if count && max
65
+ end
66
+
67
+ MESSAGES.fetch(code) { code.tr('_', ' ') }
68
+ end
69
+
70
+ def warning_parts(item)
71
+ if item.respond_to?(:fetch) && (item.key?(:code) || item.key?('code'))
72
+ code = item.fetch(:code, nil) || item.fetch('code')
73
+ message = item.fetch(:message, nil) || item.fetch('message', nil)
74
+ [code.to_s, message]
75
+ else
76
+ [item.to_s, nil]
77
+ end
78
+ end
79
+ private_class_method :warning_parts
80
+ end
81
+ 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.5.0
4
+ version: 0.6.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-12 00:00:00.000000000 Z
11
+ date: 2026-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: csv
@@ -171,6 +171,7 @@ files:
171
171
  - lib/stud_finder/scorer.rb
172
172
  - lib/stud_finder/temporal_coupling.rb
173
173
  - lib/stud_finder/version.rb
174
+ - lib/stud_finder/warnings.rb
174
175
  homepage: https://github.com/bazfer/stud-finder
175
176
  licenses:
176
177
  - MIT