Skip to content

feat: add Relay doctor diagnostics - #49

Merged
krishna916 merged 17 commits into
mainfrom
feature/issue-42-relay-doctor-diagnostics
Aug 7, 2026
Merged

feat: add Relay doctor diagnostics#49
krishna916 merged 17 commits into
mainfrom
feature/issue-42-relay-doctor-diagnostics

Conversation

@krishna916

@krishna916 krishna916 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

Implements issue #42 and the approved doctor diagnostics plan.

  • Adds relay doctor and relay doctor --output json with schema-versioned output, fixed 14-check order, stable statuses/codes, and exit semantics.
  • Adds read-only database/migration/integrity/native-addon diagnostics, ownership-aware Codex/Claude checks, generic-template handling, compatibility checks, and package/path/runtime checks.
  • Adds bounded MCP/UI probes with disposable roots, cleanup registration, timeout handling, bounded capture, loopback health validation, and package/arbitrary-CWD coverage.
  • Adds compatibility asset packaging/validation, installed-package smoke coverage, documentation, and regression tests.

Review remediation

Fresh review findings were addressed for UI request and body hangs, chunk-fragile readiness parsing, MCP cleanup registration, native-addon startup isolation, missing-database false failures, usage parsing order, package-root escape handling, Windows process-tree cleanup ordering, safe bootstrap reporting for invalid paths/malformed package setup, coordinated signal abort/cleanup, retryable temporary-root cleanup, and process-level signal verification.

Validation

  • pnpm format:check
  • pnpm lint
  • pnpm typecheck
  • Full suite with --testTimeout=15000: 77 files passed, 1 skipped; 701 tests passed, 5 skipped.
  • Coverage: 88.74% statements, 80.83% branches, 89.15% functions, 91.07% lines.
  • Build, package metadata, repository assets, tarball/package smoke, and installed doctor/MCP/UI smoke passed.
  • Command-level SIGINT/SIGTERM unit coverage verifies exit 130/143, awaited cleanup, handler removal, and suppressed output.
  • POSIX installed-process smoke verifies both signals, descendant/root cleanup, no UI startup, unchanged configured state, and sanitized stderr. Windows skips those OS-level cases because Node force-terminates child processes instead of delivering JavaScript signal handlers.
  • Invalid RELAY_DB_PATH emits a sanitized schema-valid JSON failure report rather than a stack trace.

The repository audit remains external-environment dependent: the sandbox registry request is blocked by EACCES; the network-enabled audit reports six existing transitive advisories in fast-uri, ip-address, and brace-expansion, which are not changed by this issue.

Closes #42

Summary by CodeRabbit

  • New Features

    • Added the relay doctor command with human-readable and JSON reports.
    • Added diagnostics for runtime compatibility, paths, package assets, database health, integrations, MCP connectivity, and UI availability.
    • Added stable check results, exit codes, sanitized details, and safe interruption handling.
    • Added compatibility metadata to packaged assets.
  • Bug Fixes

    • Environment-configured HTTP port 0 is now supported for automatic port assignment.
  • Documentation

    • Documented usage, output modes, safety boundaries, troubleshooting codes, and exit statuses.
    • Added setup guidance for running diagnostics from any directory.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f97ad78c-2f84-4468-82e3-9d792864ecc8

📥 Commits

Reviewing files that changed from the base of the PR and between 1cebb25 and 57a0ad3.

📒 Files selected for processing (4)
  • src/distribution/doctor/check-ui.ts
  • src/interfaces/http/create-http-server.ts
  • tests/unit/distribution/doctor/check-ui.test.ts
  • tests/unit/interfaces/http/create-http-server.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/interfaces/http/create-http-server.ts
  • tests/unit/distribution/doctor/check-ui.test.ts

📝 Walkthrough

Walkthrough

The PR adds relay doctor with fourteen ordered diagnostics, human and JSON output, non-destructive checks, signal-aware cleanup, compatibility metadata, package validation, installed-package smoke tests, and documentation.

Changes

Relay doctor diagnostics

