Skip to content

Centralize project data storage under ~/.config/sidecar#197

Merged
marcus merged 12 commits intomarcus:mainfrom
EugeneOsipenko:feature/centralized-project-storage
Feb 27, 2026
Merged

Centralize project data storage under ~/.config/sidecar#197
marcus merged 12 commits intomarcus:mainfrom
EugeneOsipenko:feature/centralized-project-storage

Conversation

@EugeneOsipenko
Copy link
Contributor

Summary

  • Adds internal/projectdir package for slug-based project directory resolution under ~/.config/sidecar/projects/
  • Moves worktree state files, workspace plugin paths, and tdroot to centralized storage instead of per-project .sidecar/ directories
  • Adds migration logic for legacy .sidecar/ project files to the new centralized location
  • Creates worktrees under the centralized project data directory

Motivation

Previously, sidecar stored project-specific data in .sidecar/ directories within each project root. This polluted project directories and required .gitignore modifications. This PR centralizes all project data under ~/.config/sidecar/projects/<slug>/, keeping project directories clean.

Test plan

  • Unit tests for projectdir package (slug generation, directory resolution)
  • Unit tests for migration logic (legacy file detection and migration)
  • Manual testing: fresh project setup creates data under ~/.config/sidecar/projects/
  • Manual testing: existing .sidecar/ data migrates on first run

EugeneOsipenko and others added 9 commits February 22, 2026 10:55
…y resolution

Introduces a new package that centralizes project-specific sidecar data
into ~/.config/sidecar/projects/<slug>/ instead of writing into project
directories. Includes Resolve() for project root lookup/creation,
WorktreeDir() for worktree subdirectories, slug collision handling
(slug-2, slug-3, etc.), and meta.json-based reverse lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all save/load functions (saveBaseBranch, loadBaseBranch,
loadTaskLink, saveAgentType, loadAgentType, savePRURL, loadPRURL)
to use projectdir.WorktreeDir() instead of writing directly to
worktree directories. Rename file constants from dot-prefixed names
(.sidecar-task, etc.) to plain names (task, agent, pr, base) since
they now live in a dedicated directory under ~/.config/sidecar/projects/.

Update all callers across worktree.go, fetch_pr.go, update.go, and
agent.go to pass projectRoot as the first argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update shell manifest, prompts config, and agent launcher script paths
to use projectdir.Resolve/WorktreeDir instead of .sidecar/ in worktree.
This is part of the migration from per-project .sidecar/ directories to
centralized ~/.config/sidecar/projects/<slug>/ storage.

- Shell manifest: Init() resolves via projectdir.Resolve(ProjectRoot)
- Prompts: LoadPrompts uses projectdir.Resolve internally for project config
- Agent launcher: writeAgentLauncher uses projectdir.WorktreeDir for start.sh
- Callers updated to pass ProjectRoot instead of WorkDir where appropriate
- Tests updated to use config.SetTestConfigPath for isolated projectdir resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ory filter

Sidecar no longer writes files to the project directory, so the
ensureSidecarGitignore() function and .sidecar directory filtering
in the file browser are no longer needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CreateTDRoot now writes td-root to the centralized project directory
(~/.config/sidecar/projects/<slug>/) instead of a .td-root file in
the worktree. ResolveTDRoot checks the centralized location first,
falling back to legacy .td-root for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rage

Add projectdir.Migrate() to move legacy .sidecar/, .td-root, and per-worktree
state files (.sidecar-task, .sidecar-agent, etc.) to ~/.config/sidecar/projects/.
Wire migration into workspace plugin Init() before shell manifest load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Project-level migration runs in Init() (before manifest load).
Worktree-level migration runs once on first RefreshDoneMsg when
worktree paths are known.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix user-facing prompt config hints to point to centralized path
- Remove .sidecar/ from initRepoGitignoreEntries
- Update comments referencing old .sidecar/shells.json location

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Worktrees are now created at ~/.config/sidecar/projects/<slug>/worktrees/<name>/
instead of as sibling directories next to the project root. This keeps all
sidecar-managed state in one place and avoids polluting the user's project
parent directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marcus
Copy link
Owner

marcus commented Feb 22, 2026

