Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/PLUGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,10 @@ Which repository a session belongs to (`plugin/paths.py`, `plugin/repos.py`):
`gitdir:` file naming one that exists -- and the walk up from a session's directory stops at
the home directory, so a versioned (or stray, empty) `~/.git` is the repository of sessions
started in `~` and of nothing below it. `GIT_CEILING_DIRECTORIES` is honoured as git does.
`$TOKENECONOMIZER_REPO`, when set, is the repository's remote URL where git names none: a
session in a directory without git metadata (an export of a checkout in a container, a new
folder per run), or in a checkout without an `origin`, is keyed as a clone of that remote and
shares what was learned there. A checkout's own `origin` wins.
* **One repository per session.** A hook's `cwd` is where the agent is now (Claude Code's
follows its `cd`), so the first hook of a session pins it (`sessions/<host>/<id>.json`) and
every later hook, and the fold it hands off, uses the repository it started in -- where the
Expand Down
56 changes: 56 additions & 0 deletions tests/test_repo_key_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""`$TOKENECONOMIZER_REPO`: the repository of a session where git names none."""
import os

from tokeneconomizer.plugin import paths


def git_repo(root, remote):
os.makedirs(os.path.join(root, ".git"), exist_ok=True)
with open(os.path.join(root, ".git", "HEAD"), "w") as fh:
fh.write("ref: refs/heads/main\n")
with open(os.path.join(root, ".git", "config"), "w") as fh:
fh.write("[core]\n\trepositoryformatversion = 0\n"
+ (f'[remote "origin"]\n\turl = {remote}\n' if remote else ""))
return root


def make_env(tmp_path, **extra):
home = tmp_path / "home"
home.mkdir(exist_ok=True)
return dict({"TOKENECONOMIZER_HOME": str(tmp_path / "te"), "HOME": str(home)}, **extra)


def test_a_named_remote_keys_an_export_without_git_metadata_as_its_clones(tmp_path):
"""An export of a checkout in a container, or a new folder per run, holds no `.git`; the
variable names its repository, so it shares its clones' key."""
env = make_env(tmp_path)
named = make_env(tmp_path, TOKENECONOMIZER_REPO="git@github.com:Microsoft/TokenEconomizer.git")
clone = git_repo(str(tmp_path / "TokenEconomizer"), "https://github.com/microsoft/TokenEconomizer")
export = tmp_path / "runs" / "job-1" / "work"
export.mkdir(parents=True)
r = paths.repo_identity(str(export), named)
assert r.key == paths.repo_identity(clone, env).key
assert r.how == paths.BY_REMOTE and r.root == str(export)
assert r.label == "microsoft/tokeneconomizer" and r.remote == "github.com/microsoft/tokeneconomizer"
# Another run's folder gets the same key; without the variable each folder is its own.
other = tmp_path / "runs" / "job-2" / "work"
other.mkdir(parents=True)
assert paths.repo_identity(str(other), named).key == r.key
plain = paths.repo_identity(str(other), env)
assert plain.how == paths.BY_DIR and plain.key != r.key


def test_an_empty_value_changes_nothing_and_a_checkouts_own_remote_wins(tmp_path):
env = make_env(tmp_path)
export = tmp_path / "work"
export.mkdir()
assert paths.repo_identity(str(export), make_env(tmp_path, TOKENECONOMIZER_REPO="")).how == paths.BY_DIR
named = make_env(tmp_path, TOKENECONOMIZER_REPO="https://github.com/microsoft/TokenEconomizer")
# A checkout keeps the repository its `origin` names: a value left set in a shell must not
# merge every repository the person opens into one.
elsewhere = git_repo(str(tmp_path / "elsewhere"), "https://example.org/team/elsewhere.git")
assert paths.repo_identity(elsewhere, named) == paths.repo_identity(elsewhere, env)
# A checkout without an `origin` (a fresh `git init` in a sandbox) takes the named one.
fresh = git_repo(str(tmp_path / "fresh"), None)
assert paths.repo_identity(fresh, env).how == paths.BY_PATH
assert paths.repo_identity(fresh, named).key == paths.repo_identity(str(export), named).key
13 changes: 12 additions & 1 deletion tokeneconomizer/plugin/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from dataclasses import dataclass

HOME_ENV = "TOKENECONOMIZER_HOME"
# Names the repository of a session whose directory holds no git metadata, by its remote URL.
REPO_ENV = "TOKENECONOMIZER_REPO"


def state_root(env=None):
Expand Down Expand Up @@ -234,10 +236,19 @@ def repo_identity(cwd, env=None):
it has one (every clone, worktree and re-clone), else the real path of its main checkout (a
linked worktree shares its clone's), else -- outside any checkout -- the directory itself.
The key's readable part is the repository's name (the remote's last component, or the main
checkout's folder), not the folder one particular checkout sits in."""
checkout's folder), not the folder one particular checkout sits in.

`$TOKENECONOMIZER_REPO`, when set, is the repository's remote URL where git names none: a
session in a directory that holds no git metadata -- an export of a checkout in a container
or a sandbox, a fresh folder per run -- or in a checkout without an `origin` is then keyed
as a clone of that remote is, and shares what was learned there. A checkout's own `origin`
wins, so a value left set in a shell never merges another repository's state into it."""
env = os.environ if env is None else env
found = find_repo_root(cwd, env)
root = found or os.path.abspath(cwd or os.getcwd())
remote = normalize_remote(origin_url(root)) if found else ""
if not remote:
remote = normalize_remote(env.get(REPO_ENV) or "")
if remote:
how, canon, name = BY_REMOTE, remote, remote.rstrip("/").rsplit("/", 1)[-1]
label = "/".join(remote.split("/")[-2:])
Expand Down
Loading