Layer / File(s) Summary
Doctor contracts and CLI execution
src/distribution/doctor/*, src/interfaces/cli/*, tests/unit/interfaces/cli/*
Defines the report schema, ordered checks, interruption handling, CLI parsing, output formats, exit codes, and command dispatch.
Diagnostic checks and runtime wiring
src/distribution/doctor/check-*.ts, src/database/migration.ts, src/interfaces/production-dependencies.ts, tests/unit/distribution/doctor/*
Adds runtime, asset, path, database, integration, compatibility, MCP, and UI diagnostics with structured results and tests.
MCP, UI, and child-process lifecycle
src/distribution/doctor/child-process-probe.ts, src/distribution/doctor/check-mcp.ts, src/distribution/doctor/check-ui.ts, tests/fixtures/doctor/*, tests/unit/distribution/doctor/*
Adds isolated probes, bounded output, timeout handling, signal interruption, process termination, temporary-root cleanup, and lifecycle coverage.
Compatibility assets and package validation
assets/compatibility.json, package.json, scripts/package/*, scripts/validate-*.ts, scripts/package/smoke-installed-package.ts, tests/integration/*, tests/unit/scripts/mcpb/*
Packages compatibility metadata, validates required assets and overrides, upgrades the MCP SDK, and verifies installed doctor output and signal behavior.
Doctor documentation and fixture rules
README.md, docs/doctor.md, docs/setup-and-configuration.md, docs/superpowers/plans/*, tests/fixtures/doctor/README.md
Documents command usage, output, exit codes, safety boundaries, interruption behavior, verification scenarios, remediation plans, and fixture restrictions.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • krishna916/relay#18 — Covers the broader relay doctor diagnostics and package distribution objectives implemented here.

Possibly related PRs

  • krishna916/relay#48 — Provides the ownership metadata and client configuration structures inspected by doctor integration checks.
  • krishna916/relay#31 — Introduces the CLI entrypoint extended here with doctor.
  • krishna916/relay#47 — Provides package, migration, runtime, and installed-package infrastructure extended by doctor.

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant runRelay
  participant runDoctorCommand
  participant runDoctor
  participant DoctorChecks
  participant DoctorOutput
  Operator->>runRelay: invoke doctor
  runRelay->>runDoctorCommand: pass doctor arguments
  runDoctorCommand->>runDoctor: execute ordered checks
  runDoctor->>DoctorChecks: run runtime, asset, database, MCP, and UI checks
  DoctorChecks-->>runDoctor: return diagnostic results
  runDoctor-->>runDoctorCommand: return versioned report
  runDoctorCommand->>DoctorOutput: write human or JSON report
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: adding Relay doctor diagnostics.
Linked Issues check ✅ Passed The implementation addresses issue #42 with deterministic diagnostics, safe probes, stable output, cleanup, tests, documentation, and installation coverage.
Out of Scope Changes check ✅ Passed The changes support the doctor feature, its packaging, safety requirements, dependency remediation, tests, and documentation without unrelated product work.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-42-relay-doctor-diagnostics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@krishna916 krishna916 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review summary

The overall implementation is strong: the diagnostic contract is explicit, database checks avoid the migrating runtime, output is schema-versioned and sanitized, installed-tarball coverage is substantial, and MCP/UI probes use isolated databases.

I found one High / merge-blocking lifecycle issue.

High: Ctrl+C / SIGTERM does not terminate relay doctor

installDoctorSignalHandlers() installs handlers that only call void cleanupDoctorChildren(). Installing the handler suppresses Node's default signal termination, but the handler neither awaits cleanup nor aborts runDoctor(). Consequently, after SIGINT/SIGTERM the command can continue executing the current check, proceed to later checks, start new MCP/UI children, and eventually print a normal report. This does not satisfy the issue's requirement to clean up on signal and is surprising operational behavior for Ctrl+C.

There is a related cleanup gap: temporary-root cleanup is performed in each check's finally, but roots are not registered with the signal cleanup coordinator. A correct immediate-exit implementation therefore also needs to ensure those cleanups are awaited before termination.

Please introduce a coordinated abort path that:

  1. records that termination was requested and prevents subsequent checks from starting;
  2. awaits registered child and temporary-resource cleanup;
  3. terminates with conventional signal semantics or an explicit non-zero interrupted exit code (for example 130 for SIGINT and 143 for SIGTERM);
  4. does not emit a successful/complete doctor report after interruption;
  5. has a process-level test that spawns installed/built relay doctor, sends SIGINT and SIGTERM during a probe, and verifies timely command exit, descendant termination, and temporary-root removal.

Once this is fixed, I would re-review the updated head.

Comment thread src/distribution/doctor/child-process-probe.ts

Copy link
Copy Markdown
Owner Author

Luna remediation plan

The execution-ready Superpowers remediation plan is committed directly to this PR branch:

docs/superpowers/plans/2026-08-04-pr-49-doctor-signal-remediation.md

Commit: 2e55ba6185b69986424729c06a456d6c047804cc

The plan locks the design so Luna does not invent lifecycle behavior:

  • command-scoped AbortController
  • typed DoctorInterruptedError
  • first signal wins
  • exit 130 for SIGINT, 143 for SIGTERM
  • no completed human or JSON report after interruption
  • no later checks start after cancellation
  • child and temporary-root cleanup is registered once, race-safe, and awaited
  • MCP/UI roots are registered immediately after allocation
  • no process.exit() inside reusable doctor modules
  • unit tests avoid signaling the Vitest worker
  • installed-command process tests cover both signals, descendant cleanup, root cleanup, unchanged configured state, and prevention of UI startup after MCP interruption

Luna should execute the plan task-by-task using superpowers:subagent-driven-development (recommended) or superpowers:executing-plans. Do not resolve review thread discussion_r3712638732 until the process-level signal tests and verification evidence are pushed.

@krishna916 krishna916 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Re-review result

The previous High / merge-blocking signal-handling finding is resolved.

Verified on the updated head:

  • runDoctorCommand() owns a command-scoped AbortController and returns 130 for SIGINT / 143 for SIGTERM.
  • interrupted runs suppress completed human and JSON reports;
  • runDoctor() checks cancellation before and after each check, so no later check starts after interruption;
  • the first signal wins and starts one coordinated cleanup workflow;
  • child termination and temporary-root cleanup are registered through once-only, race-safe cleanup entries;
  • MCP and UI temporary roots are registered immediately after creation;
  • cleanup is awaited before command completion and signal handlers are removed in finally;
  • command-level tests cover exit codes, awaited cleanup, output suppression, and handler removal;
  • installed-package smoke coverage verifies SIGINT and SIGTERM, descendant termination, root removal, prevention of UI startup, unchanged configured database/client/ownership state, and sanitized output.

I found no remaining merge-blocking correctness issue in the remediation. The original review thread has been resolved. GitHub does not permit approving a pull request owned by the authenticated account, so this is recorded as a clean COMMENT re-review.

Copy link
Copy Markdown
Owner Author

Luna CI remediation plan

I added a narrow execution-ready Superpowers plan to the PR branch:

docs/superpowers/plans/2026-08-05-pr-49-cross-platform-path-test-remediation.md

Commit: 42c1665406e75f8abc3e24d295719b3936cf3dcd

The plan locks the root cause and fix so Luna does not change production behavior:

  • the CI failure is caused by Windows-only positive test fixtures running on Ubuntu;
  • src/distribution/doctor/check-paths.ts must remain unchanged;
  • replace literals with host-native paths built using node:path resolve() and join();
  • preserve the explicit 'relative' negative case and paths.resolution.invalid assertion;
  • keep the remediation implementation commit limited to check-paths.test.ts;
  • run focused tests, adjacent doctor tests, formatting, typecheck, pnpm verify, and then verify the full GitHub Actions run on the new head;
  • do not claim the PR is ready until CI / verify is green.

Luna should execute the plan with superpowers:executing-plans. The expected implementation is one small production-neutral test commit.

Copy link
Copy Markdown
Owner Author

I added an execution-ready dependency audit remediation plan for Luna:

docs/superpowers/plans/2026-08-06-pr-49-dependency-audit-remediation.md

Plan commit: c240ae59deda41867fa9a0a41f138e6dca2a4991

Locked policy:

  1. Upgrade @modelcontextprotocol/sdk to stable ^1.30.0 first.
  2. Re-run pnpm audit and add only still-required, version-scoped overrides:
    • fast-uri@>=3.0.0 <3.1.5 -> 3.1.5
    • ip-address@<=10.3.0 -> 10.3.1
    • brace-expansion@>=4.0.0 <5.0.9 -> 5.0.9
  3. Do not use broad package overrides that could cross major versions.
  4. Use pnpm.auditConfig.ignoreGhsas only if a patched version cannot be installed safely and the exact exception is documented with evidence, exposure, review date, and removal condition.
  5. Keep pnpm audit --audit-level high unchanged and verify the full repository, installed package, and GitHub Actions run before claiming readiness.

Luna should execute the plan using superpowers:subagent-driven-development or superpowers:executing-plans, task by task.

@krishna916
krishna916 marked this pull request as ready for review August 6, 2026 08:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
scripts/package/stage-package-assets.ts (1)

28-34: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate compatibility metadata during staging.

stagePackageAssets only requires assets/compatibility.json to exist, so truncated manifests can pass packaging and only fail later as compatibility.assets.invalid. Validate the compatibility manifest fields and relationship invariants during package staging, including schemaVersion, minimumPackageVersion, contract/schema version fields, migration count against staged SQL migrations, skill metadata version with SKILL.md, and template metadata. Add a staging failure test for an unsupported or inconsistent compatibility manifest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/package/stage-package-assets.ts` around lines 28 - 34, Extend
stagePackageAssets to parse and validate assets/compatibility.json rather than
only checking existence. Validate schemaVersion, minimumPackageVersion,
contract/schema version fields, migration count against staged SQL migrations,
skill metadata against SKILL.md, and template metadata using the existing
compatibility validation rules; reject unsupported or inconsistent manifests
during staging. Add a staging failure test covering an invalid compatibility
manifest.
🧹 Nitpick comments (24)
tests/integration/packaged-assets.test.ts (1)

23-24: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add the missing-file failure test.

This change covers only the successful staging path. It does not verify that stagePackageAssets rejects a package root without assets/compatibility.json. Add a separate test that omits the file and asserts the Package asset is missing after build error.

As per coding guidelines: **/*.{test.ts,test.tsx}: Cover normal flows, validation failures, and lifecycle boundaries in the layer where they belong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/packaged-assets.test.ts` around lines 23 - 24, Add a
separate integration test for stagePackageAssets that creates a package root
with the assets directory but omits compatibility.json, then assert it rejects
with the exact “Package asset is missing after build” error while preserving the
existing successful staging test.

Source: Coding guidelines

tests/unit/distribution/doctor/check-integrations.test.ts (2)

103-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Run the check once and reuse the result.

generic.run() executes twice. Capture the result, then assert both the shape and the absence of template content.

♻️ Proposed change
-    await expect(generic.run()).resolves.toMatchObject({
+    const result = await generic.run();
+    expect(result).toMatchObject({
       status: 'failure',
       code: 'integrations.generic-mcp.template-invalid',
     });
-    const result = await generic.run();
     expect(JSON.stringify(result)).not.toContain('secret');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-integrations.test.ts` around lines 103 -
108, Update the test around generic.run() to execute it once, store the returned
result, and reuse that result for both the status/code assertions and the JSON
secret-content assertion.

