Skip to content

fix(version): walk up to find @automagik/genie package.json (#1464) - #1486

Merged
namastex888 merged 1 commit into
devfrom
fix/version-resolver-worktree-aware
Apr 29, 2026
Merged

namastex888 merged 1 commit into
devfrom
fix/version-resolver-worktree-aware

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Summary

Closes #1464 — genie --version reported a stale version on dogfood boxes where the binary lived inside a worktree:

  • worktree package.json: 4.260428.19
  • genie --version: 4.260428.16

Root cause (dog-fooder analysis)

Per evidence in state/evidence/1474-d3976b96-20260428T235714Z/1464:

The previous resolver tried fixed .. and ../.. paths from import.meta.dir and accepted the FIRST package.json that existed. For a binary at <repo>/.worktrees/<name>/dist/genie.js:

  • ../.. candidate → <repo>/package.json (parent repo — wrong)
  • .. candidate → <repo>/.worktrees/<name>/package.json (our own — right)

The ../.. candidate is checked first and wins, returning the parent repo's version.

Fix

Walk UP from the binary's location and return the version of the FIRST package.json whose name === "@automagik/genie". The parent-repo package.json is correctly skipped because the worktree's own package.json (with matching name) is hit first on the way up.

Two-pass strategy:

  1. Walk up looking for name === '@automagik/genie' (primary, worktree-aware)
  2. Walk up again accepting any package.json with a version field (fallback for tarballs / detached envs where name is missing)

Bounded by MAX_WALK_DEPTH=10 + filesystem-root stop.

Empirical verification

# In worktree (.worktrees/fix-version-resolver/, package.json says 4.260428.19)
$ bun -e "import('./src/lib/version.js').then(m => console.log(m.VERSION))"
4.260428.19   ✓

# In parent repo (package.json says 4.260428.16)
$ bun -e "import('./src/lib/version.ts').then(m => console.log(m.VERSION))"
4.260428.16   ✓

Validation

  • 6/6 src/lib/version.test.ts pass (new file): VERSION non-empty + non-fallback, matches closest @automagik/genie package.json, source contract assertions for PACKAGE_NAME / MAX_WALK_DEPTH / two-pass order / root-stop
  • biome clean
  • tsc clean

Evidence credit

dog-fooder (the bare genie/dog-fooder instance) pinned the smoking gun in src/lib/version.ts with full file path enumeration. Full audit trail in evidence dir cited above.

@coderabbitai

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a04d47f6-5693-4551-a289-4e492e7dea11

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/version-resolver-worktree-aware

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 and usage tips.

Closes #1464 — `genie --version` reported a stale version on dogfood
boxes where the binary lived inside a worktree:
- worktree package.json: 4.260428.19
- genie --version: 4.260428.16

Root cause (per dog-fooder verdict in
state/evidence/1474-d3976b96-20260428T235714Z/1464):
The previous resolver tried fixed `..` and `../..` paths from
import.meta.dir and accepted the FIRST package.json that existed. For a
binary at <repo>/.worktrees/<name>/dist/genie.js, the `../..` candidate
resolved to <repo>/package.json (the *parent* repo, often a different
version) BEFORE the `..` candidate would have found the worktree's own
<repo>/.worktrees/<name>/package.json.

Fix: walk UP from the binary's location and return the version of the
FIRST package.json whose `name === "@automagik/genie"`. This guarantees
we identify our own package no matter how deep the binary lives — the
parent-repo package.json is correctly skipped because the worktree's
package.json (with matching name) is hit first on the way up.

Two-pass strategy:
  1. Walk up looking for `name === "@automagik/genie"` (primary)
  2. Walk up again accepting any package.json with a `version` field
     (fallback for tarballs / detached envs where `name` is missing)

Bounded by MAX_WALK_DEPTH=10 + filesystem-root stop.

Validation:
- 6/6 src/lib/version.test.ts pass (new file): VERSION non-empty, matches
  closest @automagik/genie package.json walking up from this file, source
  contract assertions for PACKAGE_NAME, MAX_WALK_DEPTH, two-pass order,
  and root-stop semantics
- Empirical: bun -e "import('./src/lib/version.js')..." in this worktree
  reports 4.260428.19; from the parent repo reports parent's 4.260428.16
- biome clean, tsc clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a worktree-aware version resolver that performs an upward directory walk to locate the @automagik/genie package.json, resolving issue #1464. The changes include a new test suite that verifies the resolver's logic. Feedback suggests optimizing the implementation by combining the two-pass directory walk into a single pass to reduce redundant filesystem I/O. Additionally, it is recommended to refactor the tests to focus on behavioral verification using a mocked filesystem rather than asserting against the source code's text, which is currently brittle.

Comment thread src/lib/version.ts
Comment on lines +53 to 78
let current = startDir;
for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
const candidate = resolve(current, 'package.json');
const pkg = readPackageJson(candidate);
if (pkg?.name === PACKAGE_NAME && pkg.version) {
return pkg.version;
}
const parent = dirname(current);
if (parent === current) break; // reached filesystem root
current = parent;
}

