Skip to content

feat(cli): allow seeding worktree index from base branch#655

Merged
RaghavChamadiya merged 4 commits into
repowise-dev:mainfrom
paarthsikka:feature/seed-worktree-index
Jul 10, 2026
Merged

feat(cli): allow seeding worktree index from base branch#655
RaghavChamadiya merged 4 commits into
repowise-dev:mainfrom
paarthsikka:feature/seed-worktree-index

Conversation

@paarthsikka

Copy link
Copy Markdown
Contributor

Summary

  • Introduces the --seed-from <base-checkout-path> flag to repowise init to allow seeding a worktree index from a base branch, skipping expensive full re-indexes for heavily overlapping codebase states.
  • Implements a secure two-stage atomic copy/rename process to seed the .repowise/ state and database securely, incorporating safe fallbacks to full initializations upon invalid ancestry paths or mismatched repositories.
  • Extracts run_update() from update_cmd into a reusable module for seamless incremental update delegation, and fixes a scanner bug where git worktrees were misclassified as submodules.

Related Issues

Fixes #518

Test Plan

  • Added integration test suite TestInitSeedFrom covering the valid "slightly stale but valid base" case as well as unreachable/unrelated base fallback rejections.

  • Added unit test coverage for _is_submodule in test_scanner.py.

  • Tests pass (pytest)

  • Lint passes (ruff check .)

  • Web build passes (npm run build) (if frontend changes)

Checklist

  • My code follows the project's code style
  • I have added tests for new functionality
  • All existing tests still pass
  • I have updated documentation if needed

@repowise-bot

repowise-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

✅ Health: 7.7 (unchanged)

📋 At a glance
1 file changed health · 5 hotspots touched · 6 new findings introduced · 5 co-change pairs left out · 3 files with recent fix history.

Files & modules (2)
  • packages (3 files)
    • .../update_cmd/command.py
    • .../workspace/scanner.py
    • .../init_cmd/command.py
  • tests (2 files)
    • .../workspace/test_scanner.py
    • tests/integration/test_cli.py

