timberline 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- timberline-0.1.0/.gitignore +9 -0
- timberline-0.1.0/.timberline.toml +30 -0
- timberline-0.1.0/Makefile +29 -0
- timberline-0.1.0/PKG-INFO +112 -0
- timberline-0.1.0/README.md +100 -0
- timberline-0.1.0/pyproject.toml +42 -0
- timberline-0.1.0/tests/__init__.py +0 -0
- timberline-0.1.0/tests/conftest.py +24 -0
- timberline-0.1.0/tests/test_agent.py +116 -0
- timberline-0.1.0/tests/test_cli.py +249 -0
- timberline-0.1.0/tests/test_config.py +54 -0
- timberline-0.1.0/tests/test_display.py +52 -0
- timberline-0.1.0/tests/test_env.py +84 -0
- timberline-0.1.0/tests/test_git.py +116 -0
- timberline-0.1.0/tests/test_init_deps.py +105 -0
- timberline-0.1.0/tests/test_names.py +37 -0
- timberline-0.1.0/tests/test_shell.py +94 -0
- timberline-0.1.0/tests/test_state.py +109 -0
- timberline-0.1.0/tests/test_submodules.py +18 -0
- timberline-0.1.0/tests/test_types.py +126 -0
- timberline-0.1.0/tests/test_worktree.py +108 -0
- timberline-0.1.0/timberline/__init__.py +1 -0
- timberline-0.1.0/timberline/__main__.py +4 -0
- timberline-0.1.0/timberline/agent.py +105 -0
- timberline-0.1.0/timberline/cli.py +775 -0
- timberline-0.1.0/timberline/config.py +150 -0
- timberline-0.1.0/timberline/display.py +139 -0
- timberline-0.1.0/timberline/env.py +74 -0
- timberline-0.1.0/timberline/git.py +137 -0
- timberline-0.1.0/timberline/init_deps.py +150 -0
- timberline-0.1.0/timberline/names.py +166 -0
- timberline-0.1.0/timberline/shell.py +138 -0
- timberline-0.1.0/timberline/state.py +110 -0
- timberline-0.1.0/timberline/submodules.py +51 -0
- timberline-0.1.0/timberline/types.py +93 -0
- timberline-0.1.0/timberline/worktree.py +161 -0
- timberline-0.1.0/uv.lock +272 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[timberline]
|
|
2
|
+
worktree_dir = ".tl"
|
|
3
|
+
branch_template = "{user}/{type}/{name}"
|
|
4
|
+
user = "nc9"
|
|
5
|
+
default_type = "feature"
|
|
6
|
+
base_branch = "main"
|
|
7
|
+
naming_scheme = "minerals"
|
|
8
|
+
default_agent = "claude"
|
|
9
|
+
|
|
10
|
+
[timberline.init]
|
|
11
|
+
init_command = "uv sync"
|
|
12
|
+
auto_init = true
|
|
13
|
+
|
|
14
|
+
[timberline.env]
|
|
15
|
+
auto_copy = true
|
|
16
|
+
patterns = [
|
|
17
|
+
".env",
|
|
18
|
+
".env.*",
|
|
19
|
+
"!.env.example",
|
|
20
|
+
"!.env.template",
|
|
21
|
+
]
|
|
22
|
+
scan_depth = 3
|
|
23
|
+
|
|
24
|
+
[timberline.submodules]
|
|
25
|
+
auto_init = true
|
|
26
|
+
recursive = true
|
|
27
|
+
|
|
28
|
+
[timberline.agent]
|
|
29
|
+
auto_launch = false
|
|
30
|
+
inject_context = true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.PHONY: dev test lint fmt check link unlink
|
|
2
|
+
|
|
3
|
+
dev: ## Install in development mode
|
|
4
|
+
uv sync
|
|
5
|
+
@echo "✓ Development mode ready"
|
|
6
|
+
|
|
7
|
+
test: ## Run tests
|
|
8
|
+
uv run pytest -v
|
|
9
|
+
|
|
10
|
+
lint: ## Lint + type check
|
|
11
|
+
uv run ruff check timberline/ tests/
|
|
12
|
+
uv run basedpyright timberline/
|
|
13
|
+
|
|
14
|
+
fmt: ## Format + fix imports
|
|
15
|
+
uv run ruff format timberline/ tests/
|
|
16
|
+
uv run ruff check --fix timberline/ tests/
|
|
17
|
+
|
|
18
|
+
check: fmt lint test ## Format, lint, type check, test
|
|
19
|
+
|
|
20
|
+
link: dev ## Symlink tl to ~/.local/bin for testing
|
|
21
|
+
@mkdir -p $(HOME)/.local/bin
|
|
22
|
+
@ln -sf $(CURDIR)/.venv/bin/tl $(HOME)/.local/bin/tl
|
|
23
|
+
@echo "✓ tl linked to ~/.local/bin/tl"
|
|
24
|
+
|
|
25
|
+
unlink: ## Remove tl symlink
|
|
26
|
+
@rm -f $(HOME)/.local/bin/tl
|
|
27
|
+
@echo "✓ tl unlinked"
|
|
28
|
+
|
|
29
|
+
.DEFAULT_GOAL := dev
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: timberline
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Git worktree manager for parallel Claude Code development
|
|
5
|
+
Author-email: Nik Cubrilovic <git@nikcub.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: rich>=13.0
|
|
9
|
+
Requires-Dist: tomli-w>=1.0
|
|
10
|
+
Requires-Dist: typer>=0.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Timberline
|
|
14
|
+
|
|
15
|
+
Git worktree manager for parallel coding agent development.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install .
|
|
21
|
+
# or
|
|
22
|
+
uv pip install -e .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd your-repo
|
|
29
|
+
tl init --defaults # create .timberline.toml
|
|
30
|
+
tl new auth-refactor # create worktree + branch
|
|
31
|
+
tl new --type fix # auto-named fix worktree
|
|
32
|
+
tl ls # list all worktrees
|
|
33
|
+
cd $(tl cd auth-refactor) # jump into worktree
|
|
34
|
+
tl rm auth-refactor # clean up
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
| Command | Description |
|
|
40
|
+
|---------|-------------|
|
|
41
|
+
| `tl init` | Interactive setup, write `.timberline.toml` |
|
|
42
|
+
| `tl new [name]` | Create worktree (aliases: `create`) |
|
|
43
|
+
| `tl ls` | List worktrees (aliases: `list`). `--json`, `--paths` |
|
|
44
|
+
| `tl rm <name>` | Remove worktree (aliases: `remove`). `--force`, `--keep-branch`, `--all` |
|
|
45
|
+
| `tl cd <name>` | Print worktree path. `--shell` for subshell |
|
|
46
|
+
| `tl status` | Git status across all worktrees |
|
|
47
|
+
| `tl sync [name]` | Rebase/merge on base branch. `--all`, `--merge` |
|
|
48
|
+
| `tl agent [name]` | Launch coding agent in worktree. `--new` |
|
|
49
|
+
| `tl run-init [name]` | Re-run dependency install |
|
|
50
|
+
| `tl env sync [name]` | Re-copy .env files from main repo |
|
|
51
|
+
| `tl env ls` | List discovered .env files |
|
|
52
|
+
| `tl env diff [name]` | Show .env differences |
|
|
53
|
+
| `tl pr [name]` | Create PR via gh CLI. `--draft` |
|
|
54
|
+
| `tl clean` | Prune stale worktrees. `--dry-run` |
|
|
55
|
+
| `tl config show` | Print resolved config |
|
|
56
|
+
| `tl config set <k> <v>` | Set config value |
|
|
57
|
+
| `tl config edit` | Open config in $EDITOR |
|
|
58
|
+
| `tl shell-init` | Output shell integration script |
|
|
59
|
+
|
|
60
|
+
## Config
|
|
61
|
+
|
|
62
|
+
`.timberline.toml` in repo root:
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[timberline]
|
|
66
|
+
worktree_dir = ".tl"
|
|
67
|
+
branch_template = "{user}/{type}/{name}"
|
|
68
|
+
user = "nc9"
|
|
69
|
+
default_type = "feature"
|
|
70
|
+
base_branch = "main"
|
|
71
|
+
naming_scheme = "minerals" # minerals | cities | compound
|
|
72
|
+
default_agent = "claude" # claude | codex | opencode | aider
|
|
73
|
+
|
|
74
|
+
[timberline.init]
|
|
75
|
+
auto_init = true
|
|
76
|
+
# init_command = "bun run init"
|
|
77
|
+
# post_init = ["echo done"]
|
|
78
|
+
|
|
79
|
+
[timberline.env]
|
|
80
|
+
auto_copy = true
|
|
81
|
+
patterns = [".env", ".env.*", "!.env.example", "!.env.template"]
|
|
82
|
+
scan_depth = 3
|
|
83
|
+
|
|
84
|
+
[timberline.submodules]
|
|
85
|
+
auto_init = true
|
|
86
|
+
recursive = true
|
|
87
|
+
|
|
88
|
+
[timberline.agent]
|
|
89
|
+
auto_launch = false
|
|
90
|
+
inject_context = true
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Shell Integration
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Add to .zshrc / .bashrc:
|
|
97
|
+
eval "$(tl shell-init)"
|
|
98
|
+
|
|
99
|
+
# Then use:
|
|
100
|
+
tlcd obsidian # cd into worktree
|
|
101
|
+
tl-prompt # worktree name for PS1
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv sync
|
|
108
|
+
make test # pytest
|
|
109
|
+
make lint # ruff + basedpyright
|
|
110
|
+
make fmt # ruff format
|
|
111
|
+
make check # all of the above
|
|
112
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Timberline
|
|
2
|
+
|
|
3
|
+
Git worktree manager for parallel coding agent development.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install .
|
|
9
|
+
# or
|
|
10
|
+
uv pip install -e .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd your-repo
|
|
17
|
+
tl init --defaults # create .timberline.toml
|
|
18
|
+
tl new auth-refactor # create worktree + branch
|
|
19
|
+
tl new --type fix # auto-named fix worktree
|
|
20
|
+
tl ls # list all worktrees
|
|
21
|
+
cd $(tl cd auth-refactor) # jump into worktree
|
|
22
|
+
tl rm auth-refactor # clean up
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
|---------|-------------|
|
|
29
|
+
| `tl init` | Interactive setup, write `.timberline.toml` |
|
|
30
|
+
| `tl new [name]` | Create worktree (aliases: `create`) |
|
|
31
|
+
| `tl ls` | List worktrees (aliases: `list`). `--json`, `--paths` |
|
|
32
|
+
| `tl rm <name>` | Remove worktree (aliases: `remove`). `--force`, `--keep-branch`, `--all` |
|
|
33
|
+
| `tl cd <name>` | Print worktree path. `--shell` for subshell |
|
|
34
|
+
| `tl status` | Git status across all worktrees |
|
|
35
|
+
| `tl sync [name]` | Rebase/merge on base branch. `--all`, `--merge` |
|
|
36
|
+
| `tl agent [name]` | Launch coding agent in worktree. `--new` |
|
|
37
|
+
| `tl run-init [name]` | Re-run dependency install |
|
|
38
|
+
| `tl env sync [name]` | Re-copy .env files from main repo |
|
|
39
|
+
| `tl env ls` | List discovered .env files |
|
|
40
|
+
| `tl env diff [name]` | Show .env differences |
|
|
41
|
+
| `tl pr [name]` | Create PR via gh CLI. `--draft` |
|
|
42
|
+
| `tl clean` | Prune stale worktrees. `--dry-run` |
|
|
43
|
+
| `tl config show` | Print resolved config |
|
|
44
|
+
| `tl config set <k> <v>` | Set config value |
|
|
45
|
+
| `tl config edit` | Open config in $EDITOR |
|
|
46
|
+
| `tl shell-init` | Output shell integration script |
|
|
47
|
+
|
|
48
|
+
## Config
|
|
49
|
+
|
|
50
|
+
`.timberline.toml` in repo root:
|
|
51
|
+
|
|
52
|
+
```toml
|
|
53
|
+
[timberline]
|
|
54
|
+
worktree_dir = ".tl"
|
|
55
|
+
branch_template = "{user}/{type}/{name}"
|
|
56
|
+
user = "nc9"
|
|
57
|
+
default_type = "feature"
|
|
58
|
+
base_branch = "main"
|
|
59
|
+
naming_scheme = "minerals" # minerals | cities | compound
|
|
60
|
+
default_agent = "claude" # claude | codex | opencode | aider
|
|
61
|
+
|
|
62
|
+
[timberline.init]
|
|
63
|
+
auto_init = true
|
|
64
|
+
# init_command = "bun run init"
|
|
65
|
+
# post_init = ["echo done"]
|
|
66
|
+
|
|
67
|
+
[timberline.env]
|
|
68
|
+
auto_copy = true
|
|
69
|
+
patterns = [".env", ".env.*", "!.env.example", "!.env.template"]
|
|
70
|
+
scan_depth = 3
|
|
71
|
+
|
|
72
|
+
[timberline.submodules]
|
|
73
|
+
auto_init = true
|
|
74
|
+
recursive = true
|
|
75
|
+
|
|
76
|
+
[timberline.agent]
|
|
77
|
+
auto_launch = false
|
|
78
|
+
inject_context = true
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Shell Integration
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Add to .zshrc / .bashrc:
|
|
85
|
+
eval "$(tl shell-init)"
|
|
86
|
+
|
|
87
|
+
# Then use:
|
|
88
|
+
tlcd obsidian # cd into worktree
|
|
89
|
+
tl-prompt # worktree name for PS1
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv sync
|
|
96
|
+
make test # pytest
|
|
97
|
+
make lint # ruff + basedpyright
|
|
98
|
+
make fmt # ruff format
|
|
99
|
+
make check # all of the above
|
|
100
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "timberline"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Git worktree manager for parallel Claude Code development"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Nik Cubrilovic", email = "git@nikcub.me" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"typer>=0.12",
|
|
11
|
+
"rich>=13.0",
|
|
12
|
+
"tomli-w>=1.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
tl = "timberline.cli:app"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["timberline"]
|
|
24
|
+
|
|
25
|
+
[tool.uv]
|
|
26
|
+
dev-dependencies = [
|
|
27
|
+
"pytest>=8.0",
|
|
28
|
+
"pytest-tmp-files>=0.0.2",
|
|
29
|
+
"basedpyright>=1.20",
|
|
30
|
+
"ruff>=0.8",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
target-version = "py311"
|
|
35
|
+
line-length = 100
|
|
36
|
+
|
|
37
|
+
[tool.ruff.lint]
|
|
38
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
39
|
+
|
|
40
|
+
[tool.basedpyright]
|
|
41
|
+
pythonVersion = "3.11"
|
|
42
|
+
typeCheckingMode = "standard"
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def tmp_git_repo(tmp_path: Path) -> Path:
|
|
11
|
+
"""Create a temporary git repo with an initial commit."""
|
|
12
|
+
repo = tmp_path / "repo"
|
|
13
|
+
repo.mkdir()
|
|
14
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=repo, capture_output=True, check=True)
|
|
15
|
+
subprocess.run(
|
|
16
|
+
["git", "config", "user.email", "test@test.com"], cwd=repo, capture_output=True, check=True
|
|
17
|
+
)
|
|
18
|
+
subprocess.run(
|
|
19
|
+
["git", "config", "user.name", "Test"], cwd=repo, capture_output=True, check=True
|
|
20
|
+
)
|
|
21
|
+
(repo / "README.md").write_text("# Test")
|
|
22
|
+
subprocess.run(["git", "add", "."], cwd=repo, capture_output=True, check=True)
|
|
23
|
+
subprocess.run(["git", "commit", "-m", "init"], cwd=repo, capture_output=True, check=True)
|
|
24
|
+
return repo
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from timberline.agent import (
|
|
9
|
+
KNOWN_AGENTS,
|
|
10
|
+
buildContextBlock,
|
|
11
|
+
buildEnvVars,
|
|
12
|
+
detectInstalledAgents,
|
|
13
|
+
getAgentDef,
|
|
14
|
+
injectAgentContext,
|
|
15
|
+
)
|
|
16
|
+
from timberline.types import WorktreeInfo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _makeInfo(name: str = "obsidian") -> WorktreeInfo:
|
|
20
|
+
return WorktreeInfo(
|
|
21
|
+
name=name,
|
|
22
|
+
branch=f"nik/feature/{name}",
|
|
23
|
+
base_branch="main",
|
|
24
|
+
type="feature",
|
|
25
|
+
path=f"/repo/.tl/{name}",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_buildEnvVars():
|
|
30
|
+
info = _makeInfo()
|
|
31
|
+
env = buildEnvVars(info, Path("/repo"))
|
|
32
|
+
assert env["TL_WORKTREE"] == "obsidian"
|
|
33
|
+
assert env["TL_BRANCH"] == "nik/feature/obsidian"
|
|
34
|
+
assert env["TL_BASE"] == "main"
|
|
35
|
+
assert env["TL_ROOT"] == "/repo"
|
|
36
|
+
assert env["TL_TYPE"] == "feature"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_buildContextBlock():
|
|
40
|
+
info = _makeInfo()
|
|
41
|
+
others = [_makeInfo("alpha"), _makeInfo("beta")]
|
|
42
|
+
block = buildContextBlock(info, [info, *others], Path("/repo"))
|
|
43
|
+
assert "obsidian" in block
|
|
44
|
+
assert "nik/feature/obsidian" in block
|
|
45
|
+
assert "alpha" in block
|
|
46
|
+
assert "beta" in block
|
|
47
|
+
assert "<!-- timberline:start -->" in block
|
|
48
|
+
assert "<!-- timberline:end -->" in block
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_injectAgentContext_new_file(tmp_path: Path):
|
|
52
|
+
info = _makeInfo()
|
|
53
|
+
agent = KNOWN_AGENTS["claude"]
|
|
54
|
+
injectAgentContext(agent, tmp_path, info, [info], Path("/repo"))
|
|
55
|
+
content = (tmp_path / "CLAUDE.md").read_text()
|
|
56
|
+
assert "<!-- timberline:start -->" in content
|
|
57
|
+
assert "obsidian" in content
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_injectAgentContext_existing_without_markers(tmp_path: Path):
|
|
61
|
+
(tmp_path / "CLAUDE.md").write_text("# Existing content\n")
|
|
62
|
+
info = _makeInfo()
|
|
63
|
+
agent = KNOWN_AGENTS["claude"]
|
|
64
|
+
injectAgentContext(agent, tmp_path, info, [info], Path("/repo"))
|
|
65
|
+
content = (tmp_path / "CLAUDE.md").read_text()
|
|
66
|
+
assert "# Existing content" in content
|
|
67
|
+
assert "<!-- timberline:start -->" in content
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_injectAgentContext_replaces_existing_markers(tmp_path: Path):
|
|
71
|
+
(tmp_path / "CLAUDE.md").write_text(
|
|
72
|
+
"# Header\n<!-- timberline:start -->\nold content\n<!-- timberline:end -->\n# Footer\n"
|
|
73
|
+
)
|
|
74
|
+
info = _makeInfo()
|
|
75
|
+
agent = KNOWN_AGENTS["claude"]
|
|
76
|
+
injectAgentContext(agent, tmp_path, info, [info], Path("/repo"))
|
|
77
|
+
content = (tmp_path / "CLAUDE.md").read_text()
|
|
78
|
+
assert "old content" not in content
|
|
79
|
+
assert "obsidian" in content
|
|
80
|
+
assert "# Header" in content
|
|
81
|
+
assert "# Footer" in content
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_injectAgentContext_codex_uses_agents_md(tmp_path: Path):
|
|
85
|
+
info = _makeInfo()
|
|
86
|
+
agent = KNOWN_AGENTS["codex"]
|
|
87
|
+
injectAgentContext(agent, tmp_path, info, [info], Path("/repo"))
|
|
88
|
+
assert (tmp_path / "AGENTS.md").exists()
|
|
89
|
+
assert not (tmp_path / "CLAUDE.md").exists()
|
|
90
|
+
content = (tmp_path / "AGENTS.md").read_text()
|
|
91
|
+
assert "obsidian" in content
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_getAgentDef_known():
|
|
95
|
+
agent = getAgentDef("claude")
|
|
96
|
+
assert agent.binary == "claude"
|
|
97
|
+
assert agent.context_file == "CLAUDE.md"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_getAgentDef_unknown():
|
|
101
|
+
with pytest.raises(KeyError, match="Unknown agent"):
|
|
102
|
+
getAgentDef("nonexistent")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_detectInstalledAgents_none():
|
|
106
|
+
with patch("timberline.agent.shutil.which", return_value=None):
|
|
107
|
+
assert detectInstalledAgents() == []
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_detectInstalledAgents_some():
|
|
111
|
+
def mock_which(binary: str) -> str | None:
|
|
112
|
+
return "/usr/bin/claude" if binary == "claude" else None
|
|
113
|
+
|
|
114
|
+
with patch("timberline.agent.shutil.which", side_effect=mock_which):
|
|
115
|
+
result = detectInstalledAgents()
|
|
116
|
+
assert result == ["claude"]
|