ace-support-config 0.11.2 → 0.17.1
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/CHANGELOG.md +88 -0
- data/README.md +4 -1
- data/docs/demo/ace-config-bootstrap-root-files.tape.yml +8 -6
- data/docs/demo/ace-config-getting-started.tape.yml +3 -3
- data/docs/usage.md +36 -4
- data/lib/ace/support/config/cli.rb +56 -12
- data/lib/ace/support/config/molecules/setup_doctor_reporter.rb +270 -0
- data/lib/ace/support/config/organisms/{config_initializer.rb → config_synchronizer.rb} +7 -7
- data/lib/ace/support/config/organisms/setup_doctor.rb +1139 -0
- data/lib/ace/support/config/version.rb +1 -1
- data/lib/ace/support/config.rb +3 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f7f5cddbf50a8da6f9f42cc644f193cd9a2898757a3417849fc4104546c8ba2
|
|
4
|
+
data.tar.gz: 54c887961b4c3473a7a4be0bc5383b2ab4975d2d779c86e98c02e180a298c082
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a3291da675776cd51aa9f5d888bc8f001c3f0221206c684551ec0bfaa3cbdfe94361ed3c8a56826bce3cd838a172873cdf31afa95202a4ea0dc6278afce111d
|
|
7
|
+
data.tar.gz: 570b40415d3a4b63b3762d09fea0b43c74628dd91f286818215aaa388371d26dc586f4ab00316b6c781ae38e1555477cf07a03d0340fa2e4f8d1b9ec7b0463a9
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.17.1] - 2026-06-30
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Updated `ace-config doctor` skill projection health to ignore disabled provider projections reported by `ace-handbook status`.
|
|
14
|
+
|
|
15
|
+
## [0.17.0] - 2026-06-30
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Added `ace-config doctor` warnings for projects whose root agent guidance or `docs/tools.md` are missing generated agent-engineering guidance markers.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Corrected `ace-config-bootstrap-root-files` to sync `ace-support-core` and record from the sandbox root layout where the bootstrap files are actually created.
|
|
22
|
+
|
|
23
|
+
### Technical
|
|
24
|
+
- Expanded bootstrap sync coverage for generated `docs/tools.md`, cost-bias markers, preservation without `--force`, force refresh, and subdirectory root targeting.
|
|
25
|
+
|
|
26
|
+
## [0.16.2] - 2026-04-24
|
|
27
|
+
|
|
28
|
+
### Technical
|
|
29
|
+
- Expanded bootstrap feature coverage so generated `AGENTS.md` and `CLAUDE.md` files must retain ACE provenance, customization, and refresh guidance.
|
|
30
|
+
|
|
31
|
+
## [0.16.1] - 2026-04-24
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Clarified setup-readiness documentation so `ace-llm --list-providers` remains the provider discovery command while `ace-config doctor` is documented as the quick-start readiness check, including blocker versus warning guidance and `--no-probe` usage.
|
|
35
|
+
|
|
36
|
+
## [0.16.0] - 2026-04-23
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- Renamed `ace-config init` to `ace-config sync` and removed the old `init` command path.
|
|
40
|
+
- Updated quick-start setup guidance to sync only `ace-llm-providers-cli` config by default because other package config should come from packaged `.ace-defaults` unless project overrides are needed.
|
|
41
|
+
|
|
42
|
+
## [0.15.0] - 2026-04-23
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Extended `ace-config doctor` with informational project `.ace` vs package `.ace-defaults` counts, provider skill projection sync warnings, and streamed fast-check progress before the final report.
|
|
46
|
+
- Updated doctor provider pings to order API targets before CLI targets, use 15-second API timeouts and 30-second CLI timeouts, and distinguish timeout failures from other provider errors.
|
|
47
|
+
|
|
48
|
+
### Technical
|
|
49
|
+
- Added doctor coverage for config-default counts, skill sync drift, provider timeout selection, timeout row formatting, and progress ordering.
|
|
50
|
+
|
|
51
|
+
## [0.14.1] - 2026-04-23
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
- Updated `ace-config doctor` live provider checks to probe deduped `_utility` plus `commit` role candidates, preserving alias labels alongside resolved provider/model names.
|
|
55
|
+
- Changed doctor provider-ping progress and summaries to show pass/fail/running glyphs, live TTY line updates, and explicit passed/total counts for full and partial success.
|
|
56
|
+
|
|
57
|
+
### Technical
|
|
58
|
+
- Added doctor regression coverage for utility-plus-commit target selection, alias-preserving ping commands, append-only non-TTY progress, and partial-success summary output.
|
|
59
|
+
|
|
60
|
+
## [0.14.0] - 2026-04-23
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
- Split `ace-config doctor` output into health checks that control exit status and a concise hygiene summary, with `--hygiene` for full alias/role drift details.
|
|
64
|
+
- Made provider health live by default again through concurrent `ace-llm TARGET "ping" --no-fallback` checks against deduped `_utility` and `commit` role candidates; `--no-probe` disables live pings.
|
|
65
|
+
- Streamed human `ace-config doctor` output so utility provider ping lines show alias labels, resolved model names, and pass counts as checks complete.
|
|
66
|
+
|
|
67
|
+
### Technical
|
|
68
|
+
- Added doctor coverage for health-only exit status, hidden-by-default hygiene findings, expanded hygiene output, JSON health/hygiene counts, first-role base-provider distillation, and concurrent ping delegation.
|
|
69
|
+
|
|
70
|
+
## [0.13.0] - 2026-04-22
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
- Made `ace-config doctor` fast by default by replacing automatic live probes with structural role-default readiness checks; live probes now require `--probe` and target only resolved role-default providers.
|
|
74
|
+
|
|
75
|
+
### Technical
|
|
76
|
+
- Added doctor regression coverage for non-live defaults, opt-in provider probes, blocker-gated probes, and role-default target validation.
|
|
77
|
+
|
|
78
|
+
## [0.12.1] - 2026-04-22
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- Extended `SetupDoctor` stale-alias detection to validate `aliases.global` provider targets (`provider:model`) in addition to provider-local model aliases.
|
|
82
|
+
- Made `.ace-local` artifact-hygiene detection accept equivalent `.gitignore` forms (for example `/.ace-local/`, `.ace-local`, `.ace-local/**`) instead of requiring an exact literal line.
|
|
83
|
+
|
|
84
|
+
### Technical
|
|
85
|
+
- Added fast-test coverage for stale global alias detection and semantic `.ace-local` ignore pattern acceptance.
|
|
86
|
+
|
|
87
|
+
## [0.12.0] - 2026-04-22
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
- Added `ace-config doctor` with text/JSON readiness output and `--no-probe` support for non-mutating setup diagnostics.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
- Extended `ace-config` command routing and help output to include the new `doctor` workflow.
|
|
94
|
+
|
|
95
|
+
### Technical
|
|
96
|
+
- Added `SetupDoctor` organism coverage for blocker/warn/skip classification and CLI contract behavior.
|
|
97
|
+
|
|
10
98
|
## [0.11.2] - 2026-04-13
|
|
11
99
|
|
|
12
100
|
### Fixed
|
data/README.md
CHANGED
|
@@ -22,13 +22,16 @@
|
|
|
22
22
|
This package now owns the `ace-config` executable for managing `.ace` configuration files and templates.
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
ace-config
|
|
25
|
+
ace-config sync [GEM] [--force] [--dry-run] [--global] [--verbose]
|
|
26
|
+
ace-config doctor [--json] [--hygiene] [--probe] [--no-probe] [--verbose] [--quiet] [--no-color]
|
|
26
27
|
ace-config diff [GEM] [--global] [--local] [--file PATH] [--one-line]
|
|
27
28
|
ace-config list [--verbose]
|
|
28
29
|
ace-config version
|
|
29
30
|
ace-config help
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
Use `ace-config doctor` after a fresh setup to check quick-start readiness without mutating the project. By default it prints each fast setup check as it completes, streams concurrent `ace-llm TARGET "ping" --no-fallback` results for deduped `_utility` and `commit` role candidates, and then prints a final doctor report. The report separates health warnings from informational config-default drift and hidden-by-default hygiene findings. Add `--hygiene` to print full hygiene details, `--json` for machine-readable output, or `--no-probe` to skip live provider pings.
|
|
34
|
+
|
|
32
35
|
## Testing
|
|
33
36
|
|
|
34
37
|
`ace-support-config` uses the ACE deterministic test taxonomy:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Showcase ace-config
|
|
2
|
+
description: Showcase ace-config sync bootstrapping project-root guidance files from a subdirectory
|
|
3
3
|
tags:
|
|
4
4
|
- ace-config
|
|
5
5
|
- ace-support-config
|
|
@@ -10,17 +10,19 @@ settings:
|
|
|
10
10
|
height: 700
|
|
11
11
|
format: gif
|
|
12
12
|
scenes:
|
|
13
|
-
- name:
|
|
13
|
+
- name: Sync config from a nested working directory
|
|
14
14
|
commands:
|
|
15
|
-
- type: mkdir -p
|
|
15
|
+
- type: mkdir -p subdir && git init
|
|
16
16
|
sleep: 3s
|
|
17
|
-
- type: cd subdir && ace-config
|
|
17
|
+
- type: cd subdir && ace-config sync ace-support-core
|
|
18
18
|
sleep: 5s
|
|
19
19
|
- name: Verify bootstrap files landed at the repository root
|
|
20
20
|
commands:
|
|
21
|
-
- type: cd
|
|
21
|
+
- type: cd ..
|
|
22
|
+
sleep: 1s
|
|
23
|
+
- type: ls .gitignore AGENTS.md CLAUDE.md
|
|
22
24
|
sleep: 4s
|
|
23
|
-
- type:
|
|
25
|
+
- type: sed -n '1,20p' AGENTS.md
|
|
24
26
|
sleep: 4s
|
|
25
27
|
setup:
|
|
26
28
|
- sandbox
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Showcase ace-config CLI for listing,
|
|
2
|
+
description: Showcase ace-config CLI for listing, syncing, and diffing ace-* gem configurations
|
|
3
3
|
tags:
|
|
4
4
|
- ace-config
|
|
5
5
|
- ace-support-config
|
|
@@ -14,9 +14,9 @@ scenes:
|
|
|
14
14
|
commands:
|
|
15
15
|
- type: ace-config list
|
|
16
16
|
sleep: 3s
|
|
17
|
-
- name: Preview config
|
|
17
|
+
- name: Preview config sync
|
|
18
18
|
commands:
|
|
19
|
-
- type: ace-config
|
|
19
|
+
- type: ace-config sync --dry-run
|
|
20
20
|
sleep: 4s
|
|
21
21
|
- name: Diff configs against defaults
|
|
22
22
|
commands:
|
data/docs/usage.md
CHANGED
|
@@ -3,8 +3,8 @@ doc-type: user
|
|
|
3
3
|
title: ace-support-config Usage Guide
|
|
4
4
|
purpose: Documentation for ace-support-config/docs/usage.md
|
|
5
5
|
ace-docs:
|
|
6
|
-
last-updated: '2026-
|
|
7
|
-
last-checked: '2026-
|
|
6
|
+
last-updated: '2026-04-22'
|
|
7
|
+
last-checked: '2026-04-22'
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ace-support-config Usage Guide
|
|
@@ -15,16 +15,48 @@ The `ace-support-config` gem provides a generic configuration cascade system tha
|
|
|
15
15
|
|
|
16
16
|
## ace-config Command
|
|
17
17
|
|
|
18
|
-
`ace-support-config` ships the `ace-config` CLI for template discovery,
|
|
18
|
+
`ace-support-config` ships the `ace-config` CLI for template discovery, config sync, and drift checks.
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
ace-config
|
|
21
|
+
ace-config sync [GEM] [--force] [--dry-run] [--global] [--verbose]
|
|
22
|
+
ace-config doctor [--json] [--hygiene] [--probe] [--no-probe]
|
|
22
23
|
ace-config diff [GEM] [--global] [--local] [--file PATH] [--one-line]
|
|
23
24
|
ace-config list [--verbose]
|
|
24
25
|
ace-config version
|
|
25
26
|
ace-config help
|
|
26
27
|
```
|
|
27
28
|
|
|
29
|
+
### Setup Readiness Doctor
|
|
30
|
+
|
|
31
|
+
Run `ace-config doctor` after `ace-config sync ace-llm-providers-cli` and provider setup to verify that the quick-start
|
|
32
|
+
path is ready without changing files. `ace-llm --list-providers` is still the provider discovery command; doctor is the
|
|
33
|
+
separate readiness command that tells you whether the documented setup can actually execute.
|
|
34
|
+
|
|
35
|
+
The doctor checks:
|
|
36
|
+
|
|
37
|
+
- health markers for generated local artifacts and `.ace-local/` gitignore readiness
|
|
38
|
+
- `ace-llm-providers-cli` availability and bundled gem/package readiness
|
|
39
|
+
- `ace-llm --list-providers` discovery for configured providers
|
|
40
|
+
- project `.ace` vs package `.ace-defaults` config-default mode counts
|
|
41
|
+
- usable core role defaults
|
|
42
|
+
- provider-native skill projection sync status from `ace-handbook status`
|
|
43
|
+
- a one-line hygiene summary for configured provider aliases and wider role/default drift
|
|
44
|
+
- concurrent `ace-llm TARGET "ping" --no-fallback` checks for deduped `_utility` and `commit` role candidates
|
|
45
|
+
|
|
46
|
+
Interpret results as setup blockers versus actionable warnings:
|
|
47
|
+
|
|
48
|
+
- Missing provider packages, unresolved provider/model configuration, or failed readiness requirements for the active
|
|
49
|
+
quick-start path are blockers.
|
|
50
|
+
- Missing credentials or missing local CLI account access are reported as setup readiness issues with next actions,
|
|
51
|
+
not as ACE install corruption.
|
|
52
|
+
- API-only users can still pass doctor when their configured API providers are ready, even if unrelated CLI providers
|
|
53
|
+
are inactive.
|
|
54
|
+
|
|
55
|
+
Use `--hygiene` to expand full hygiene findings and `--json` for structured output in automation. By default, doctor
|
|
56
|
+
prints fast health/info checks as they complete, runs concurrent `ace-llm TARGET "ping" --no-fallback` checks for
|
|
57
|
+
deduped `_utility` and `commit` role candidates, and then prints the final report. Use `--no-probe` to skip live
|
|
58
|
+
provider calls when you want a readiness check based only on local setup and configuration.
|
|
59
|
+
|
|
28
60
|
## Testing Contract
|
|
29
61
|
|
|
30
62
|
`ace-support-config` follows the ACE deterministic package targets:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
-
require_relative "organisms/
|
|
4
|
+
require_relative "organisms/config_synchronizer"
|
|
5
5
|
require_relative "organisms/config_diff"
|
|
6
|
+
require_relative "organisms/setup_doctor"
|
|
6
7
|
require_relative "models/config_templates"
|
|
7
8
|
|
|
8
9
|
module Ace
|
|
@@ -19,12 +20,14 @@ module Ace
|
|
|
19
20
|
command = argv.shift
|
|
20
21
|
|
|
21
22
|
case command
|
|
22
|
-
when "
|
|
23
|
-
|
|
23
|
+
when "sync"
|
|
24
|
+
run_sync(argv)
|
|
24
25
|
when "diff"
|
|
25
26
|
run_diff(argv)
|
|
26
27
|
when "list"
|
|
27
28
|
run_list(argv)
|
|
29
|
+
when "doctor"
|
|
30
|
+
run_doctor(argv)
|
|
28
31
|
when "version", "--version"
|
|
29
32
|
show_version
|
|
30
33
|
when "help", "--help", "-h"
|
|
@@ -39,16 +42,16 @@ module Ace
|
|
|
39
42
|
|
|
40
43
|
private
|
|
41
44
|
|
|
42
|
-
def
|
|
45
|
+
def run_sync(argv)
|
|
43
46
|
options = {}
|
|
44
47
|
|
|
45
48
|
parser = OptionParser.new do |opts|
|
|
46
49
|
opts.banner = <<~BANNER.chomp
|
|
47
50
|
NAME
|
|
48
|
-
ace-config
|
|
51
|
+
ace-config sync - Sync configuration for ace-* gems
|
|
49
52
|
|
|
50
53
|
USAGE
|
|
51
|
-
ace-config
|
|
54
|
+
ace-config sync [GEM] [OPTIONS]
|
|
52
55
|
|
|
53
56
|
OPTIONS
|
|
54
57
|
BANNER
|
|
@@ -65,12 +68,12 @@ module Ace
|
|
|
65
68
|
parser.parse!(argv)
|
|
66
69
|
gem_name = argv.shift
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
synchronizer = Organisms::ConfigSynchronizer.new(**options)
|
|
69
72
|
|
|
70
73
|
if gem_name
|
|
71
|
-
|
|
74
|
+
synchronizer.sync_gem(gem_name)
|
|
72
75
|
else
|
|
73
|
-
|
|
76
|
+
synchronizer.sync_all
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
@@ -160,8 +163,48 @@ module Ace
|
|
|
160
163
|
end
|
|
161
164
|
end
|
|
162
165
|
|
|
163
|
-
puts "\nUse 'ace-config
|
|
164
|
-
puts "Use 'ace-config
|
|
166
|
+
puts "\nUse 'ace-config sync [GEM]' to sync a specific gem's configuration"
|
|
167
|
+
puts "Use 'ace-config sync' to sync all configurations"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def run_doctor(argv)
|
|
171
|
+
options = {json: false, no_probe: false, probe: false, hygiene: false, verbose: false, no_color: false, quiet: false}
|
|
172
|
+
|
|
173
|
+
parser = OptionParser.new do |opts|
|
|
174
|
+
opts.banner = <<~BANNER.chomp
|
|
175
|
+
NAME
|
|
176
|
+
ace-config doctor - Check setup readiness for quick-start workflows
|
|
177
|
+
|
|
178
|
+
USAGE
|
|
179
|
+
ace-config doctor [OPTIONS]
|
|
180
|
+
|
|
181
|
+
OPTIONS
|
|
182
|
+
BANNER
|
|
183
|
+
opts.on("--json", "Output checks as JSON") { options[:json] = true }
|
|
184
|
+
opts.on("--hygiene", "Show full hygiene findings") { options[:hygiene] = true }
|
|
185
|
+
opts.on("-v", "--verbose", "Show full diagnostic detail") { options[:verbose] = true }
|
|
186
|
+
opts.on("-q", "--quiet", "Suppress output; use exit status only") { options[:quiet] = true }
|
|
187
|
+
opts.on("--no-color", "Disable colored output") { options[:no_color] = true }
|
|
188
|
+
opts.on("--probe", "Run live provider probes (default)") { options[:probe] = true }
|
|
189
|
+
opts.on("--no-probe", "Disable live provider probes") { options[:no_probe] = true }
|
|
190
|
+
opts.on("-h", "--help", "Show this help") do
|
|
191
|
+
puts opts
|
|
192
|
+
exit
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
parser.parse!(argv)
|
|
197
|
+
|
|
198
|
+
exit_code = Organisms::SetupDoctor.new.run(
|
|
199
|
+
json: options[:json],
|
|
200
|
+
no_probe: options[:no_probe],
|
|
201
|
+
probe: options[:probe],
|
|
202
|
+
hygiene: options[:hygiene],
|
|
203
|
+
verbose: options[:verbose],
|
|
204
|
+
colors: !options[:no_color],
|
|
205
|
+
quiet: options[:quiet]
|
|
206
|
+
)
|
|
207
|
+
exit(exit_code) if exit_code.positive?
|
|
165
208
|
end
|
|
166
209
|
|
|
167
210
|
def show_version
|
|
@@ -177,9 +220,10 @@ module Ace
|
|
|
177
220
|
ace-config COMMAND [OPTIONS]
|
|
178
221
|
|
|
179
222
|
COMMANDS
|
|
180
|
-
|
|
223
|
+
sync [GEM] Sync configuration for specific gem or all
|
|
181
224
|
diff [GEM] Compare configs with examples
|
|
182
225
|
list List available ace-* gems with example configs
|
|
226
|
+
doctor Check setup readiness for quick-start workflows
|
|
183
227
|
version Show version
|
|
184
228
|
help Show this help
|
|
185
229
|
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Support
|
|
7
|
+
module Config
|
|
8
|
+
module Molecules
|
|
9
|
+
class SetupDoctorReporter
|
|
10
|
+
COLORS = {
|
|
11
|
+
red: "\e[31m",
|
|
12
|
+
yellow: "\e[33m",
|
|
13
|
+
green: "\e[32m",
|
|
14
|
+
blue: "\e[34m",
|
|
15
|
+
cyan: "\e[36m",
|
|
16
|
+
reset: "\e[0m",
|
|
17
|
+
bold: "\e[1m"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
ICONS = {
|
|
21
|
+
doctor: "🏥",
|
|
22
|
+
stats: "📊",
|
|
23
|
+
success: "✅",
|
|
24
|
+
error: "❌",
|
|
25
|
+
warning: "⚠️",
|
|
26
|
+
info: "ℹ️"
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
STATUS_GLYPHS = {
|
|
30
|
+
"pass" => "✓",
|
|
31
|
+
"warn" => "○",
|
|
32
|
+
"blocker" => "✗",
|
|
33
|
+
"skip" => "○",
|
|
34
|
+
"info" => "○"
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
STATUS_COLORS = {
|
|
38
|
+
"pass" => :green,
|
|
39
|
+
"warn" => :yellow,
|
|
40
|
+
"blocker" => :red,
|
|
41
|
+
"skip" => :yellow,
|
|
42
|
+
"info" => :cyan
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def self.format_results(results, format: :terminal, hygiene: false, verbose: false, colors: true)
|
|
46
|
+
case format.to_sym
|
|
47
|
+
when :json
|
|
48
|
+
JSON.pretty_generate(results)
|
|
49
|
+
else
|
|
50
|
+
new(results, hygiene: hygiene, verbose: verbose, colors: colors).format_terminal
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def initialize(results, hygiene:, verbose:, colors:)
|
|
55
|
+
@results = results
|
|
56
|
+
@hygiene = hygiene
|
|
57
|
+
@verbose = verbose
|
|
58
|
+
@colors = colors
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def format_terminal
|
|
62
|
+
output = []
|
|
63
|
+
output << "\n#{colorize("#{ICONS[:doctor]} Setup Health Check", :bold)}"
|
|
64
|
+
output << "=" * 40
|
|
65
|
+
output.concat(format_overview)
|
|
66
|
+
output.concat(format_readiness)
|
|
67
|
+
output.concat(format_info)
|
|
68
|
+
output.concat(format_provider_pings)
|
|
69
|
+
output.concat(format_issues)
|
|
70
|
+
output << "=" * 40
|
|
71
|
+
output << format_summary_line
|
|
72
|
+
output << "\n#{colorize("Completed in #{format_duration(@results[:duration])}", :blue)}" if @results[:duration]
|
|
73
|
+
output << final_status_line
|
|
74
|
+
output.join("\n")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def format_overview
|
|
80
|
+
stats = @results[:stats] || {}
|
|
81
|
+
[
|
|
82
|
+
"",
|
|
83
|
+
colorize("#{ICONS[:stats]} Overview", :cyan),
|
|
84
|
+
"-" * 20,
|
|
85
|
+
" Health checks: #{stats[:health_checks] || health_checks.length}",
|
|
86
|
+
" Info checks: #{stats[:info_checks] || info_checks.length}",
|
|
87
|
+
" Utility providers checked: #{stats[:provider_targets] || provider_outcomes.length}",
|
|
88
|
+
" Hygiene findings: #{stats[:hygiene_findings] || 0}"
|
|
89
|
+
]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def format_readiness
|
|
93
|
+
rows = health_checks.reject { |check| check[:id] == "probe-readiness" }
|
|
94
|
+
return [] if rows.empty?
|
|
95
|
+
|
|
96
|
+
output = ["", colorize("Readiness", :cyan), "-" * 20]
|
|
97
|
+
rows.each do |check|
|
|
98
|
+
output << " #{status_glyph(check[:status])} #{check[:message]}"
|
|
99
|
+
if check[:status] == "blocker" || check[:status] == "warn" || @verbose
|
|
100
|
+
Array(check[:details]).each { |detail| output << " - #{detail}" }
|
|
101
|
+
output << " Next: #{check[:next_action]}" if check[:next_action]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
output
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def format_info
|
|
108
|
+
return [] if info_checks.empty?
|
|
109
|
+
|
|
110
|
+
output = ["", colorize("Info", :cyan), "-" * 20]
|
|
111
|
+
info_checks.each do |check|
|
|
112
|
+
output << " #{status_glyph(check[:status])} #{check[:message]}"
|
|
113
|
+
if @verbose
|
|
114
|
+
Array(check[:details]).each { |detail| output << " - #{detail}" }
|
|
115
|
+
output << " Next: #{check[:next_action]}" if check[:next_action]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
output
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def format_provider_pings
|
|
122
|
+
check = health_checks.find { |row| row[:id] == "probe-readiness" }
|
|
123
|
+
return [] unless check
|
|
124
|
+
|
|
125
|
+
output = ["", colorize("Utility Provider Pings", :cyan), "-" * 20]
|
|
126
|
+
output << " #{status_glyph(check[:status])} #{check[:message]}"
|
|
127
|
+
details = provider_outcomes.any? ? provider_outcomes.map { |outcome| provider_outcome_line(outcome) } : Array(check[:details])
|
|
128
|
+
details.each { |detail| output << " #{detail}" }
|
|
129
|
+
output << " Next: #{check[:next_action]}" if check[:next_action]
|
|
130
|
+
output
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def format_issues
|
|
134
|
+
blockers = health_checks.select { |check| check[:status] == "blocker" }
|
|
135
|
+
warnings = health_checks.select { |check| check[:status] == "warn" }
|
|
136
|
+
hygiene_warnings = hygiene_checks.reject { |check| check[:status] == "pass" }
|
|
137
|
+
|
|
138
|
+
if blockers.empty? && warnings.empty? && hygiene_warnings.empty?
|
|
139
|
+
return ["", colorize("#{ICONS[:success]} No issues found", :green)]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
output = ["", colorize("Issues Found:", :yellow), "-" * 20]
|
|
143
|
+
output.concat(format_issue_group("#{ICONS[:error]} Blockers", blockers, :red, include_details: true))
|
|
144
|
+
output.concat(format_issue_group("#{ICONS[:warning]} Warnings", warnings, :yellow, include_details: true))
|
|
145
|
+
output.concat(format_hygiene_group(hygiene_warnings))
|
|
146
|
+
output
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def format_issue_group(title, checks, color, include_details:)
|
|
150
|
+
return [] if checks.empty?
|
|
151
|
+
|
|
152
|
+
output = ["", colorize("#{title} (#{checks.length})", color)]
|
|
153
|
+
checks.each_with_index do |check, index|
|
|
154
|
+
output << "#{index + 1}. #{check[:message]}"
|
|
155
|
+
if include_details
|
|
156
|
+
Array(check[:details]).each { |detail| output << " - #{detail}" }
|
|
157
|
+
end
|
|
158
|
+
output << " Next: #{check[:next_action]}" if check[:next_action]
|
|
159
|
+
end
|
|
160
|
+
output
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def format_hygiene_group(checks)
|
|
164
|
+
finding_count = @results.dig(:hygiene, :finding_count) || 0
|
|
165
|
+
return [] if finding_count.zero?
|
|
166
|
+
|
|
167
|
+
if !@hygiene && !@verbose
|
|
168
|
+
return [
|
|
169
|
+
"",
|
|
170
|
+
colorize("#{ICONS[:warning]} Hygiene", :yellow),
|
|
171
|
+
"1. Hygiene findings detected (#{finding_count}); rerun with --hygiene for details"
|
|
172
|
+
]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
output = ["", colorize("#{ICONS[:warning]} Hygiene (#{finding_count})", :yellow)]
|
|
176
|
+
issue_number = 1
|
|
177
|
+
checks.each do |check|
|
|
178
|
+
output << "#{issue_number}. #{check[:message]}"
|
|
179
|
+
Array(check[:details]).each { |detail| output << " - #{detail}" }
|
|
180
|
+
output << " Next: #{check[:next_action]}" if check[:next_action]
|
|
181
|
+
issue_number += 1
|
|
182
|
+
end
|
|
183
|
+
output
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def format_summary_line
|
|
187
|
+
health = @results[:health] || {}
|
|
188
|
+
hygiene = @results[:hygiene] || {}
|
|
189
|
+
info = @results[:info] || {}
|
|
190
|
+
parts = []
|
|
191
|
+
parts << colorize("#{health[:blocker_count]} blockers", health[:blocker_count].to_i.positive? ? :red : :green)
|
|
192
|
+
parts << colorize("#{health[:warning_count]} warnings", health[:warning_count].to_i.positive? ? :yellow : :green)
|
|
193
|
+
parts << colorize("#{info[:count]} info", :cyan)
|
|
194
|
+
parts << colorize("#{hygiene[:finding_count]} hygiene findings", hygiene[:finding_count].to_i.positive? ? :yellow : :green)
|
|
195
|
+
parts.join(", ")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def final_status_line
|
|
199
|
+
if @results[:valid]
|
|
200
|
+
if @results.dig(:health, :warning_count).to_i.positive? || @results.dig(:hygiene, :finding_count).to_i.positive?
|
|
201
|
+
colorize("Setup check passed with warnings", :yellow)
|
|
202
|
+
else
|
|
203
|
+
colorize("Setup check passed", :green)
|
|
204
|
+
end
|
|
205
|
+
else
|
|
206
|
+
colorize("Setup check failed", :red)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def provider_outcome_line(outcome)
|
|
211
|
+
elapsed = outcome[:elapsed_ms] ? " in #{outcome[:elapsed_ms]}ms" : ""
|
|
212
|
+
glyph = outcome[:status] == "pass" ? status_glyph(outcome[:status]) : colorize("✗", :red)
|
|
213
|
+
suffix = if outcome[:status] == "pass"
|
|
214
|
+
elapsed
|
|
215
|
+
elsif outcome[:failure_type] == "timeout"
|
|
216
|
+
" timed out after #{outcome[:timeout_seconds]}s"
|
|
217
|
+
else
|
|
218
|
+
" failed"
|
|
219
|
+
end
|
|
220
|
+
"#{glyph} #{target_display(outcome)}#{suffix}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def target_display(target)
|
|
224
|
+
label = target[:label].to_s
|
|
225
|
+
selector = target[:selector].to_s
|
|
226
|
+
label = selector if label.empty?
|
|
227
|
+
selector.empty? || selector == label ? label : "#{label} (#{selector})"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def status_glyph(status)
|
|
231
|
+
glyph = STATUS_GLYPHS.fetch(status.to_s, STATUS_GLYPHS["warn"])
|
|
232
|
+
colorize(glyph, STATUS_COLORS.fetch(status.to_s, :yellow))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def health_checks
|
|
236
|
+
@health_checks ||= checks.select { |check| check[:kind] == "health" }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def hygiene_checks
|
|
240
|
+
@hygiene_checks ||= checks.select { |check| check[:kind] == "hygiene" }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def info_checks
|
|
244
|
+
@info_checks ||= checks.select { |check| check[:kind] == "info" }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def provider_outcomes
|
|
248
|
+
@provider_outcomes ||= Array(health_checks.find { |row| row[:id] == "probe-readiness" }&.fetch(:outcomes, []))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def checks
|
|
252
|
+
@results[:checks] || []
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def format_duration(duration)
|
|
256
|
+
return "0ms" unless duration
|
|
257
|
+
|
|
258
|
+
duration < 1 ? "#{(duration * 1000).round}ms" : "#{duration.round(2)}s"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def colorize(text, color)
|
|
262
|
+
return text unless @colors && COLORS[color]
|
|
263
|
+
|
|
264
|
+
"#{COLORS[color]}#{text}#{COLORS[:reset]}"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|