Class: StudFinder::Newness
- Inherits:
-
Object
- Object
- StudFinder::Newness
- Defined in:
- lib/stud_finder/newness.rb
Overview
rubocop:disable Metrics/ClassLength
Defined Under Namespace
Constant Summary collapse
- DEFAULT_DAYS =
30- DEFAULT_MIN_COMMITS =
3- SECONDS_PER_DAY =
86_400- SHALLOW_CLONE_WARNING =
{ code: 'shallow_clone_newness_disabled', message: 'shallow git clone detected; newness rules disabled (use fetch-depth: 0 in CI for full newness behavior)' }.freeze
- UNSHALLOW_FAILED_WARNING =
{ code: 'shallow_clone_unshallow_failed', message: 'auto-unshallow failed; evidence unavailable (use fetch-depth: 0 in CI or pass --no-auto-unshallow)' }.freeze
Class Method Summary collapse
- .apply(rows:, edges:, metadata:, branch_threshold: 'branch', coverage: nil) ⇒ Object
- .disabled_metadata(files) ⇒ Object
- .evidence(metadata, explicit_coverage) ⇒ Object
- .newness_fields(metadata) ⇒ Object
- .shallow_repository?(repo_path) ⇒ Boolean
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(repo_path:, files:, days: DEFAULT_DAYS, min_commits: DEFAULT_MIN_COMMITS, enabled: true, now: Time.now) ⇒ Newness
constructor
A new instance of Newness.
Constructor Details
#initialize(repo_path:, files:, days: DEFAULT_DAYS, min_commits: DEFAULT_MIN_COMMITS, enabled: true, now: Time.now) ⇒ Newness
Returns a new instance of Newness.
26 27 28 29 30 31 32 33 34 |
# File 'lib/stud_finder/newness.rb', line 26 def initialize(repo_path:, files:, days: DEFAULT_DAYS, min_commits: DEFAULT_MIN_COMMITS, enabled: true, now: Time.now) @repo_path = File.(repo_path) @files = files @days = days @min_commits = min_commits @enabled = enabled @now = now.to_i end |
Class Method Details
.apply(rows:, edges:, metadata:, branch_threshold: 'branch', coverage: nil) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/stud_finder/newness.rb', line 52 def self.apply(rows:, edges:, metadata:, branch_threshold: 'branch', coverage: nil) rows = rows.map do |row| = .fetch(row[:path], nil) nf = newness_fields() # Preserve floor escalation from the scorer (complexity_floor / fan_in_floor); newness rules # may still override it below for new files (trunk_adjacent or recency_floor always win). existing_escalation = row.fetch(:escalation, '') nf = nf.merge(escalation: existing_escalation) unless existing_escalation.to_s.empty? row.merge(nf).merge(evidence: evidence(, coverage&.key?(row[:path]))) end # Rule 2: scorer "trunk" now means high composite risk, not fan-in-only structural coupling. trunk_paths = rows.select { |row| row[:classification] == 'trunk' }.to_set { |row| row[:path] } rows.map do |row| next row unless row[:new_file] dependencies = edges.fetch(row[:path], {}).fetch(:dependencies, []) if dependencies.any? { |path| trunk_paths.include?(path) } row.merge(classification: 'trunk', escalation: 'trunk_adjacent') elsif row[:classification] == 'leaf' || %w[complexity_floor fan_in_floor].include?(row[:escalation].to_s) row.merge(classification: branch_threshold, escalation: 'recency_floor') else row end end end |
.disabled_metadata(files) ⇒ Object
79 80 81 82 83 |
# File 'lib/stud_finder/newness.rb', line 79 def self.(files) files.to_h do |file| [file, { new_file: false, age_days: 0, total_commits: 0, escalation: '', metadata_available: false }] end end |
.evidence(metadata, explicit_coverage) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/stud_finder/newness.rb', line 106 def self.evidence(, explicit_coverage) return nil unless &.fetch(:metadata_available, false) age_component = [.fetch(:age_days, 0) / 30.0, 1.0].min commits_component = [.fetch(:total_commits, 0) / 3.0, 1.0].min history_only = (age_component + commits_component) / 2.0 with_coverage = ((age_component + commits_component + 1.0) / 3.0) if explicit_coverage [history_only, with_coverage].compact.max.round(4) end |
.newness_fields(metadata) ⇒ Object
95 96 97 98 99 100 101 102 103 104 |
# File 'lib/stud_finder/newness.rb', line 95 def self.newness_fields() ||= { new_file: false, age_days: 0, total_commits: 0, metadata_available: false } { new_file: .fetch(:new_file, false), age_days: .fetch(:age_days, 0), total_commits: .fetch(:total_commits, 0), newness_metadata_available: .fetch(:metadata_available, false), escalation: '' } end |
.shallow_repository?(repo_path) ⇒ Boolean
85 86 87 88 89 90 91 92 93 |
# File 'lib/stud_finder/newness.rb', line 85 def self.shallow_repository?(repo_path) repo_path = File.(repo_path) stdout, _stderr, status = Open3.capture3('git', 'rev-parse', '--is-shallow-repository', chdir: repo_path) return stdout.strip == 'true' if status.success? git_shallow_file?(repo_path) rescue Errno::ENOENT git_shallow_file?(repo_path) end |
Instance Method Details
#call ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/stud_finder/newness.rb', line 36 def call return self.class.(@files) unless @enabled return self.class.(@files) if self.class.shallow_repository?(@repo_path) history = git_history @files.to_h do |file| first_epoch = history.first_commit_epoch[file] total_commits = history.total_commits.fetch(file, 0) age_days = age_days(first_epoch) is_new = new_file?(age_days, total_commits) [file, { new_file: is_new, age_days: age_days || 0, total_commits: total_commits, escalation: '', metadata_available: true }] end end |