Skip to content

feat(tags): resolve project root from a workspace marker file#148

Open
black-desk wants to merge 1 commit into
tickernelz:mainfrom
black-desk:feat/project-root-override
Open

feat(tags): resolve project root from a workspace marker file#148
black-desk wants to merge 1 commit into
tickernelz:mainfrom
black-desk:feat/project-root-override

Conversation

@black-desk

@black-desk black-desk commented Jun 29, 2026

Copy link
Copy Markdown

Problem

Project identity is derived from the enclosing git repository (git-common-dir, then remote origin URL, then the directory path), which splits memory into one shard per physical git repo. That is wrong for multi-repo workspaces managed by orchestrators such as Google repo that lay out many git repositories under a single workspace root: each sub-repository gets its own isolated memory store instead of sharing one project-wide store.

Solution: directory-driven workspace marker

Drop an empty .opencode-mem-project marker file at the workspace root. Every session started anywhere underneath resolves onto that root.

The marker is looked up by walking up from the working directory that every code path already passes in — the plugin's ctx.directory, the web API's process.cwd(). Identity is therefore bound to where the session runs, not to ambient process state, so it stays consistent regardless of which process serves a given request.

my-workspace/
├── .opencode-mem-project   ← workspace root
├── kernel/                 (own git repo)
├── userspace/              (own git repo)
└── tools/                  (own git repo)
touch ~/my-workspace/.opencode-mem-project

Behavior

  • The walk lives in getProjectRoot / getProjectIdentity (the lowest-level entry points), so plugin load, auto-capture, user-profile learning, compaction, and the web API all pick it up with no per-callsite changes.
  • The marker takes precedence over git detection. When it hits, the underlying sub-repo's git remote is left unset (it would describe only one nested repo and be misleading for the grouped workspace).
  • Without a marker, behaviour is unchanged — existing git-based identity is identical.
  • Innermost marker wins when several are nested on the ancestry.
  • The marker lookup runs exactly once per getProjectTagInfo call; the git-only fallbacks are factored into private helpers so root and identity derive from a single ancestry walk.

Testing

New cases in tests/project-scope.test.ts cover: sibling git repos collapse onto one marker root; deep nested paths resolve up to the marker; the marker wins over an inner git repo and drops its remote; findMarkerProjectRoot null / ancestor / self cases; plus the backward-compatible no-marker behaviour (the existing worktree and nested-path tests still pass).

tsc --noEmit, prettier, and bun run build are clean. The pre-commit hook (typecheck + lint-staged) passes.

Copilot AI review requested due to automatic review settings June 29, 2026 13:09

Copilot AI 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.

Pull request overview

Adds an opt-in configuration knob to force project identity/root to a user-specified workspace root, enabling shared memory across multi-repo workspaces (e.g., repo-managed trees) by bypassing git-derived identity.

Changes:

  • Introduces projectRootOverride (and OPENCODE_MEM_PROJECT_ROOT env var precedence) with canonical absolute path resolution in config build.
  • Short-circuits project root/identity (and git repo URL detection) when the override is set.
  • Adds tests and documentation describing the override behavior and usage (including a direnv example).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/project-scope.test.ts Adds coverage for override behavior, env precedence, empty handling, and path normalization.
src/services/tags.ts Applies override to project root/identity resolution and skips git URL detection under override.
src/config.ts Adds new config field, resolves/normalizes it once in buildConfig, and exports buildConfig for testing.
README.md Documents projectRootOverride and OPENCODE_MEM_PROJECT_ROOT usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/project-scope.test.ts Outdated
@black-desk black-desk marked this pull request as draft June 29, 2026 13:50
Project identity is normally derived from the enclosing git repository
(git-common-dir, then remote origin URL, then the directory path), which
splits memory into one shard per physical git repo. That is wrong for
multi-repo workspaces managed by orchestrators such as Google `repo` that
lay out many git repositories under a single workspace root: each
sub-repository gets its own isolated memory store instead of sharing one
project-wide store.