Hey @EugeneOsipenko! Starling here (AI assistant on the project). 👋

Centralizing project data away from .sidecar/ is a great quality-of-life improvement — no more .gitignore pollution and project directories stay clean. The slug-based layout under ~/.config/sidecar/projects/ is a natural fit given how other tools (LSPs, dev tools) handle this.

A few things that stand out as worth Marcus paying attention to on review:

  • Migration logic — the legacy-to-centralized migration path is the most fragile part of any storage refactor. Would be good to confirm the migration is idempotent and handles edge cases (missing files, partial migrations, symlinks in old paths).
  • Worktree creation under centralized dir — if worktrees are being created under ~/.config/sidecar/projects/<slug>/, is the path surfaced to the user anywhere? Some workflows depend on knowing the worktree disk location.
  • Two manual test cases unchecked — fresh setup and migration path. Would be great to get those verified before merge.

Flagging for @marcus to review. This touches core data layout so want to make sure he's in the loop on the approach before it lands. ✦

@Amolith
Copy link
Contributor

Amolith commented Feb 22, 2026

Worktree creation under centralized dir — if worktrees are being created under ~/.config/sidecar/projects//, is the path surfaced to the user anywhere? Some workflows depend on knowing the worktree disk location.

I'd very much prefer sidecar not adopt this approach, or to somehow make it optional! I lay my repositories and worktrees out as follows and sidecar's existing sibling worktree approach (even though the project- prefix is unnecessary with my layout) is one of the biggest reasons I adopted it over other worktree tools like amux.

My approach

❯ exa --tree -L 1 sidecar/ plexus/
sidecar
├── main
├── sidecar-fix-fish-compatibility
├── sidecar-integrate-amp
├── sidecar-integrate-crush
└── sidecar-integrate-octofriend
plexus
├── main
├── plexus-kimi-code-quota-checker
└── plexus-minimax-coding-quota-checker

You end up with all the worktrees related to a project in that project's folder right next to each other. Following is what current sidecar expects, which works well enough for my layout.

❯ ls -1 repos/
plexus
plexus-kimi-code-quota-checker
plexus-minimax-coding-quota-checker
sidecar
sidecar-fix-fish-compatibility
sidecar-integrate-amp
sidecar-integrate-crush
sidecar-integrate-octofriend

People commonly clone projects with git clone https://github.com/marcus/sidecar.git, but using the more hierarchical setup just requires git clone https://github.com/marcus/sidecar.git sidecar/main instead. Then you cd sidecar/main and can create worktrees with git worktree add ../fancy-new-feature. You end up with a nice, convenient hierarchy with just one folder per project and all of a project's worktrees stay in that project's one folder, rather than ending up with a mix of all projects and all worktrees in the same folder (what most people do and what sidecar currently expects) or all projects in one folder and all worktrees scattered across your filesystem wherever the worktree tool you're currently using decided to put them. And if you happen to use multiple worktree tools that each have their own opinions about where to put worktrees, good luck! Now you've got some worktrees over in ~/.tool/projects/worktrees, some in ~/.local/share/other-tool/worktrees/, some in .git/third-tool/wt/ and it's just maddening. I'd honestly end up forking sidecar to restore the current functionality to avoid that mess :/

The existing project- prefix makes sense for the common default of throwing everything in one directory, but I'd much prefer some configurable way to tell sidecar how and where to create worktrees than moving them all to some hidden directory that's incredibly cumbersome to type out when I want to go there myself and do things.

@Amolith
Copy link
Contributor

Amolith commented Feb 22, 2026

To be clear, I think moving sidecar and td metadata elsewhere would be great! My only concern with this PR is how worktrees are handled.

@marcus
Copy link
Owner

marcus commented Feb 22, 2026

Hey @Amolith! Starling here. 👋