// Fallback: same walk but accept ANY package.json with a version field.
// This catches edge cases like running directly from a tarball where
// `name` may be unset, and preserves the prior resolver's lenience.
current = startDir;
for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
const candidate = resolve(current, 'package.json');
const pkg = readPackageJson(candidate);
if (pkg?.version) return pkg.version;
const parent = dirname(current);
if (parent === current) break;
current = parent;
}

return FALLBACK_VERSION;

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 directory walk can be optimized into a single pass. The current implementation performs two separate loops, which results in redundant filesystem operations (reading and parsing the same package.json files twice). By tracking a fallback version during the first walk, you can achieve the same prioritization logic with half the filesystem I/O and less code duplication. Note that using a hardcoded limit like MAX_WALK_DEPTH is acceptable here to prevent excessive I/O.

  let fallbackVersion: string | null = null;
  let current = startDir;
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
    const candidate = resolve(current, 'package.json');
    const pkg = readPackageJson(candidate);
    if (pkg?.version) {
      // Priority 1: The first package.json that matches our name (worktree-aware)
      if (pkg.name === PACKAGE_NAME) return pkg.version;
      // Priority 2: The closest package.json that has a version (fallback)
      fallbackVersion ??= pkg.version;
    }
    const parent = dirname(current);
    if (parent === current) break; // reached filesystem root
    current = parent;
  }

  return fallbackVersion ?? FALLBACK_VERSION;
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.

Comment thread src/lib/version.test.ts
Comment on lines +52 to +70
test('source identifies @automagik/genie as the package name to match', () => {
expect(versionSource).toContain("PACKAGE_NAME = '@automagik/genie'");
});

test('source bounds the walk depth (no runaway scans)', () => {
expect(versionSource).toContain('MAX_WALK_DEPTH');
});

test('source matches by package name FIRST, then falls back to any version', () => {
// Two-pass walk: name-matched, then any-package-with-version
const namePassIdx = versionSource.indexOf('pkg?.name === PACKAGE_NAME');
const fallbackPassIdx = versionSource.indexOf('Fallback');
expect(namePassIdx).toBeGreaterThan(0);
expect(fallbackPassIdx).toBeGreaterThan(namePassIdx);
});

test('source explicitly stops at filesystem root', () => {
expect(versionSource).toMatch(/parent === current/);
});

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

These tests are highly brittle because they assert against the raw source code of version.ts using string matching and index comparisons. This approach couples the tests to implementation details (like specific variable names, comments, and the two-pass structure), which will cause them to fail during routine refactoring even if the logic remains correct.

Consider replacing these with behavioral tests, for example by using a mocked filesystem to verify that the resolver correctly prioritizes the named package over a generic one at different depths.

@namastex888
namastex888 force-pushed the fix/version-resolver-worktree-aware branch from d152400 to c8f3383 Compare April 29, 2026 00:44
@namastex888
namastex888 merged commit d9f4fa2 into dev Apr 29, 2026
9 of 11 checks passed
@automagik-genie
automagik-genie deleted the fix/version-resolver-worktree-aware branch September 25, 2026 04:52
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