carson 2.23.0 → 2.24.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: e192c17c03b702b370cf76636a3c78674c719ecb4244ba779549301fa3564f0f
4
- data.tar.gz: 72c7f78fe5cb5140f25aa50d0f4312a9e4cb3c704eb7f3d7b996a3da90f3298d
3
+ metadata.gz: f0834352d33c889752494f99e13d8a73cb3f0d08ad3c46f0c3c5d5ed276a4223
4
+ data.tar.gz: 50b67148ebfd901429f4b2d421b8c801dcabd835aabc775cd6596c8d3fdc0bbc
5
5
  SHA512:
6
- metadata.gz: 9cad7952c709755faba1df36e1b247dea7ea21885c7d7987ec4c8aa42a4f2c1e2eb139ca02d8ff392129b61d4d821eeb57c2f768431aab73ec45b199d69c77c2
7
- data.tar.gz: 6e95e4916ed3635bb1fb502d3e8068b02cd737803304ea1d54c5b8205c0d85b31700baf6bc2d00c76bdf66f5e27ab1ba11b9bc5d7e8522faa7791e003aad6f4e
6
+ metadata.gz: 56cb4e7ae94c7f593dede60394729e57da55f70605d317603a4e7f6c1448ab77213a5a46c743685e4597197bf088f39eb134d06ee22e94054d7274b7f9c3be4d
7
+ data.tar.gz: 4bb26dad28da8adb21dfe6a039402b58ea9e3e2a3abf0f6cfc76f4a2a12f71615b601c951ad4fe72d8dd312525a38eb1206a8e793dcea2feb75fe564a72623b3
@@ -1,8 +1,6 @@
1
1
  ## Shared Scope and Validation
2
2
 
3
3
  - [ ] `single_business_intent`: this PR is one coherent domain or feature intent.
4
- - [ ] `single_scope_group`: non-doc files stay within one scope group.
5
- - [ ] `cross-boundary_changes_justified`: any cross-boundary change has explicit rationale.
6
4
  - [ ] `carson audit` before commit.
7
5
  - [ ] `carson audit` before push.
8
6
  - [ ] `gh pr list --state open --limit 50` checked at session start (capture competing active PRs).
data/MANUAL.md CHANGED
@@ -230,7 +230,7 @@ These are starting points chosen during `carson setup`. Every default has a reas
230
230
 
231
231
  How code reaches main.
232
232
 
233
- - **`branch`** (default) — every change goes through a PR. Hooks block direct commits and pushes to main/master. PRs enforce review, scope integrity, and CI gates before code reaches main.
233
+ - **`branch`** (default) — every change goes through a PR. Hooks block direct commits and pushes to main/master. PRs enforce review and CI gates before code reaches main.
234
234
  - **`trunk`** — commit directly to main. Hooks allow all commits. Suits solo projects or flat teams that don't need PR-based review.
235
235
 
236
236
  Change: `carson setup` or `CARSON_WORKFLOW_STYLE`.
@@ -279,14 +279,6 @@ Where lint configuration files come from and where they land.
279
279
 
280
280
  Change: `carson lint policy --source <path-or-git-url>` or `lint.policy_source` in config. After changing policy, run `carson refresh --all` to propagate to all governed repositories.
281
281
 
282
- #### Scope integrity
283
-
284
- Whether cross-module changes are flagged.
285
-
286
- - Default: **advisory** (attention, not block). Carson informs you when staged files span multiple module groups (e.g. both `domain` and `ui`), but doesn't prevent the commit. Useful for awareness; not a hard gate.
287
-
288
- Customise groups: `scope.path_groups` in config.
289
-
290
282
  #### Review disposition
291
283
 
292
284
  Whether reviewer findings require acknowledgement.
data/RELEASE.md CHANGED
@@ -5,6 +5,18 @@ Release-note scope rule:
5
5
  - `RELEASE.md` records only version deltas, breaking changes, and migration actions.
6
6
  - Operational usage guides live in `MANUAL.md` and `API.md`.
7
7
 
