carson 2.22.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 +4 -4
- data/.github/pull_request_template.md +0 -2
- data/MANUAL.md +1 -9
- data/RELEASE.md +18 -0
- data/VERSION +1 -1
- data/lib/carson/config.rb +1 -20
- data/lib/carson/runtime/audit.rb +0 -115
- data/lib/carson/runtime/local.rb +14 -2
- data/templates/.github/pull_request_template.md +0 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0834352d33c889752494f99e13d8a73cb3f0d08ad3c46f0c3c5d5ed276a4223
|
|
4
|
+
data.tar.gz: 50b67148ebfd901429f4b2d421b8c801dcabd835aabc775cd6596c8d3fdc0bbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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,24 @@ 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
|
+
|
|
20
|
+
## 2.23.0 — Warm Onboard Welcome Guide
|
|
21
|
+
|
|
22
|
+
### What changed
|
|
23
|
+
|
|
24
|
+
- **`carson onboard` closing block rewritten as a warm next-step guide.** Replaces the terse "Carson is ready" message with a concierge-style welcome that explains what Carson placed in `.github/`, why it matters, and what to do before the first push.
|
|
25
|
+
|
|
8
26
|
## 2.22.0 — Setup Prompts for Canonical Templates
|
|
9
27
|
|
|
10
28
|
### What changed
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
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
|
-
:
|
|
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
|
data/lib/carson/runtime/audit.rb
CHANGED
|
@@ -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
|
|
data/lib/carson/runtime/local.rb
CHANGED
|
@@ -1162,11 +1162,23 @@ module Carson
|
|
|
1162
1162
|
audit_status = onboard_run_audit!
|
|
1163
1163
|
|
|
1164
1164
|
puts_line ""
|
|
1165
|
-
puts_line "Carson
|
|
1166
|
-
puts_line "Reconfigure anytime: carson setup"
|
|
1165
|
+
puts_line "Carson at your service."
|
|
1167
1166
|
|
|
1168
1167
|
prompt_govern_registration! if self.in.respond_to?( :tty? ) && self.in.tty?
|
|
1169
1168
|
|
|
1169
|
+
puts_line ""
|
|
1170
|
+
puts_line "Your repository is set up. Carson has placed files in your"
|
|
1171
|
+
puts_line "project's .github/ directory — pull request templates,"
|
|
1172
|
+
puts_line "guidelines for AI coding assistants, and any CI or lint"
|
|
1173
|
+
puts_line "rules you've configured. Once pushed to GitHub, they'll"
|
|
1174
|
+
puts_line "ensure every pull request follows a consistent standard"
|
|
1175
|
+
puts_line "and all checks run automatically."
|
|
1176
|
+
puts_line ""
|
|
1177
|
+
puts_line "Before your first push, have a look through .github/ to"
|
|
1178
|
+
puts_line "make sure everything is to your liking."
|
|
1179
|
+
puts_line ""
|
|
1180
|
+
puts_line "To adjust any setting: carson setup"
|
|
1181
|
+
|
|
1170
1182
|
audit_status
|
|
1171
1183
|
end
|
|
1172
1184
|
|
|
@@ -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).
|