zcode-supervisor 0.0.1__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.
@@ -0,0 +1,439 @@
1
+ """Install repo-local Codex-to-ZCode routing hints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ ROUTING_FILE = Path(".codex/zcode-routing.json")
13
+ DELEGATION_FILE = Path(".codex/ZCODE_DELEGATION.md")
14
+ AGENTS_FILE = Path("AGENTS.md")
15
+ VISION_MCP_FILE = Path(".agents/mcp.json")
16
+ AGENTS_BEGIN = "<!-- BEGIN ZCODE SUPERVISOR ROUTING -->"
17
+ AGENTS_END = "<!-- END ZCODE SUPERVISOR ROUTING -->"
18
+ DEFAULT_VISION_MCP_SERVER = "zai-mcp-server"
19
+ DEFAULT_VISION_MCP_PACKAGE = "@z_ai/mcp-server"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class InstallResult:
24
+ repo: Path
25
+ routing_file: Path
26
+ delegation_file: Path
27
+ agents_file: Path
28
+ vision_mcp_file: Path
29
+ agents_status: str
30
+ vision_mcp_status: str
31
+ written: list[str]
32
+ skipped: list[str]
33
+
34
+
35
+ def zcode_repo_root() -> Path:
36
+ return Path(__file__).resolve().parents[2]
37
+
38
+
39
+ def routing_payload(
40
+ repo: Path,
41
+ *,
42
+ vision_mcp_server: str = DEFAULT_VISION_MCP_SERVER,
43
+ vision_mcp_package: str = DEFAULT_VISION_MCP_PACKAGE,
44
+ ) -> dict[str, Any]:
45
+ root = zcode_repo_root()
46
+ supervisor = root / "tools/zcode_supervisor/zcode_supervisor.py"
47
+ controller = root / "tools/zcode_control/zcodectl.mjs"
48
+ return {
49
+ "version": 1,
50
+ "repo": str(repo.resolve()),
51
+ "orchestrator": "codex",
52
+ "implementation_worker": "zcode",
53
+ "routing_mode": "auto",
54
+ "control_repo": str(root),
55
+ "policy": {
56
+ "codex_owns": ["planning", "orchestration", "audit", "validation", "final_acceptance"],
57
+ "zcode_owns": ["bounded_implementation"],
58
+ "never_delegate": ["secret_handling", "destructive_git_ops", "final_acceptance"],
59
+ "codex_direct_edit_allowed": ["read_only", "trivial", "zcode_unavailable_recovery"],
60
+ "ask_user_before": ["destructive", "migration", "credentials", "production_risk"],
61
+ "skip_triggers": ["no-zcode"],
62
+ },
63
+ "defaults": {
64
+ "mode": "Auto Edit",
65
+ "effort": "max",
66
+ "task_class": "root-cause",
67
+ "risk_budget": "low",
68
+ "workspace_kind": "regular",
69
+ "usage_snapshot_source": "auto",
70
+ "max_attempts": 2,
71
+ "retry_delay_ms": 60000,
72
+ "max_auto_retries": 2,
73
+ },
74
+ "paths": {
75
+ "zcode_supervisor": str(supervisor),
76
+ "zcodectl": str(controller),
77
+ "packets": ".codex/zcode/packets",
78
+ "snapshots": ".codex/zcode/snapshots",
79
+ "runs": ".codex/zcode/runs",
80
+ },
81
+ "vision": {
82
+ "service": vision_mcp_server,
83
+ "mcp_file": str(VISION_MCP_FILE),
84
+ "mcp_package": vision_mcp_package,
85
+ },
86
+ "trigger": (
87
+ "For implementation work in this repo, Codex should plan the task, "
88
+ "run auto-route, create a bounded packet, run ZCode, then audit and validate the result. "
89
+ "Skip ZCode for read-only answers, trivial one-line edits, or when the user says no-zcode."
90
+ ),
91
+ }
92
+
93
+
94
+ def delegation_doc(
95
+ repo: Path,
96
+ *,
97
+ vision_mcp_server: str = DEFAULT_VISION_MCP_SERVER,
98
+ vision_mcp_package: str = DEFAULT_VISION_MCP_PACKAGE,
99
+ ) -> str:
100
+ payload = routing_payload(
101
+ repo,
102
+ vision_mcp_server=vision_mcp_server,
103
+ vision_mcp_package=vision_mcp_package,
104
+ )
105
+ supervisor = payload["paths"]["zcode_supervisor"]
106
+ controller = payload["paths"]["zcodectl"]
107
+ return f"""# ZCode Delegation
108
+
109
+ Use this repo-local contract when the user wants Codex to keep planning,
110
+ orchestration, audit, and final acceptance while ZCode handles implementation.
111
+
112
+ ## Default Routing
113
+
114
+ - Codex owns planning, packet creation, audit, validation, and final acceptance.
115
+ - ZCode owns only bounded implementation inside this repo or an isolated worktree.
116
+ - Before implementation edits, run `auto-route` so this repo's
117
+ `.codex/zcode-routing.json` decides whether to delegate to ZCode.
118
+ - Use ZCode for implementation tasks unless the task is read-only, trivial,
119
+ high-risk plan-only, or the user says `no-zcode`.
120
+ - Do not delegate secrets, destructive Git operations, dependency installs, or final
121
+ acceptance to ZCode.
122
+
123
+ ## Normal Flow
124
+
125
+ 1. Codex classifies the task through the routing contract:
126
+
127
+ ```bash
128
+ python3 {supervisor} auto-route \\
129
+ --workspace . \\
130
+ --objective "<specific outcome>"
131
+ ```
132
+
133
+ 2. For implementation tasks, Codex writes a small plan, allowed-file set, and
134
+ validation command, then delegates:
135
+
136
+ ```bash
137
+ python3 {supervisor} auto-route \\
138
+ --workspace . \\
139
+ --objective "<specific outcome>" \\
140
+ --allowed "<file>" \\
141
+ --validation "<command>" \\
142
+ --execute
143
+ ```
144
+
145
+ The command creates the packet, calls `zcodectl run-packet`, and returns a
146
+ machine-readable result. If it returns `needs_codex_planning`, Codex should add
147
+ a tighter allowed-file set and validation command, not ask the user.
148
+
149
+ ## Manual Packet Flow
150
+
151
+ If manual control is needed, Codex can create a packet directly:
152
+
153
+ ```bash
154
+ python3 {supervisor} packet \\
155
+ --workspace . \\
156
+ --objective "<specific outcome>" \\
157
+ --allowed "<file>" \\
158
+ --validation "<command>" \\
159
+ --mode "Auto Edit" \\
160
+ --effort max \\
161
+ --task-class root-cause \\
162
+ --risk-budget low \\
163
+ --out .codex/zcode/packets/<task>.json \\
164
+ --prompt-out .codex/zcode/packets/<task>.prompt.txt
165
+ ```
166
+
167
+ 3. Codex runs ZCode:
168
+
169
+ Use `zcodectl run-packet` through this repository's controller:
170
+
171
+ ```bash
172
+ node {controller} run-packet \\
173
+ --packet .codex/zcode/packets/<task>.json \\
174
+ --mode edit \\
175
+ --max-attempts 2 \\
176
+ --retry-delay-ms 60000 \\
177
+ --usage-snapshot-source auto \\
178
+ --out .codex/zcode/runs/<task>.zcode.json
179
+ ```
180
+
181
+ 4. Codex reviews the `supervisor_state`, changed files, validation result, and
182
+ risk notes before accepting the work.
183
+
184
+ ## Vision MCP
185
+
186
+ Vision-capable packets use `{vision_mcp_server}` through `.agents/mcp.json`.
187
+ The configured stdio command is `npx -y {vision_mcp_package}`. Do not write API
188
+ keys into repo files; `zcodectl run-packet` passes available local credentials
189
+ to the ZCode child process without printing secret values.
190
+
191
+ Check setup with:
192
+
193
+ ```bash
194
+ node {controller} vision-preflight --workspace .
195
+ ```
196
+
197
+ ## Local Routing Config
198
+
199
+ See `.codex/zcode-routing.json` for machine-readable defaults.
200
+ """
201
+
202
+
203
+ def agents_block() -> str:
204
+ return f"""{AGENTS_BEGIN}
205
+ ## ZCode Supervisor Routing
206
+
207
+ For implementation tasks in this repository, read `.codex/ZCODE_DELEGATION.md`.
208
+ Default split: Codex plans, runs `auto-route`, orchestrates, audits, validates,
209
+ and final-accepts; ZCode performs only bounded implementation through
210
+ `zcodectl run-packet`.
211
+ Before direct implementation edits, Codex should run:
212
+ `python3 <zcode-supervisor>/tools/zcode_supervisor/zcode_supervisor.py auto-route --workspace . --objective "<task>"`.
213
+ Do not delegate secrets, destructive operations, or final acceptance to ZCode.
214
+ {AGENTS_END}
215
+ """
216
+
217
+
218
+ def ensure_repo_local_output(repo: Path, path: Path) -> None:
219
+ repo = repo.resolve()
220
+ try:
221
+ path.relative_to(repo)
222
+ except ValueError as exc:
223
+ raise ValueError(f"output path escapes repo: {path}") from exc
224
+
225
+ cursor = path
226
+ while True:
227
+ if cursor.exists() or cursor.is_symlink():
228
+ if cursor.is_symlink():
229
+ raise ValueError(f"output path uses symlink: {cursor.relative_to(repo)}")
230
+ if not cursor.resolve().is_relative_to(repo):
231
+ raise ValueError(f"output path resolves outside repo: {cursor.relative_to(repo)}")
232
+ if cursor == repo:
233
+ break
234
+ cursor = cursor.parent
235
+
236
+
237
+ def read_repo_text(repo: Path, path: Path) -> str:
238
+ ensure_repo_local_output(repo, path)
239
+ return path.read_text(encoding="utf-8")
240
+
241
+
242
+ def write_repo_text(repo: Path, path: Path, text: str) -> None:
243
+ ensure_repo_local_output(repo, path)
244
+ path.parent.mkdir(parents=True, exist_ok=True)
245
+ ensure_repo_local_output(repo, path)
246
+ path.write_text(text, encoding="utf-8")
247
+
248
+
249
+ def write_text_if_allowed(repo: Path, path: Path, text: str, *, force: bool) -> str:
250
+ ensure_repo_local_output(repo, path)
251
+ if path.exists() and not force:
252
+ return "exists"
253
+ write_repo_text(repo, path, text)
254
+ return "written"
255
+
256
+
257
+ def install_agents_block(repo: Path, *, force: bool) -> str:
258
+ path = repo / AGENTS_FILE
259
+ block = agents_block()
260
+ ensure_repo_local_output(repo, path)
261
+ if not path.exists():
262
+ write_repo_text(repo, path, f"# AGENTS.md\n\n{block}")
263
+ return "created"
264
+ text = read_repo_text(repo, path)
265
+ if AGENTS_BEGIN in text and AGENTS_END in text:
266
+ if not force:
267
+ return "already_present"
268
+ start = text.index(AGENTS_BEGIN)
269
+ end = text.index(AGENTS_END, start) + len(AGENTS_END)
270
+ write_repo_text(repo, path, text[:start].rstrip() + "\n\n" + block + text[end:].lstrip())
271
+ return "replaced"
272
+ write_repo_text(repo, path, text.rstrip() + "\n\n" + block)
273
+ return "appended"
274
+
275
+
276
+ def vision_mcp_server_payload(package: str) -> dict[str, Any]:
277
+ return {
278
+ "type": "stdio",
279
+ "command": "npx",
280
+ "args": ["-y", package],
281
+ "enable": True,
282
+ }
283
+
284
+
285
+ def as_record(value: Any, *, label: str) -> dict[str, Any]:
286
+ if not isinstance(value, dict):
287
+ raise ValueError(f"{label} must be a JSON object")
288
+ return value
289
+
290
+
291
+ def mcp_servers_for_update(config: dict[str, Any]) -> dict[str, Any]:
292
+ mcp = config.get("mcp")
293
+ if isinstance(mcp, dict) and isinstance(mcp.get("servers"), dict):
294
+ return mcp["servers"]
295
+ if isinstance(config.get("mcpServers"), dict):
296
+ return config["mcpServers"]
297
+ config["mcpServers"] = {}
298
+ return config["mcpServers"]
299
+
300
+
301
+ def install_vision_mcp(repo: Path, *, force: bool, server_name: str, package: str) -> str:
302
+ path = repo / VISION_MCP_FILE
303
+ server = vision_mcp_server_payload(package)
304
+ ensure_repo_local_output(repo, path)
305
+ if not path.exists():
306
+ write_repo_text(
307
+ repo,
308
+ path,
309
+ json.dumps({"mcpServers": {server_name: server}}, indent=2, sort_keys=True) + "\n",
310
+ )
311
+ return "written"
312
+
313
+ config = as_record(json.loads(read_repo_text(repo, path)), label=str(VISION_MCP_FILE))
314
+ servers = mcp_servers_for_update(config)
315
+ if server_name in servers and not force:
316
+ return "already_present"
317
+ status = "replaced" if server_name in servers else "added"
318
+ servers[server_name] = server
319
+ write_repo_text(repo, path, json.dumps(config, indent=2, sort_keys=True) + "\n")
320
+ return status
321
+
322
+
323
+ def install_repo(
324
+ repo: Path,
325
+ *,
326
+ write_agents: bool,
327
+ force: bool,
328
+ vision_mcp: bool = True,
329
+ vision_mcp_server: str = DEFAULT_VISION_MCP_SERVER,
330
+ vision_mcp_package: str = DEFAULT_VISION_MCP_PACKAGE,
331
+ ) -> InstallResult:
332
+ repo = repo.resolve()
333
+ if not repo.is_dir():
334
+ raise ValueError(f"repo does not exist: {repo}")
335
+ written: list[str] = []
336
+ skipped: list[str] = []
337
+
338
+ routing_status = write_text_if_allowed(
339
+ repo,
340
+ repo / ROUTING_FILE,
341
+ json.dumps(
342
+ routing_payload(
343
+ repo,
344
+ vision_mcp_server=vision_mcp_server,
345
+ vision_mcp_package=vision_mcp_package,
346
+ ),
347
+ indent=2,
348
+ sort_keys=True,
349
+ ) + "\n",
350
+ force=force,
351
+ )
352
+ (written if routing_status == "written" else skipped).append(str(ROUTING_FILE))
353
+
354
+ doc_status = write_text_if_allowed(
355
+ repo,
356
+ repo / DELEGATION_FILE,
357
+ delegation_doc(
358
+ repo,
359
+ vision_mcp_server=vision_mcp_server,
360
+ vision_mcp_package=vision_mcp_package,
361
+ ),
362
+ force=force,
363
+ )
364
+ (written if doc_status == "written" else skipped).append(str(DELEGATION_FILE))
365
+
366
+ vision_mcp_status = "not_requested"
367
+ if vision_mcp:
368
+ vision_mcp_status = install_vision_mcp(
369
+ repo,
370
+ force=force,
371
+ server_name=vision_mcp_server,
372
+ package=vision_mcp_package,
373
+ )
374
+ if vision_mcp_status in {"written", "added", "replaced"}:
375
+ written.append(str(VISION_MCP_FILE))
376
+ else:
377
+ skipped.append(str(VISION_MCP_FILE))
378
+
379
+ agents_status = "not_requested"
380
+ if write_agents:
381
+ agents_status = install_agents_block(repo, force=force)
382
+ if agents_status in {"created", "appended", "replaced"}:
383
+ written.append(str(AGENTS_FILE))
384
+ else:
385
+ skipped.append(str(AGENTS_FILE))
386
+
387
+ return InstallResult(
388
+ repo=repo,
389
+ routing_file=repo / ROUTING_FILE,
390
+ delegation_file=repo / DELEGATION_FILE,
391
+ agents_file=repo / AGENTS_FILE,
392
+ vision_mcp_file=repo / VISION_MCP_FILE,
393
+ agents_status=agents_status,
394
+ vision_mcp_status=vision_mcp_status,
395
+ written=written,
396
+ skipped=skipped,
397
+ )
398
+
399
+
400
+ def install_repo_command(args: Any) -> int:
401
+ result = install_repo(
402
+ args.repo,
403
+ write_agents=args.write_agents,
404
+ force=args.force,
405
+ vision_mcp=not args.skip_vision_mcp,
406
+ vision_mcp_server=args.vision_mcp_server,
407
+ vision_mcp_package=args.vision_mcp_package,
408
+ )
409
+ payload = {
410
+ "ok": True,
411
+ "repo": str(result.repo),
412
+ "routing_file": str(result.routing_file),
413
+ "delegation_file": str(result.delegation_file),
414
+ "agents_file": str(result.agents_file),
415
+ "vision_mcp_file": str(result.vision_mcp_file),
416
+ "agents_status": result.agents_status,
417
+ "vision_mcp_status": result.vision_mcp_status,
418
+ "written": result.written,
419
+ "skipped": result.skipped,
420
+ "next_action": "Read .codex/ZCODE_DELEGATION.md and run auto-route before implementation edits; run vision-preflight before required image tasks.",
421
+ }
422
+ print(json.dumps(payload, indent=2, sort_keys=True))
423
+ return 0
424
+
425
+
426
+ def install_repo_entrypoint(argv: list[str] | None = None) -> int:
427
+ parser = argparse.ArgumentParser(description="Install ZCode delegation defaults into a target repo.")
428
+ parser.add_argument("repo", type=Path, help="Target repository path.")
429
+ parser.add_argument("--no-write-agents", dest="write_agents", action="store_false", default=True)
430
+ parser.add_argument("--skip-vision-mcp", action="store_true")
431
+ parser.add_argument("--vision-mcp-server", default=DEFAULT_VISION_MCP_SERVER)
432
+ parser.add_argument("--vision-mcp-package", default=DEFAULT_VISION_MCP_PACKAGE)
433
+ parser.add_argument("--force", action="store_true")
434
+ args = parser.parse_args(argv)
435
+ return install_repo_command(args)
436
+
437
+
438
+ if __name__ == "__main__":
439
+ raise SystemExit(install_repo_entrypoint(sys.argv[1:]))