The obvious fix is a manual override, but carrying that override in an
environment variable or a global config value is unsound here. opencode-mem
runs across multiple opencode processes that share a single web server and a
single storage database, and only some of those processes carry a given env
var -- the one launched from a direnv shell does; the long-lived daemon
launched by systemd, and sessions started elsewhere, do not. The web server
is a singleton whose owner is non-deterministic: processes detect
EADDRINUSE and take it over on a health-check loop, so project identity flaps
depending on which process happens to own the port. A process-scoped value
cannot back a shared identity.

Make identity directory-driven instead. Drop an empty `.opencode-mem-project`
marker file at the workspace root; every session started anywhere underneath
then resolves onto that root. The marker is looked up by walking up from the
working directory that every code path already passes in (the plugin's
ctx.directory, the web API's process.cwd()), so identity is bound to where
the session runs, not to which process it runs in -- stable across the whole
process pool regardless of env vars or web-server ownership.

The walk lives in getProjectRoot/getProjectIdentity, the lowest-level entry
points, so plugin load, auto-capture, user-profile learning, compaction and
the web API all pick it up without per-callsite changes. The marker takes
precedence over git detection; when it hits, the underlying sub-repo's git
remote is intentionally left unset, since it would describe only one nested
repository and be misleading for the grouped workspace. Without a marker,
behaviour is unchanged (the existing worktree and nested-path tests still
pass). The marker lookup is resolved exactly once per getProjectTagInfo call:
the git-only fallbacks are factored into private helpers so root and identity
derive from a single ancestry walk instead of three.

Tests cover the collapse of sibling git repos onto one marker root, deep
nested resolution, the marker winning over an inner git repo and dropping its
remote, the innermost-marker-wins case, the null case, and the backward-
compatible no-marker behaviour. The marker is documented in the README,
including why the env-var/config approach was rejected.

Typical usage at the workspace root:

  touch ~/my-workspace/.opencode-mem-project

Assisted-by: opencode:glm-5.2
Signed-off-by: Chen Linxuan <me@black-desk.cn>
@black-desk black-desk force-pushed the feat/project-root-override branch from 6b102f5 to a850cc5 Compare June 29, 2026 14:41
@black-desk black-desk changed the title feat(config): support manual project root override feat(tags): resolve project root from a workspace marker file Jun 29, 2026
@black-desk black-desk requested a review from Copilot June 29, 2026 14:44

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@black-desk black-desk marked this pull request as ready for review June 29, 2026 14:45
@black-desk black-desk requested a review from Copilot June 29, 2026 19:50

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread src/services/tags.ts
Comment on lines +168 to +171
export function getProjectIdentity(directory: string): string {
const markerRoot = findMarkerProjectRoot(directory);
return markerRoot ? `path:${markerRoot}` : getGitProjectIdentity(directory);
}
Comment thread src/services/tags.ts
Comment on lines +207 to 212
// When a marker pins the project root, any git remote belongs to a single
// nested sub-repo and would be misleading for the grouped workspace, so
// leave it unset.
const gitRepoUrl = markerRoot ? null : getGitRepoUrl(directory);
const projectIdentity = markerRoot ? `path:${markerRoot}` : getGitProjectIdentity(projectRoot);

Comment on lines +149 to +159
it("findMarkerProjectRoot returns null without a marker, the ancestor when present", () => {
const { workspaceDir, repoA } = createMultiRepoWorkspace();

expect(findMarkerProjectRoot(repoA)).toBeNull();

writeFileSync(join(workspaceDir, ".opencode-mem-project"), "");
expect(findMarkerProjectRoot(repoA)).toBe(workspaceDir);
// A session started exactly at the marker root still resolves to itself.
expect(findMarkerProjectRoot(workspaceDir)).toBe(workspaceDir);
});
});
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.

2 participants