director-cli 0.3.0__py3-none-any.whl

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.
director/setup.py ADDED
@@ -0,0 +1,101 @@
1
+ """Install Director's OpenCode agent definitions into a target repo.
2
+
3
+ Director drives OpenCode with `--agent <role> --model <tier>`; the agent markdown
4
+ supplies the role's system prompt + permissions, and the resolved tier model
5
+ comes from config via `--model`. This module renders the bundled agent templates
6
+ into <repo>/.opencode/agents/ (a.k.a. the `director sync-agents` step).
7
+
8
+ Provider auth (Bedrock/OpenRouter keys, the LM Studio endpoint) is the operator's
9
+ responsibility — it lives in the user's *global* OpenCode config. We add the
10
+ project-local agent files, a starter opencode.json if the repo has none, and seed
11
+ a ready-to-edit .director/config.toml from the bundled example (if none exists).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import importlib.resources as ir
17
+ from pathlib import Path
18
+
19
+ AGENT_FILES = (
20
+ "brainstorm.md",
21
+ "planner.md",
22
+ "explorer.md",
23
+ "test-author.md",
24
+ "executor.md",
25
+ "reviewer.md",
26
+ )
27
+
28
+ # A complete, commented example config ships inside the package. sync-agents seeds
29
+ # it to <repo>/.director/config.toml (if missing) so a pip-installed user has a
30
+ # ready-to-edit config rather than nothing.
31
+ CONFIG_EXAMPLE = "config.example.toml"
32
+
33
+ # Runtime artifacts director writes into <repo>/.director/. These must never be
34
+ # committed: director's own commits use `git add -A` (to capture whatever the
35
+ # executor created in the allowlist), which in a repo without this ignore file
36
+ # would sweep these throwaway files into the job branch — then a later run that
37
+ # rewrites them leaves a dirty TRACKED file, which makes `git checkout` refuse
38
+ # (this bit `director bench` live). config.toml + profiles/ stay tracked.
39
+ _DIRECTOR_GITIGNORE = """\
40
+ # director runtime artifacts (generated per run). config.toml + profiles/ stay tracked.
41
+ state.json
42
+ plan.json
43
+ plan_stage.json
44
+ spec.md
45
+ recon.md
46
+ costs.jsonl
47
+ metrics.jsonl
48
+ logs/
49
+ bench/
50
+ worktrees/
51
+ """
52
+
53
+
54
+ def _template(name: str) -> str:
55
+ return ir.files("director.agent_templates").joinpath(name).read_text()
56
+
57
+
58
+ def _example_config() -> str:
59
+ return ir.files("director").joinpath(CONFIG_EXAMPLE).read_text()
60
+
61
+
62
+ def ensure_director_gitignore(repo: str | Path) -> None:
63
+ """Seed <repo>/.director/.gitignore so director's `git add -A` commits never
64
+ sweep its own runtime files into the repo. Idempotent; safe to call on every
65
+ plan/run. (Does not retroactively untrack files already committed in a repo
66
+ that ran director before this existed — for those, `git rm --cached` once.)"""
67
+ fdir = Path(repo) / ".director"
68
+ fdir.mkdir(parents=True, exist_ok=True)
69
+ gi = fdir / ".gitignore"
70
+ current = gi.read_text() if gi.exists() else None
71
+ if current != _DIRECTOR_GITIGNORE:
72
+ gi.write_text(_DIRECTOR_GITIGNORE)
73
+
74
+
75
+ def sync_agents(repo: str | Path) -> list[str]:
76
+ """Render agent_templates/*.md into <repo>/.opencode/agents/. Returns the
77
+ list of files written."""
78
+ repo = Path(repo)
79
+ ensure_director_gitignore(repo)
80
+ agents_dir = repo / ".opencode" / "agents"
81
+ agents_dir.mkdir(parents=True, exist_ok=True)
82
+ written = []
83
+ for name in AGENT_FILES:
84
+ dest = agents_dir / name
85
+ dest.write_text(_template(name))
86
+ written.append(str(dest.relative_to(repo)))
87
+
88
+ # Starter opencode.json only if the repo has none (never clobber).
89
+ oc = repo / ".opencode" / "opencode.json"
90
+ if not oc.exists():
91
+ oc.write_text(_template("opencode.json"))
92
+ written.append(str(oc.relative_to(repo)))
93
+
94
+ # Seed a ready-to-edit config from the bundled example — but never clobber an
95
+ # existing config the user may have edited.
96
+ cfg = repo / ".director" / "config.toml"
97
+ if not cfg.exists():
98
+ cfg.parent.mkdir(parents=True, exist_ok=True)
99
+ cfg.write_text(_example_config())
100
+ written.append(str(cfg.relative_to(repo)))
101
+ return written
director/state.py ADDED
@@ -0,0 +1,43 @@
1
+ """Resumable run state persisted to .director/state.json."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import asdict
7
+ from pathlib import Path
8
+
9
+ from director.models import DONE, PENDING, NodeState
10
+
11
+
12
+ class RunState:
13
+ def __init__(self, path: Path, job_id: str):
14
+ self.path = Path(path)
15
+ self.job_id = job_id
16
+ self.nodes: dict[str, NodeState] = {}
17
+
18
+ @classmethod
19
+ def load_or_init(cls, repo: Path, plan) -> RunState:
20
+ path = Path(repo) / ".director" / "state.json"
21
+ rs = cls(path, plan.job_id)
22
+ if path.exists():
23
+ d = json.loads(path.read_text())
24
+ if d.get("job_id") == plan.job_id:
25
+ rs.nodes = {k: NodeState(**v) for k, v in d.get("nodes", {}).items()}
26
+ for n in plan.nodes:
27
+ rs.nodes.setdefault(n.id, NodeState(id=n.id, status=PENDING))
28
+ return rs
29
+
30
+ def save(self) -> None:
31
+ self.path.parent.mkdir(parents=True, exist_ok=True)
32
+ self.path.write_text(
33
+ json.dumps(
34
+ {"job_id": self.job_id, "nodes": {k: asdict(v) for k, v in self.nodes.items()}},
35
+ indent=2,
36
+ )
37
+ )
38
+
39
+ def done_ids(self) -> set[str]:
40
+ return {k for k, v in self.nodes.items() if v.status == DONE}
41
+
42
+ def __getitem__(self, node_id: str) -> NodeState:
43
+ return self.nodes[node_id]
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: director-cli
3
+ Version: 0.3.0
4
+ Summary: Model-agnostic decomposition coding harness — a thin orchestrator over OpenCode
5
+ Project-URL: Homepage, https://github.com/manziman/director
6
+ Project-URL: Repository, https://github.com/manziman/director
7
+ Project-URL: Issues, https://github.com/manziman/director/issues
8
+ Project-URL: Changelog, https://github.com/manziman/director/blob/main/CHANGELOG.md
9
+ Author-email: Christopher Manzi <chris@minimus.tech>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,cli,code-generation,coding-agent,decomposition,llm,opencode,orchestration,tdd
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: Software Development :: Code Generators
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+
26
+ # director
27
+
28
+ **A model-agnostic decomposition coding harness — a thin orchestrator over [OpenCode](https://opencode.ai).**
29
+
30
+ director tests one hypothesis:
31
+
32
+ > A strong **planner** model decomposes a coding task into small, atomic, well-specified
33
+ > units with acceptance tests written *first*. A cheaper **executor** model — local or
34
+ > low-cost cloud — implements each unit in an isolated, fresh context. **Deterministic
35
+ > gates** (tests, lint, typecheck — exit codes, never an LLM's opinion) decide what
36
+ > merges. This cuts token cost dramatically with minimal quality loss.
37
+
38
+ It is **model-agnostic by construction**: roles (`planner`, `executor`, `reviewer`, …)
39
+ bind to `provider/model` strings in config. Switching the executor from a local 27B to
40
+ a frontier model — or anything in between — is a one-line config edit, never a code
41
+ change. director drives OpenCode headlessly, so it inherits OpenCode's 75+ providers.
42
+
43
+ > **Status:** beta. Validated end-to-end (plan → run → bench) under local, cheap-cloud,
44
+ > and all-frontier executor tiers.
45
+
46
+ ---
47
+
48
+ ## Install
49
+
50
+ director is a pure-standard-library Python CLI (no dependencies), so it installs anywhere
51
+ Python 3.11+ runs:
52
+
53
+ ```bash
54
+ uv tool install director-cli # recommended
55
+ # or
56
+ pipx install director-cli
57
+ # or
58
+ pip install director-cli
59
+ ```
60
+
61
+ ### Prerequisites (runtime)
62
+
63
+ director orchestrates other tools rather than replacing them, so it needs:
64
+
65
+ - **Python ≥ 3.11**
66
+ - **git** on `PATH` (isolation is real git worktrees + branches)
67
+ - **[OpenCode](https://opencode.ai)** on `PATH` (the agent runtime director drives)
68
+ - **Provider auth** configured in OpenCode (`opencode auth`): your planner/executor
69
+ model providers — e.g. Anthropic/Bedrock, OpenRouter, or a local OpenAI-compatible
70
+ endpoint such as LM Studio for the `local-first` profile.
71
+
72
+ director never manages provider keys itself — that lives in your OpenCode config.
73
+
74
+ ---
75
+
76
+ ## Quickstart
77
+
78
+ ```bash
79
+ cd your-repo
80
+
81
+ # 1. Install director's role agents into .opencode/ and seed .director/config.toml
82
+ director sync-agents
83
+
84
+ # 2. Edit .director/config.toml — bind roles to models, set your gate commands.
85
+ # (sync-agents seeded it from the bundled, fully-commented example.)
86
+ $EDITOR .director/config.toml
87
+
88
+ # 3. Plan: brainstorm → spec → test-gated task DAG (two approval gates)
89
+ director plan "Add a --json flag to the export command"
90
+
91
+ # …review .director/spec.md, then continue; review the plan, then continue:
92
+ director plan --continue # after approving the spec
93
+ director plan --continue # after approving the plan + failing tests
94
+
95
+ # 4. Run the DAG: isolated worktree per node, deterministic gates, auto-merge
96
+ director run
97
+
98
+ # 5. Inspect
99
+ director status
100
+ ```
101
+
102
+ Unattended? Let the planner self-critique at each gate instead of pausing for you:
103
+
104
+ ```bash
105
+ director plan "…" --auto # planner self-critiques at each gate
106
+ director plan "…" --auto --no-critique # gates auto-pass, fully hands-off
107
+ director run
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Commands
113
+
114
+ | Command | What it does |
115
+ | --- | --- |
116
+ | `director plan "<task>" [--auto] [--no-critique] [--continue]` | Brainstorm → spec → test-gated task DAG, with two artifact-based approval gates. |
117
+ | `director run [--parallel N] [--max-attempts K]` | Execute the DAG: each node in an isolated git worktree, gated by tests/lint/typecheck, auto-merged on pass; escalates a stuck node one tier up. |
118
+ | `director status` | Per-node progress, attempts, cost, and the executor-tier completion rate. |
119
+ | `director bench "<task>" --profiles a,b,c` | Run the **same** task (same frozen acceptance tests) across profile variants and diff cost / quality / wall-time. |
120
+ | `director sync-agents` | (Re)install the role agents into `<repo>/.opencode` and seed `.director/config.toml`. |
121
+
122
+ All state lives under `.director/` (resumable, debuggable): `plan.json`, `state.json`,
123
+ `costs.jsonl`, `metrics.jsonl`, per-call `logs/`, and `bench/`.
124
+
125
+ ## Configuration
126
+
127
+ `director sync-agents` seeds `.director/config.toml` from a complete, commented example
128
+ (also at [`director/config.example.toml`](director/config.example.toml)). A config is
129
+ just roles → `provider/model` strings, the deterministic gate commands, per-model
130
+ pricing, and run limits — the example shows how to bind the executor tier to a local
131
+ model (≈ $0 implementation), a low-cost cloud model (zero local infra), or a frontier
132
+ model (the expensive baseline). See [`director/README.md`](director/README.md) for the
133
+ full architecture (gates, two-stage review, red-green hardening, metrics).
134
+
135
+ ### Comparing setups with `bench`
136
+
137
+ `director bench` plans a task once, then runs the **same** frozen acceptance tests under
138
+ several config variants to compare cost/quality/wall-time. Create the variants as
139
+ `.director/profiles/<name>.toml` (copy your `config.toml` and change the executor tier in
140
+ each), then:
141
+
142
+ ```bash
143
+ director bench "<task>" --profiles all-frontier,cheap-cloud,local-first
144
+ ```
145
+
146
+ ## For agents & scripting
147
+
148
+ director is built to be driven by humans *or* by another agent:
149
+
150
+ - **Deterministic, non-interactive:** `--auto --no-critique` runs plan→run with no
151
+ prompts; every merge decision is an exit code, never a chat.
152
+ - **Machine-readable output:** `.director/metrics.jsonl` (per-node + per-run records)
153
+ and `.director/bench/summary.json` are stable JSON for downstream tooling.
154
+ - **Resumable:** re-running `plan --continue` / `run` picks up from `.director/` state.
155
+
156
+ ---
157
+
158
+ ## Development
159
+
160
+ ```bash
161
+ uv sync # create the dev environment
162
+ uv run python -m unittest discover -s tests -q # tests
163
+ uvx ruff check . && uvx ruff format --check . # lint + format
164
+ uv build # build the wheel/sdist
165
+ ```
166
+
167
+ Releases are automated with [python-semantic-release](https://python-semantic-release.readthedocs.io/)
168
+ on merge to `main` (conventional-commit messages drive the version bump, changelog, and
169
+ PyPI publish via Trusted Publishing). See [`CONTRIBUTING.md`](CONTRIBUTING.md).
170
+
171
+ ## License
172
+
173
+ [MIT](LICENSE) © Christopher Manzi. The ported TDD/review *discipline* is adapted from
174
+ [obra/superpowers](https://github.com/obra/superpowers) (MIT).
@@ -0,0 +1,32 @@
1
+ director/README.md,sha256=YaLAJhNewVOt2eLCfo2Ijd2_58VcyaNlBkCa8Q0dUL4,7542
2
+ director/__init__.py,sha256=UAIGy5FwtqPhiOgKgylft5WAzMG5s8IhpV3j4KRkmQg,490
3
+ director/__main__.py,sha256=vYft2_c7dl5XuL8T5xQw5z07YLZKsyVLtXebblBKJro,87
4
+ director/bench.py,sha256=RUU8m6S9g2T8MijnZzYMWOKq6gYCREeszjHUrCaFi80,9158
5
+ director/cli.py,sha256=5dl3_GvZU6dOJXilSSbZd50noJS_YE7Pcp762GY2v28,4856
6
+ director/config.example.toml,sha256=5CM4kqCLJn3NqyAwn30i5FeByplR9Bs2tBHPfgkn5A0,3744
7
+ director/config.py,sha256=T44fhI_5VXYcUJicjyuGaAvooaiLKxGM1owRXY4Oe2w,4236
8
+ director/cost.py,sha256=Y7e22DRohIAcDU8xuc2fXseNa87gZhn9M3_d1SSkMFg,2495
9
+ director/dag.py,sha256=vaFYDrYeJzdaRj41TK6u4Gi3eWdaDD4GdOFCpGrbYR8,3800
10
+ director/gates.py,sha256=uqi3DZixTCkuDQVL3CzSFZ__YVjrAvOHSBF8h1DE0jU,5405
11
+ director/gitutil.py,sha256=t_Os0dNrvxOiE9v05vOCDQSWzp51f9vNeMDLrE6dd1A,2744
12
+ director/metrics.py,sha256=OIPfAip_2gzBwhicGsr0b7AgrgpF2iNcz6EH0S83x-Y,1684
13
+ director/models.py,sha256=t286U_vWion-bOF9qpselDu8FzAQmti2NhTX0qQGcX4,3933
14
+ director/opencode.py,sha256=0RKBH1GEZBNFZCRBpGWHBYRcXvgGzZxBFYlorNjAnjc,8371
15
+ director/plan.py,sha256=mJetHGObCqAs4IRFFDtb4igOEAkFAYhsOojU11iwZM0,19316
16
+ director/report.py,sha256=WSvU-FfrwyMV9aBgZjQwJ9djE0FzqpRklxZhHWVm17k,4275
17
+ director/review.py,sha256=dXMG344m1xuyPOVt2JmW0JdS3zR89C6yK4hKp7MVO24,5948
18
+ director/run.py,sha256=LvrotO8kR5q4-SOg_o8Wje0ECLvTI3ZlvCyBrpYs3mc,17907
19
+ director/setup.py,sha256=H8yDL31KVwsBLYMIB7DC7d2NEDnDeYKrSUyHa1AOwA0,3824
20
+ director/state.py,sha256=mow0zZa2Hh75mk-3V0P271oNu-fzn13sBEyCaZ2ockk,1371
21
+ director/agent_templates/brainstorm.md,sha256=xArLZukX6WOqHlqDSzDOBv0g05aej_QqLfpuTDXhynU,1784
22
+ director/agent_templates/executor.md,sha256=gxNpxq8f94NomthbraqP0Djc_lqx6hfW2r4mkH1X4Ms,1713
23
+ director/agent_templates/explorer.md,sha256=tmQ5zdm78W7vPgwJaqB72itbR93cQScVsDCORcUkncM,1024
24
+ director/agent_templates/opencode.json,sha256=w27_6OEZfYBVhVhiOExlrZFerBr0eKbJLHO7nt_w3Yg,858
25
+ director/agent_templates/planner.md,sha256=yzeszZoOlsqD1MYHFYnxdbs-NwkQuhCjCX7IrCokxAw,3044
26
+ director/agent_templates/reviewer.md,sha256=szFV2YQ8LrULGVfjEYM7xAAFFzUDbRsnhVApLY4dHQ4,1928
27
+ director/agent_templates/test-author.md,sha256=nn_dBAHyrKj9jBZsgpz_vKIoMQ4zDOg5rOnD4xdm7gQ,1240
28
+ director_cli-0.3.0.dist-info/METADATA,sha256=ZuR3SPbevt07EB3tDPVNNor7n6VMIg_15hne9k_LwU4,7301
29
+ director_cli-0.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
30
+ director_cli-0.3.0.dist-info/entry_points.txt,sha256=TsqsEJcOoXZdwdG6Lh9COffQwHYkYsZKimPt8TFk2mA,47
31
+ director_cli-0.3.0.dist-info/licenses/LICENSE,sha256=8kYT3veTsiC6QImfym_6HdBTqNUct_mEkd2C3saQJpw,1074
32
+ director_cli-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ director = director.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christopher Manzi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.