566910e3f8c2
tidy Doctor's terminal report into labelled rows and add --fast to skip the remote audit (3.9.0)
- time
- 2026-10-06 21:11 UTC · 6 days ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- tidy doctor output
- parent
- 38f9fd36df11
↓ koh-566910e3.tar.gzatlas at this save
files changed5 files
VERSION+1 −1shell/koh3.plugin.zsh+1 −1src/cli/cmd_doctor.v+95 −34tests/cli_test.v+4 −1v.mod+1 −1shell/koh3.plugin.zsh+1 −1
@@ -167,7 +167,7 @@167167 steal) _arguments '--yes' '--list' '--mine' '--theirs' '--json' ;;168168 ignore) _arguments '--global[edit ~/.kohignore for every repo]' '--yes[skip the question]' '--json' '*:path:_files' ;;169169 strip) _arguments '--yes[skip the yes/no question; paths still typed]' '--save[one save]:save:_koh3_ids' '*:path:_files' ;;170- doctor) _arguments '--offline[skip remote content checks]' '--json[print the report as JSON]' '--yes[remove old peek folders]' '--backup[copy .koh to]:path:_files' ;;170+ doctor) _arguments '--offline[skip remote content checks]' '--fast[skip the slow remote audit]' '--json[print the report as JSON]' '--yes[remove old peek folders]' '--backup[copy .koh to]:path:_files' ;;171171 build) _arguments '*--platform[target platform]:platform:(macos-aarch64 macos-x86_64 linux-aarch64 linux-x86_64)' '--all' ;;172172 update) _arguments '--check' '--yes' '--json' ;;173173 migrate) _arguments '--lane[Koh 2 lane]:lane:(dev main)' '*--with[a lost version, from a backup]:file:_files' '--yes[skip the question]' '--json' ;;src/cli/cmd_doctor.v+95 −34
@@ -17,7 +17,7 @@1717 pub fn cmd_doctor(raw []string) int {1818 args := parse('doctor', raw, Options{1919 values: ['backup']20- flags: ['json', 'yes', 'offline']20+ flags: ['json', 'yes', 'offline', 'fast']2121 }) or { return usage_error(err.msg()) }2222 if args.positional.len > 0 {2323 return usage_error('doctor takes no positional arguments')@@ -67,13 +67,17 @@6767 report.size_bytes = os.file_size(report.database)6868 report.wal_bytes = os.file_size('${report.database}-wal')6969 doctor_remote_config(mut report)70- if !args.flag('offline') {70+ // The remote audit is nearly all of Doctor's time, so --fast skips exactly that.71+ skip := if args.flag('offline') {72+ 'offline'73+ } else if args.flag('fast') { 'fast' } else { '' }74+ if skip != '' {75+ report.checks << DoctorCheck{'remote audit', 'skipped', '--${skip} requested; remote content not checked'}76+ } else {7177 working('auditing remote history and releases (read-only)')7278 doctor_remote(store, mut report) or { report.check('remote audit', [err.msg()]) }73- } else {74- report.checks << DoctorCheck{'remote audit', 'skipped', '--offline requested; remote content not checked'}7579 }76- report.scope = 'repository data and configuration; application tests, backup recovery and remote write permissions are not verified'80+ report.scope = 'repository data and configuration only; not application tests, backup recovery or remote write access'7781 report.health = if report.problems.len > 0 {7882 'unhealthy'7983 } else if report.warnings.len > 0 { 'warning' } else { 'healthy' }@@ -93,50 +97,107 @@9397 }9498 9599 fn print_doctor(report DoctorJson) {96- if report.problems.len == 0 {97- ok('${report.root} is sound · local health: ${report.health}')100+ root := report.root.replace_once(os.home_dir(), '~')101+ if report.problems.len > 0 {102+ warn('${root} has ${fmt.plural(report.problems.len, 'problem')}')103+ } else if report.warnings.len > 0 {104+ ok('${root} is sound · ${fmt.plural(report.warnings.len, 'warning')}')98105 } else {99- warn('${report.root} has ${fmt.plural(report.problems.len, 'problem')}')106+ ok('${root} is healthy')100107 }101- note('database: ${fmt.human_bytes(i64(report.size_bytes))} + ${fmt.human_bytes(i64(report.wal_bytes))} WAL · schema ${report.schema_version}')108+ println('')102109 if report.metadata_available {103- if report.head.id != '' {104- note('head [${report.head.id}] ${report.head.message.all_before('\n')} · ${fmt.stamp(report.head.created_at)}')105- note('${report.head_files} files at head · agent: ${report.head.agent} · session: ${report.head.session}')106- } else {107- note('head: ${if report.saves == 0 { 'no saves yet' } else { report.head_id }}')108- }109- note('${report.saves} stored saves · ${report.line_saves} on line · ${report.off_line_saves} off line · ${report.checkpoints} checkpoints')110- note('${report.objects} objects · ${fmt.human_bytes(report.object_bytes)} content · ${fmt.human_bytes(report.packed_bytes)} compressed')111- note('${report.events} events · ${report.flagged_saves} flagged saves · ${report.file_entries} file entries')110+ doctor_metadata(report)112111 } else {113- note('repository metadata unavailable; see failed checks')112+ println(' ${fmt.dim('repository metadata unavailable; see failed checks')}')114113 }115- for check in report.checks {116- println(' ${check.status}: ${check.name} · ${check.detail}')114+ println('')115+ passed := report.checks.filter(it.status == 'pass').map(it.name)116+ if passed.len > 0 {117+ ok('${fmt.plural(passed.len, 'check')} passed')118+ for line in wrap_items(passed, 76) {119+ println(' ${fmt.dim(line)}')120+ }117121 }122+ for check in report.checks.filter(it.status != 'pass') {123+ mark := match check.status {124+ 'fail' { fmt.red(fmt.glyph_fail) }125+ 'skipped' { fmt.dim('–') }126+ else { fmt.amber('!') }127+ }128+ println(' ${mark} ${check.name} ${fmt.dim('· ${check.detail}')}')129+ }118130 for warning in report.warnings {119131 warn(warning)120132 }121- if report.remote_target != '' { note('remote: ${report.remote_target}') }122- if report.source != '' { note('read-only source: ${report.source}') }123- if report.last_offered_head != '' {124- note('last successful offer: [${report.last_offered_head}] (stored value)')125- }126- if report.project_version != '' {127- note('project ${report.project_version} · ${report.build_platforms.join(', ')}')128- }129- if report.cache_cleared {130- note('cleared ${report.cached_files} cached hashes; working files read again')131- }132133 if report.peek_folders_removed > 0 {133- note('removed ${report.peek_folders_removed} old peek folders')134+ ok('removed ${report.peek_folders_removed} old peek folders')134135 }135136 if report.backup != '' { ok('backed up to ${report.backup}') }136137 if report.problems.len > 0 {137138 note('keep .koh intact before recovering history; use a known-good backup or another verified copy')138139 }139- note('scope: ${report.scope}')140+ println('')141+ note(report.scope)142+}143+144+fn doctor_metadata(report DoctorJson) {145+ if report.head.id != '' {146+ doctor_row('head', '[${report.head.id}] ${report.head.message.all_before('\n')}')147+ doctor_row('', fmt.dim([report.head.agent, report.head.session,148+ fmt.stamp(report.head.created_at), fmt.plural(report.head_files, 'file')].filter(it != '').join(' · ')))149+ } else {150+ doctor_row('head', if report.saves == 0 { 'no saves yet' } else { report.head_id })151+ }152+ mut saves := ['${report.saves} saves', '${report.checkpoints} checkpoints']153+ if report.off_line_saves > 0 { saves << '${report.off_line_saves} off line' }154+ if report.events > 0 { saves << fmt.plural(report.events, 'event') }155+ if report.flagged_saves > 0 { saves << '${report.flagged_saves} flagged' }156+ doctor_row('history', saves.join(' · '))157+ doctor_row('storage', '${fmt.human_bytes(i64(report.size_bytes))} + ${fmt.human_bytes(i64(report.wal_bytes))} WAL · schema ${report.schema_version}')158+ doctor_row('', fmt.dim('${report.objects} objects · ${fmt.human_bytes(report.object_bytes)} → ${fmt.human_bytes(report.packed_bytes)} compressed · ${report.file_entries} file entries'))159+ changes := report.changes160+ counts := ['${changes.added.len} added', '${changes.modified.len} modified',161+ '${changes.removed.len} removed'].filter(!it.starts_with('0 '))162+ mut tree := if counts.len == 0 { 'no changes' } else { counts.join(', ') }163+ if report.cache_cleared {164+ tree += fmt.dim(' · ${report.cached_files} cached hashes re-read')165+ }166+ doctor_row('tree', tree)167+ if report.remote_target != '' {168+ offer := if report.last_offered_head != '' {169+ fmt.dim(' · last offer [${report.last_offered_head}]')170+ } else {171+ ''172+ }173+ doctor_row('remote', report.remote_target + offer)174+ }175+ if report.source != '' { doctor_row('source', '${report.source} (read-only)') }176+ if report.project_version != '' {177+ doctor_row('build', '${report.project_version} · ${report.build_platforms.join(', ')}')178+ }179+}180+181+fn doctor_row(label string, value string) {182+ println(' ${fmt.dim('${label:-9}')}${value}')183+}184+185+// wrap_items joins items with a dot, breaking lines between items so none passes width.186+fn wrap_items(items []string, width int) []string {187+ mut lines := []string{}188+ mut line := ''189+ for item in items {190+ if line != '' && fmt.visible_width(line) + 3 + item.len > width {191+ lines << line192+ line = item193+ } else {194+ line = if line == '' { item } else { '${line} · ${item}' }195+ }196+ }197+ if line != '' {198+ lines << line199+ }200+ return lines140201 }141202 142203 fn shell_quote(text string) string {tests/cli_test.v+4 −1
@@ -836,7 +836,7 @@836836 backup := os.join_path(dir, 'doctor.backup')837837 healthy := run(dir, 'doctor')838838 assert healthy.code == 0839- assert healthy.out.contains('is sound')839+ assert healthy.out.contains('is healthy')840840 report := run(dir, 'doctor', '--json')841841 assert report.code == 0842842 for key in ['problems', 'objects', 'saves', 'checkpoints', 'cached_files', 'peek_folders_removed',@@ -2128,6 +2128,9 @@21282128 assert offline.code == 021292129 assert offline.out.contains('--offline requested')21302130 assert offline.out.contains('"status":"skipped"')2131+ fast := run_home(repo, home, 'doctor', '--fast', '--json')2132+ assert fast.code == 02133+ assert fast.out.contains('--fast requested')21312134 }21322135 21332136 fn test_doctor_returns_a_report_when_the_database_cannot_be_opened() {v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.8.1'3+ version: '3.9.0'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }