Skip to content

feat: version update check in selftune doctor#60

Merged
WellDunDun merged 5 commits intomainfrom
feat/doctor-version-check
Mar 16, 2026
Merged

feat: version update check in selftune doctor#60
WellDunDun merged 5 commits intomainfrom
feat/doctor-version-check

Conversation

@WellDunDun
Copy link
Collaborator

Summary

  • selftune doctor now checks npm registry for newer versions (3s timeout)
  • Warns with update instructions when installed version is behind latest
  • Add "Updating" section to README explaining skill+CLI ship as one package
  • doctor() is now async; all 8 callers updated

Test plan

  • CI passes (lint + test)
  • selftune doctor shows version_up_to_date: pass when current
  • selftune doctor shows version_up_to_date: warn when behind
  • Doctor degrades gracefully when offline (no fail, just informational message)

🤖 Generated with Claude Code

- doctor now checks npm registry for newer versions (3s timeout, non-blocking)
- Warns when installed version is behind latest with update instructions
- Add "Updating" section to README explaining skill+CLI ship together
- doctor() is now async; all callers updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

Warning

Rate limit exceeded

@WellDunDun has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dd763669-aa74-4fde-a9b0-04fc99bae5ec

📥 Commits

Reviewing files that changed from the base of the PR and between f1c7d17 and 6485893.

📒 Files selected for processing (7)
  • biome.json
  • cli/selftune/badge/badge.ts
  • cli/selftune/evolution/evolve-body.ts
  • cli/selftune/evolution/evolve.ts
  • cli/selftune/observability.ts
  • cli/selftune/status.ts
  • package.json
📝 Walkthrough

Walkthrough

The doctor() function has been converted to asynchronous, incorporating a new version health check that queries the npm registry to verify the locally installed package is current. All call sites throughout the codebase have been updated to await the now-async function. Version bumped from 0.2.3 to 0.2.4; documentation added for update instructions.

Changes

Cohort / File(s) Summary
doctor() core implementation
cli/selftune/observability.ts
Added checkVersionHealth() async function to query npm registry and validate local package version. Converted doctor() from synchronous to async, integrating version check results into the health checks array.
CLI command handlers
cli/selftune/index.ts, cli/selftune/status.ts, cli/selftune/quickstart.ts, cli/selftune/init.ts, cli/selftune/badge/badge.ts
Updated all doctor() invocations to await doctor(). Made cliMain() in status.ts async to support await expression.
API and orchestration
cli/selftune/dashboard-server.ts, cli/selftune/orchestrate.ts
Converted computeStatusFromLogs() to async and updated /api/v2/doctor endpoint handler to await doctor(). Updated orchestrate.ts to await doctor() call.
Version, tests, and documentation
package.json, tests/observability.test.ts, README.md
Bumped version to 0.2.4. Made three doctor tests async to await promise. Added "Updating" section with npm/npx reinstall instructions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (feat:) and accurately describes the main change: adding version update checks to selftune doctor.
Description check ✅ Passed Description is directly related to the changeset, detailing version check additions, async doctor() conversion, README updates, and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/doctor-version-check
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cli/selftune/badge/badge.ts (1)

33-43: ⚠️ Potential issue | 🔴 Critical

Make cliMain() async before awaiting doctor().

Line 74 uses await inside a non-async function, which is a parse-time error. Change cliMain() to return Promise<void>, then update the direct-entry guard and the badge route in cli/selftune/index.ts to properly await/catch the result so failures propagate correctly.

