Skip to content

feat(cli): add gentle-ai doctor command for ecosystem health diagnostics#304

Open
baltasarblanco wants to merge 4 commits intoGentleman-Programming:mainfrom
baltasarblanco:feat/doctor-mvp
Open

feat(cli): add gentle-ai doctor command for ecosystem health diagnostics#304
baltasarblanco wants to merge 4 commits intoGentleman-Programming:mainfrom
baltasarblanco:feat/doctor-mvp

Conversation

@baltasarblanco
Copy link
Copy Markdown

🔗 Linked Issue

Closes #302


🏷️ PR Type

  • type:bug
  • type:feature
  • type:docs
  • type:refactor
  • type:chore
  • type:breaking-change

📝 Summary

Implements the MVP foundation for gentle-ai doctor — a read-only diagnostic command that scans the ecosystem and reports health status. This PR establishes the architecture (Check struct, concurrent runner, rendering) and ships the first check category: duplicate binary detection across PATH.

The command detects shadowed binaries (e.g., engram installed via both Homebrew AND go install), which is the root cause behind issues like #114, #177, and #246.


📂 Changes

File / Area What Changed
internal/system/path.go Added FindAllBinaryCopies() — walks all PATH entries, resolves symlinks, deduplicates
internal/system/binaries_test.go Table-driven tests: duplicates, symlink dedup, directories, non-executable, empty PATH
internal/doctor/doctor.go Core types (Check, CheckResult, Report, Options) and concurrent runner with bounded semaphore
internal/doctor/doctor_test.go Runner tests: execution, category filter, timeout, concurrency, report summary
internal/doctor/deps.go DuplicateBinaryCheck for gentle-ai/engram/gga + AllChecks() entry point + DI setter
internal/doctor/deps_test.go Check tests with injected fakes: single/none/duplicate copies, context cancellation, nil lookup
internal/doctor/render.go Human-readable + JSON rendering, grouped by category
internal/doctor/render_test.go Render tests: healthy/unhealthy/warnings states, JSON structure
internal/app/app.go Added case "doctor" dispatch (first switch block) + runDoctor handler + DI wiring
internal/app/help.go Added doctor to COMMANDS list

🧪 Test Plan

Unit Tests

go test ./internal/system/ -run TestFindAllBinaryCopies -v
go test ./internal/doctor/ -v
go test -race ./internal/system/ ./internal/doctor/
go test ./internal/app/ ./internal/verify/

Manual Tests

go build -o gentle-ai ./cmd/gentle-ai
./gentle-ai doctor              # human-readable output
./gentle-ai doctor --json       # machine-readable JSON
./gentle-ai doctor --category deps  # filtered by category
./gentle-ai help                # doctor appears in command list
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • Manually tested locally

🤖 Automated Checks

Check Status Description
Check Issue Reference PR body must contain Closes/Fixes/Resolves #N
Check Issue Has status:approved Linked issue must have been approved before work began
Check PR Has type:* Label Exactly one type:* label must be applied
Unit Tests go test ./... must pass
E2E Tests cd e2e && ./docker-test.sh must pass

✅ Contributor Checklist

  • PR is linked to an issue with status:approved
  • I have added the appropriate type:* label to this PR
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • I have updated documentation if necessary
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

This is the MVP foundation — intentionally scoped to one check category (deps: duplicate binary detection) to keep the PR reviewable. The architecture supports incremental addition of the remaining categories from the spec:

  • deps (future): version comparison via update.CheckFiltered, engram HTTP health, GGA assets
  • platform: PM recommendation, DetectScoop(), npm writability
  • agents: three-way cross-reference (state.json × config dir × binary)
  • env: Node.js >= 18, git/curl, PATH coherence

Design follows existing project patterns: DI via package-level vars (like updateCheckAll), reuses verify.CheckStatus constants, Check struct mirrors verify.Check, concurrent runner matches deps.go WaitGroup pattern.

28 test cases across 2 packages, all pass with -race.

Copy link
Copy Markdown
Contributor

