carson 2.18.0 → 2.19.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: c9bf941a78e322ebb3086476fe99f2ec31ef59847e94e54de4e8acf3aa2517be
4
- data.tar.gz: 962ee4ef4b7618bc23d4208d610459030974f8b4482d891f6a27ad7e26d39640
3
+ metadata.gz: b6b2ecf51c931d6c85244d96e294ce87a86583e3a807782ddd74c9058c647563
4
+ data.tar.gz: 19b49e67b2bdefe3443aa00500394cc479951eb074498d82b69ff3f776b42b15
5
5
  SHA512:
6
- metadata.gz: 8f0f970f45b6951ce5b56aeb675f5876e515694c01f005d9182f7a2d39d9e56eb7b428b15fc40d93996229487fc62302a56cf3d3a7b5b6d3f1fc799a100f7e48
7
- data.tar.gz: 239edae42c9be23c0a273caa110387c090ab33d93f9b2780918e766aeb6aa0d2c837cc85367d22cab139a87e5082e11e245520979d5d6746a50bbb8545e88be9
6
+ metadata.gz: acc11f185996f6e424b18bbc55ed6194ae87d31db6de09b9b34fc50237c87ada00ab8c1c3809cf9d0b3654353e24cf1a2fb1b9fd6134610a57ded49d4fbe1a5c
7
+ data.tar.gz: c27ac61fba18b5cf449a33b8703337282b5a30f0cf87568e24320acd56b030b57093ba8d24466c92450745dc61f0eaa83f2c5db603cd242e0e6b2413233331d0
data/API.md CHANGED
@@ -79,7 +79,7 @@ Allowed Carson-managed persistence in host repositories:
79
79
  - `.github/CLAUDE.md` — agent discovery pointer for Claude Code
80
80
  - `.github/AGENTS.md` — agent discovery pointer for Codex
81
81
  - `.github/pull_request_template.md` — PR template
82
- - `.github/workflows/carson-lint.yml` — MegaLinter CI workflow
82
+ - Any file discovered from `template.canonical` — user's canonical `.github/` files
83
83
 
84
84
  ## Configuration interface
85
85
 
@@ -134,6 +134,19 @@ Environment overrides:
134
134
  - `merge.authority`: `true` (default) — Carson may merge autonomously. Set to `false` to require explicit enablement.
135
135
  - `merge.method`: `"squash"` (default), `"merge"`, or `"rebase"`.
136
136
 
137
+ `template` schema:
138
+
139
+ ```json
140
+ {
141
+ "template": {
142
+ "canonical": "~/AI/LINT"
143
+ }
144
+ }
145
+ ```
146
+
147
+ `template` semantics:
148
+ - `canonical`: path to a directory of canonical `.github/` files. Carson discovers files in this directory and syncs them to governed repos alongside its own governance files. The directory mirrors `.github/` structure — `workflows/lint.yml` deploys to `.github/workflows/lint.yml`. Default: `nil` (no canonical files).
149
+
137
150
  `lint` schema:
138
151
 