55-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add cases for the remaining native-client branches.

createNativeClientCheck has five outcomes. The tests cover not-configured and entry-conflict. Three remain untested:

  • ownership-invalid, when ownershipStore.read() rejects.
  • disabled, when every owned record has status !== 'enabled'.
  • file-unreadable, when access or readFile rejects.

The access stub at Line 79 always resolves, so the read-failure branch never runs.

The coding guidelines require tests to cover validation failures and lifecycle boundaries in the layer where they belong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-integrations.test.ts` around lines 55 -
86, Add unit-test cases covering the remaining createNativeClientCheck outcomes:
ownership-invalid when ownershipStore.read() rejects, disabled when all owned
records are not enabled, and file-unreadable when access or readFile rejects.
Extend the existing createIntegrationChecks fixtures and assertions to verify
each failure code and message, and configure access/readFile rejection so the
unreadable-file branch is exercised.

Source: Coding guidelines

src/database/migration.ts (1)

57-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Accumulate MigrationManifestEntry instead of placeholder MigrationFile values.

loadMigrationManifest builds MigrationFile objects with sql: '' and checksum: '', then strips both fields in a final .map(). The empty checksum is a valid-looking value that never matches a real ledger row. If a later change returns files directly, the empty checksum reaches inspectDatabaseReadOnly and marks every applied migration as unknown.

Type the accumulator as MigrationManifestEntry[]. The placeholders and the projection then disappear.

♻️ Proposed refactor
 export function loadMigrationManifest(migrationsDir: string): readonly MigrationManifestEntry[] {
   const entries = readdirSync(migrationsDir, { withFileTypes: true });
-  const files: MigrationFile[] = [];
+  const files: MigrationManifestEntry[] = [];
-    files.push({ version, name, filename: entry.name, sql: '', checksum: '' });
+    files.push({ version, name, filename: entry.name });
   }
 
-  return files
-    .sort((a, b) => a.version - b.version)
-    .map(({ version, name, filename }) => ({ version, name, filename }));
+  return files.sort((a, b) => a.version - b.version);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/database/migration.ts` around lines 57 - 62, Update loadMigrationManifest
to accumulate MigrationManifestEntry objects rather than MigrationFile
placeholders with empty sql and checksum values. Type the files accumulator as
MigrationManifestEntry[], remove the final projection that strips placeholder
fields, and retain the existing version sorting and manifest entry data.
src/distribution/doctor/doctor-types.ts (1)

54-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Enforce exhaustiveness between DoctorCheckId and DOCTOR_CHECK_ORDER.

satisfies readonly DoctorCheckId[] proves each entry is a valid id. It does not prove that every id appears, and it does not prevent duplicates. If a later change adds an id to the union but not to the array, the check silently never runs, and assertCheckOrder still passes because it compares against the array itself.

Add a compile-time exhaustiveness assertion.

♻️ Proposed compile-time exhaustiveness guard
 ] as const satisfies readonly DoctorCheckId[];
+
+// Fails to compile if an id is missing from DOCTOR_CHECK_ORDER.
+type MissingCheckId = Exclude<DoctorCheckId, (typeof DOCTOR_CHECK_ORDER)[number]>;
+const _exhaustiveCheckOrder: MissingCheckId extends never ? true : never = true;
+void _exhaustiveCheckOrder;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/doctor-types.ts` around lines 54 - 69, Add a
compile-time exhaustiveness assertion adjacent to DOCTOR_CHECK_ORDER that
verifies the array contains every DoctorCheckId, while preserving the existing
valid-id constraint. Ensure the assertion also detects duplicate entries so
newly added or repeated check IDs cannot pass compilation unnoticed.
tests/unit/distribution/doctor/check-database.test.ts (2)

97-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This test asserts its own stub value, not production behavior.

openProbe is supplied by the caller. The stub at Line 101 sets openedPath = ':memory:', and Line 108 asserts that same literal. createNativeAddonCheck never selects the probe path, so the assertion cannot fail and the test title is not verified.

The in-memory isolation is decided in src/interfaces/production-dependencies.ts. Assert it there. Keep this test focused on the healthy result and on close() being called.

💚 Proposed replacement assertion
   it('loads the native addon through an isolated in-memory probe', async () => {
-    let openedPath: string | undefined;
+    let closed = false;
     const result = await createNativeAddonCheck({
       openProbe: () => {
-        openedPath = ':memory:';
-        return { close: () => undefined } as never;
+        return {
+          close: () => {
+            closed = true;
+          },
+        } as never;
       },
       nodeAbi: '137',
       packageVersion: '13.0.1',
     }).run();
     expect(result.status).toBe('healthy');
-    expect(openedPath).toBe(':memory:');
+    expect(closed).toBe(true);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-database.test.ts` around lines 97 - 109,
Update the test around createNativeAddonCheck to stop asserting the
caller-controlled ':memory:' stub value. Focus it on the healthy result and
verify the probe handle’s close() callback is invoked; move coverage of the
in-memory probe selection to production-dependencies.ts.

24-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the non-creation assertion out of finally.

Line 25 asserts inside finally. If the assertion at Line 23 fails, this assertion runs and can throw first, which replaces the original failure message. Cleanup and assertion also belong in separate blocks.

Assert before the finally, and use existsSync to state the intent directly.

💚 Proposed test restructure
       expect(result).toMatchObject({ status: 'warning', code: 'database.missing' });
+      expect(existsSync(databasePath)).toBe(false);
+      expect(existsSync(join(root, 'data'))).toBe(false);
     } finally {
-      expect(() => new Database(databasePath)).toThrow();
       rmSync(root, { recursive: true, force: true });
     }

Add existsSync to the node:fs import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-database.test.ts` around lines 24 - 27,
Move the Database non-creation assertion out of the finally block and perform it
before cleanup, using existsSync(databasePath) to assert that the database file
was not created. Add existsSync to the node:fs imports, and leave finally
responsible only for removing root.
tests/unit/distribution/doctor/run-doctor.test.ts (1)

59-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for result sanitization.

This test covers the thrown-error path. Two validation paths in run-doctor.ts remain untested:

  • sanitizeResult rejects an invalid status, or a non-string code or message.
  • sanitizeDetails drops keys that do not match /^[a-z][a-zA-Z0-9]*$/ and sorts the remaining keys.

Add cases for both. The key filter is the part of the contract that keeps unexpected fields out of the JSON report, so a regression there is silent.

The coding guidelines require tests to cover validation failures in the layer where they belong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/run-doctor.test.ts` around lines 59 - 89, Add
unit-test cases in run-doctor coverage for checks returning invalid status,
non-string code, or non-string message, asserting they become sanitized
internal-error results. Add a separate case with details containing valid and
invalid keys, asserting sanitizeDetails removes keys failing
/^[a-z][a-zA-Z0-9]*$/ and sorts the retained keys in the report. Keep these
tests focused on runDoctor’s result-sanitization paths.

Source: Coding guidelines

src/distribution/doctor/check-integrations.ts (1)

88-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Broad catch blocks map several distinct causes to one stable check code. Both integration checks wrap filesystem access, reading, and content validation in a single try, so the reported code cannot tell the user which step failed. Doctor codes are part of the documented, stable output contract, so each cause needs its own code.

  • src/distribution/doctor/check-integrations.ts#L88-L111: return the invalid-shape result directly instead of throwing new Error('invalid template'), and narrow the catch to the access, readFile, and JSON.parse failures under a separate code such as integrations.generic-mcp.template-unreadable.
  • src/distribution/doctor/check-integrations.ts#L58-L77: move adapter.parse(content) out of the read try, and report a parse failure under a separate code such as integrations.${client}.config-unparsable.

