Skip to content

fix(doctor): note when a store checkout is behind its upstream ref#1287

Open
mc856 wants to merge 2 commits into
Fission-AI:mainfrom
mc856:fix/store-drift-doctor-warning
Open

fix(doctor): note when a store checkout is behind its upstream ref#1287
mc856 wants to merge 2 commits into
Fission-AI:mainfrom
mc856:fix/store-drift-doctor-warning

Conversation

@mc856

@mc856 mc856 commented Jul 1, 2026

Copy link
Copy Markdown

Why

Stores have no commit pin, so two teammates on different commits of the
same store silently resolve different specs (#1273). openspec doctor
already reports store health, but only checks recorded-remote vs origin —
it never looks at commit drift, so nothing surfaces the mismatch.

What

Adds a read-only info diagnostic, store_checkout_drift, to
openspec doctor. For a git-backed store checkout it reports how far HEAD
is ahead/behind its upstream tracking ref:

  • Fires only when behind — ahead-only is normal for a store (OpenSpec
    never pushes them), so it stays quiet.
  • Calls out a diverged checkout (both ahead and behind) in the message.
  • Surfaces the counts in doctor --json as store.drift.

It mirrors the existing store_remote_divergence info diagnostic in
severity and shape, and leaves the exit code unchanged.

Scope

Not a commit pin, and it doesn't touch the "OpenSpec never
clones/pulls/pushes" contract. The comparison is against the local
upstream ref (@{upstream}), so it flags drift already known locally — it
can't see a remote that advanced before you fetched. A true reproducible
pin (an expected store commit per code repo) is a separate design question;
happy to open a proposal for that rather than fold it in here.

agent-contract.md (§4.9 shape, §6 catalog) and cli.md are updated to match.

Tests

  • relationship-health.test.ts: behind / diverged / ahead-only-quiet /
    in-sync / no-drift composition.
  • doctor.test.ts: real-git fixtures (isolated via isolatedGitEnv) for
    behind, diverged, and no-upstream — asserting JSON and human output.

No changeset yet — happy to add one if you'd like.

Refs #1273

Summary by CodeRabbit

  • New Features

    • Added store checkout drift reporting to doctor, including optional JSON store.drift with ahead/behind counts when an upstream tracking branch exists.
    • Introduced informational diagnostic store_checkout_drift when the checkout is behind or diverged.
  • Bug Fixes

    • Improved store health messaging to accurately describe drift direction and commit counts (including correct singular/plural wording).
  • Documentation

    • Updated doctor --json and relationship-health documentation to reflect the new drift details.
  • Tests

    • Added coverage for behind, diverged, in-sync, ahead-only, and no-upstream scenarios in both JSON and human output.

Stores have no commit pin, so teammates on different commits of the same
store silently resolve different specs. Add a read-only info diagnostic
(store_checkout_drift) reporting ahead/behind counts against the local
upstream ref, and surface them in doctor --json. Fires only when behind
(ahead-only is normal — OpenSpec never pushes stores) and never fetches,
so it flags already-known drift, not a live cross-machine check.

Refs Fission-AI#1273
@mc856 mc856 requested a review from TabishB as a code owner July 1, 2026 12:11
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 445b6236-9d31-483a-8dfc-b7e030c2c8b9

📥 Commits

Reviewing files that changed from the base of the PR and between 16ac9a1 and 5392646.

📒 Files selected for processing (2)
  • src/commands/doctor.ts
  • test/commands/doctor.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/commands/doctor.test.ts
  • src/commands/doctor.ts

📝 Walkthrough

Walkthrough

Adds git checkout drift detection (ahead/behind upstream tracking counts) to the store health system. A new gitTrackingDrift function computes drift, doctor command wires it into storeFacts, and inspectRelationships emits an info diagnostic (store_checkout_drift) when the checkout is behind. Docs and tests were updated accordingly.

Changes

Store Checkout Drift Reporting

Layer / File(s) Summary
Git tracking drift computation
src/core/store/git.ts
Adds GitTrackingDrift and gitTrackingDrift for ahead/behind counts from @{upstream}...HEAD.
Doctor command wiring for drift
src/commands/doctor.ts
Imports drift probing and includes drift in store facts for git-backed roots.
Relationship health drift diagnostics
src/core/relationship-health.ts
Extends store health types, emits store_checkout_drift when behind, and returns drift in store health.
Tests for drift behavior
test/commands/doctor.test.ts, test/core/relationship-health.test.ts
Adds doctor and relationship-health coverage for behind, diverged, ahead-only, in-sync, and no-upstream cases.
Documentation updates
docs/agent-contract.md, docs/cli.md
Documents store.drift and the new store_checkout_drift diagnostic code.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DoctorCommand
  participant GitModule
  participant RelationshipHealth

  User->>DoctorCommand: run openspec doctor
  DoctorCommand->>GitModule: isGitRepositoryAtRoot(storeRoot)
  GitModule-->>DoctorCommand: isRepo
  DoctorCommand->>GitModule: gitTrackingDrift(storeRoot)
  GitModule-->>DoctorCommand: drift or null
  DoctorCommand->>RelationshipHealth: inspectRelationships(storeFacts with drift)
  RelationshipHealth->>RelationshipHealth: check drift.behind > 0
  RelationshipHealth-->>DoctorCommand: store health with status/drift
  DoctorCommand-->>User: JSON/human output
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main doctor change: noting when a store checkout is behind its upstream ref.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (3)
test/core/relationship-health.test.ts (1)

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

Consider splitting scenarios into separate it blocks.

This single test covers behind, singular-behind, diverged, ahead-only, in-sync, and no-drift cases. A failure partway through aborts later assertions in the same run and the failure message won't clearly identify which scenario broke. Splitting into named it.each or individual it blocks would improve failure isolation and readability, though the current coverage itself is correct.

🤖 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 `@test/core/relationship-health.test.ts` around lines 100 - 149, Split the
broad scenario coverage in inspectRelationships tests into separate named cases
so failures are isolated and easier to read. Keep the existing assertions for
behind, singular-behind, diverged, ahead-only, in-sync, and no-drift, but move
them into individual it blocks or an it.each table in
relationship-health.test.ts. Use the inspectRelationships and baseInput helpers
to preserve the same coverage while making each scenario’s intent explicit.
test/commands/doctor.test.ts (1)

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

Extract shared git-bootstrap helper to reduce duplication.

The git init/add/commit/capture-head boilerplate (Lines 216-226, 254-264, 293-299) is repeated near-verbatim across all three new tests. Consider a shared helper (e.g. createGitStoreWithBase(storeRoot, tempDir)) that returns { git, head } to keep the scenario-specific logic (upstream setup, divergence) as the only differentiator per test.

♻️ Sketch of a shared helper
function createGitStore(storeRoot: string, tempDir: string) {
  const gitEnv = { ...process.env, ...isolatedGitEnv(tempDir) };
  const git = (args: string[]) =>
    execFileSync('git', args, { cwd: storeRoot, env: gitEnv, stdio: 'ignore' });
  git(['init']);
  git(['add', '-A']);
  git(['commit', '-m', 'shared base']);
  const head = execFileSync('git', ['branch', '--show-current'], { cwd: storeRoot })
    .toString()
    .trim();
  return { git, head };
}
🤖 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 `@test/commands/doctor.test.ts` around lines 215 - 310, The three doctor tests
repeat the same git bootstrap setup, so extract that shared setup into a helper
to reduce duplication. Move the repeated git init/add/commit and current-branch
capture logic into a reusable helper near the test block, returning the shared
`git` command wrapper and `head` value, then update the `notes an
upstream-behind store checkout as info drift`, `reports diverged drift when the
checkout is both ahead and behind`, and `reports no drift for a store checkout
with no upstream tracking branch` tests to use it while keeping only the
scenario-specific upstream/divergence steps inline.
src/commands/doctor.ts (1)

62-64: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Parallelize the two independent git probes.

gitOriginUrl and gitTrackingDrift are independent async git subprocess calls but are awaited sequentially, adding avoidable latency to doctor.

♻️ Proposed refactor
     const isRepo = await isGitRepositoryAtRoot(root.path);
-    const originUrl = isRepo ? await gitOriginUrl(root.path) : null;
-    const drift = isRepo ? await gitTrackingDrift(root.path) : null;
+    const [originUrl, drift] = isRepo
+      ? await Promise.all([gitOriginUrl(root.path), gitTrackingDrift(root.path)])
+      : [null, null];
🤖 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/commands/doctor.ts` around lines 62 - 64, In doctor() the independent git
subprocess probes gitOriginUrl and gitTrackingDrift are awaited one after the
other, which adds unnecessary latency. Refactor the isRepo branch to start both
calls together and await them in parallel, keeping the existing
isGitRepositoryAtRoot check and preserving the current null behavior when the
root is not a git repository.
🤖 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.

Nitpick comments:
In `@src/commands/doctor.ts`:
- Around line 62-64: In doctor() the independent git subprocess probes
gitOriginUrl and gitTrackingDrift are awaited one after the other, which adds
unnecessary latency. Refactor the isRepo branch to start both calls together and
await them in parallel, keeping the existing isGitRepositoryAtRoot check and
preserving the current null behavior when the root is not a git repository.

In `@test/commands/doctor.test.ts`:
- Around line 215-310: The three doctor tests repeat the same git bootstrap
setup, so extract that shared setup into a helper to reduce duplication. Move
the repeated git init/add/commit and current-branch capture logic into a
reusable helper near the test block, returning the shared `git` command wrapper
and `head` value, then update the `notes an upstream-behind store checkout as
info drift`, `reports diverged drift when the checkout is both ahead and
behind`, and `reports no drift for a store checkout with no upstream tracking
branch` tests to use it while keeping only the scenario-specific
upstream/divergence steps inline.

In `@test/core/relationship-health.test.ts`:
- Around line 100-149: Split the broad scenario coverage in inspectRelationships
tests into separate named cases so failures are isolated and easier to read.
Keep the existing assertions for behind, singular-behind, diverged, ahead-only,
in-sync, and no-drift, but move them into individual it blocks or an it.each
table in relationship-health.test.ts. Use the inspectRelationships and baseInput
helpers to preserve the same coverage while making each scenario’s intent
explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9de3b19e-f46d-451c-bce6-749ad6dd7ba8

📥 Commits

Reviewing files that changed from the base of the PR and between 546224e and 16ac9a1.

📒 Files selected for processing (7)
  • docs/agent-contract.md
  • docs/cli.md
  • src/commands/doctor.ts
  • src/core/relationship-health.ts
  • src/core/store/git.ts
  • test/commands/doctor.test.ts
  • test/core/relationship-health.test.ts

Run the independent gitOriginUrl / gitTrackingDrift probes concurrently,
and extract the repeated git-bootstrap in the drift tests into a shared
initGitStore() helper. No behavior change.
@mc856

mc856 commented Jul 1, 2026

Copy link
Copy Markdown
Author

Took two of the three — git probes now run in parallel, and the drift tests share an initGitStore() helper.

Left the test-split one on purpose: keeping it a single it matches the store_remote_divergence test right next to it.

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