⚠️ Change risk: moderate (riskier than 55% of this repo's commits · raw 9.1/10)
This change's risk is driven by:

  • more lines added than baseline
  • more scattered than baseline

🩹 Review priority (files here with the most recent bug-fix history — defects cluster, so review these first)

File Score Δ Why
.../update_cmd/command.py 1.9 → 1.8 ▼ -0.1 🔻 introduced primitive obsession · ✅ resolved function hotspot, error handling

💡 .../update_cmd/command.py: Introduce a parameter object. Group the related primitives passed in here into a dataclass so the type names tell the story and adding another field doesn't break every caller.

🔎 More signals (2)

🔥 Hotspots touched (5)

  • .../workspace/scanner.py — 1 commits/90d, 3 dependents · primary owner: Raghav Chamadiya (100%)
  • .../init_cmd/command.py — 13 commits/90d, 1 dependents · primary owner: Raghav Chamadiya (98%)
  • .../update_cmd/command.py — 9 commits/90d, 2 dependents · primary owner: Raghav Chamadiya (93%)
2 more
  • .../workspace/test_scanner.py — 1 commits/90d, 0 dependents · primary owner: Raghav Chamadiya (100%)
  • tests/integration/test_cli.py — 7 commits/90d, 1 dependents · primary owner: Swati Ahuja (38%)

🔗 Hidden coupling (2 files)

  • .../init_cmd/command.py co-changes with these files (not in this PR):
    • .../init_cmd/workspace.py (6× — 🟢 routine)
    • docs/CLI_REFERENCE.md (6× — 🟢 routine)
    • .../cli/main.py (3× — 🟢 routine)
    • docs/USER_GUIDE.md (3× — 🟢 routine)
  • .../update_cmd/command.py co-changes with .../update_cmd/persistence.py (5× — 🟢 routine) — not in this PR.

📊 Full report · ⭐ Star Repowise · 📥 Install bot · Last updated 2026-07-10 09:27 UTC
Silence on a single PR with [skip repowise] in the title · Per-repo toggle on repowise.dev/settings?tab=bot

@RaghavChamadiya

Copy link
Copy Markdown
Member

Thanks for this, the overall shape is right and the fallback validation reads well. A few things before it can go in, the first two are the blockers.

  1. The copy only carries wiki.db and state.json, but a real .repowise/ has more.

The seed builds a temp dir with just those two files and renames it over .repowise/. What gets dropped:

  • lancedb/ (the vector store). Since the delegated update only re-embeds the base..HEAD delta, the seeded worktree ends up with embeddings for only the changed files. Semantic search and decision matching silently cover a fraction of the repo, which defeats most of the point of seeding.
  • config.yaml. Provider, model, embedder, language, and exclude_patterns are lost, so run_update runs against defaults.
  • health-rules.json. Per-file severity overrides vanish, so health scoring on the worktree diverges from base.

Easiest fix is to copy the whole .repowise/ tree rather than cherry-picking two files. If you want to be selective, at least add lancedb/, config.yaml, and health-rules.json to the list.

  1. Docs. The "updated documentation" box is checked but I don't see any doc changes. Please add:
  • --seed-from to docs/CLI_REFERENCE.md under the init flags.
  • An ### Added line in packages/core/src/repowise/core/upgrade/_data/CHANGELOG.md (this is the in-app what's-new feed), and docs/CHANGELOG.md.
  • A short note in docs/WORKSPACES.md since worktrees are the workflow this targets.
  1. config_fingerprint is popped. Combined with config.yaml not being copied, the update recomputes a fingerprint against a default config and probably full-rescores anyway. Can you confirm this is intentional, and once Bump next-mdx-remote from 5.0.0 to 6.0.0 #1 is fixed, whether popping it is still needed?

  2. The .repowise.bak.* sweep runs on every init. That cleanup loop is above the if seed_from: guard, so every repowise init now globs and removes backups across all scanned repos. Please move it inside the seed branch so we're not adding a side effect to the normal path.

  3. On "atomic". For a workspace with multiple repos it isn't atomic as a unit, and if the process dies between target.rename(backup) and temp_dir.rename(target), the only real copy sits in a .bak that the next init's sweep deletes. Low risk in practice since target usually doesn't exist on a fresh worktree, but let's soften the wording in the description.

  4. Tests. The happy-path test only checks that last_sync_commit advanced and the files exist, so it would pass even if seeding carried nothing useful over. Could you add an assertion that unchanged files' pages/symbols actually survived (the delta-only reindex is the real value), and ideally one case with a provider so the embeddings and config carry-over is exercised?

Small stuff: run_update is called with progress, verbose, and no_cost_tracking hardcoded, so a scripted seed can't get JSON progress. And --include-submodules from the seeding init is silently overridden by the copied state, worth a warning if they don't match.

Happy to re-review once 1 and 2 are in.

@paarthsikka

Copy link
Copy Markdown
Contributor Author

All blocker and non-blocking feedback addressed. Switched to shutil.copytree for the full .repowise/ tree (lancedb, config.yaml, health-rules.json all carry over), removed the config_fingerprint pop since config.yaml is now copied atomically (avoids unnecessary rescore), added all four doc locations, relocated the bak sweep inside the seed branch, wired through the progress/verbose/no-cost-tracking flags, and added the include-submodules conflict warning. Happy-path test now asserts unchanged pages survived in SQLite; also added a provider test case covering config and lancedb carry-over.

@RaghavChamadiya

Copy link
Copy Markdown
Member

The red CI is the actual blocker.

The failing test across all 3 Python versions is test_update_command_uses_editor_refresh_abstraction. It does inspect.getsource(update_command.callback) and asserts "refresh_editor_project_files" is in there. Since you extracted the body into run_update() and left the callback as a thin wrapper, that string now lives in run_update, not the callback, so the assert fails. The refactor is fine, the guard test just needs to point at run_update instead. Mind updating it?

The other failure (test_add_fourth_repo_round_trip) only shows on 3.13 with a GeneratorExit error, which looks like a flake from async teardown. Since this PR touches scanner.py though, worth a re-run to confirm it's not the _is_submodule change.

@paarthsikka

Copy link
Copy Markdown
Contributor Author

Updated test_update_command_uses_editor_refresh_abstraction to inspect run_update directly instead of update_command.callback, since that's where the logic lives after the refactor. Passes locally across all versions.
Also ran test_add_fourth_repo_round_trip locally on 3.13, clean pass, confirming the GeneratorExit was a transient async teardown flake and not a regression from the _is_submodule change.

@swati510

swati510 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

The single-repo path reuses the existing last_sync_commit→HEAD incremental contract and gates on merge-base --is-ancestor, and the fallback cases are covered. Two things to fix before merge.

  1. --seed-from ignores --dry-run. The rmtree of the old backup, the copytree, and the stage-2 rename all run before dry_run is checked. So repowise init --seed-from X --dry-run deletes the target's existing .repowise when the user asked for a no-op. Could you gate the whole seed block on if not dry_run, or reject the combination up front?

  2. The workspace path isn't fully atomic. Stage 2 renames and deletes each repo's backup inside a loop, so if repo N fails, repos 1..N-1 are already committed with their backups gone, and there's no rollback. This path also has no test. It would help to make the rollback real (or document the partial-failure behavior) and add a workspace-path test.

Smaller items. init --force is forwarded as update's --full, which means something different, and a crashed seed leaves .repowise-seed-* temp dirs that the startup sweep doesn't clean, since it only clears .repowise.bak.*.

@repowise-bot repowise-bot Bot added repowise:health-decline Repowise: repository health score declined repowise:hotspot Repowise: touches a temporal hotspot file repowise:risk-medium Repowise: change-risk elevated for this repo's history labels Jul 8, 2026

@RaghavChamadiya RaghavChamadiya left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@RaghavChamadiya RaghavChamadiya merged commit 4cf51af into repowise-dev:main Jul 10, 2026
1 check passed
@vrajpal-jhala

vrajpal-jhala commented Jul 10, 2026

Copy link
Copy Markdown

This is great, thanks for working on it everyone! I'm gonna try it out.

RaghavChamadiya added a commit that referenced this pull request Jul 10, 2026
repowise init and repowise update inside a linked worktree now detect the
base checkout via git rev-parse --git-common-dir and seed the worktree's
index from it automatically, then catch up incrementally. --seed-from
becomes an explicit override; --no-seed forces a cold init.

The seed logic moves from init_cmd into a shared cli/worktree.py used by
both commands, and gains a repository-identity adoption step: the copied
wiki.db row is re-pointed (name + local_path) at the worktree, since
upsert_repository matches by local_path and the first update otherwise
minted a second repository row and split the index in two.

Also fixes the workspace auto-detect notice, whose [workspace] prefix was
silently consumed as console markup, and repairs the #655 seed tests,
which were red on main CI (raw git commits without identity) and on
Windows (default-branch assumption, unclosed sqlite handles breaking
worktree cleanup, REPOWISE_DB_URL fixture routing both repos into one db,
line-wrapped substring assertions). The new-file page assertion is
dropped: update-time coverage budgeting prevents it repo-wide (#746).

Docs: new WORKTREES.md, WORKSPACES FAQ, CLI_REFERENCE, README,
QUICKSTART, changelogs.
@paarthsikka

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the merge, @RaghavChamadiya, @vrajpal-jhala and @swati510! @swati510 I saw your feedback regarding the --dry-run bypass. Since this is already merged, I'd love to open a quick follow-up PR to address those exact edge cases so we don't leave any technical debt behind.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

repowise:health-decline Repowise: repository health score declined repowise:hotspot Repowise: touches a temporal hotspot file repowise:risk-medium Repowise: change-risk elevated for this repo's history

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Seed worktree index from an existing base-branch checkout

4 participants