9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
# File 'lib/carson/runtime/audit.rb', line 9
def audit!( json_output: false )
fingerprint_status = block_if_outsider_fingerprints!
return fingerprint_status unless fingerprint_status.nil?
unless head_exists?
if json_output
output.puts JSON.pretty_generate( { command: "audit", status: "skipped", reason: "no commits yet", exit_code: EXIT_OK } )
else
puts_line "No commits yet — audit skipped for initial commit."
end
return EXIT_OK
end
audit_state = "ok"
audit_concise_problems = []
puts_verbose ""
puts_verbose "[Repository]"
puts_verbose "root: #{repo_root}"
puts_verbose "current_branch: #{current_branch}"
puts_verbose ""
puts_verbose "[Working Tree]"
puts_verbose git_capture!( "status", "--short", "--branch" ).strip
working_tree = audit_working_tree_report
if working_tree.fetch( :status ) == "block"
puts_verbose "ACTION: #{working_tree.fetch( :error )}; #{working_tree.fetch( :recovery )}."
audit_state = "block"
audit_concise_problems << "Working tree: #{working_tree.fetch( :error )} — #{working_tree.fetch( :recovery )}."
end
puts_verbose ""
puts_verbose "[Hooks]"
hooks_ok = hooks_health_report
hooks_status = hooks_ok ? "ok" : "mismatch"
unless hooks_ok
audit_state = "block"
audit_concise_problems << "Hooks don't match — run carson refresh."
end
puts_verbose ""
puts_verbose "[Main Sync Status]"
ahead_count, behind_count, main_error = main_sync_counts
main_sync = { ahead: 0, behind: 0, status: "ok" }
if main_error
puts_verbose "main_vs_remote_main: unknown"
puts_verbose "WARN: unable to calculate main sync status (#{main_error})."
audit_state = "attention" if audit_state == "ok"
audit_concise_problems << "Main sync: unable to determine — check remote connectivity."
main_sync = { ahead: 0, behind: 0, status: "unknown", error: main_error }
elsif ahead_count.positive?
puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
puts_verbose "main_vs_remote_main_behind: #{behind_count}"
puts_verbose "ACTION: local #{config.main_branch} is ahead of #{config.git_remote}/#{config.main_branch} by #{ahead_count} commit#{plural_suffix( count: ahead_count )}; reset local drift before commit/push workflows."
audit_state = "block"
audit_concise_problems << "Main sync (#{config.git_remote}): ahead by #{ahead_count} — git fetch #{config.git_remote}, or carson setup to switch remote."
main_sync = { ahead: ahead_count, behind: behind_count, status: "ahead" }
elsif behind_count.positive?
puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
puts_verbose "main_vs_remote_main_behind: #{behind_count}"
puts_verbose "ACTION: local #{config.main_branch} is behind #{config.git_remote}/#{config.main_branch} by #{behind_count} commit#{plural_suffix( count: behind_count )}; run carson sync."
audit_state = "attention" if audit_state == "ok"
audit_concise_problems << "Main sync (#{config.git_remote}): behind by #{behind_count} — run carson sync."
main_sync = { ahead: ahead_count, behind: behind_count, status: "behind" }
else
puts_verbose "main_vs_remote_main_ahead: 0"
puts_verbose "main_vs_remote_main_behind: 0"
puts_verbose "ACTION: local #{config.main_branch} is in sync with #{config.git_remote}/#{config.main_branch}."
end
puts_verbose ""
puts_verbose "[PR and Required Checks (gh)]"
monitor_report = pr_and_check_report
audit_state = "attention" if audit_state == "ok" && !%w[ok skipped].include?( monitor_report.fetch( :status ) )
if monitor_report.fetch( :status ) == "attention"
checks = monitor_report.fetch( :checks )
failing_count = checks.fetch( :failing_count )
pending_count = checks.fetch( :pending_count )
total = checks.fetch( :required_total )
fail_names = checks.fetch( :failing ).map { |entry| entry.fetch( :name ) }.join( ", " )
if failing_count.positive? && pending_count.positive?
audit_concise_problems << "Checks: #{failing_count} failing (#{fail_names}), #{pending_count} pending of #{total} required."
elsif failing_count.positive?
audit_concise_problems << "Checks: #{failing_count} of #{total} failing (#{fail_names})."
elsif pending_count.positive?
audit_concise_problems << "Checks: pending (#{total - pending_count} of #{total} complete)."
end
end
puts_verbose ""
puts_verbose "[Default Branch CI Baseline (gh)]"
default_branch_baseline = default_branch_ci_baseline_report
audit_state = "attention" if audit_state == "ok" && !%w[ok skipped].include?( default_branch_baseline.fetch( :status ) )
baseline_status = default_branch_baseline.fetch( :status )
if baseline_status == "block"
parts = []
if default_branch_baseline.fetch( :failing_count ).positive?
names = default_branch_baseline.fetch( :failing ).map { |entry| entry.fetch( :name ) }.join( ", " )
parts << "#{default_branch_baseline.fetch( :failing_count )} failing (#{names})"
end
if default_branch_baseline.fetch( :pending_count ).positive?
names = default_branch_baseline.fetch( :pending ).map { |entry| entry.fetch( :name ) }.join( ", " )
parts << "#{default_branch_baseline.fetch( :pending_count )} pending (#{names})"
end
parts << "no check-runs for active workflows" if default_branch_baseline.fetch( :no_check_evidence )
audit_concise_problems << "Baseline (#{default_branch_baseline.fetch( :default_branch, config.main_branch )}): #{parts.join( ', ' )} — fix before merge."
elsif baseline_status == "attention"
parts = []
if default_branch_baseline.fetch( :advisory_failing_count ).positive?
names = default_branch_baseline.fetch( :advisory_failing ).map { |entry| entry.fetch( :name ) }.join( ", " )
parts << "#{default_branch_baseline.fetch( :advisory_failing_count )} advisory failing (#{names})"
end
if default_branch_baseline.fetch( :advisory_pending_count ).positive?
names = default_branch_baseline.fetch( :advisory_pending ).map { |entry| entry.fetch( :name ) }.join( ", " )
parts << "#{default_branch_baseline.fetch( :advisory_pending_count )} advisory pending (#{names})"
end
audit_concise_problems << "Baseline (#{default_branch_baseline.fetch( :default_branch, config.main_branch )}): #{parts.join( ', ' )}."
end
if config.lint_canonical.nil? || config.lint_canonical.to_s.empty?
puts_verbose ""
puts_verbose "[Canonical Lint Policy]"
puts_verbose "HINT: lint.canonical not configured — run carson setup to enable."
end
write_and_print_pr_monitor_report(
report: monitor_report.merge(
default_branch_baseline: default_branch_baseline,
audit_status: audit_state
)
)
exit_code = audit_state == "block" ? EXIT_BLOCK : EXIT_OK
if json_output
result = {
command: "audit",
status: audit_state,
branch: current_branch,
working_tree: working_tree,
hooks: { status: hooks_status },
main_sync: main_sync,
pr: monitor_report[ :pr ],
checks: monitor_report.fetch( :checks ),
baseline: {
status: default_branch_baseline.fetch( :status ),
repository: default_branch_baseline[ :repository ],
failing_count: default_branch_baseline.fetch( :failing_count ),
pending_count: default_branch_baseline.fetch( :pending_count ),
advisory_failing_count: default_branch_baseline.fetch( :advisory_failing_count ),
advisory_pending_count: default_branch_baseline.fetch( :advisory_pending_count )
},
problems: audit_concise_problems,
exit_code: exit_code
}
output.puts JSON.pretty_generate( result )
else
puts_verbose ""
puts_verbose "[Audit Result]"
puts_verbose "status: #{audit_state}"
puts_verbose( audit_state == "block" ? "ACTION: local policy block must be resolved before commit/push." : "ACTION: no local hard block detected." )
unless verbose?
audit_concise_problems.each { |problem| puts_line problem }
puts_line format_audit_state( audit_state )
end
end
exit_code
end
|