Thanks for the detailed breakdown — the hierarchical worktree layout (sidecar/main, sidecar/<branch>) is a really clean approach and I can see why moving worktrees to ~/.config/sidecar/projects/<slug>/ would be a dealbreaker for that workflow. The concern is valid: scattering worktrees to a hidden dot-dir trades one problem (project dir pollution) for another (you can't find your own worktrees without the tool).

Flagging this for @marcus as a design question to resolve before PR #197 lands. The metadata-centralization part of this PR is clearly good; the worktree placement behavior is the piece that needs a decision. ✦

@mcowger
Copy link

mcowger commented Feb 22, 2026

I'm in agreement with @Amolith here.

This change would basically be a deal killer for me as well.

@marcus
Copy link
Owner

marcus commented Feb 22, 2026

Thanks @mcowger — noted, another strong signal on the worktree placement concern. Logged for @marcus. ✦

EugeneOsipenko and others added 2 commits February 24, 2026 16:06
Keep only project-level metadata centralization (shells.json, config.json,
td-root, prompts) in ~/.config/sidecar/projects/<slug>/. Worktree creation
stays as sibling directories and per-worktree state files (.sidecar-task,
.sidecar-agent, .sidecar-pr, .sidecar-base, .sidecar-start.sh) remain in
the worktree directories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move per-worktree state files (task, agent, pr, base, start.sh) to
~/.config/sidecar/projects/<slug>/worktrees/<wt>/ while keeping worktree
creation as sibling directories. Remove all migration logic for legacy
file locations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mcowger
Copy link

mcowger commented Feb 24, 2026

I like the adjustment here @EugeneOsipenko

Keeping the sidecar - specific stuff tucked away but the worktrees in place is solid.

I would suggest that the state files should be in $XDG_STATE_HOME rather than .config.

The XDG spec says that data files generated on behalf of the user but that are not user-defined/created belong there.

Similarly, config files should be in $XDG_CONFIG_HOME (which defaults to ~/.config)

@marcus
Copy link
Owner

marcus commented Feb 24, 2026

Hey @mcowger! Great point on the XDG distinction — this is exactly the kind of spec detail that's easy to overlook.

For anyone following along, the XDG Base Directory spec separates these concerns:

  • $XDG_CONFIG_HOME (default: ~/.config) — user-defined configuration: settings, preferences, things the user deliberately wrote
  • $XDG_STATE_HOME (default: ~/.local/state) — application-generated state data: last-run timestamps, session tracking, workspace state that the app generates on the user's behalf
  • $XDG_DATA_HOME (default: ~/.local/share) — persistent app data: databases, caches, larger stored data

For sidecar specifically:

  • Project configuration that the user creates → $XDG_CONFIG_HOME/sidecar/
  • Worktree state, session tracking, plugin state → $XDG_STATE_HOME/sidecar/ (mcowger's suggestion)
  • Potentially: the project data DB → $XDG_DATA_HOME/sidecar/ if it grows

Flagging this for @EugeneOsipenko and @marcus to consider before merge — it's a relatively small change in the path routing but gets the semantics right and makes sidecar a better XDG citizen. Whether to thread all three paths or just config + state is a call for @marcus to make.

Thanks for catching this! ✦

Per XDG Base Directory Specification, project state files (meta.json,
worktree data, td-root) belong in $XDG_STATE_HOME/sidecar/ (defaults
to ~/.local/state/sidecar/) rather than ~/.config/sidecar/. Config
files remain in ~/.config/sidecar/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@EugeneOsipenko
Copy link
Contributor Author

I like the adjustment here @EugeneOsipenko

Keeping the sidecar - specific stuff tucked away but the worktrees in place is solid.

I would suggest that the state files should be in $XDG_STATE_HOME rather than .config.

The XDG spec says that data files generated on behalf of the user but that are not user-defined/created belong there.

Similarly, config files should be in $XDG_CONFIG_HOME (which defaults to ~/.config)

Good call, changed, thanks!

marcus added a commit that referenced this pull request Feb 27, 2026
@marcus marcus merged commit ac71e54 into marcus:main Feb 27, 2026
@marcus
Copy link
Owner

marcus commented Feb 27, 2026

Merged to main! 🎉

Thanks @EugeneOsipenko for this excellent contribution — centralizing project data under XDG-compliant paths is a big quality-of-life improvement. The slug-based resolution and collision handling are well designed.

We added migration logic on top of your work (via internal/migration) to ensure existing users with legacy .sidecar/ directories get a smooth transition. All tests passing across the board.

Appreciate the thorough PR description and test coverage. Welcome to the project! 🪶

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.

4 participants