🐛 Proposed fix
-export function cliMain(): void {
+export async function cliMain(): Promise<void> {
   const { values } = parseArgs({
     args: process.argv.slice(2),
     options: {
       skill: { type: "string" },
       format: { type: "string" },
 if (import.meta.main) {
-  cliMain();
+  cliMain().catch((err) => {
+    const message = err instanceof Error ? err.message : String(err);
+    console.error(`[FATAL] ${message}`);
+    process.exit(1);
+  });
 }
 case "badge": {
   const { cliMain } = await import("./badge/badge.js");
-  cliMain();
+  await cliMain();
   break;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/badge/badge.ts` around lines 33 - 43, Make cliMain() async and
return Promise<void> so it can await doctor(): change the function signature of
cliMain() to async function cliMain(): Promise<void> and update any internal
awaits (e.g., await doctor()) accordingly; then update the direct-entry guard
(the block that checks require.main === module) to call and await cliMain() and
handle rejections (e.g., cliMain().catch(err => { /* log and process.exit(1) */
})), and likewise update the badge route in cli/selftune/index.ts to await the
call to cliMain() (or handle the returned Promise) and propagate or catch errors
so failures are not swallowed. Ensure references: cliMain(), doctor(), and the
badge route caller are updated to use async/await or Promise.then/.catch.
cli/selftune/dashboard-server.ts (1)

49-55: ⚠️ Potential issue | 🔴 Critical

Update statusLoader type and await the async result.

Line 103 made computeStatusFromLogs() async, but statusLoader at line 54 is still typed as returning StatusResult (not a Promise). At line 488, cachedStatusResult = getStatusResult(); assigns without await, so when computeStatusFromLogs is used as the default, a Promise<StatusResult> gets stored instead of the resolved value. The type assertion at line 494 masks this mismatch, but downstream code accessing statusResult.skills will fail at runtime.

Fix by allowing the Promise return and awaiting the assignment:

Proposed fix
 export interface DashboardServerOptions {
   port?: number;
   host?: string;
   spaDir?: string;
   openBrowser?: boolean;
-  statusLoader?: () => StatusResult;
+  statusLoader?: () => StatusResult | Promise<StatusResult>;
   evidenceLoader?: () => EvolutionEvidenceEntry[];
   overviewLoader?: () => OverviewResponse;
   skillReportLoader?: (skillName: string) => SkillReportResponse | null;
   actionRunner?: typeof runAction;
 }

     statusRefreshPromise = (async () => {
-      cachedStatusResult = getStatusResult();
+      cachedStatusResult = await getStatusResult();
       lastStatusCacheRefreshAt = Date.now();
     })();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/dashboard-server.ts` around lines 49 - 55, The current
DashboardServerOptions.statusLoader is typed as () => StatusResult but
computeStatusFromLogs is async, causing getStatusResult() to return a Promise
that gets stored into cachedStatusResult without awaiting and masked by a type
assertion; update DashboardServerOptions.statusLoader to return
Promise<StatusResult> (i.e., () => Promise<StatusResult>), change any default
assignment that uses computeStatusFromLogs to return that Promise, and in the
code where cachedStatusResult = getStatusResult() (and where you read it, e.g.,
before accessing statusResult.skills) await getStatusResult() and remove the
unsafe type assertion so cachedStatusResult holds a resolved StatusResult;
ensure functions computeStatusFromLogs, getStatusResult, and any callers all use
the Promise-returning signature consistently.
cli/selftune/status.ts (1)

327-333: ⚠️ Potential issue | 🔴 Critical

Fix syntax error: await requires async function.

Line 333 uses await doctor() but cliMain() is not declared async. This will fail at parse time.

Proposed fix
-export function cliMain(): void {
+export async function cliMain(): Promise<void> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/status.ts` around lines 327 - 333, The function cliMain
currently calls await doctor() but is not declared async; update the cliMain
declaration to be async (e.g., async function cliMain) and change its return
type to Promise<void> if applicable so awaiting doctor() is valid; ensure any
places that call cliMain handle the returned Promise (await or .then) as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/selftune/observability.ts`:
- Around line 219-224: The fetch timeout handling and version comparison in the
selftune update check need fixes: move clearTimeout(timeout) into a finally
block so the abort timer is always cleared (wrap the fetch and response handling
in try { ... } finally { clearTimeout(timeout); }), ensure you still call
controller.abort() on timeout as currently done, and replace the strict equality
check against the fetched version with a semver-aware comparison (use a semver
utility like semver.compare/lt or semver.gt) when comparing the local package
version to the fetched "latest" from the registry (referencing the
AbortController instance, the timeout variable, the fetch call, and the version
equality logic) so versions like 0.2.4 are not incorrectly marked outdated vs
0.2.3.

---

Outside diff comments:
In `@cli/selftune/badge/badge.ts`:
- Around line 33-43: Make cliMain() async and return Promise<void> so it can
await doctor(): change the function signature of cliMain() to async function
cliMain(): Promise<void> and update any internal awaits (e.g., await doctor())
accordingly; then update the direct-entry guard (the block that checks
require.main === module) to call and await cliMain() and handle rejections
(e.g., cliMain().catch(err => { /* log and process.exit(1) */ })), and likewise
update the badge route in cli/selftune/index.ts to await the call to cliMain()
(or handle the returned Promise) and propagate or catch errors so failures are
not swallowed. Ensure references: cliMain(), doctor(), and the badge route
caller are updated to use async/await or Promise.then/.catch.

In `@cli/selftune/dashboard-server.ts`:
- Around line 49-55: The current DashboardServerOptions.statusLoader is typed as
() => StatusResult but computeStatusFromLogs is async, causing getStatusResult()
to return a Promise that gets stored into cachedStatusResult without awaiting
and masked by a type assertion; update DashboardServerOptions.statusLoader to
return Promise<StatusResult> (i.e., () => Promise<StatusResult>), change any
default assignment that uses computeStatusFromLogs to return that Promise, and
in the code where cachedStatusResult = getStatusResult() (and where you read it,
e.g., before accessing statusResult.skills) await getStatusResult() and remove
the unsafe type assertion so cachedStatusResult holds a resolved StatusResult;
ensure functions computeStatusFromLogs, getStatusResult, and any callers all use
the Promise-returning signature consistently.

In `@cli/selftune/status.ts`:
- Around line 327-333: The function cliMain currently calls await doctor() but
is not declared async; update the cliMain declaration to be async (e.g., async
function cliMain) and change its return type to Promise<void> if applicable so
awaiting doctor() is valid; ensure any places that call cliMain handle the
returned Promise (await or .then) as needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7e22862-2d9a-4893-bd26-49030941dfac

📥 Commits

Reviewing files that changed from the base of the PR and between 6470c94 and f1c7d17.

📒 Files selected for processing (11)
  • README.md
  • cli/selftune/badge/badge.ts
  • cli/selftune/dashboard-server.ts
  • cli/selftune/index.ts
  • cli/selftune/init.ts
  • cli/selftune/observability.ts
  • cli/selftune/orchestrate.ts
  • cli/selftune/quickstart.ts
  • cli/selftune/status.ts
  • package.json
  • tests/observability.test.ts

WellDunDun and others added 3 commits March 16, 2026 13:50
- Replace strict equality with semver-aware comparison (dev versions
  ahead of npm latest are not flagged as outdated)
- Move clearTimeout into finally block so abort timer is always cleared

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update biome.json schema version 2.4.6 → 2.4.7
- Make badge.ts and status.ts cliMain async for await doctor()
- Use optional chaining in evolve-body.ts and evolve.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@WellDunDun WellDunDun merged commit 9c5753c into main Mar 16, 2026
8 checks passed
@WellDunDun WellDunDun deleted the feat/doctor-version-check branch March 16, 2026 14:07
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.

1 participant