If you add codes, document them in docs/doctor.md alongside the existing ones.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-integrations.ts` around lines 88 - 111, In
src/distribution/doctor/check-integrations.ts:88-111, update createGenericCheck
so invalid isGenericRelayEntry results return the invalid-shape failure
directly, while access, readFile, and JSON.parse failures are caught separately
with integrations.generic-mcp.template-unreadable; in
src/distribution/doctor/check-integrations.ts:58-77, move adapter.parse(content)
outside the read try block and report failures with
integrations.${client}.config-unparsable. Document both new stable codes in
docs/doctor.md.
src/distribution/doctor/run-doctor.ts (1)

21-44: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cancel doctor checks when input.signal arrives.

installDoctorSignalHandlers() aborts the doctor controller on SIGINT/SIGTERM, and runDoctor() checks the signal between checks. However, each check.run() is awaited without a signal, so mcp.handshake can still run for its 5s timeout and ui.loopback can still run for its UI timeout while interruption is pending.

Pass the signal into DoctorCheck.run() so checks can route it to child-process probes, MCP connections, and HTTP fetches during runDoctor().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/run-doctor.ts` around lines 21 - 44, Update the
DoctorCheck.run contract and the runDoctor loop to pass input.signal into every
check execution. Propagate that signal through each check implementation to
child-process probes, MCP connections, and HTTP fetches so in-flight checks
cancel promptly, while preserving the existing interruption handling and result
collection behavior.
tests/unit/interfaces/cli/doctor-command.test.ts (1)

100-117: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test the warning-only exit path.

The test name states that warning-only reports return 0, but this fixture creates only healthy and failure results. Add a warning result and assert that runDoctorCommand() returns 0. This locks the documented exit contract against future changes.

As per coding guidelines, cover normal flows, validation failures, and lifecycle boundaries in the layer where they belong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/interfaces/cli/doctor-command.test.ts` around lines 100 - 117, Add
a warning-status check fixture in the doctor command test and assert that
runDoctorCommand() returns 0 for warning-only results, while preserving the
existing validation-error and failure assertions. Use the existing createChecks
dependency setup and DOCTOR_CHECK_ORDER symbols.

Source: Coding guidelines

tests/unit/distribution/doctor/check-compatibility.test.ts (1)

56-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the version and migration-count branches.

The malformed-manifest test fails at isManifest, so it returns before the comparison block at check-compatibility.ts lines 39-47. Those comparisons carry the compatibility semantics, and no test reaches them.

Add two cases: an applicationVersion below minimumPackageVersion, and a migrationCount that disagrees with the migration directory.

💚 Proposed tests
   it('fails a malformed or newer compatibility manifest safely', async () => {
  it('fails when the installed version is below the minimum', async () => {
    const root = fixture();
    try {
      await expect(
        createCompatibilityCheck({
          applicationVersion: '0.0.9',
          migrationsDir: join(root, 'assets', 'migrations'),
          skillsDir: join(root, 'skills'),
          integrationsDir: join(root, 'integrations'),
        }).run(),
      ).resolves.toMatchObject({ status: 'failure', code: 'compatibility.assets.invalid' });
    } finally {
      rmSync(root, { recursive: true, force: true });
    }
  });

  it('fails when the manifest migration count disagrees with the packaged migrations', async () => {
    const root = fixture();
    try {
      writeFileSync(
        join(root, 'assets', 'migrations', '0002_extra.sql'),
        'CREATE TABLE extra (id INTEGER);',
      );
      await expect(
        createCompatibilityCheck({
          applicationVersion: '0.1.0',
          migrationsDir: join(root, 'assets', 'migrations'),
          skillsDir: join(root, 'skills'),
          integrationsDir: join(root, 'integrations'),
        }).run(),
      ).resolves.toMatchObject({ status: 'failure', code: 'compatibility.assets.invalid' });
    } finally {
      rmSync(root, { recursive: true, force: true });
    }
  });
As per coding guidelines: "Cover normal flows, validation failures, and lifecycle boundaries in the layer where they belong."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-compatibility.test.ts` around lines 56 -
74, Extend the compatibility-check tests with separate cases for an
applicationVersion below minimumPackageVersion and a migrationCount mismatch
caused by adding an extra migration file under migrationsDir. Run
createCompatibilityCheck(...).run() in each case and assert
compatibility.assets.invalid, preserving fixture cleanup with the existing
try/finally pattern.

Source: Coding guidelines

src/distribution/doctor/check-compatibility.ts (1)

27-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass the compatibility manifest path explicitly.

The check derives the manifest path with dirname(input.migrationsDir). This encodes the assets/migrations layout of resolvePackageAssets (src/distribution/package-assets.ts, lines 33-43) as an implicit contract. If the asset layout changes, this check fails with compatibility.assets.invalid instead of a build error.

Add an explicit input field and pass it from createDoctorDependencies.

