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
5 files VERSION+1 −1shell/koh3.plugin.zsh+1 −1src/cli/cmd_doctor.v+95 −34tests/cli_test.v+4 −1v.mod+1 −1
VERSION+1 −1
@@ -1 +1 @@1-3.8.11+3.9.0

view file

shell/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' ;;

view file

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 {

view file

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() {

view file

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 }

view file