8
+ ## 2.24.0 — Remove Scope Integrity Guard
9
+
10
+ ### What changed
11
+
12
+ - **Scope integrity guard removed from `carson audit`.** The guard classified changed files into path groups (tool, ui, test, domain, docs) and flagged commits crossing multiple groups. This required maintaining an explicit `scope.path_groups` list in `~/.carson/config.json` that went stale whenever a repository's directory structure changed. The maintenance burden outweighed the value.
13
+
14
+ ### Migration
15
+
16
+ - The `scope.path_groups` config key is now ignored. Existing configs with this key will not cause errors — the data is silently unused.
17
+ - The `path_groups` attribute has been removed from `Carson::Config`. Code referencing `config.path_groups` will raise `NoMethodError`.
18
+ - PR template no longer includes `single_scope_group` and `cross-boundary_changes_justified` checklist items. The `single_business_intent` check remains — that is a human-level focus check, not mechanical path classification.
19
+
8
20
  ## 2.23.0 — Warm Onboard Welcome Guide
9
21
 
10
22
  ### What changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.23.0
1
+ 2.24.0
data/lib/carson/config.rb CHANGED
@@ -8,7 +8,7 @@ module Carson
8
8
  class Config
9
9
  attr_accessor :git_remote
10
10
  attr_reader :main_branch, :protected_branches, :hooks_base_path, :required_hooks,
11
- :path_groups, :template_managed_files, :template_superseded_files, :template_canonical,
11
+ :template_managed_files, :template_superseded_files, :template_canonical,
12
12
  :lint_policy_source,
13
13
  :review_wait_seconds, :review_poll_seconds, :review_max_polls, :review_sweep_window_days,
14
14
  :review_sweep_states, :review_disposition_prefix, :review_risk_keywords,
@@ -38,15 +38,6 @@ module Carson
38
38
  "base_path" => "~/.carson/hooks",
39
39
  "required_hooks" => [ "pre-commit", "prepare-commit-msg", "pre-merge-commit", "pre-push" ]
40
40
  },