139
152
  ```json
data/MANUAL.md CHANGED
@@ -99,16 +99,33 @@ Notes:
99
99
  - `CARSON_READ_TOKEN` must have read access to your policy source repository so CI can run `carson lint policy`.
100
100
  - The reusable workflow installs a pinned RuboCop gem before `carson audit`; mirror the same pin in host governance workflows for deterministic checks.
101
101
 
102
- ### MegaLinter in CI
102
+ ### Canonical Templates
103
103
 
104
- Carson manages a MegaLinter workflow template (`carson-lint.yml`) that is applied to governed repositories via `carson template apply`. MegaLinter auto-discovers lint configs from `.github/linters/` — the same directory populated by `carson lint policy`. This means:
104
+ Carson manages 5 governance files (carson.md, CLAUDE.md, AGENTS.md, copilot-instructions.md, pull_request_template.md). Beyond those, you can tell Carson about your own canonical `.github/` files CI workflows, linter configs, dependabot settings, anything that belongs in `.github/`.
105
105
 
106
- - **Policy source** (your central lint repo) defines the rules.
107
- - **`carson lint policy`** distributes the rules into each repo.
108
- - **MegaLinter** (in GitHub Actions) enforces the rules.
109
- - **`carson govern`** reads CI check status (including MegaLinter results) and acts on it.
106
+ Set `template.canonical` in `~/.carson/config.json`:
110
107
 
111
- Carson's `audit` gates on CI check status reported by GitHub — it does not duplicate MegaLinter's work locally. If MegaLinter fails in CI, `carson govern` classifies the PR accordingly and can dispatch a coding agent to fix the issues.
108
+ ```json
109
+ {
110
+ "template": {
111
+ "canonical": "~/AI/LINT"
112
+ }
113
+ }
114
+ ```
115
+
116
+ That directory mirrors the `.github/` structure:
117
+
118
+ ```
119
+ ~/AI/LINT/
120
+ ├── workflows/
121
+ │ └── lint.yml → deployed to .github/workflows/lint.yml
122
+ ├── .mega-linter.yml → deployed to .github/.mega-linter.yml
123
+ └── dependabot.yml → deployed to .github/dependabot.yml
124
+ ```
125
+
126
+ Carson discovers files in this directory and syncs them to governed repos alongside its own governance files. `carson template check` detects drift, `carson template apply` writes them, and `carson refresh` propagates them to the remote.
127
+
128
+ **Why this design.** Lint, CI, and tooling config are personal decisions — not governance decisions. Carson's job is to deliver your canonical files reliably, not to decide what they should contain.
112
129
 
113
130
  ## Daily Operations
114
131
 
@@ -200,7 +217,7 @@ Carson's `govern.merge.method` controls how `carson govern` merges ready PRs. Th
200
217
  These define what Carson *is*. They are not configurable.
201
218
 
202
219
  - **Outsider boundary** — Carson never places its own artefacts inside a governed repository.
203
- - **Centralised lint** — one policy source distributed into each repo's `.github/linters/`, zero per-repo drift. MegaLinter enforces it in CI.
220
+ - **Canonical delivery** — your canonical `.github/` files distributed into each governed repo, zero per-repo drift. What those files contain is your call.
204
221
  - **Active review** — undisposed reviewer findings block merge; feedback must be acknowledged.
205
222
  - **Self-diagnosing output** — every warning and error names what went wrong, why, and what to do next.
206
223
  - **Transparent governance** — Carson prepares everything for merge but never makes decisions without telling you.
data/RELEASE.md CHANGED
@@ -5,6 +5,30 @@ 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.19.0 — Canonical Templates, Lint Removed
9
+
10
+ ### What changed
11
+
12
+ - **Lint templates removed from Carson.** `carson-lint.yml` and `.mega-linter.yml` are no longer managed by Carson. Both are now superseded — `carson refresh` will delete them from governed repos automatically. Lint is a personal decision, not a governance decision.
13
+ - **New `template.canonical` config key.** Point Carson at a directory of your canonical `.github/` files and Carson syncs them to all governed repos alongside its own governance files. You control the content; Carson handles the delivery.
14
+
15
+ ### Migration
16
+
17
+ 1. Run `carson refresh` in each governed repo. Carson will remove the old lint files automatically.
18
+ 2. If you want lint, create your own workflow files and set `template.canonical` in `~/.carson/config.json`:
19
+
20
+ ```json
21
+ {
22
+ "template": {
23
+ "canonical": "~/AI/LINT"
24
+ }
25
+ }
26
+ ```
27
+
28
+ That directory mirrors `.github/` structure — for example, `workflows/lint.yml` deploys to `.github/workflows/lint.yml`.
29
+
30
+ 3. Run `carson refresh` again to deploy your canonical files.
31
+
8
32
  ## 2.18.0 — Audit Attention Detail
9
33
 
10
34
  ### What changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.18.0
1
+ 2.19.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,
11
+ :path_groups, :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,
@@ -48,8 +48,9 @@ module Carson
48
48
  }
49
49
  },
50
50
  "template" => {
51
- "managed_files" => [ ".github/carson.md", ".github/copilot-instructions.md", ".github/CLAUDE.md", ".github/AGENTS.md", ".github/pull_request_template.md", ".github/workflows/carson-lint.yml", ".github/.mega-linter.yml" ],
52
- "superseded_files" => [ ".github/carson-instructions.md" ]
51
+ "managed_files" => [ ".github/carson.md", ".github/copilot-instructions.md", ".github/CLAUDE.md", ".github/AGENTS.md", ".github/pull_request_template.md" ],
52
+ "superseded_files" => [ ".github/carson-instructions.md", ".github/workflows/carson-lint.yml", ".github/.mega-linter.yml" ],
53
+ "canonical" => nil
53
54
  },
54
55
  "lint" => {
55
56
  "policy_source" => "wanghailei/lint.git"
@@ -218,6 +219,8 @@ module Carson
218
219
 
219
220
  @template_managed_files = fetch_string_array( hash: fetch_hash( hash: data, key: "template" ), key: "managed_files" )
220
221
  @template_superseded_files = fetch_optional_string_array( hash: fetch_hash( hash: data, key: "template" ), key: "superseded_files" )
222
+ @template_canonical = fetch_optional_path( hash: fetch_hash( hash: data, key: "template" ), key: "canonical" )
223
+ resolve_canonical_files!
221
224
  lint_hash = fetch_hash( hash: data, key: "lint" )
222
225
  @lint_policy_source = lint_hash.fetch( "policy_source", "" ).to_s.strip
223
226
 
@@ -347,5 +350,28 @@ module Carson
347
350
  rescue ArgumentError
348
351
  path
349
352
  end
353
+
354
+ # Returns an expanded path string, or nil when the value is absent/blank.
355
+ def fetch_optional_path( hash:, key: )
356
+ value = hash[ key ]
357
+ return nil if value.nil?
358
+ text = value.to_s.strip
359
+ return nil if text.empty?
360
+ safe_expand_path( text )
361
+ end
362
+
363
+ # Discovers files in the canonical directory and appends them to managed_files.
364
+ # Canonical files mirror the .github/ structure and are synced alongside Carson's own governance files.
365
+ def resolve_canonical_files!
366
+ return if @template_canonical.nil? || @template_canonical.empty?
367
+ return unless Dir.exist?( @template_canonical )
368
+
369
+ Dir.glob( File.join( @template_canonical, "**", "*" ) ).sort.each do |absolute_path|
370
+ next unless File.file?( absolute_path )
371
+ relative = absolute_path.delete_prefix( "#{@template_canonical}/" )
372
+ managed_path = ".github/#{relative}"
373
+ @template_managed_files << managed_path unless @template_managed_files.include?( managed_path )
374
+ end
375
+ end
350
376
  end
351
377
  end
@@ -483,10 +483,8 @@ module Carson
483
483
  # Also removes superseded files present in the worktree.
484
484
  def template_propagate_write_files!( worktree_dir: )
485
485
  config.template_managed_files.each do |managed_file|
486
- relative_within_github = managed_file.delete_prefix( ".github/" )
487
- template_path = File.join( github_templates_dir, relative_within_github )
488
- template_path = File.join( github_templates_dir, File.basename( managed_file ) ) unless File.file?( template_path )
489
- next unless File.file?( template_path )
486
+ template_path = template_source_path( managed_file: managed_file )
487
+ next if template_path.nil?
490
488
 
491
489
  target_path = File.join( worktree_dir, managed_file )
492
490
  FileUtils.mkdir_p( File.dirname( target_path ) )
@@ -665,12 +663,8 @@ module Carson
665
663
 
666
664
  # Calculates whole-file expected content and returns sync status plus apply payload.
667
665
  def template_result_for_file( managed_file: )
668
- # Try subdirectory-aware path first (e.g. .github/workflows/carson-lint.yml),
669
- # then fall back to flat basename lookup for backward compatibility.
670
- relative_within_github = managed_file.delete_prefix( ".github/" )
671
- template_path = File.join( github_templates_dir, relative_within_github )
672
- template_path = File.join( github_templates_dir, File.basename( managed_file ) ) unless File.file?( template_path )
673
- return { file: managed_file, status: "error", reason: "missing template #{File.basename( managed_file )}", applied_content: nil } unless File.file?( template_path )
666
+ template_path = template_source_path( managed_file: managed_file )
667
+ return { file: managed_file, status: "error", reason: "missing template #{File.basename( managed_file )}", applied_content: nil } if template_path.nil?
674
668
 
675
669
  expected_content = normalize_text( text: File.read( template_path ) )
676
670
  file_path = resolve_repo_path!( relative_path: managed_file, label: "template.managed_files entry #{managed_file}" )
@@ -692,6 +686,28 @@ module Carson
692
686
  File.join( tool_root, "templates", ".github" )
693
687
  end
694
688
 
689
+ # Resolves the source file path for a managed template.
690
+ # Checks the user's canonical directory first, then falls back to Carson's built-in templates.
691
+ def template_source_path( managed_file: )
692
+ relative_within_github = managed_file.delete_prefix( ".github/" )
693
+
694
+ # Canonical source: the user's canonical .github/ files.
695
+ canonical = config.template_canonical
696
+ if canonical && !canonical.empty?
697
+ canonical_path = File.join( canonical, relative_within_github )
698
+ return canonical_path if File.file?( canonical_path )
699
+ end
700
+
701
+ # Carson built-in templates: subdirectory-aware path first, then flat basename fallback.
702
+ template_path = File.join( github_templates_dir, relative_within_github )
703
+ return template_path if File.file?( template_path )
704
+
705
+ basename_path = File.join( github_templates_dir, File.basename( managed_file ) )
706
+ return basename_path if File.file?( basename_path )
707
+
708
+ nil
709
+ end
710
+
695
711
  # Canonical hook template location inside Carson repository.
696
712
  def hook_template_path( hook_name: )
697
713
  File.join( tool_root, "hooks", hook_name )
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.18.0
4
+ version: 2.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang
@@ -63,13 +63,11 @@ files:
63
63
  - lib/carson/runtime/review/utility.rb
64
64
  - lib/carson/runtime/setup.rb
65
65
  - lib/carson/version.rb
66
- - templates/.github/.mega-linter.yml
67
66
  - templates/.github/AGENTS.md
68
67
  - templates/.github/CLAUDE.md
69
68
  - templates/.github/carson.md
70
69
  - templates/.github/copilot-instructions.md
71
70
  - templates/.github/pull_request_template.md
72
- - templates/.github/workflows/carson-lint.yml
73
71
  homepage: https://github.com/wanghailei/carson
74
72
  licenses:
75
73
  - MIT
@@ -1,29 +0,0 @@
1
- # Carson-managed MegaLinter configuration.
2
- # Pushed to governed repositories by `carson template apply`.
3
- # To override, add entries to a local .mega-linter.yml — MegaLinter merges both.
4
-
5
- # Use project-root linter configs (.rubocop.yml, .eslintrc, etc.)
6
- # instead of MegaLinter's built-in defaults.
7
- LINTER_RULES_PATH: "."
8
-
9
- # Only lint changed files on PRs, not the entire codebase.
10
- VALIDATE_ALL_CODEBASE: false
11
-
12
- # Exclude vendored, generated, and dependency directories.
13
- FILTER_REGEX_EXCLUDE: "(vendor/|node_modules/|public/packs|public/assets|tmp/|log/|coverage/)"
14
-
15
- # Lint code, not prose. Disable documentation-oriented descriptors.
16
- DISABLE:
17
- - MARKDOWN
18
- - RST
19
- - SPELL
20
-
21
- # Disable linters that are too noisy without per-project configuration.
22
- # checkov/kics flag Carson workflow permissions as overly permissive.
23
- # devskim floods Rails apps with false-positive security warnings.
24
- DISABLE_LINTERS:
25
- - COPYPASTE_JSCPD
26
- - HTML_DJLINT
27
- - REPOSITORY_CHECKOV
28
- - REPOSITORY_DEVSKIM
29
- - REPOSITORY_KICS
@@ -1,23 +0,0 @@
1
- name: Carson Lint
2
- on:
3
- pull_request:
4
- branches: [main, master]
5
- push:
6
- branches: [main, master]
7
-
8
- jobs:
9
- lint:
10
- runs-on: ubuntu-latest
11
- permissions:
12
- contents: read
13
- issues: write
14
- pull-requests: write
15
- steps:
16
- - uses: actions/checkout@v4
17
- with:
18
- fetch-depth: 0
19
- - uses: oxsecurity/megalinter@v8
20
- env:
21
- MEGALINTER_CONFIG: .github/.mega-linter.yml
22
- VALIDATE_ALL_CODEBASE: false
23
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}