@GerardoFC8 GerardoFC8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great foundation — nice work!

Hey @baltasarblanco, I'm the author of #302. Really solid implementation — you nailed the core architecture: concurrent runner with bounded semaphore, DI via package-level vars (matching the existing updateCheckAll pattern), symlink deduplication, Windows support, and 28 test cases with -race. That's thorough.

A few things to clean up before this is merge-ready, and some suggestions for the future:

1. Leftover review/debug comments (must fix)

There are three comments that look like development notes left in the code — see inline comments below. These should be removed before merge.

2. Docstring regression in path.go (must fix)

The diff introduces an unintended change to the escapePowerShellString docstring — it changes '' (correct: PowerShell escapes a single quote with two single quotes) to " (incorrect: that's a double quote, completely different semantics). The function body still does ReplaceAll("'", "''"), so the code is correct but the doc now contradicts it. See inline comment.

3. FindAllBinaryCopies — missing doc comment (suggestion)

The function is exported and is a new public API. A GoDoc comment explaining what it does, its return value semantics (ordered by PATH precedence, symlinks resolved for dedup but original paths returned), and edge cases (empty PATH returns nil) would help future contributors.

4. Future PRs — general tips

  • Clean your diff before opening: run git diff and scan for comments like // <-- ADD THIS or // NUEVO. These are useful while coding but shouldn't ship.
  • Avoid unrelated changes in the same PR: the escapePowerShellString docstring change is unrelated to the doctor feature. One PR = one concern makes reviews faster and reverts safer.

I'd love to contribute the remaining check categories (platform, agents, env) as follow-up PRs once this lands. The full spec with requirements and scenarios is in #302 if that helps as reference. Happy to collaborate!

Comment thread internal/app/app.go Outdated
"github.com/gentleman-programming/gentle-ai/internal/backup"
"github.com/gentleman-programming/gentle-ai/internal/cli"
componentuninstall "github.com/gentleman-programming/gentle-ai/internal/components/uninstall"
componentuninstall "github.com/gentleman-programming/gentle-ai/internal/components/uninstall" // ← 1a. NUEVO
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover development comment — // ← 1a. NUEVO should be removed. This line wasn't changed functionally (just the import that was already there), so the comment adds noise to the diff.

Comment thread internal/app/app.go Outdated
case "uninstall":
_, err := cli.RunUninstall(args[1:], stdout)
return err
case "doctor": // <-- ADD THIS
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both // <-- ADD THIS comments (this line and the next) are development notes that should be removed before merge. The code is self-explanatory — a case "doctor" dispatch doesn't need annotation.

Comment thread internal/system/path.go Outdated

// escapePowerShellString escapes a string for safe use inside a PowerShell
// single-quoted string literal by replacing each ' with '' (PowerShell's escape
// single-quoted string literal by replacing each ' with (PowerShell's escape
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: this change is incorrect. The original comment said '' (two single quotes), which is correct — PowerShell escapes a literal single quote inside a single-quoted string with ''. The new text says " (a double quote), which is a completely different character and contradicts the function body (strings.ReplaceAll(s, "'", "''")).

This looks like an unintended edit — the original docstring was correct. Should be reverted.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough review @GerardoFC8! All three issues are fixed in 3d94efe:

  1. Removed // ← 1a. NUEVO from import line
  2. Removed both // <-- ADD THIS comments from case dispatch
  3. Restored escapePowerShellString docstring ('' instead of the Unicode right double quote that slipped in)

Also added a GoDoc comment to FindAllBinaryCopies per your suggestion.

Appreciate the clean-diff lesson — I'll run git diff as a final check before every push going forward.

@GerardoFC8
Copy link
Copy Markdown
Contributor

Awesome, all three fixes look good in 3d94efe — thanks for the quick turnaround!

The git diff habit before pushing is a game-changer, it catches so much noise before it ever hits a review.

Looking forward to building on this foundation with the remaining check categories (platform, agents, env) once this lands. Great first contribution to doctor! 🤝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): add gentle-ai doctor command for ecosystem health diagnostics

2 participants