♻️ Proposed change
 export function createCompatibilityCheck(input: {
   readonly applicationVersion: string;
+  readonly compatibilityManifestPath: string;
   readonly migrationsDir: string;
   readonly skillsDir: string;
   readonly integrationsDir: string;
 }): DoctorCheck {
@@
-        const manifest = JSON.parse(
-          readFileSync(join(dirname(input.migrationsDir), 'compatibility.json'), 'utf8'),
-        ) as CompatibilityManifest;
+        const manifest = JSON.parse(
+          readFileSync(input.compatibilityManifestPath, 'utf8'),
+        ) as CompatibilityManifest;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-compatibility.ts` around lines 27 - 31, Update
the doctor compatibility check input to include an explicit compatibility
manifest path, and use that field when reading the manifest instead of deriving
it from input.migrationsDir. Update createDoctorDependencies to provide the
resolved manifest path while preserving manifest validation and migration
loading.
tests/unit/distribution/doctor/check-package-assets.test.ts (1)

7-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Build the asset fixtures with host-native paths.

The fixture hardcodes POSIX separators. The verdicts still hold on Windows, because every literal shares one root and relative produces a relative result in both cases. The PR remediation plan for cross-platform tests states that fixtures must use host-native resolve() and join(). This file does not follow that convention.

Align the fixture with the convention so later assertions on details paths stay platform-correct.

♻️ Proposed change
 import { describe, expect, it } from 'vitest';
+import { join, resolve } from 'node:path';
 import { createPackageAssetsCheck } from '../../../../src/distribution/doctor/check-package-assets.js';
@@
-const assets: PackageAssets = {
-  packageRoot: '/tmp/relay-package',
-  migrationsDir: '/tmp/relay-package/assets/migrations',
-  webRoot: '/tmp/relay-package/dist/web',
-  skillsDir: '/tmp/relay-package/skills',
-  integrationsDir: '/tmp/relay-package/integrations',
-};
+const packageRoot = resolve('/tmp/relay-package');
+const assets: PackageAssets = {
+  packageRoot,
+  migrationsDir: join(packageRoot, 'assets', 'migrations'),
+  webRoot: join(packageRoot, 'dist', 'web'),
+  skillsDir: join(packageRoot, 'skills'),
+  integrationsDir: join(packageRoot, 'integrations'),
+};
+const executablePath = join(packageRoot, 'dist', 'cli', 'main.js');
+const outsidePath = join(resolve('/tmp/outside'), 'web');

Then replace the remaining literals with executablePath and outsidePath.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-package-assets.test.ts` around lines 7 -
13, Update the asset fixture around assets to build paths with host-native
resolve() and join() rather than hardcoded POSIX separators, reusing the
existing executablePath and outsidePath symbols for the remaining path literals
so details assertions remain platform-correct.
src/interfaces/mcp/main.ts (1)

10-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Test instrumentation is embedded in the MCP production entry point.

This block ships in the published package and runs on every MCP start. The RELAY_RUN_PACKAGE_SMOKE === '1' gate keeps it active in an installed package, outside NODE_ENV=test.

The equivalent doctor probes stay inside src/distribution/doctor/check-mcp.ts and check-ui.ts. Move this readiness signal out of the delivery adapter. One option is a separate probe entry module that the signal tests spawn, which keeps runMcpServer free of test state.

If the smoke test requires the marker from the real entry point, extract the block into a named helper such as writeDoctorProbeMarker() in a dedicated module, and keep runMcpServer limited to one guarded call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/interfaces/mcp/main.ts` around lines 10 - 16, Remove the inline test
instrumentation from the MCP production entry point and relocate it to a
dedicated probe module, following the existing doctor probe structure. Expose a
named helper such as writeDoctorProbeMarker() that owns the environment checks
and marker write, then keep runMcpServer limited to a single guarded helper call
so production startup contains no embedded test state.
src/distribution/doctor/check-mcp.ts (3)

49-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The stderr handler discards output and counts bytes that nothing reads.

stderrBytes is only written. The handler never stores the chunks, so no stderr is captured and the counter has no effect. Either drop the listener, or capture bounded stderr and use it. If you keep a bound, reuse DOCTOR_MAX_CAPTURE_BYTES from ./child-process-probe.js instead of the literal 32_768.

♻️ Proposed removal of the dead counter
-      let stderrBytes = 0;
-      transport.stderr?.on('data', (chunk: Buffer | string) => {
-        const remaining = Math.max(0, 32_768 - stderrBytes);
-        stderrBytes += Math.min(remaining, Buffer.byteLength(chunk));
-      });
+      transport.stderr?.resume();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-mcp.ts` around lines 49 - 53, Remove the unused
stderr data listener and stderrBytes counter from the transport setup, since
they neither capture nor consume output. If stderr is needed for diagnostics,
instead capture it with a bounded buffer using DOCTOR_MAX_CAPTURE_BYTES from
child-process-probe.js and use the captured value.

112-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use node:path join for the isolated database path.

joinDatabasePath builds the path with a hard-coded forward slash. src/distribution/doctor/check-ui.ts Line 41 builds the same relay.db path with join(root.path, 'relay.db'). Use one mechanism for one concept.

♻️ Proposed change
+import { join } from 'node:path';
-        env: { ...process.env, RELAY_DB_PATH: joinDatabasePath(root.path) },
+        env: { ...process.env, RELAY_DB_PATH: join(root.path, 'relay.db') },
-function joinDatabasePath(root: string): string {
-  return `${root.replace(/[\\/]$/, '')}/relay.db`;
-}
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-mcp.ts` around lines 112 - 114, Update
joinDatabasePath to use node:path’s join function with the root directory and
“relay.db”, matching the existing path construction used by check-ui.ts. Remove
the manual trailing-separator removal and hard-coded slash while preserving the
same database filename.

58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the exported timeout constant.

child-process-probe.ts exports DOCTOR_MCP_TIMEOUT_MS = 5_000. The check duplicates the literal 5_000 twice. tests/unit/distribution/doctor/child-process-probe.test.ts treats these constants as locked values, so a future change to the constant will silently not apply here.

♻️ Proposed change
-import { registerDoctorCleanup, registerDoctorTemporaryRoot } from './child-process-probe.js';
+import {
+  DOCTOR_MCP_TIMEOUT_MS,
+  registerDoctorCleanup,
+  registerDoctorTemporaryRoot,
+} from './child-process-probe.js';
-        await withTimeout(client.connect(transport), 5_000);
+        await withTimeout(client.connect(transport), DOCTOR_MCP_TIMEOUT_MS);
-        const tools = (await withTimeout(client.listTools(), 5_000)).tools.map((tool) => tool.name);
+        const tools = (await withTimeout(client.listTools(), DOCTOR_MCP_TIMEOUT_MS)).tools.map(
+          (tool) => tool.name,
+        );

Also applies to: 69-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-mcp.ts` at line 58, Update the timeout
arguments in the MCP check flow around client.connect and the second duplicated
timeout use to import and reuse the exported DOCTOR_MCP_TIMEOUT_MS constant from
child-process-probe.ts instead of hardcoding 5_000.
tests/fixtures/doctor/process/ui-ready-child.mjs (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Explain the split scheme literal.

${'http' + '://'} produces http://. The concatenation hides the literal from a scanner or lint rule, but the reason is not visible in the file. Add a one-line comment that states why the literal is split, so a later reader does not "simplify" it back and break the check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/fixtures/doctor/process/ui-ready-child.mjs` at line 1, Add a one-line
comment immediately above the process.stderr.write call explaining that the HTTP
scheme is intentionally split to avoid scanner or lint-rule detection; leave the
existing output behavior unchanged.
tests/unit/distribution/doctor/check-ui.test.ts (1)

25-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the ui.non-loopback and ui.health-invalid branches.

The two timeout tests are correct. The file does not cover two validation-failure branches of createUiLoopbackCheck:

  • ui.non-loopback at src/distribution/doctor/check-ui.ts Lines 67-73. Add a fixture that reports a non-loopback readiness URL.
  • ui.health-invalid at Lines 100-106. Return a well-formed JSON body with an unexpected name or status.

The coding guidelines require tests to cover validation failures in the layer where they belong.

Do you want me to generate these two tests?

As per coding guidelines: "Cover normal flows, validation failures, and lifecycle boundaries in the layer where they belong."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/check-ui.test.ts` around lines 25 - 63, Add
tests in the existing check-ui test suite for the two uncovered validation
branches of createUiLoopbackCheck: use a readiness fixture reporting a
non-loopback URL and assert code ui.non-loopback, then mock a successful health
response with valid JSON containing an unexpected name or status and assert code
ui.health-invalid. Reuse the existing command, temporaryRootFactory, and run
setup from the timeout tests.

Source: Coding guidelines

tests/unit/distribution/doctor/child-process-probe.test.ts (1)

152-153: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the truncated content, not only its length.

toBeLessThanOrEqual(4) also passes when stdout is empty. An empty capture would hide a regression in the capture helper at src/distribution/doctor/child-process-probe.ts Lines 79-83. hanging-child.mjs writes hanging-output, so the bounded result is deterministic.

💚 Proposed fix
     expect(result.timedOut).toBe(true);
-    expect(result.stdout.length).toBeLessThanOrEqual(4);
+    expect(result.stdout).toBe('hang');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/distribution/doctor/child-process-probe.test.ts` around lines 152
- 153, Update the timeout assertion in the child-process probe test to verify
the expected truncated stdout content produced by hanging-child.mjs, rather than
only checking stdout.length. Keep the timedOut assertion and ensure the
expectation would fail if capture returned an empty string.
src/distribution/doctor/check-ui.ts (1)

125-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Terminate only this check's child instead of calling the global cleanup.

cleanupDoctorChildren() runs every registered cleanup in the process, including children and temporary roots owned by other checks. src/distribution/doctor/check-mcp.ts Lines 94-100 does the opposite: it closes only its own transport and root.

No present bug exists, because createChecks() in src/interfaces/production-dependencies.ts orders the MCP check before the UI check and each check cleans its own root. The coupling breaks if check execution ever overlaps. Expose the child from runChildProcessProbe and terminate that child here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/check-ui.ts` around lines 125 - 129, Update the UI
check’s cleanup flow around runChildProcessProbe to expose and retain this
check’s child process, then terminate only that child in the finally block
instead of calling the global cleanupDoctorChildren(). Preserve the existing
probe error handling and registeredRoot.cleanup() behavior, following the
per-check cleanup pattern used by the MCP check.
src/distribution/doctor/child-process-probe.ts (2)

219-261: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Windows branch runs taskkill even after the child exited.

Line 242 computes exited, but the win32 branch at Line 243 ignores it. If the child already exited after SIGTERM, the code still spawns taskkill. Return early when exited is true.

♻️ Proposed early return
   const exited = await waitForExit(child, 500);
+  if (exited) return;
   if (process.platform === 'win32' && pid !== undefined) {
     await taskkill(pid);
     await waitForExit(child, 500);
     return;
   }
-  if (exited) return;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/child-process-probe.ts` around lines 219 - 261,
Update terminateChildInternal so the Windows-specific taskkill branch checks
exited and returns immediately when waitForExit confirms the child has exited.
Only invoke taskkill and the subsequent waitForExit when exited is false;
preserve the existing behavior for non-Windows platforms.

39-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused activeChildren registry.

activeChildren is only declared, added to, and deleted from. runChildProcessProbe() uses registerDoctorCleanup() for termination, and activeCleanups backs the cleanup paths that run on exit or abort. Remove activeChildren and its five mutation sites.

Also applies to: 71, 96, 107, 111, 134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/distribution/doctor/child-process-probe.ts` at line 39, Remove the unused
activeChildren registry from child-process probe logic, including its
declaration and all five add/delete mutation sites near runChildProcessProbe.
Preserve registerDoctorCleanup() and activeCleanups cleanup behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-08-06-pr-49-dependency-audit-remediation.md`:
- Around line 79-88: Update Step 3’s pnpm audit baseline capture instructions to
resolve the conflict with the document-wide || true prohibition: either
explicitly scope that prohibition to release-gate commands or describe an
alternative method that captures the expected non-zero audit result without
relying on || true. Preserve the requirement to write the JSON baseline to
.artifacts/audit/before.json.

In `@scripts/package/smoke-installed-package.ts`:
- Around line 200-203: Update verifyInstalledDoctorSignals so the
RELAY_DOCTOR_TEST_CHILD_MARKER PID is captured and cleaned up on the timeout and
finally paths, not only after waitForDoctorExit resolves. Ensure every failure
kills and awaits waitForProcessExit(childPid) before caseRoot is removed, while
preserving the existing successful exit flow.

In `@scripts/validate-repository-assets.ts`:
- Around line 193-198: Update validateDoctorFixtures so its disallowed-path
validation rejects UNC paths and absolute POSIX paths such as /tmp, while
preserving drive-letter and existing sensitive-root detection. Prefer path-aware
checks using path.posix and path.win32, or expand the matcher accordingly, and
add regression fixtures covering these cross-platform absolute path forms.

In `@src/distribution/doctor/check-database.ts`:
- Around line 34-36: Update the database inspection flow around the
migration-ledger query to first verify whether _relay_migrations exists. When
the database file exists but the ledger table is absent, return exists: true
with appliedMigrations empty and pendingMigrations containing all available
migrations; preserve the existing query and error handling for databases where
the ledger table is present.

In `@src/distribution/doctor/check-paths.ts`:
- Around line 103-111: Replace the boolean result from directoryState with an
observed directory state that distinguishes absence, readability, writability,
and whether the path is a directory. Update each caller in the required-root,
cache, and database-parent checks to use those fields when populating exists,
readable, and writable, preserving the existing diagnostics while reporting
actual permissions and directory status.

In `@src/distribution/doctor/check-ui.ts`:
- Line 34: Update the UI child startup flow around findFreePort so
RELAY_HTTP_PORT is set to 0 rather than using a preallocated port. Remove the
findFreePort call and preserve the existing stderr parsing that reads the UI’s
actual bound URL.

In `@src/interfaces/production-dependencies.ts`:
- Line 78: Update the argument passed to resolvePackageAssets so pathToFileURL
is applied only to process.argv[1] when present, while the undefined fallback
uses import.meta.url directly. Preserve resolvePackageAssets’s support for both
filesystem paths converted to file URLs and the existing file URL fallback.

In `@tests/unit/distribution/doctor/check-mcp.test.ts`:
- Around line 21-26: The spawn-failure fixtures use a Windows-only temporary
root, masking the intended missing-command failure on other hosts. In
tests/unit/distribution/doctor/check-mcp.test.ts lines 21-26, update the
temporaryRootFactory fixture to use host-native join(tmpdir(),
'relay-doctor-mcp-test') and create that directory before the run; in
tests/unit/distribution/doctor/check-ui.test.ts lines 12-17, replace the Windows
path with process.cwd(), matching the other tests in that file.

In `@tests/unit/distribution/doctor/check-paths.test.ts`:
- Line 54: Update the assertion for accessed paths to use a content-aware array
matcher, such as checking that no element satisfies the probe substring
condition, instead of passing expect.stringContaining to toContain. Ensure the
test fails when accessed contains a path including “probe”.

In `@tests/unit/distribution/doctor/child-process-probe.test.ts`:
- Around line 172-184: Validate childPid immediately after parsing result.stdout
in the “terminates a spawned grandchild with the timed-out parent” test,
asserting it is a positive integer before calling waitForProcessExit. Keep the
existing timeout assertion and process-exit verification unchanged.

In `@tests/unit/scripts/mcpb/stage-linux-mcpb.test.ts`:
- Around line 40-41: The staged lockfile fixture in the test setup must include
both declared overrides: the existing tmp override and brace-expansion@>=4.0.0
<5.0.9. Regenerate or update the fixture lockfile using the repository’s pnpm
version so it matches the source manifest before stageLinuxMcpb runs frozen
installation.

---

Outside diff comments:
In `@scripts/package/stage-package-assets.ts`:
- Around line 28-34: Extend stagePackageAssets to parse and validate
assets/compatibility.json rather than only checking existence. Validate
schemaVersion, minimumPackageVersion, contract/schema version fields, migration
count against staged SQL migrations, skill metadata against SKILL.md, and
template metadata using the existing compatibility validation rules; reject
unsupported or inconsistent manifests during staging. Add a staging failure test
covering an invalid compatibility manifest.

---

Nitpick comments:
In `@src/database/migration.ts`:
- Around line 57-62: Update loadMigrationManifest to accumulate
MigrationManifestEntry objects rather than MigrationFile placeholders with empty
sql and checksum values. Type the files accumulator as MigrationManifestEntry[],
remove the final projection that strips placeholder fields, and retain the
existing version sorting and manifest entry data.

In `@src/distribution/doctor/check-compatibility.ts`:
- Around line 27-31: Update the doctor compatibility check input to include an
explicit compatibility manifest path, and use that field when reading the
manifest instead of deriving it from input.migrationsDir. Update
createDoctorDependencies to provide the resolved manifest path while preserving
manifest validation and migration loading.

In `@src/distribution/doctor/check-integrations.ts`:
- Around line 88-111: In src/distribution/doctor/check-integrations.ts:88-111,
update createGenericCheck so invalid isGenericRelayEntry results return the
invalid-shape failure directly, while access, readFile, and JSON.parse failures
are caught separately with integrations.generic-mcp.template-unreadable; in
src/distribution/doctor/check-integrations.ts:58-77, move adapter.parse(content)
outside the read try block and report failures with
integrations.${client}.config-unparsable. Document both new stable codes in
docs/doctor.md.

In `@src/distribution/doctor/check-mcp.ts`:
- Around line 49-53: Remove the unused stderr data listener and stderrBytes
counter from the transport setup, since they neither capture nor consume output.
If stderr is needed for diagnostics, instead capture it with a bounded buffer
using DOCTOR_MAX_CAPTURE_BYTES from child-process-probe.js and use the captured
value.
- Around line 112-114: Update joinDatabasePath to use node:path’s join function
with the root directory and “relay.db”, matching the existing path construction
used by check-ui.ts. Remove the manual trailing-separator removal and hard-coded
slash while preserving the same database filename.
- Line 58: Update the timeout arguments in the MCP check flow around
client.connect and the second duplicated timeout use to import and reuse the
exported DOCTOR_MCP_TIMEOUT_MS constant from child-process-probe.ts instead of
hardcoding 5_000.

In `@src/distribution/doctor/check-ui.ts`:
- Around line 125-129: Update the UI check’s cleanup flow around
runChildProcessProbe to expose and retain this check’s child process, then
terminate only that child in the finally block instead of calling the global
cleanupDoctorChildren(). Preserve the existing probe error handling and
registeredRoot.cleanup() behavior, following the per-check cleanup pattern used
by the MCP check.

In `@src/distribution/doctor/child-process-probe.ts`:
- Around line 219-261: Update terminateChildInternal so the Windows-specific
taskkill branch checks exited and returns immediately when waitForExit confirms
the child has exited. Only invoke taskkill and the subsequent waitForExit when
exited is false; preserve the existing behavior for non-Windows platforms.
- Line 39: Remove the unused activeChildren registry from child-process probe
logic, including its declaration and all five add/delete mutation sites near
runChildProcessProbe. Preserve registerDoctorCleanup() and activeCleanups
cleanup behavior unchanged.

In `@src/distribution/doctor/doctor-types.ts`:
- Around line 54-69: Add a compile-time exhaustiveness assertion adjacent to
DOCTOR_CHECK_ORDER that verifies the array contains every DoctorCheckId, while
preserving the existing valid-id constraint. Ensure the assertion also detects
duplicate entries so newly added or repeated check IDs cannot pass compilation
unnoticed.

In `@src/distribution/doctor/run-doctor.ts`:
- Around line 21-44: Update the DoctorCheck.run contract and the runDoctor loop
to pass input.signal into every check execution. Propagate that signal through
each check implementation to child-process probes, MCP connections, and HTTP
fetches so in-flight checks cancel promptly, while preserving the existing
interruption handling and result collection behavior.

In `@src/interfaces/mcp/main.ts`:
- Around line 10-16: Remove the inline test instrumentation from the MCP
production entry point and relocate it to a dedicated probe module, following
the existing doctor probe structure. Expose a named helper such as
writeDoctorProbeMarker() that owns the environment checks and marker write, then
keep runMcpServer limited to a single guarded helper call so production startup
contains no embedded test state.

In `@tests/fixtures/doctor/process/ui-ready-child.mjs`:
- Line 1: Add a one-line comment immediately above the process.stderr.write call
explaining that the HTTP scheme is intentionally split to avoid scanner or
lint-rule detection; leave the existing output behavior unchanged.

In `@tests/integration/packaged-assets.test.ts`:
- Around line 23-24: Add a separate integration test for stagePackageAssets that
creates a package root with the assets directory but omits compatibility.json,
then assert it rejects with the exact “Package asset is missing after build”
error while preserving the existing successful staging test.

In `@tests/unit/distribution/doctor/check-compatibility.test.ts`:
- Around line 56-74: Extend the compatibility-check tests with separate cases
for an applicationVersion below minimumPackageVersion and a migrationCount
mismatch caused by adding an extra migration file under migrationsDir. Run
createCompatibilityCheck(...).run() in each case and assert
compatibility.assets.invalid, preserving fixture cleanup with the existing
try/finally pattern.

In `@tests/unit/distribution/doctor/check-database.test.ts`:
- Around line 97-109: Update the test around createNativeAddonCheck to stop
asserting the caller-controlled ':memory:' stub value. Focus it on the healthy
result and verify the probe handle’s close() callback is invoked; move coverage
of the in-memory probe selection to production-dependencies.ts.
- Around line 24-27: Move the Database non-creation assertion out of the finally
block and perform it before cleanup, using existsSync(databasePath) to assert
that the database file was not created. Add existsSync to the node:fs imports,
and leave finally responsible only for removing root.

In `@tests/unit/distribution/doctor/check-integrations.test.ts`:
- Around line 103-108: Update the test around generic.run() to execute it once,
store the returned result, and reuse that result for both the status/code
assertions and the JSON secret-content assertion.
- Around line 55-86: Add unit-test cases covering the remaining
createNativeClientCheck outcomes: ownership-invalid when ownershipStore.read()
rejects, disabled when all owned records are not enabled, and file-unreadable
when access or readFile rejects. Extend the existing createIntegrationChecks
fixtures and assertions to verify each failure code and message, and configure
access/readFile rejection so the unreadable-file branch is exercised.

In `@tests/unit/distribution/doctor/check-package-assets.test.ts`:
- Around line 7-13: Update the asset fixture around assets to build paths with
host-native resolve() and join() rather than hardcoded POSIX separators, reusing
the existing executablePath and outsidePath symbols for the remaining path
literals so details assertions remain platform-correct.

In `@tests/unit/distribution/doctor/check-ui.test.ts`:
- Around line 25-63: Add tests in the existing check-ui test suite for the two
uncovered validation branches of createUiLoopbackCheck: use a readiness fixture
reporting a non-loopback URL and assert code ui.non-loopback, then mock a
successful health response with valid JSON containing an unexpected name or
status and assert code ui.health-invalid. Reuse the existing command,
temporaryRootFactory, and run setup from the timeout tests.

In `@tests/unit/distribution/doctor/child-process-probe.test.ts`:
- Around line 152-153: Update the timeout assertion in the child-process probe
test to verify the expected truncated stdout content produced by
hanging-child.mjs, rather than only checking stdout.length. Keep the timedOut
assertion and ensure the expectation would fail if capture returned an empty
string.

In `@tests/unit/distribution/doctor/run-doctor.test.ts`:
- Around line 59-89: Add unit-test cases in run-doctor coverage for checks
returning invalid status, non-string code, or non-string message, asserting they
become sanitized internal-error results. Add a separate case with details
containing valid and invalid keys, asserting sanitizeDetails removes keys
failing /^[a-z][a-zA-Z0-9]*$/ and sorts the retained keys in the report. Keep
these tests focused on runDoctor’s result-sanitization paths.

In `@tests/unit/interfaces/cli/doctor-command.test.ts`:
- Around line 100-117: Add a warning-status check fixture in the doctor command
test and assert that runDoctorCommand() returns 0 for warning-only results,
while preserving the existing validation-error and failure assertions. Use the
existing createChecks dependency setup and DOCTOR_CHECK_ORDER symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fa6cecc8-559f-4a27-b4f5-5d8faa175f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 22da2f1 and 9d8c9ca.

⛔ Files ignored due to path filters (2)
  • integrations/claude-desktop/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (59)
  • README.md
  • assets/compatibility.json
  • docs/doctor.md
  • docs/setup-and-configuration.md
  • docs/superpowers/plans/2026-08-04-issue-42-relay-doctor-diagnostics.md
  • docs/superpowers/plans/2026-08-04-pr-49-doctor-signal-remediation.md
  • docs/superpowers/plans/2026-08-05-pr-49-cross-platform-path-test-remediation.md
  • docs/superpowers/plans/2026-08-06-pr-49-dependency-audit-remediation.md
  • integrations/claude-desktop/package.json
  • package.json
  • scripts/package/package-files.ts
  • scripts/package/smoke-installed-package.ts
  • scripts/package/stage-package-assets.ts
  • scripts/package/verify-package-metadata.ts
  • scripts/validate-mcpb-assets.ts
  • scripts/validate-repository-assets.ts
  • src/database/migration.ts
  • src/distribution/doctor/check-compatibility.ts
  • src/distribution/doctor/check-database.ts
  • src/distribution/doctor/check-integrations.ts
  • src/distribution/doctor/check-mcp.ts
  • src/distribution/doctor/check-package-assets.ts
  • src/distribution/doctor/check-paths.ts
  • src/distribution/doctor/check-runtime.ts
  • src/distribution/doctor/check-ui.ts
  • src/distribution/doctor/child-process-probe.ts
  • src/distribution/doctor/doctor-interruption.ts
  • src/distribution/doctor/doctor-types.ts
  • src/distribution/doctor/run-doctor.ts
  • src/interfaces/cli/doctor-output.ts
  • src/interfaces/cli/main.ts
  • src/interfaces/cli/parse-doctor-command.ts
  • src/interfaces/cli/run-doctor-command.ts
  • src/interfaces/cli/run-relay.ts
  • src/interfaces/mcp/main.ts
  • src/interfaces/production-dependencies.ts
  • tests/fixtures/doctor/README.md
  • tests/fixtures/doctor/process/hanging-child.mjs
  • tests/fixtures/doctor/process/healthy-child.mjs
  • tests/fixtures/doctor/process/spawn-grandchild.mjs
  • tests/fixtures/doctor/process/ui-ready-child.mjs
  • tests/integration/database-path-parity.test.ts
  • tests/integration/doctor-installed-package.test.ts
  • tests/integration/installed-package.test.ts
  • tests/integration/packaged-assets.test.ts
  • tests/unit/distribution/doctor/check-compatibility.test.ts
  • tests/unit/distribution/doctor/check-database.test.ts
  • tests/unit/distribution/doctor/check-integrations.test.ts
  • tests/unit/distribution/doctor/check-mcp.test.ts
  • tests/unit/distribution/doctor/check-package-assets.test.ts
  • tests/unit/distribution/doctor/check-paths.test.ts
  • tests/unit/distribution/doctor/check-runtime.test.ts
  • tests/unit/distribution/doctor/check-ui.test.ts
  • tests/unit/distribution/doctor/child-process-probe.test.ts
  • tests/unit/distribution/doctor/run-doctor.test.ts
  • tests/unit/interfaces/cli/doctor-command.test.ts
  • tests/unit/interfaces/cli/run-relay.test.ts
  • tests/unit/scripts/mcpb/model.test.ts
  • tests/unit/scripts/mcpb/stage-linux-mcpb.test.ts

Comment thread scripts/package/smoke-installed-package.ts
Comment thread scripts/validate-repository-assets.ts
Comment thread src/distribution/doctor/check-database.ts
Comment thread src/distribution/doctor/check-paths.ts Outdated
Comment thread src/interfaces/production-dependencies.ts Outdated
Comment thread tests/unit/distribution/doctor/check-mcp.test.ts
Comment thread tests/unit/distribution/doctor/check-paths.test.ts Outdated
Comment thread tests/unit/distribution/doctor/child-process-probe.test.ts
Comment thread tests/unit/scripts/mcpb/stage-linux-mcpb.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/distribution/doctor/check-ui.ts`:
- Line 79: Update the fetch and response-body flow in the UI health check so the
abort listener remains registered through readHealthBody() and is removed only
after body processing completes. In the outer catch, detect an aborted signal
and rethrow signal.reason instead of converting cancellation into
ui.start-failed; preserve existing handling for non-cancellation errors.

In `@src/interfaces/http/create-http-server.ts`:
- Around line 80-82: Update the RELAY_HTTP_PORT validation around parsed so
envPort must match the complete non-negative decimal-integer format before
conversion; reject values with trailing characters, exponent notation, or
hexadecimal prefixes such as 123abc, 1e3, and 0x10, while preserving the
existing 0–65535 range validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 24663b7b-7f09-4a49-ba08-b003d8db6ec9

📥 Commits

Reviewing files that changed from the base of the PR and between 9d8c9ca and 1cebb25.

📒 Files selected for processing (35)
  • docs/doctor.md
  • docs/superpowers/plans/2026-08-06-pr-49-dependency-audit-remediation.md
  • scripts/package/smoke-installed-package.ts
  • scripts/package/stage-package-assets.ts
  • scripts/validate-repository-assets.ts
  • src/database/migration.ts
  • src/distribution/doctor/check-compatibility.ts
  • src/distribution/doctor/check-database.ts
  • src/distribution/doctor/check-integrations.ts
  • src/distribution/doctor/check-mcp.ts
  • src/distribution/doctor/check-paths.ts
  • src/distribution/doctor/check-ui.ts
  • src/distribution/doctor/child-process-probe.ts
  • src/distribution/doctor/doctor-types.ts
  • src/distribution/doctor/run-doctor.ts
  • src/interfaces/http/create-http-server.ts
  • src/interfaces/mcp/doctor-probe-marker.ts
  • src/interfaces/mcp/main.ts
  • src/interfaces/production-dependencies.ts
  • tests/fixtures/doctor/process/ui-non-loopback-child.mjs
  • tests/fixtures/doctor/process/ui-ready-child.mjs
  • tests/integration/packaged-assets.test.ts
  • tests/unit/distribution/doctor/check-compatibility.test.ts
  • tests/unit/distribution/doctor/check-database.test.ts
  • tests/unit/distribution/doctor/check-integrations.test.ts
  • tests/unit/distribution/doctor/check-mcp.test.ts
  • tests/unit/distribution/doctor/check-package-assets.test.ts
  • tests/unit/distribution/doctor/check-paths.test.ts
  • tests/unit/distribution/doctor/check-ui.test.ts
  • tests/unit/distribution/doctor/child-process-probe.test.ts
  • tests/unit/distribution/doctor/run-doctor.test.ts
  • tests/unit/interfaces/cli/doctor-command.test.ts
  • tests/unit/interfaces/http/create-http-server.test.ts
  • tests/unit/scripts/mcpb/stage-linux-mcpb.test.ts
  • tests/unit/scripts/validate-repository-assets.test.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • tests/unit/distribution/doctor/check-mcp.test.ts
  • src/distribution/doctor/check-mcp.ts
  • tests/unit/distribution/doctor/check-package-assets.test.ts
  • docs/doctor.md
  • src/distribution/doctor/run-doctor.ts
  • src/distribution/doctor/check-database.ts
  • tests/unit/distribution/doctor/child-process-probe.test.ts
  • tests/unit/distribution/doctor/check-paths.test.ts
  • src/distribution/doctor/check-integrations.ts
  • src/interfaces/mcp/main.ts
  • docs/superpowers/plans/2026-08-06-pr-49-dependency-audit-remediation.md
  • scripts/validate-repository-assets.ts
  • tests/unit/scripts/mcpb/stage-linux-mcpb.test.ts
  • tests/fixtures/doctor/process/ui-ready-child.mjs
  • src/distribution/doctor/check-paths.ts

Comment thread src/distribution/doctor/check-ui.ts
Comment thread src/interfaces/http/create-http-server.ts Outdated
@krishna916

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@krishna916 krishna916 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fresh re-review after the large CodeRabbit remediation pass (1cebb25 + 57a0ad3). I re-checked the behavior-changing doctor code rather than relying on the prior review.

No merge-blocking correctness findings remain. In particular, the current head preserves the interruption contract through MCP/UI probes, keeps UI health cancellation active through response-body parsing, uses ephemeral port 0 without the prior allocation race, reports path access state accurately, handles databases without _relay_migrations as pre-migration state, validates compatibility assets during packaging, and retains the non-destructive/read-only boundaries. All CodeRabbit inline threads are resolved, and CI run 31120819744 is green across the verification gate and Linux MCPB staging/integration/validation/packaging steps.

Non-blocking cleanup: the PR description is now stale where it says six audit advisories remain and are unchanged. The dependency remediation is present (@modelcontextprotocol/sdk ^1.30.0 plus the scoped brace-expansion override) and the current pnpm audit --audit-level high gate passes, so that paragraph should be updated before merge to keep the PR record accurate.

@krishna916
krishna916 merged commit 67a05d7 into main Aug 7, 2026
2 of 4 checks passed
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.

Implement Relay doctor and non-destructive operational diagnostics

1 participant