familiar-cli 0.5.1__tar.gz → 0.6.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.
Potentially problematic release.
This version of familiar-cli might be problematic. Click here for more details.
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/PKG-INFO +14 -5
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/pyproject.toml +2 -2
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/readme.md +12 -3
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/__init__.py +1 -1
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/_plugins.py +11 -9
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/agents.py +16 -15
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/cli.py +18 -21
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/lint.py +167 -85
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/render.py +68 -32
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/PKG-INFO +14 -5
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/requires.txt +1 -1
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/tests/test_agents.py +5 -4
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/tests/test_cli.py +108 -95
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/tests/test_integration.py +20 -3
- familiar_cli-0.6.0/tests/test_lint.py +703 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/tests/test_render.py +140 -2
- familiar_cli-0.5.1/tests/test_lint.py +0 -398
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/LICENSE +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/setup.cfg +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/core.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/data.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/docs.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/frontend.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/infra.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/python.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/rust.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/conjurings/sec.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/__noop__.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/add-ci.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/add-tests.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/audit.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/bootstrap-python.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/bootstrap-rust.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/code-review.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/explain.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/implement-feature.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/infra-change.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/performance.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/refactor.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/release.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/invocations/security-review.md +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/node/github-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/node/gitlab-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/python/github-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/python/gitlab-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/python/pyproject.toml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/rust/Cargo.toml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/rust/github-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/data/snippets/rust/gitlab-ci.yml +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar/py.typed +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/SOURCES.txt +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/dependency_links.txt +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/entry_points.txt +0 -0
- {familiar_cli-0.5.1 → familiar_cli-0.6.0}/src/familiar_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: familiar-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: compose and invoke ai agent prompts from reusable templates
|
|
5
5
|
Author-email: cyberwitchery lab <contact@cyberwitchery.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Requires-Python: >=3.10
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Provides-Extra: dev
|
|
23
|
-
Requires-Dist: ruff; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff==0.16.1; extra == "dev"
|
|
24
24
|
Requires-Dist: mypy; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest; extra == "dev"
|
|
26
26
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -46,18 +46,21 @@ pip install familiar-cli
|
|
|
46
46
|
## usage
|
|
47
47
|
|
|
48
48
|
```
|
|
49
|
-
usage: familiar [-h] {conjure,invoke,list} ...
|
|
49
|
+
usage: familiar [-h] [--version] [--debug] {conjure,invoke,list,lint} ...
|
|
50
50
|
|
|
51
51
|
conjure and invoke familiars
|
|
52
52
|
|
|
53
53
|
positional arguments:
|
|
54
|
-
{conjure,invoke,list}
|
|
54
|
+
{conjure,invoke,list,lint}
|
|
55
55
|
conjure compose system instructions for an agent
|
|
56
56
|
invoke render an invocation and run the agent
|
|
57
|
-
list list available conjurings
|
|
57
|
+
list list available conjurings and invocations
|
|
58
|
+
lint lint conjurings and invocations
|
|
58
59
|
|
|
59
60
|
options:
|
|
60
61
|
-h, --help show this help message and exit
|
|
62
|
+
--version show program's version number and exit
|
|
63
|
+
--debug show full traceback on error
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
conjure conjurings to create system instructions for an agent:
|
|
@@ -98,6 +101,12 @@ list available conjurings and invocations:
|
|
|
98
101
|
familiar list
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
lint conjurings and invocations, including local `.familiar/` overrides:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
familiar lint
|
|
108
|
+
```
|
|
109
|
+
|
|
101
110
|
## customization
|
|
102
111
|
|
|
103
112
|
add your own conjurings and invocations by creating files in `.familiar/` in your repo:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "familiar-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "compose and invoke ai agent prompts from reusable templates"
|
|
5
5
|
readme = "readme.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -36,7 +36,7 @@ claude = "familiar.agents:ClaudeAgent"
|
|
|
36
36
|
package-dir = {"" = "src"}
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
39
|
-
dev = ["ruff", "mypy", "pytest", "pytest-cov", "bandit"]
|
|
39
|
+
dev = ["ruff==0.16.1", "mypy", "pytest", "pytest-cov", "bandit"]
|
|
40
40
|
|
|
41
41
|
[tool.setuptools.package-data]
|
|
42
42
|
"familiar.data.conjurings" = ["*.md"]
|
|
@@ -17,18 +17,21 @@ pip install familiar-cli
|
|
|
17
17
|
## usage
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
usage: familiar [-h] {conjure,invoke,list} ...
|
|
20
|
+
usage: familiar [-h] [--version] [--debug] {conjure,invoke,list,lint} ...
|
|
21
21
|
|
|
22
22
|
conjure and invoke familiars
|
|
23
23
|
|
|
24
24
|
positional arguments:
|
|
25
|
-
{conjure,invoke,list}
|
|
25
|
+
{conjure,invoke,list,lint}
|
|
26
26
|
conjure compose system instructions for an agent
|
|
27
27
|
invoke render an invocation and run the agent
|
|
28
|
-
list list available conjurings
|
|
28
|
+
list list available conjurings and invocations
|
|
29
|
+
lint lint conjurings and invocations
|
|
29
30
|
|
|
30
31
|
options:
|
|
31
32
|
-h, --help show this help message and exit
|
|
33
|
+
--version show program's version number and exit
|
|
34
|
+
--debug show full traceback on error
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
conjure conjurings to create system instructions for an agent:
|
|
@@ -69,6 +72,12 @@ list available conjurings and invocations:
|
|
|
69
72
|
familiar list
|
|
70
73
|
```
|
|
71
74
|
|
|
75
|
+
lint conjurings and invocations, including local `.familiar/` overrides:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
familiar lint
|
|
79
|
+
```
|
|
80
|
+
|
|
72
81
|
## customization
|
|
73
82
|
|
|
74
83
|
add your own conjurings and invocations by creating files in `.familiar/` in your repo:
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""shared plugin loading utilities."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import warnings
|
|
6
|
+
from collections.abc import Callable
|
|
6
7
|
from importlib.metadata import entry_points
|
|
7
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def load_plugins(
|
|
@@ -14,16 +15,16 @@ def load_plugins(
|
|
|
14
15
|
label: str,
|
|
15
16
|
invalid_msg: str = "validation failed",
|
|
16
17
|
) -> list[Any]:
|
|
17
|
-
"""
|
|
18
|
+
"""load plugins from entry points with validation.
|
|
18
19
|
|
|
19
20
|
Args:
|
|
20
|
-
group:
|
|
21
|
-
validate:
|
|
22
|
-
label:
|
|
23
|
-
invalid_msg:
|
|
21
|
+
group: entry point group name.
|
|
22
|
+
validate: predicate that returns True for valid loaded objects.
|
|
23
|
+
label: human-readable label for warning messages (e.g. "linter plugin").
|
|
24
|
+
invalid_msg: message when validation fails.
|
|
24
25
|
|
|
25
26
|
Returns:
|
|
26
|
-
|
|
27
|
+
list of loaded and validated plugin objects.
|
|
27
28
|
"""
|
|
28
29
|
results: list[Any] = []
|
|
29
30
|
for ep in entry_points(group=group):
|
|
@@ -36,7 +37,8 @@ def load_plugins(
|
|
|
36
37
|
)
|
|
37
38
|
continue
|
|
38
39
|
results.append(obj)
|
|
39
|
-
|
|
40
|
+
# a broken third-party entry point must not take down the cli
|
|
41
|
+
except Exception as e: # noqa: BLE001
|
|
40
42
|
warnings.warn(
|
|
41
43
|
f"failed to load {label} '{ep.name}': {e}",
|
|
42
44
|
stacklevel=2,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""agent implementations for familiar."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ from ._plugins import load_plugins
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Agent(ABC):
|
|
14
|
-
"""
|
|
14
|
+
"""base class for AI coding agents."""
|
|
15
15
|
|
|
16
16
|
name: str
|
|
17
17
|
output_file: str
|
|
@@ -24,24 +24,24 @@ class Agent(ABC):
|
|
|
24
24
|
def run(
|
|
25
25
|
self, repo_root: Path, prompt: str, headless: bool, auto: bool = False
|
|
26
26
|
) -> int:
|
|
27
|
-
"""
|
|
27
|
+
"""run the agent with the given prompt."""
|
|
28
28
|
|
|
29
29
|
def supports_skills(self) -> bool:
|
|
30
|
-
"""
|
|
30
|
+
"""whether this agent supports reusable skill files."""
|
|
31
31
|
return self.skill_dir is not None
|
|
32
32
|
|
|
33
33
|
def skill_path(self, repo_root: Path, skill_name: str) -> Path:
|
|
34
|
-
"""
|
|
34
|
+
"""build the skill path for a given skill name."""
|
|
35
35
|
if self.skill_dir is None:
|
|
36
36
|
raise ValueError(f"agent does not support skills: {self.name}")
|
|
37
37
|
return repo_root / self.skill_dir / skill_name / self.skill_file
|
|
38
38
|
|
|
39
39
|
def supports_subagents(self) -> bool:
|
|
40
|
-
"""
|
|
40
|
+
"""whether this agent supports reusable subagent files."""
|
|
41
41
|
return self.subagent_dir is not None
|
|
42
42
|
|
|
43
43
|
def subagent_path(self, repo_root: Path, subagent_name: str) -> Path:
|
|
44
|
-
"""
|
|
44
|
+
"""build the subagent path for a given subagent name."""
|
|
45
45
|
if self.subagent_dir is None:
|
|
46
46
|
raise ValueError(f"agent does not support subagents: {self.name}")
|
|
47
47
|
return repo_root / self.subagent_dir / subagent_name / self.subagent_file
|
|
@@ -61,7 +61,7 @@ class CodexAgent(Agent):
|
|
|
61
61
|
if auto:
|
|
62
62
|
cmd.append("--full-auto")
|
|
63
63
|
cmd.extend(["exec", "--skip-git-repo-check", "-C", str(repo_root), "-"])
|
|
64
|
-
proc = subprocess.run(cmd, input=prompt, text=True)
|
|
64
|
+
proc = subprocess.run(cmd, input=prompt, text=True, check=False)
|
|
65
65
|
return proc.returncode
|
|
66
66
|
else:
|
|
67
67
|
cmd = ["codex"]
|
|
@@ -91,11 +91,11 @@ class ClaudeAgent(Agent):
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def load_agents() -> dict[str, Agent]:
|
|
94
|
-
"""
|
|
94
|
+
"""load all registered agent plugins via entry points.
|
|
95
95
|
|
|
96
96
|
Returns:
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
dictionary mapping agent names to Agent instances.
|
|
98
|
+
plugins that fail to load are skipped with a warning.
|
|
99
99
|
"""
|
|
100
100
|
plugins = load_plugins(
|
|
101
101
|
"familiar.agents",
|
|
@@ -108,7 +108,8 @@ def load_agents() -> dict[str, Agent]:
|
|
|
108
108
|
try:
|
|
109
109
|
instance = cls()
|
|
110
110
|
agents[instance.name] = instance
|
|
111
|
-
|
|
111
|
+
# a broken agent plugin must not take down the cli
|
|
112
|
+
except Exception as e: # noqa: BLE001
|
|
112
113
|
warnings.warn(
|
|
113
114
|
f"failed to load agent plugin '{cls.__name__}': {e}",
|
|
114
115
|
stacklevel=2,
|
|
@@ -120,9 +121,9 @@ _agents_cache: dict[str, Agent] | None = None
|
|
|
120
121
|
|
|
121
122
|
|
|
122
123
|
def get_agents() -> dict[str, Agent]:
|
|
123
|
-
"""
|
|
124
|
+
"""get all available agents.
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
returns a cached dictionary of agent name -> Agent instance.
|
|
126
127
|
"""
|
|
127
128
|
global _agents_cache
|
|
128
129
|
if _agents_cache is None:
|
|
@@ -131,7 +132,7 @@ def get_agents() -> dict[str, Agent]:
|
|
|
131
132
|
|
|
132
133
|
|
|
133
134
|
def get_agent(name: str) -> Agent:
|
|
134
|
-
"""
|
|
135
|
+
"""get an agent by name.
|
|
135
136
|
|
|
136
137
|
Raises:
|
|
137
138
|
KeyError: if the agent name is not recognized.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""command-line interface for familiar."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ import warnings
|
|
|
14
14
|
from importlib.metadata import version
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
-
from .agents import Agent,
|
|
17
|
+
from .agents import Agent, get_agent, get_agents
|
|
18
18
|
from .lint import lint_all
|
|
19
19
|
from .render import (
|
|
20
20
|
NotFoundError,
|
|
@@ -24,7 +24,6 @@ from .render import (
|
|
|
24
24
|
render_invocation,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
# exit codes
|
|
28
27
|
EXIT_SUCCESS = 0
|
|
29
28
|
EXIT_ERROR = 1 # general error (agent failed, etc.)
|
|
30
29
|
EXIT_USAGE = 2 # usage error (bad args, missing files, etc.)
|
|
@@ -43,12 +42,12 @@ class CliError(Exception):
|
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
def _agent_hint() -> str:
|
|
46
|
-
"""
|
|
45
|
+
"""return a hint string listing valid agent names."""
|
|
47
46
|
return f"valid agents: {', '.join(get_agents().keys())}"
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
def _resolve_agent(name: str) ->
|
|
51
|
-
"""
|
|
49
|
+
def _resolve_agent(name: str) -> Agent:
|
|
50
|
+
"""look up an agent by name, raising CliError on unknown names."""
|
|
52
51
|
try:
|
|
53
52
|
return get_agent(name)
|
|
54
53
|
except KeyError as e:
|
|
@@ -56,7 +55,7 @@ def _resolve_agent(name: str) -> "Agent":
|
|
|
56
55
|
|
|
57
56
|
|
|
58
57
|
def _write_file(path: Path, content: str, label: str = "") -> None:
|
|
59
|
-
"""
|
|
58
|
+
"""write content to path, creating parent dirs. wraps OSError as CliError."""
|
|
60
59
|
prefix = f"{label} " if label else ""
|
|
61
60
|
try:
|
|
62
61
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -69,9 +68,9 @@ def _write_file(path: Path, content: str, label: str = "") -> None:
|
|
|
69
68
|
|
|
70
69
|
|
|
71
70
|
def find_repo_root(start: Path) -> Path:
|
|
72
|
-
"""
|
|
71
|
+
"""find the repository root by looking for .git directory.
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
walks up from start directory. falls back to start directory itself
|
|
75
74
|
if no .git is found, allowing use outside of git repositories.
|
|
76
75
|
"""
|
|
77
76
|
cur = start.resolve()
|
|
@@ -82,7 +81,7 @@ def find_repo_root(start: Path) -> Path:
|
|
|
82
81
|
|
|
83
82
|
|
|
84
83
|
def _remove_worktree(repo_root: Path, worktree_path: Path) -> None:
|
|
85
|
-
"""
|
|
84
|
+
"""best-effort removal of a git worktree."""
|
|
86
85
|
try:
|
|
87
86
|
result = subprocess.run(
|
|
88
87
|
[
|
|
@@ -95,6 +94,7 @@ def _remove_worktree(repo_root: Path, worktree_path: Path) -> None:
|
|
|
95
94
|
str(worktree_path),
|
|
96
95
|
],
|
|
97
96
|
capture_output=True,
|
|
97
|
+
check=False,
|
|
98
98
|
)
|
|
99
99
|
if result.returncode != 0:
|
|
100
100
|
stderr = result.stderr.decode(errors="replace").strip()
|
|
@@ -107,8 +107,7 @@ def _remove_worktree(repo_root: Path, worktree_path: Path) -> None:
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
def create_worktree(repo_root: Path) -> Path:
|
|
110
|
-
"""
|
|
111
|
-
# Ensure it's a git repo
|
|
110
|
+
"""create a temporary git worktree for the repository."""
|
|
112
111
|
try:
|
|
113
112
|
subprocess.run(
|
|
114
113
|
["git", "-C", str(repo_root), "rev-parse", "--is-inside-work-tree"],
|
|
@@ -121,8 +120,8 @@ def create_worktree(repo_root: Path) -> Path:
|
|
|
121
120
|
)
|
|
122
121
|
|
|
123
122
|
tmpdir = tempfile.mkdtemp(prefix="familiar-")
|
|
124
|
-
# git worktree add requires the
|
|
125
|
-
# mkdtemp
|
|
123
|
+
# git worktree add requires the target to not exist, so remove the
|
|
124
|
+
# mkdtemp-created directory and let git create it
|
|
126
125
|
os.rmdir(tmpdir)
|
|
127
126
|
|
|
128
127
|
try:
|
|
@@ -155,7 +154,7 @@ def write_instruction(repo_root: Path, agent_name: str, system: str) -> None:
|
|
|
155
154
|
def write_skill(
|
|
156
155
|
repo_root: Path, agent_name: str, invocation: str, skill_name: str, prompt: str
|
|
157
156
|
) -> Path:
|
|
158
|
-
"""
|
|
157
|
+
"""write a reusable skill file for supported agents."""
|
|
159
158
|
if not _VALID_NAME.match(skill_name):
|
|
160
159
|
raise CliError(
|
|
161
160
|
f"invalid skill name: {skill_name}",
|
|
@@ -188,7 +187,7 @@ def write_subagent(
|
|
|
188
187
|
subagent_name: str,
|
|
189
188
|
system: str,
|
|
190
189
|
) -> Path:
|
|
191
|
-
"""
|
|
190
|
+
"""write a reusable subagent file for supported agents."""
|
|
192
191
|
if not _VALID_NAME.match(subagent_name):
|
|
193
192
|
raise CliError(
|
|
194
193
|
f"invalid subagent name: {subagent_name}",
|
|
@@ -291,7 +290,7 @@ def cmd_invoke(args: argparse.Namespace) -> int:
|
|
|
291
290
|
target_dir = create_worktree(repo_root)
|
|
292
291
|
print(f"created worktree at {target_dir}")
|
|
293
292
|
|
|
294
|
-
#
|
|
293
|
+
# best-effort copy of instruction file into the worktree
|
|
295
294
|
try:
|
|
296
295
|
agent = get_agent(args.agent)
|
|
297
296
|
instr_file = repo_root / agent.output_file
|
|
@@ -332,7 +331,6 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
332
331
|
repo_root = find_repo_root(Path(args.into or os.getcwd()))
|
|
333
332
|
|
|
334
333
|
if args.kind is None:
|
|
335
|
-
# list all
|
|
336
334
|
conjurings = list_items(repo_root, "conjurings")
|
|
337
335
|
invocations = list_items(repo_root, "invocations")
|
|
338
336
|
snippets = list_snippets(repo_root)
|
|
@@ -363,7 +361,6 @@ def cmd_lint(args: argparse.Namespace) -> int:
|
|
|
363
361
|
|
|
364
362
|
messages = lint_all(repo_root)
|
|
365
363
|
|
|
366
|
-
# Filter by level if requested
|
|
367
364
|
if args.errors_only:
|
|
368
365
|
messages = [m for m in messages if m.level == "error"]
|
|
369
366
|
|
|
@@ -371,7 +368,6 @@ def cmd_lint(args: argparse.Namespace) -> int:
|
|
|
371
368
|
print("all checks passed")
|
|
372
369
|
return EXIT_SUCCESS
|
|
373
370
|
|
|
374
|
-
# Group by level for output
|
|
375
371
|
errors = [m for m in messages if m.level == "error"]
|
|
376
372
|
warnings = [m for m in messages if m.level == "warning"]
|
|
377
373
|
|
|
@@ -514,7 +510,8 @@ def main() -> None:
|
|
|
514
510
|
if e.hint:
|
|
515
511
|
print(f"hint: {e.hint}", file=sys.stderr)
|
|
516
512
|
raise SystemExit(e.exit_code)
|
|
517
|
-
|
|
513
|
+
# top-level handler: any unexpected error becomes an exit code, not a traceback
|
|
514
|
+
except Exception as e: # noqa: BLE001
|
|
518
515
|
if args.debug:
|
|
519
516
|
traceback.print_exc()
|
|
520
517
|
print(f"error: {e}", file=sys.stderr)
|