carson 1.0.1 → 2.7.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/copilot-instructions.md +1 -12
- data/.github/workflows/carson_policy.yml +1 -1
- data/API.md +50 -13
- data/MANUAL.md +140 -65
- data/README.md +108 -35
- data/RELEASE.md +410 -6
- data/SKILL.md +102 -0
- data/VERSION +1 -1
- data/carson.gemspec +3 -1
- data/{assets/hooks → hooks}/pre-commit +1 -1
- data/{assets/hooks → hooks}/pre-merge-commit +4 -0
- data/{assets/hooks → hooks}/pre-push +4 -0
- data/{assets/hooks → hooks}/prepare-commit-msg +4 -0
- data/icon.svg +651 -0
- data/lib/carson/adapters/agent.rb +15 -0
- data/lib/carson/adapters/claude.rb +45 -0
- data/lib/carson/adapters/codex.rb +45 -0
- data/lib/carson/adapters/prompt.rb +60 -0
- data/lib/carson/cli.rb +65 -20
- data/lib/carson/config.rb +100 -14
- data/lib/carson/policy/ruby/lint.rb +1 -1
- data/lib/carson/runtime/audit.rb +33 -10
- data/lib/carson/runtime/govern.rb +641 -0
- data/lib/carson/runtime/lint.rb +3 -3
- data/lib/carson/runtime/local.rb +51 -12
- data/lib/carson/runtime/review/gate_support.rb +14 -1
- data/lib/carson/runtime/review.rb +3 -3
- data/lib/carson/runtime.rb +10 -3
- data/lib/carson.rb +9 -0
- data/templates/.github/AGENTS.md +1 -0
- data/templates/.github/CLAUDE.md +1 -0
- data/templates/.github/carson-instructions.md +12 -0
- data/templates/.github/copilot-instructions.md +1 -12
- metadata +15 -5
data/lib/carson/runtime/audit.rb
CHANGED
|
@@ -48,7 +48,6 @@ module Carson
|
|
|
48
48
|
audit_state = "block" if default_branch_baseline.fetch( :status ) == "block"
|
|
49
49
|
audit_state = "attention" if audit_state == "ok" && default_branch_baseline.fetch( :status ) != "ok"
|
|
50
50
|
scope_guard = print_scope_integrity_guard
|
|
51
|
-
audit_state = "block" if scope_guard.fetch( :split_required )
|
|
52
51
|
audit_state = "attention" if audit_state == "ok" && scope_guard.fetch( :status ) == "attention"
|
|
53
52
|
write_and_print_pr_monitor_report(
|
|
54
53
|
report: monitor_report.merge(
|
|
@@ -279,7 +278,7 @@ module Carson
|
|
|
279
278
|
local_rubocop_path = File.join( repo_root, ".rubocop.yml" )
|
|
280
279
|
if File.file?( local_rubocop_path )
|
|
281
280
|
report[ :status ] = "block"
|
|
282
|
-
report[ :reason ] = "repo-local RuboCop config is forbidden: #{relative_path( local_rubocop_path )}; remove it and use
|
|
281
|
+
report[ :reason ] = "repo-local RuboCop config is forbidden: #{relative_path( local_rubocop_path )}; remove it and use ~/.carson/lint/rubocop.yml."
|
|
283
282
|
report[ :exit_code ] = EXIT_BLOCK
|
|
284
283
|
puts_line "lint_#{language}_status: block"
|
|
285
284
|
puts_line "lint_#{language}_reason: #{report.fetch( :reason )}"
|
|
@@ -297,7 +296,7 @@ module Carson
|
|
|
297
296
|
report[ :exit_code ] = EXIT_BLOCK
|
|
298
297
|
puts_line "lint_#{language}_status: block"
|
|
299
298
|
puts_line "lint_#{language}_reason: #{report.fetch( :reason )}"
|
|
300
|
-
puts_line "ACTION: run carson lint setup --source <path-or-git-url> to prepare
|
|
299
|
+
puts_line "ACTION: run carson lint setup --source <path-or-git-url> to prepare ~/.carson/lint policy files."
|
|
301
300
|
return report
|
|
302
301
|
end
|
|
303
302
|
|
|
@@ -403,9 +402,13 @@ module Carson
|
|
|
403
402
|
check_runs_total: 0,
|
|
404
403
|
failing_count: 0,
|
|
405
404
|
pending_count: 0,
|
|
405
|
+
advisory_failing_count: 0,
|
|
406
|
+
advisory_pending_count: 0,
|
|
406
407
|
no_check_evidence: false,
|
|
407
408
|
failing: [],
|
|
408
|
-
pending: []
|
|
409
|
+
pending: [],
|
|
410
|
+
advisory_failing: [],
|
|
411
|
+
advisory_pending: []
|
|
409
412
|
}
|
|
410
413
|
unless gh_available?
|
|
411
414
|
report[ :status ] = "skipped"
|
|
@@ -443,15 +446,23 @@ module Carson
|
|
|
443
446
|
)
|
|
444
447
|
check_runs = Array( check_runs_payload[ "check_runs" ] )
|
|
445
448
|
failing, pending = partition_default_branch_check_runs( check_runs: check_runs )
|
|
449
|
+
advisory_names = config.audit_advisory_check_names
|
|
450
|
+
critical_failing, advisory_failing = separate_advisory_check_entries( entries: failing, advisory_names: advisory_names )
|
|
451
|
+
critical_pending, advisory_pending = separate_advisory_check_entries( entries: pending, advisory_names: advisory_names )
|
|
446
452
|
report[ :check_runs_total ] = check_runs.count
|
|
447
|
-
report[ :failing ] = normalise_default_branch_check_entries( entries:
|
|
448
|
-
report[ :pending ] = normalise_default_branch_check_entries( entries:
|
|
453
|
+
report[ :failing ] = normalise_default_branch_check_entries( entries: critical_failing )
|
|
454
|
+
report[ :pending ] = normalise_default_branch_check_entries( entries: critical_pending )
|
|
455
|
+
report[ :advisory_failing ] = normalise_default_branch_check_entries( entries: advisory_failing )
|
|
456
|
+
report[ :advisory_pending ] = normalise_default_branch_check_entries( entries: advisory_pending )
|
|
449
457
|
report[ :failing_count ] = report.fetch( :failing ).count
|
|
450
458
|
report[ :pending_count ] = report.fetch( :pending ).count
|
|
459
|
+
report[ :advisory_failing_count ] = report.fetch( :advisory_failing ).count
|
|
460
|
+
report[ :advisory_pending_count ] = report.fetch( :advisory_pending ).count
|
|
451
461
|
report[ :no_check_evidence ] = report.fetch( :workflows_total ).positive? && report.fetch( :check_runs_total ).zero?
|
|
452
462
|
report[ :status ] = "block" if report.fetch( :failing_count ).positive?
|
|
453
463
|
report[ :status ] = "block" if report.fetch( :pending_count ).positive?
|
|
454
464
|
report[ :status ] = "block" if report.fetch( :no_check_evidence )
|
|
465
|
+
report[ :status ] = "attention" if report.fetch( :status ) == "ok" && ( report.fetch( :advisory_failing_count ).positive? || report.fetch( :advisory_pending_count ).positive? )
|
|
455
466
|
puts_line "default_branch_repository: #{report.fetch( :repository )}"
|
|
456
467
|
puts_line "default_branch_name: #{report.fetch( :default_branch )}"
|
|
457
468
|
puts_line "default_branch_head_sha: #{report.fetch( :head_sha )}"
|
|
@@ -459,8 +470,12 @@ module Carson
|
|
|
459
470
|
puts_line "default_branch_check_runs_total: #{report.fetch( :check_runs_total )}"
|
|
460
471
|
puts_line "default_branch_failing: #{report.fetch( :failing_count )}"
|
|
461
472
|
puts_line "default_branch_pending: #{report.fetch( :pending_count )}"
|
|
473
|
+
puts_line "default_branch_advisory_failing: #{report.fetch( :advisory_failing_count )}"
|
|
474
|
+
puts_line "default_branch_advisory_pending: #{report.fetch( :advisory_pending_count )}"
|
|
462
475
|
report.fetch( :failing ).each { |entry| puts_line "default_branch_check_fail: #{entry.fetch( :workflow )} / #{entry.fetch( :name )} #{entry.fetch( :link )}".strip }
|
|
463
476
|
report.fetch( :pending ).each { |entry| puts_line "default_branch_check_pending: #{entry.fetch( :workflow )} / #{entry.fetch( :name )} #{entry.fetch( :link )}".strip }
|
|
477
|
+
report.fetch( :advisory_failing ).each { |entry| puts_line "default_branch_check_advisory_fail: #{entry.fetch( :workflow )} / #{entry.fetch( :name )} (advisory) #{entry.fetch( :link )}".strip }
|
|
478
|
+
report.fetch( :advisory_pending ).each { |entry| puts_line "default_branch_check_advisory_pending: #{entry.fetch( :workflow )} / #{entry.fetch( :name )} (advisory) #{entry.fetch( :link )}".strip }
|
|
464
479
|
if report.fetch( :no_check_evidence )
|
|
465
480
|
puts_line "ACTION: default branch has workflow files but no check-runs; align workflow triggers and branch protection check names."
|
|
466
481
|
end
|
|
@@ -525,6 +540,14 @@ module Carson
|
|
|
525
540
|
[ failing, pending ]
|
|
526
541
|
end
|
|
527
542
|
|
|
543
|
+
# Separates check-run entries into critical and advisory buckets based on configured advisory names.
|
|
544
|
+
def separate_advisory_check_entries( entries:, advisory_names: )
|
|
545
|
+
advisory, critical = Array( entries ).partition do |entry|
|
|
546
|
+
advisory_names.include?( entry[ "name" ].to_s.strip )
|
|
547
|
+
end
|
|
548
|
+
[ critical, advisory ]
|
|
549
|
+
end
|
|
550
|
+
|
|
528
551
|
# Failing means completed with a non-successful conclusion.
|
|
529
552
|
def default_branch_check_run_failing?( entry: )
|
|
530
553
|
status = entry[ "status" ].to_s.strip.downcase
|
|
@@ -701,11 +724,11 @@ module Carson
|
|
|
701
724
|
scope.fetch( :unmatched_paths ).each { |path| puts_line "unmatched_path: #{path}" }
|
|
702
725
|
puts_line "violating_files_count: #{scope.fetch( :violating_files ).count}"
|
|
703
726
|
scope.fetch( :violating_files ).each { |path| puts_line "violating_file: #{path} (group=#{scope.fetch( :grouped_paths ).fetch( path )})" }
|
|
704
|
-
puts_line "checklist_single_business_intent:
|
|
705
|
-
puts_line "checklist_single_scope_group: #{scope.fetch( :split_required ) ? '
|
|
706
|
-
puts_line "checklist_cross_boundary_changes_justified: #{( scope.fetch( :split_required ) || scope.fetch( :misc_present ) ) ? '
|
|
727
|
+
puts_line "checklist_single_business_intent: pass"
|
|
728
|
+
puts_line "checklist_single_scope_group: #{scope.fetch( :split_required ) ? 'advisory' : 'pass'}"
|
|
729
|
+
puts_line "checklist_cross_boundary_changes_justified: #{( scope.fetch( :split_required ) || scope.fetch( :misc_present ) ) ? 'advisory' : 'pass'}"
|
|
707
730
|
if scope.fetch( :split_required )
|
|
708
|
-
puts_line "ACTION:
|
|
731
|
+
puts_line "ACTION: multiple module groups detected (informational only)."
|
|
709
732
|
elsif scope.fetch( :misc_present )
|
|
710
733
|
puts_line "ACTION: unmatched paths detected; classify via scope.path_groups for stricter module checks."
|
|
711
734
|
else
|