ace-git 0.18.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.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.ace-defaults/git/config.yml +83 -0
  3. data/.ace-defaults/nav/protocols/guide-sources/ace-git.yml +10 -0
  4. data/.ace-defaults/nav/protocols/tmpl-sources/ace-git.yml +19 -0
  5. data/.ace-defaults/nav/protocols/wfi-sources/ace-git.yml +19 -0
  6. data/CHANGELOG.md +762 -0
  7. data/LICENSE +21 -0
  8. data/README.md +48 -0
  9. data/Rakefile +14 -0
  10. data/docs/demo/ace-git-getting-started.gif +0 -0
  11. data/docs/demo/ace-git-getting-started.tape.yml +18 -0
  12. data/docs/demo/fixtures/README.md +3 -0
  13. data/docs/demo/fixtures/sample.txt +1 -0
  14. data/docs/getting-started.md +87 -0
  15. data/docs/handbook.md +50 -0
  16. data/docs/usage.md +259 -0
  17. data/exe/ace-git +37 -0
  18. data/handbook/guides/version-control/ruby.md +41 -0
  19. data/handbook/guides/version-control/rust.md +49 -0
  20. data/handbook/guides/version-control/typescript.md +47 -0
  21. data/handbook/guides/version-control-system-git.g.md +829 -0
  22. data/handbook/skills/as-git-rebase/SKILL.md +43 -0
  23. data/handbook/skills/as-git-reorganize-commits/SKILL.md +41 -0
  24. data/handbook/skills/as-github-pr-create/SKILL.md +60 -0
  25. data/handbook/skills/as-github-pr-update/SKILL.md +41 -0
  26. data/handbook/skills/as-github-release-publish/SKILL.md +58 -0
  27. data/handbook/templates/commit/squash.template.md +59 -0
  28. data/handbook/templates/pr/bugfix.template.md +103 -0
  29. data/handbook/templates/pr/default.template.md +40 -0
  30. data/handbook/templates/pr/feature.template.md +41 -0
  31. data/handbook/workflow-instructions/git/rebase.wf.md +402 -0
  32. data/handbook/workflow-instructions/git/reorganize-commits.wf.md +158 -0
  33. data/handbook/workflow-instructions/github/pr/create.wf.md +282 -0
  34. data/handbook/workflow-instructions/github/pr/update.wf.md +199 -0
  35. data/handbook/workflow-instructions/github/release-publish.wf.md +162 -0
  36. data/lib/ace/git/atoms/command_executor.rb +253 -0
  37. data/lib/ace/git/atoms/date_resolver.rb +129 -0
  38. data/lib/ace/git/atoms/diff_numstat_parser.rb +82 -0
  39. data/lib/ace/git/atoms/diff_parser.rb +110 -0
  40. data/lib/ace/git/atoms/file_grouper.rb +152 -0
  41. data/lib/ace/git/atoms/git_scope_filter.rb +86 -0
  42. data/lib/ace/git/atoms/git_status_fetcher.rb +29 -0
  43. data/lib/ace/git/atoms/grouped_stats_formatter.rb +233 -0
  44. data/lib/ace/git/atoms/lock_error_detector.rb +79 -0
  45. data/lib/ace/git/atoms/pattern_filter.rb +156 -0
  46. data/lib/ace/git/atoms/pr_identifier_parser.rb +88 -0
  47. data/lib/ace/git/atoms/repository_checker.rb +97 -0
  48. data/lib/ace/git/atoms/repository_state_detector.rb +92 -0
  49. data/lib/ace/git/atoms/stale_lock_cleaner.rb +247 -0
  50. data/lib/ace/git/atoms/status_formatter.rb +180 -0
  51. data/lib/ace/git/atoms/task_pattern_extractor.rb +57 -0
  52. data/lib/ace/git/atoms/time_formatter.rb +84 -0
  53. data/lib/ace/git/cli/commands/branch.rb +62 -0
  54. data/lib/ace/git/cli/commands/diff.rb +252 -0
  55. data/lib/ace/git/cli/commands/pr.rb +119 -0
  56. data/lib/ace/git/cli/commands/status.rb +84 -0
  57. data/lib/ace/git/cli.rb +87 -0
  58. data/lib/ace/git/models/diff_config.rb +185 -0
  59. data/lib/ace/git/models/diff_result.rb +94 -0
  60. data/lib/ace/git/models/repo_status.rb +202 -0
  61. data/lib/ace/git/molecules/branch_reader.rb +92 -0
  62. data/lib/ace/git/molecules/config_loader.rb +108 -0
  63. data/lib/ace/git/molecules/diff_filter.rb +102 -0
  64. data/lib/ace/git/molecules/diff_generator.rb +160 -0
  65. data/lib/ace/git/molecules/git_status_fetcher.rb +32 -0
  66. data/lib/ace/git/molecules/pr_metadata_fetcher.rb +286 -0
  67. data/lib/ace/git/molecules/recent_commits_fetcher.rb +53 -0
  68. data/lib/ace/git/organisms/diff_orchestrator.rb +178 -0
  69. data/lib/ace/git/organisms/repo_status_loader.rb +264 -0
  70. data/lib/ace/git/version.rb +7 -0
  71. data/lib/ace/git.rb +230 -0
  72. metadata +201 -0
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Git
5
+ module Organisms
6
+ # Orchestrates loading complete repository status
7
+ # Combines branch info, task pattern detection, and PR metadata
8
+ class RepoStatusLoader
9
+ class << self
10
+ # Load complete repository status
11
+ # @param options [Hash] Options for status loading
12
+ # @option options [Boolean] :include_pr Whether to fetch PR metadata (default: true)
13
+ # @option options [Boolean] :include_pr_activity Whether to fetch PR activity (default: true)
14
+ # @option options [Boolean] :include_commits Whether to fetch recent commits (default: true)
15
+ # @option options [Integer] :commits_limit Number of recent commits to fetch (default: 3)
16
+ # @option options [Integer] :timeout Timeout for network operations like PR fetch (default: network_timeout)
17
+ # @return [Models::RepoStatus] Complete repository status
18
+ def load(options = {})
19
+ include_pr = options.fetch(:include_pr, true)
20
+ include_pr_activity = options.fetch(:include_pr_activity, true)
21
+ include_commits = options.fetch(:include_commits, true)
22
+ commits_limit = options.fetch(:commits_limit, Ace::Git.commits_limit)
23
+ timeout = options.fetch(:timeout, Ace::Git.network_timeout)
24
+
25
+ # Get repository type and state
26
+ repo_type = Atoms::RepositoryChecker.repository_type
27
+ repo_state = Atoms::RepositoryStateDetector.detect
28
+
29
+ # Check if we can proceed
30
+ unless Atoms::RepositoryChecker.usable?
31
+ return Models::RepoStatus.new(
32
+ branch: nil,
33
+ repository_type: repo_type,
34
+ repository_state: repo_state
35
+ )
36
+ end
37
+
38
+ # Get branch information
39
+ branch_info = Molecules::BranchReader.full_info
40
+
41
+ # Extract task pattern from branch name
42
+ task_pattern = nil
43
+ if branch_info[:name] && branch_info[:name] != "HEAD"
44
+ task_pattern = Atoms::TaskPatternExtractor.extract(branch_info[:name])
45
+ end
46
+
47
+ # Fetch git status (always, it's fast and local)
48
+ git_status_sb = fetch_git_status
49
+
50
+ # Fetch recent commits if requested
51
+ recent_commits = nil
52
+ if include_commits && commits_limit > 0
53
+ recent_commits = fetch_recent_commits(limit: commits_limit)
54
+ end
55
+
56
+ # Fetch PR data (metadata and activity) - parallelized for performance
57
+ pr_metadata = nil
58
+ pr_activity = nil
59
+ if !branch_info[:detached]
60
+ if include_pr && include_pr_activity
61
+ # Both requested: use parallel fetch for ~50% speedup
62
+ pr_data = fetch_pr_data_parallel(
63
+ current_branch: branch_info[:name],
64
+ timeout: timeout
65
+ )
66
+ pr_metadata = pr_data[:pr_metadata]
67
+ pr_activity = pr_data[:pr_activity]
68
+ elsif include_pr
69
+ # Only PR metadata requested
70
+ pr_metadata = fetch_pr_metadata(timeout: timeout)
71
+ elsif include_pr_activity
72
+ # Only activity requested
73
+ pr_activity = fetch_pr_activity(
74
+ current_branch: branch_info[:name],
75
+ timeout: timeout
76
+ )
77
+ end
78
+ end
79
+
80
+ # Build and return status
81
+ Models::RepoStatus.from_data(
82
+ branch_info: branch_info,
83
+ task_pattern: task_pattern,
84
+ pr_metadata: pr_metadata,
85
+ pr_activity: pr_activity,
86
+ git_status_sb: git_status_sb,
87
+ recent_commits: recent_commits,
88
+ repo_type: repo_type,
89
+ repo_state: repo_state
90
+ )
91
+ end
92
+
93
+ # Load status for a specific PR
94
+ # @param pr_identifier [String] PR identifier
95
+ # @param options [Hash] Options
96
+ # @return [Models::RepoStatus] Status with PR data
97
+ def load_for_pr(pr_identifier, options = {})
98
+ timeout = options.fetch(:timeout, Ace::Git.network_timeout)
99
+
100
+ # Get basic status (skip PR activity since we're fetching a specific PR)
101
+ status = load(include_pr: false, include_pr_activity: false)
102
+
103
+ # Fetch specific PR metadata
104
+ begin
105
+ result = Molecules::PrMetadataFetcher.fetch_metadata(pr_identifier, timeout: timeout)
106
+ pr_metadata = result[:success] ? result[:metadata] : nil
107
+ rescue Ace::Git::Error
108
+ pr_metadata = nil
109
+ end
110
+
111
+ # Return status with PR data
112
+ Models::RepoStatus.from_data(
113
+ branch_info: {
114
+ name: status.branch,
115
+ tracking: status.tracking,
116
+ ahead: status.ahead,
117
+ behind: status.behind
118
+ },
119
+ task_pattern: status.task_pattern,
120
+ pr_metadata: pr_metadata,
121
+ repo_type: status.repository_type,
122
+ repo_state: status.repository_state
123
+ )
124
+ end
125
+
126
+ # Load minimal status (branch only, no PR)
127
+ # @return [Models::RepoStatus] Minimal status
128
+ def load_minimal
129
+ load(include_pr: false, include_pr_activity: false, include_commits: false)
130
+ end
131
+
132
+ private
133
+
134
+ # Fetch git status in short branch format
135
+ # @return [String, nil] Git status output or nil
136
+ def fetch_git_status
137
+ result = Molecules::GitStatusFetcher.fetch_status_sb
138
+ result[:success] ? result[:output] : nil
139
+ rescue
140
+ nil
141
+ end
142
+
143
+ # Fetch recent commits
144
+ # @param limit [Integer] Number of commits to fetch
145
+ # @return [Array, nil] Array of commit hashes or nil
146
+ def fetch_recent_commits(limit:)
147
+ result = Molecules::RecentCommitsFetcher.fetch(limit: limit)
148
+ result[:success] ? result[:commits] : nil
149
+ rescue
150
+ nil
151
+ end
152
+
153
+ # Fetch PR metadata for current branch
154
+ # @param timeout [Integer] Timeout in seconds
155
+ # @return [Hash, nil] PR metadata or nil
156
+ def fetch_pr_metadata(timeout:)
157
+ # First try to find PR for current branch
158
+ pr_number = Molecules::PrMetadataFetcher.find_pr_for_branch(timeout: timeout)
159
+ return nil unless pr_number
160
+
161
+ # Then fetch full metadata
162
+ result = Molecules::PrMetadataFetcher.fetch_metadata(pr_number, timeout: timeout)
163
+ result[:success] ? result[:metadata] : nil
164
+ rescue Ace::Git::GhNotInstalledError, Ace::Git::GhAuthenticationError
165
+ # gh not available, skip PR metadata
166
+ nil
167
+ rescue Ace::Git::PrNotFoundError
168
+ # No PR for this branch
169
+ nil
170
+ rescue Ace::Git::TimeoutError
171
+ # Timeout, skip PR metadata
172
+ nil
173
+ rescue
174
+ # Any other error, skip PR metadata
175
+ nil
176
+ end
177
+
178
+ # Fetch PR activity (recently merged and open PRs)
179
+ # @param current_branch [String] Current branch name to exclude from open PRs
180
+ # @param timeout [Integer] Timeout in seconds
181
+ # @return [Hash, nil] PR activity with :merged and :open arrays (symbol keys), or nil
182
+ # Each PR in the arrays has string keys from JSON parsing: "number", "title", etc.
183
+ def fetch_pr_activity(current_branch:, timeout:)
184
+ merged_result = Molecules::PrMetadataFetcher.fetch_recently_merged(
185
+ limit: Ace::Git.merged_prs_limit,
186
+ timeout: timeout
187
+ )
188
+ open_result = Molecules::PrMetadataFetcher.fetch_open_prs(
189
+ exclude_branch: current_branch,
190
+ timeout: timeout
191
+ )
192
+
193
+ # Return nil if both failed
194
+ return nil unless merged_result[:success] || open_result[:success]
195
+
196
+ # Use symbol keys for outer hash, string keys for PR data (from JSON)
197
+ # This is documented behavior - consumers should access via pr_activity[:merged]
198
+ # and individual PRs via pr["number"], pr["title"], etc.
199
+ {
200
+ merged: merged_result[:success] ? merged_result[:prs] : [],
201
+ open: open_result[:success] ? open_result[:prs] : []
202
+ }
203
+ rescue
204
+ # Any error, skip PR activity
205
+ nil
206
+ end
207
+
208
+ # Fetch PR metadata and activity in a single API call for optimal performance
209
+ # Uses gh pr list --state all to get all PRs, then filters locally
210
+ # @param current_branch [String] Current branch name to match/exclude
211
+ # @param timeout [Integer] Timeout in seconds
212
+ # @return [Hash] Result with :pr_metadata and :pr_activity keys
213
+ def fetch_pr_data_parallel(current_branch:, timeout:)
214
+ # Single API call gets all recent PRs
215
+ result = Molecules::PrMetadataFetcher.fetch_all_prs(
216
+ limit: 15, # Enough for current + merged + open
217
+ timeout: timeout
218
+ )
219
+
220
+ return {pr_metadata: nil, pr_activity: nil} unless result[:success]
221
+
222
+ prs = result[:prs]
223
+
224
+ # Find current PR (matching branch, prefer OPEN > MERGED > CLOSED)
225
+ branch_prs = prs.select { |pr| pr["headRefName"] == current_branch }
226
+ current_pr = branch_prs.min_by { |pr|
227
+ case pr["state"]
228
+ when "OPEN" then 0
229
+ when "MERGED" then 1
230
+ when "CLOSED" then 2
231
+ else 3
232
+ end
233
+ }
234
+
235
+ # Get merged PRs (sorted by mergedAt descending, limited)
236
+ merged_prs = prs
237
+ .select { |pr| pr["state"] == "MERGED" }
238
+ .sort_by { |pr| pr["mergedAt"] || "" }
239
+ .reverse
240
+ .take(Ace::Git.merged_prs_limit)
241
+
242
+ # Get open PRs (exclude current branch, limited)
243
+ open_prs = prs
244
+ .select { |pr| pr["state"] == "OPEN" && pr["headRefName"] != current_branch }
245
+ .take(Ace::Git.open_prs_limit)
246
+
247
+ # Build activity hash (nil if no activity to show)
248
+ pr_activity = nil
249
+ if merged_prs.any? || open_prs.any?
250
+ pr_activity = {merged: merged_prs, open: open_prs}
251
+ end
252
+
253
+ {
254
+ pr_metadata: current_pr,
255
+ pr_activity: pr_activity
256
+ }
257
+ rescue
258
+ {pr_metadata: nil, pr_activity: nil}
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Git
5
+ VERSION = "0.18.0"
6
+ end
7
+ end
data/lib/ace/git.rb ADDED
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/support/config"
4
+ require_relative "git/version"
5
+
6
+ module Ace
7
+ module Git
8
+ # Error handling pattern:
9
+ # - Atoms: Pure functions, raise exceptions for invalid inputs
10
+ # - Molecules: May return error hashes for "expected" errors (e.g., not in git repo)
11
+ # or raise exceptions for unexpected failures
12
+ # - Organisms: Orchestrate molecules, propagate or wrap exceptions
13
+ # - Commands: Catch exceptions and return exit codes (0=success, 1=error)
14
+ #
15
+ # All custom exceptions inherit from Ace::Git::Error for consistent catching.
16
+ class Error < StandardError; end
17
+ class GitError < Error; end
18
+ class ConfigError < Error; end
19
+ class GhNotInstalledError < Error; end
20
+ class GhAuthenticationError < Error; end
21
+ class PrNotFoundError < Error; end
22
+ class TimeoutError < Error; end
23
+
24
+ # Mutex for thread-safe config initialization
25
+ @config_mutex = Mutex.new
26
+
27
+ # Get configuration for ace-git
28
+ # Follows ADR-022: Configuration Default and Override Pattern
29
+ # Uses Ace::Support::Config.create() for configuration cascade resolution
30
+ # Thread-safe: uses mutex for initialization
31
+ # @return [Hash] merged configuration hash
32
+ # @example Get current configuration
33
+ # config = Ace::Git.config
34
+ # puts config["default_branch"] # => "main"
35
+ # @example Access nested diff config
36
+ # exclude = Ace::Git.config["exclude_patterns"]
37
+ def self.config
38
+ # Fast path: return cached config if already initialized
39
+ return @config if defined?(@config) && @config
40
+
41
+ # Thread-safe initialization
42
+ @config_mutex.synchronize do
43
+ @config ||= load_config
44
+ end
45
+ end
46
+
47
+ # Load configuration using Ace::Support::Config cascade
48
+ # Resolves gem defaults from .ace-defaults/ and user overrides from .ace/
49
+ # @return [Hash] Merged and transformed configuration
50
+ def self.load_config
51
+ gem_root = Gem.loaded_specs["ace-git"]&.gem_dir ||
52
+ File.expand_path("../..", __dir__)
53
+
54
+ resolver = Ace::Support::Config.create(
55
+ config_dir: ".ace",
56
+ defaults_dir: ".ace-defaults",
57
+ gem_path: gem_root
58
+ )
59
+
60
+ # Resolve config for git namespace
61
+ config = resolver.resolve_namespace("git")
62
+
63
+ # Extract and flatten the git section for backward compatibility
64
+ raw_config = config.data["git"] || config.data
65
+ extract_git_config(raw_config)
66
+ rescue Ace::Support::Config::YamlParseError => e
67
+ warn "ace-git: YAML syntax error in configuration"
68
+ warn " #{e.message}"
69
+ # Fall back to gem defaults instead of empty hash to prevent silent config erasure
70
+ extract_git_config(load_gem_defaults_fallback(gem_root))
71
+ rescue => e
72
+ warn "ace-git: Failed to load configuration: #{e.message}"
73
+ # Fall back to gem defaults instead of empty hash to prevent silent config erasure
74
+ gem_root = Gem.loaded_specs["ace-git"]&.gem_dir ||
75
+ File.expand_path("../..", __dir__)
76
+ extract_git_config(load_gem_defaults_fallback(gem_root))
77
+ end
78
+ private_class_method :load_config
79
+
80
+ # Load gem defaults directly as fallback when cascade resolution fails
81
+ # This ensures configuration is never silently erased due to YAML errors
82
+ # or user config issues
83
+ # @param gem_root [String] Path to gem root directory
84
+ # @return [Hash] Defaults hash or empty hash if defaults also fail
85
+ def self.load_gem_defaults_fallback(gem_root)
86
+ defaults_path = File.join(gem_root, ".ace-defaults", "git", "config.yml")
87
+
88
+ return {} unless File.exist?(defaults_path)
89
+
90
+ data = YAML.safe_load_file(defaults_path, permitted_classes: [Date], aliases: true) || {}
91
+ data["git"] || data
92
+ rescue
93
+ {} # Only return empty hash if even defaults fail to load
94
+ end
95
+ private_class_method :load_gem_defaults_fallback
96
+
97
+ # Extract git configuration from YAML structure
98
+ #
99
+ # BACKWARD COMPATIBILITY NOTE:
100
+ # This method flattens the nested `diff:` section to top-level keys.
101
+ # This is required because the original default_config structure used flat keys
102
+ # (e.g., `exclude_patterns`, `exclude_whitespace`) rather than nested `diff.exclude_patterns`.
103
+ # The YAML config uses `git.diff.exclude_patterns` for clarity, but internally we flatten
104
+ # to maintain compatibility with DiffConfig.from_hash and existing consumers.
105
+ #
106
+ # Example transformation:
107
+ # git:
108
+ # diff:
109
+ # exclude_patterns: ["*.log"]
110
+ # becomes:
111
+ # { "exclude_patterns" => ["*.log"] }
112
+ #
113
+ # Keys are kept as strings for consistency with YAML loading.
114
+ # Use config["key"] or config.key?("key") for access.
115
+ #
116
+ # @param git_section [Hash] The git: section from YAML
117
+ # @return [Hash] Flattened configuration with string keys
118
+ def self.extract_git_config(git_section)
119
+ return {} if git_section.nil? || git_section.empty?
120
+
121
+ config = {}
122
+
123
+ # Normalize keys to strings for consistency
124
+ normalized = normalize_keys(git_section)
125
+
126
+ # Copy top-level settings
127
+ %w[default_branch remote verbose timeout network_timeout].each do |key|
128
+ config[key] = normalized[key] if normalized.key?(key)
129
+ end
130
+
131
+ # Flatten diff: section to top-level for backward compatibility (see note above)
132
+ diff_section = normalized["diff"]
133
+ if diff_section.is_a?(Hash)
134
+ normalize_keys(diff_section).each do |key, value|
135
+ config[key] = value
136
+ end
137
+ end
138
+
139
+ # Copy other sections as-is (rebase, pr, squash, status, lock_retry, etc.)
140
+ %w[rebase pr squash status lock_retry].each do |key|
141
+ config[key] = normalized[key] if normalized.key?(key)
142
+ end
143
+
144
+ config
145
+ end
146
+
147
+ # Normalize hash keys to strings for consistent access
148
+ # @param hash [Hash] Hash with potentially mixed string/symbol keys
149
+ # @return [Hash] Hash with string keys
150
+ def self.normalize_keys(hash)
151
+ return {} unless hash.is_a?(Hash)
152
+
153
+ hash.transform_keys(&:to_s)
154
+ end
155
+
156
+ # Reset configuration cache (mainly for testing)
157
+ # Thread-safe: uses mutex to prevent race conditions
158
+ def self.reset_config!
159
+ @config_mutex.synchronize do
160
+ @config = nil
161
+ end
162
+ end
163
+
164
+ # ---- Configuration Helper Methods (ADR-022 compliant) ----
165
+ # These read from config instead of using hardcoded constants
166
+
167
+ # Timeout for local git operations (diff, status, log)
168
+ # @return [Integer] Timeout in seconds (default: 30)
169
+ def self.git_timeout
170
+ config["timeout"] || 30
171
+ end
172
+
173
+ # Timeout for network operations (gh CLI, remote operations)
174
+ # @return [Integer] Timeout in seconds (default: 60)
175
+ def self.network_timeout
176
+ config["network_timeout"] || 60
177
+ end
178
+
179
+ # Number of recent commits to show in status output
180
+ # @return [Integer] Limit (default: 3)
181
+ def self.commits_limit
182
+ config.dig("status", "commits_limit") || 3
183
+ end
184
+
185
+ # Number of recently merged PRs to show in status output
186
+ # @return [Integer] Limit (default: 3)
187
+ def self.merged_prs_limit
188
+ config.dig("status", "merged_prs_limit") || 3
189
+ end
190
+
191
+ # Number of open PRs to show in status output
192
+ # @return [Integer] Limit (default: 10)
193
+ def self.open_prs_limit
194
+ config.dig("status", "open_prs_limit") || 10
195
+ end
196
+ end
197
+ end
198
+
199
+ # Require ATOM architecture components
200
+ require_relative "git/atoms/command_executor"
201
+ require_relative "git/atoms/pattern_filter"
202
+ require_relative "git/atoms/diff_parser"
203
+ require_relative "git/atoms/diff_numstat_parser"
204
+ require_relative "git/atoms/file_grouper"
205
+ require_relative "git/atoms/grouped_stats_formatter"
206
+ require_relative "git/atoms/date_resolver"
207
+ require_relative "git/atoms/task_pattern_extractor"
208
+ require_relative "git/atoms/pr_identifier_parser"
209
+ require_relative "git/atoms/repository_state_detector"
210
+ require_relative "git/atoms/repository_checker"
211
+ require_relative "git/atoms/git_scope_filter"
212
+ require_relative "git/atoms/time_formatter"
213
+ require_relative "git/atoms/status_formatter"
214
+ require_relative "git/atoms/lock_error_detector"
215
+ require_relative "git/atoms/stale_lock_cleaner"
216
+
217
+ require_relative "git/molecules/diff_generator"
218
+ require_relative "git/molecules/config_loader"
219
+ require_relative "git/molecules/diff_filter"
220
+ require_relative "git/molecules/branch_reader"
221
+ require_relative "git/molecules/pr_metadata_fetcher"
222
+ require_relative "git/molecules/recent_commits_fetcher"
223
+ require_relative "git/molecules/git_status_fetcher"
224
+
225
+ require_relative "git/organisms/diff_orchestrator"
226
+ require_relative "git/organisms/repo_status_loader"
227
+
228
+ require_relative "git/models/diff_result"
229
+ require_relative "git/models/diff_config"
230
+ require_relative "git/models/repo_status"