Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/genie-commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,97 @@ export function checkLegacyAgentFrontmatter(workspaceRoot?: string): CheckResult
return results;
}

/**
* Check canonical pgserve state — whether the pgserve binary is on PATH,
* registered under pm2, and listening on the canonical port. Mirrors omni
* doctor's `pgserve-canonical` check so both halves of the canonical-stack
* surface the same shared-backbone visibility.
*
* Three outcomes:
* - pgserve missing → WARN with install hint (not FAIL because genie can
* auto-spawn its own daemon as a fallback for fingerprint-routed CLI).
* - pgserve installed but not under pm2 → WARN pointing at `pgserve install`.
* - pgserve registered + reachable → PASS, surface the canonical URL so
* operators can verify what genie-serve / omni-api are connecting to.
*/
async function checkPgserveCanonical(): Promise<CheckResult[]> {
const results: CheckResult[] = [];

// Step 1: pgserve binary on PATH? Probe via `pgserve port` (NOT --version
// — that flag doesn't exist in pgserve@^2.1.0 and false-negatived in
// historical doctor implementations).
let canonicalPort: number | null = null;
try {
const out = execFileSync('pgserve', ['port'], {
encoding: 'utf8',
timeout: 3000,
stdio: ['ignore', 'pipe', 'ignore'],
});
Comment on lines +736 to +740

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.

medium

The checkPgserveCanonical function is marked as async, but it uses the synchronous execFileSync for probing the binary. For consistency with other async checks in this file (like checkTmux) and to avoid blocking the event loop unnecessarily, consider using Bun's asynchronous shell primitive ($). The use of a hardcoded timeout is acceptable here to prevent performance issues during the probe, as per repository guidelines.

    const out = await $("pgserve port").timeout(3000).quiet().text();
References
  1. It is acceptable to use hardcoded numeric limits (magic numbers) in non-critical fallback logic, especially when they serve as intentional caps to prevent performance issues like excessive I/O.

const parsed = Number.parseInt(out.trim(), 10);
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 65535) {
canonicalPort = parsed;
}
} catch {
/* pgserve binary missing or non-zero exit — handled below */
}

if (canonicalPort === null) {
results.push({
name: 'pgserve binary',
status: 'warn',
message: 'not on PATH (or `pgserve port` failed)',
suggestion:
'Install canonical pgserve: bun add -g pgserve@^2.1.0 (then run `pgserve install` to register under pm2)',
});
return results;
}
results.push({
name: 'pgserve binary',
status: 'pass',
message: `canonical port ${canonicalPort}`,
});

// Step 2: pm2-supervised? Check via `pgserve status --json`.
try {
const status = execFileSync('pgserve', ['status', '--json'], {
encoding: 'utf8',
timeout: 3000,
stdio: ['ignore', 'pipe', 'ignore'],
});
Comment on lines +767 to +771

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.

medium

Similar to the binary probe above, consider using Bun's asynchronous shell primitive ($) instead of execFileSync to maintain consistency within this async function and the rest of the file's async check logic. The hardcoded timeout is permitted in this context to prevent the process from hanging.

    const status = await $("pgserve status --json").timeout(3000).quiet().text();
References
  1. It is acceptable to use hardcoded numeric limits (magic numbers) in non-critical fallback logic, especially when they serve as intentional caps to prevent performance issues like excessive I/O.

const parsed = JSON.parse(status) as { installed?: boolean; status?: string };
if (parsed.installed === true && parsed.status === 'online') {
results.push({
name: 'pgserve under pm2',
status: 'pass',
message: `online — shared backbone for genie-serve + omni-api on :${canonicalPort}`,
});
} else if (parsed.installed === true) {
results.push({
name: 'pgserve under pm2',
status: 'warn',
message: `registered but status=${parsed.status ?? 'unknown'}`,
suggestion: 'Recover with: pm2 restart pgserve (logs: ~/.pgserve/logs/)',
});
} else {
results.push({
name: 'pgserve under pm2',
status: 'warn',
message: 'binary present but not registered under pm2',
suggestion: 'Register canonical pgserve: pgserve install',
});
}
} catch {
results.push({
name: 'pgserve under pm2',
status: 'warn',
message: '`pgserve status` failed (pm2 unreachable?)',
suggestion: 'Verify pm2: pm2 list | Re-register: pgserve install',
});
}

return results;
}

/**
* Main doctor command
*/
Expand Down Expand Up @@ -765,6 +856,7 @@ export async function doctorCommand(options?: {
runCheckSection('Tmux', await checkTmux(), counts);
runCheckSection('Tmux Configs', checkTmuxConfigs(), counts);
runCheckSection('Worker Profiles', await checkWorkerProfiles(), counts);
runCheckSection('Pgserve (canonical backbone)', await checkPgserveCanonical(), counts);
runCheckSection('Omni Bridge', await checkBridge(), counts);
runCheckSection('Agent Config', checkLegacyAgentFrontmatter(), counts);
runCheckSection('Genie Specialist', checkGenieAgentTemplate(), counts);
Expand Down
Loading