41
- "scope" => {
42
- "path_groups" => {
43
- "tool" => [ "exe/**", "bin/**", "lib/**", "script/**", ".github/**", "templates/.github/**", "hooks/**", "install.sh", "README.md", "RELEASE.md", "VERSION", "carson.gemspec" ],
44
- "ui" => [ "app/views/**", "app/assets/**", "app/javascript/**", "docs/ui_*.md" ],
45
- "test" => [ "test/**", "spec/**", "features/**" ],
46
- "domain" => [ "app/**", "db/**", "config/**" ],
47
- "docs" => [ "docs/**", "*.md" ]
48
- }
49
- },
50
41
  "template" => {
51
42
  "managed_files" => [ ".github/carson.md", ".github/copilot-instructions.md", ".github/CLAUDE.md", ".github/AGENTS.md", ".github/pull_request_template.md" ],
52
43
  "superseded_files" => [ ".github/carson-instructions.md", ".github/workflows/carson-lint.yml", ".github/.mega-linter.yml" ],
@@ -215,8 +206,6 @@ module Carson
215
206
  @hooks_base_path = fetch_string( hash: fetch_hash( hash: data, key: "hooks" ), key: "base_path" )
216
207
  @required_hooks = fetch_string_array( hash: fetch_hash( hash: data, key: "hooks" ), key: "required_hooks" )
217
208
 
218
- @path_groups = fetch_hash( hash: fetch_hash( hash: data, key: "scope" ), key: "path_groups" ).transform_values { |value| normalize_patterns( value: value ) }
219
-
220
209
  @template_managed_files = fetch_string_array( hash: fetch_hash( hash: data, key: "template" ), key: "managed_files" )
221
210
  @template_superseded_files = fetch_optional_string_array( hash: fetch_hash( hash: data, key: "template" ), key: "superseded_files" )
222
211
  @template_canonical = fetch_optional_path( hash: fetch_hash( hash: data, key: "template" ), key: "canonical" )
@@ -267,7 +256,6 @@ module Carson
267
256
  raise ConfigError, "git.protected_branches must include #{main_branch}" unless protected_branches.include?( main_branch )
268
257
  raise ConfigError, "hooks.base_path cannot be empty" if hooks_base_path.empty?
269
258
  raise ConfigError, "hooks.required_hooks cannot be empty" if required_hooks.empty?
270
- raise ConfigError, "scope.path_groups cannot be empty" if path_groups.empty?
271
259
  raise ConfigError, "review.required_disposition_prefix cannot be empty" if review_disposition_prefix.empty?
272
260
  raise ConfigError, "review.risk_keywords cannot be empty" if review_risk_keywords.empty?
273
261
  raise ConfigError, "review.sweep.states must contain one or both of open, closed" if ( review_sweep_states - [ "open", "closed" ] ).any? || review_sweep_states.empty?
@@ -328,13 +316,6 @@ module Carson
328
316
  rescue ArgumentError, TypeError
329
317
  raise ConfigError, "config key #{key} must be an integer"
330
318
  end
331
-
332
- def normalize_patterns( value: )
333
- patterns = Array( value ).map { |entry| entry.to_s.strip }.reject( &:empty? )
334
- raise ConfigError, "scope.path_groups entries must contain at least one glob" if patterns.empty?
335
- patterns
336
- end
337
-
338
319
  def fetch_optional_boolean( hash:, key:, default:, key_path: nil )
339
320
  value = hash.fetch( key, default )
340
321
  return true if value == true
@@ -92,15 +92,6 @@ module Carson
92
92
  elsif baseline_st == "skipped"
93
93
  audit_concise_problems << "Baseline: skipped (#{default_branch_baseline.fetch( :skip_reason )})."
94
94
  end
95
- scope_guard = print_scope_integrity_guard
96
- audit_state = "attention" if audit_state == "ok" && scope_guard.fetch( :status ) == "attention"
97
- if scope_guard.fetch( :status ) == "attention"
98
- if scope_guard.fetch( :split_required )
99
- audit_concise_problems << "Scope: multiple module groups touched."
100
- else
101
- audit_concise_problems << "Scope: unmatched paths — classify via scope.path_groups."
102
- end
103
- end
104
95
  if config.template_canonical.nil? || config.template_canonical.to_s.empty?
105
96
  puts_verbose ""
106
97
  puts_verbose "[Canonical Templates]"
@@ -554,112 +545,6 @@ module Carson
554
545
  lines.join( "\n" )
555
546
  end
556
547
 
557
- # Evaluates scope integrity using staged paths first, then working-tree paths as fallback.
558
- def print_scope_integrity_guard
559
- staged = staged_files
560
- files = staged.empty? ? changed_files : staged
561
- files_source = staged.empty? ? "working_tree" : "staged"
562
- return { status: "ok", split_required: false } if files.empty?
563
-
564
- scope = scope_integrity_status( files: files, branch: current_branch )
565
- puts_verbose ""
566
- puts_verbose "[Scope Integrity Guard]"
567
- puts_verbose "scope_file_source: #{files_source}"
568
- puts_verbose "scope_file_count: #{files.count}"
569
- puts_verbose "branch: #{scope.fetch( :branch )}"
570
- puts_verbose "scope_basis: changed_paths_only"
571
- puts_verbose "detected_groups: #{scope.fetch( :detected_groups ).sort.join( ', ' )}"
572
- puts_verbose "core_groups: #{scope.fetch( :core_groups ).empty? ? 'none' : scope.fetch( :core_groups ).sort.join( ', ' )}"
573
- puts_verbose "non_doc_groups: #{scope.fetch( :non_doc_groups ).empty? ? 'none' : scope.fetch( :non_doc_groups ).sort.join( ', ' )}"
574
- puts_verbose "docs_only_changes: #{scope.fetch( :docs_only )}"
575
- puts_verbose "unmatched_paths_count: #{scope.fetch( :unmatched_paths ).count}"
576
- scope.fetch( :unmatched_paths ).each { |path| puts_verbose "unmatched_path: #{path}" }
577
- puts_verbose "violating_files_count: #{scope.fetch( :violating_files ).count}"
578
- scope.fetch( :violating_files ).each { |path| puts_verbose "violating_file: #{path} (group=#{scope.fetch( :grouped_paths ).fetch( path )})" }
579
- puts_verbose "checklist_single_business_intent: pass"
580
- puts_verbose "checklist_single_scope_group: #{scope.fetch( :split_required ) ? 'advisory' : 'pass'}"
581
- puts_verbose "checklist_cross_boundary_changes_justified: #{( scope.fetch( :split_required ) || scope.fetch( :misc_present ) ) ? 'advisory' : 'pass'}"
582
- if scope.fetch( :split_required )
583
- puts_verbose "ACTION: multiple module groups detected (informational only)."
584
- elsif scope.fetch( :misc_present )
585
- puts_verbose "ACTION: unmatched paths detected; classify via scope.path_groups for stricter module checks."
586
- else
587
- puts_verbose "ACTION: scope integrity is within commit policy."
588
- end
589
- { status: scope.fetch( :status ), split_required: scope.fetch( :split_required ) }
590
- end
591
-
592
- # Evaluates whether changed files stay within one core module group.
593
- def scope_integrity_status( files:, branch: )
594
- grouped_paths = files.map { |path| [ path, scope_group_for_path( path: path ) ] }.to_h
595
- detected_groups = grouped_paths.values.uniq
596
- non_doc_groups = detected_groups - [ "docs" ]
597
- # Tests are supporting changes; they may travel with one core module group.
598
- core_groups = non_doc_groups - [ "test", "misc" ]
599
- mixed_core_groups = core_groups.length > 1
600
- misc_present = non_doc_groups.include?( "misc" )
601
- split_required = mixed_core_groups
602
- unmatched_paths = files.select { |path| grouped_paths.fetch( path ) == "misc" }
603
- violating_files = if split_required
604
- files.select do |path|
605
- group = grouped_paths.fetch( path )
606
- next false if [ "docs", "test", "misc" ].include?( group )
607
- core_groups.include?( group )
608
- end
609
- else
610
- []
611
- end
612
- {
613
- branch: branch,
614
- grouped_paths: grouped_paths,
615
- detected_groups: detected_groups,
616
- non_doc_groups: non_doc_groups,
617
- core_groups: core_groups,
618
- docs_only: non_doc_groups.empty?,
619
- mixed_core_groups: mixed_core_groups,
620
- misc_present: misc_present,
621
- split_required: split_required,
622
- unmatched_paths: unmatched_paths,
623
- violating_files: violating_files,
624
- status: ( split_required || misc_present ) ? "attention" : "ok"
625
- }
626
- end
627
-
628
- # Resolves a path to configured scope group; unmatched paths become misc.
629
- def scope_group_for_path( path: )
630
- config.path_groups.each do |group, patterns|
631
- return group if patterns.any? { |pattern| pattern_matches_path?( pattern: pattern, path: path ) }
632
- end
633
- "misc"
634
- end
635
-
636
- # Supports directory-wide /** prefixes and fnmatch for other patterns.
637
- def pattern_matches_path?( pattern:, path: )
638
- if pattern.end_with?( "/**" )
639
- prefix = pattern.delete_suffix( "/**" )
640
- return path == prefix || path.start_with?( "#{prefix}/" )
641
- end
642
- File.fnmatch?( pattern, path, File::FNM_PATHNAME | File::FNM_DOTMATCH )
643
- end
644
-
645
- # Uses index-only paths so commit hooks evaluate exactly what is being committed.
646
- def staged_files
647
- git_capture!( "diff", "--cached", "--name-only" ).lines.map do |line|
648
- raw_path = line.to_s.strip
649
- next if raw_path.empty?
650
- raw_path.split( " -> " ).last
651
- end.compact
652
- end
653
-
654
- # Parses `git status --porcelain` and normalises rename targets.
655
- def changed_files
656
- git_capture!( "status", "--porcelain" ).lines.map do |line|
657
- raw_path = line[ 3.. ].to_s.strip
658
- next if raw_path.empty?
659
- raw_path.split( " -> " ).last
660
- end.compact
661
- end
662
-
663
548
  # True when there are no staged/unstaged/untracked file changes.
664
549
  end
665
550
 
@@ -1,8 +1,6 @@
1
1
  ## Shared Scope and Validation
2
2
 
3
3
  - [ ] `single_business_intent`: this PR is one coherent domain or feature intent.
4
- - [ ] `single_scope_group`: non-doc files stay within one scope group.
5
- - [ ] `cross-boundary_changes_justified`: any cross-boundary change has explicit rationale.
6
4
  - [ ] `carson audit` before commit.
7
5
  - [ ] `carson audit` before push.
8
6
  - [ ] `gh pr list --state open --limit 50` checked at session start (capture competing active PRs).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.23.0
4
+ version: 2.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang