docs: document and package standalone coordinator

This commit is contained in:
sid 2026-07-30 23:23:53 -06:00
parent 296f24c59e
commit c9a16229b1
9 changed files with 544 additions and 15 deletions

38
AGENTS.md Normal file
View file

@ -0,0 +1,38 @@
# AGENTS.md — notes for Codex and other repository agents
**Full project instructions live in [`CLAUDE.md`](CLAUDE.md); read it first.** It is the source of
truth for the file map, invariants, change workflow, and validation contract. Read
[`DESIGN.md`](DESIGN.md) before changing isolation, identity, leases, cleanup, tmux, or hooks.
## This is a Jujutsu repository
The repository uses **Jujutsu (`jj`) with a colocated Git backend**. Use `jj` for status, diffs,
history, commits, bookmarks, rewriting, and pushes. Do not use Git history commands just because
`.git/` exists. Never force-push or rewrite published work.
Run these before editing:
```sh
hostname
jj st
```
## Do not regress packaging or safety
- Edit standalone sources, never a consumer repositorys vendored `.coord/` copy.
- `.coord/` is canonical; `.claude/coord/` is compatibility only.
- Keep the canonical skill in `skills/coord/` and install identical copies for Codex and Claude.
- Preflight every source, identity, wrapper, skill, and JSON input before replacing target files.
- Preserve unrelated hooks, skills, settings, `AGENTS.md`, and `CLAUDE.md` in target repositories.
- Keep runtime SQLite state outside repositories and preserve immutable project IDs.
- Use independent `jj git clone --colocate` instances for concurrent writable work.
- Treat full IDs as authority; short names and five-word handles are presentation/resolution aids.
- Never guess during workspace cleanup or target tmux objects by their names.
Validate with:
```sh
python3 -m unittest discover -s tests -v
python3 -m py_compile coord hook.py install session.py store.py workspace.py
python3 ~/.codex/skills/.system/skill-creator/scripts/quick_validate.py skills/coord
```

134
CLAUDE.md Normal file
View file

@ -0,0 +1,134 @@
# CLAUDE.md — coord maintainer and agent guide
This is the complete instruction source for agents working on `coord`. Read [`README.md`](README.md)
for the user workflow and [`DESIGN.md`](DESIGN.md) for the architecture, isolation boundary, and
proved failure modes.
## Repository and VCS
This is a **Jujutsu (`jj`) repository with a colocated Git backend**. Use `jj` for status, diffs,
history, descriptions, commits, bookmarks, rebases, and pushes. Do not use `git add`, `git commit`,
`git rebase`, or Git history mutation merely because `.git/` exists.
Useful commands:
```sh
jj st
jj diff
jj log
jj describe -m "type: concise change"
jj new
jj bookmark set master -r @
jj git push
```
Never force-push or rewrite published changes. A remote is not part of a fresh checkout of this
standalone repository; inspect configuration before proposing publication.
## Purpose and source map
- `coord` — command parser and coordinator operations.
- `store.py` — SQLite schema, identities, leases, messaging, and audit records.
- `workspace.py` — independent Jujutsu clones, lifecycle, integration evidence, tmux, and cleanup.
- `hook.py` — pre-write lease enforcement and context delivery.
- `session.py` — harness lifecycle registration, heartbeat, and termination.
- `install` — preflighted vendoring, compatibility shims, hook merge, and skill installation.
- `skills/coord/` — canonical concise skill installed into both harness discovery locations.
- `tests/` — behavioral installer, CLI, documentation, and packaging tests.
- `DESIGN.md` — architectural rationale and security boundaries.
The standalone files above are authoritative. Installed `.coord/` copies and `.claude/coord/`
compatibility shims in consumer repositories are generated artifacts, not alternate sources.
## Invariants
Preserve these properties in every change:
1. `.coord/` is the canonical installed runtime. `.claude/coord/` remains a compatible shim for
existing sessions, including argv, cwd, environment, exit status, and module imports.
2. `skills/coord/SKILL.md` is the repository skill source. Install byte-identical skill trees at
`.coord/skills/coord/`, `.agents/skills/coord/`, and `.claude/skills/coord/`; do not embed
repository-specific policy in the generic skill.
3. A project ID is explicit on first install, immutable thereafter, and shared by clones of one
logical project. Never silently generate or replace it.
4. Runtime SQLite state stays outside repositories. Install, upgrade, check, and cleanup must not
copy, delete, reset, or rewrite unrelated state.
5. Finish all fallible source reads, project-ID checks, wrapper generation, skill reads, and hook
JSON validation before the first target replacement. Invalid input must leave the target
untouched.
6. Replace only exact known coord hook endpoints. Preserve unrelated hooks, JSON keys, agent
instructions, skills, and repository files.
7. Writes use a complete write loop, fsync, and atomic replacement. Do not leave partial files.
8. Full opaque IDs remain canonical evidence. Exact five-word handles may resolve identities;
prefixes and display-only shortened VCS IDs never carry authority.
9. Managed writable work defaults to independent colocated `jj git clone` instances. A native
`jj workspace` is opt-in because it shares an operation log and object store.
10. Workspace deletion never guesses. Require registry/on-disk ownership agreement, clean state,
gate evidence, integration evidence where needed, and exact confirmation.
11. Tmux names are presentation only. Create concise `<task>-<sid4>` names, but inspect and mutate
sessions, windows, and panes only through recorded immutable tmux IDs.
12. `batch` accepts only the low-risk allowlist, inherits one already-resolved identity, executes
ordinary handlers sequentially, stops on failure, and performs no hidden rollback.
13. Hooks fail open when coordinator infrastructure cannot load, but a positively recognized
conflicting write fails closed. Do not claim shell-command parsing or security isolation the
hook does not provide.
14. Publishing, integration, activation, deployment, and other external side effects require
explicit resource scopes and authoritative before/after evidence. Never force.
## Change workflow
Before editing, run `hostname` and `jj st`. Preserve unrelated working-copy changes. In a shared
consumer repository, use its installed coord policy and an isolated managed clone; do not develop
the standalone source by editing a vendored copy.
When changing runtime behavior:
1. Update the authoritative module.
2. Add a behavioral regression test that observes the property, not merely a source string.
3. Run the focused test and the full standard-library suite.
4. If the installed interface or model changes, update README, DESIGN, and the coord skill.
When changing installation or packaging:
1. Keep `CORE_FILES` and `SKILL_FILES` explicit.
2. Build the entire install plan before the first replacement.
3. Test fresh install, legacy migration, invalid-input no-write behavior, drift detection, hook
preservation, compatibility entry points, and identical dual-harness skills.
4. Confirm the installer vendors docs under `.coord/` without overwriting target-root `AGENTS.md`
or `CLAUDE.md`.
When changing the skill:
1. Keep the frontmatter trigger description specific and the body concise.
2. Put detailed architecture in DESIGN rather than duplicating it into model context.
3. Regenerate `agents/openai.yaml` when its UI text becomes stale.
4. Run the skill validator and the packaging tests.
## Validation
Run:
```sh
python3 -m unittest discover -s tests -v
python3 -m py_compile coord hook.py install session.py store.py workspace.py
python3 ~/.codex/skills/.system/skill-creator/scripts/quick_validate.py skills/coord
./coord --help
./coord work --help
./install --help
jj st
```
Tests must use temporary directories and an explicit `COORD_DB`; never exercise cleanup against
an operators real registry. If a required external binary such as `jj` or `tmux` is unavailable,
report that portion as unverified rather than implying it passed.
## Documentation ownership
- README: installation, common operations, state locations, compatibility, and development entry.
- CLAUDE: complete maintainer/agent rules and validation contract.
- AGENTS: short high-risk front-load that points here.
- DESIGN: implementation-independent rationale, boundaries, and threat model.
- Skill: minimal operational procedure loaded into model context.
Update or remove stale statements rather than appending contradictory history. Historical names
retained for compatibility must be labelled as such.

View file

@ -1,6 +1,7 @@
# Agent coordination design
Status: implemented. Packaging and installation: [`README.md`](README.md).
Status: implemented. Packaging and installation: [`README.md`](README.md). Maintainer invariants:
[`CLAUDE.md`](CLAUDE.md).
## Boundary that actually isolates work
@ -19,8 +20,10 @@ one repository is intentional, but it shares the operation log/object store and
default isolation boundary. In this repository its secondary directory also lacks an ordinary
`.git`, so `nix flake metadata .` does not discover the colocated Git flake normally.
Managed clones live under `/var/tmp/fleet-audit`, the large non-snapshotted scratch dataset.
`/tmp` and `/home` are on the nearly-full snapshotted dataset and must not receive agent clones.
The compatibility default for managed clones is `/var/tmp/fleet-audit`, the large
non-snapshotted scratch dataset in the original deployment. Other installations should select a
safe local dataset with `--scratch-root`; avoid any location whose capacity, snapshots, backup,
or mount semantics make disposable clones expensive.
## Identity

176
README.md
View file

@ -1,25 +1,177 @@
# coord
Harness-neutral coordination for Claude Code, Codex, and other repository agents.
`coord` is a small, harness-neutral coordinator for concurrent Claude Code, Codex, and other
repository agents. It combines:
The standalone project is the source of truth. Install a vendored copy into another repository:
- exact five-word agent and workspace handles;
- transactional resource leases and durable messages in host-local SQLite;
- isolated Jujutsu clones with recorded lifecycle, integration, and cleanup evidence;
- tmux launch, resume, attach, and safe-close operations using immutable tmux object IDs;
- shared lifecycle and write-conflict hooks for Claude Code and Codex; and
- a concise `coord` skill installed for both harnesses.
The standalone project is the source of truth. [`DESIGN.md`](DESIGN.md) explains the isolation and
threat model. [`CLAUDE.md`](CLAUDE.md) is the complete maintainer and agent guide; [`AGENTS.md`](AGENTS.md)
front-loads its most important rules.
## Requirements
- Python 3 with the standard-library `sqlite3` module
- Jujutsu (`jj`) and Git for managed independent clones
- tmux plus `codex-direct` and/or `claude-direct` for managed interactive sessions
The coordinator itself has no third-party Python dependencies.
## Install or upgrade a repository
Choose a stable project ID shared by every clone of the same logical project:
```sh
./install /path/to/repository --project-id my-logical-project
```
An installation provides:
- `.coord/` — canonical CLI, hooks, runtime modules, installer, design, and immutable project ID.
- `.claude/coord/` — compatibility shims for existing sessions and old instructions.
- merged `.claude/settings.json` and `.codex/hooks.json` entries pointing at `.coord/`.
Upgrade an existing installation with the same command. Existing project IDs are preserved and a
conflicting requested ID is refused. Check drift without writing:
On later upgrades, omit `--project-id`; the installer preserves the existing value and refuses a
conflicting one:
```sh
./install /path/to/repository
./install /path/to/repository --check
```
Runtime state remains outside repositories in the private host-local SQLite database described in
[`DESIGN.md`](DESIGN.md). Installing or upgrading never copies, deletes, or rewrites that state.
`--check` is read-only and exits nonzero on drift. Installation preflights all source files,
identity, wrappers, and hook JSON before replacing any target file.
An installation provides:
| Path | Purpose |
|---|---|
| `.coord/` | Canonical CLI, hook/runtime modules, installer, docs, skill source, and project ID |
| `.agents/skills/coord/` | Codex/project skill, including UI metadata |
| `.claude/skills/coord/` | Claude Code copy of the same skill |
| `.claude/coord/` | Compatibility shims for older sessions and instructions |
| `.codex/hooks.json` | Merged Codex lifecycle and write-conflict hooks |
| `.claude/settings.json` | Merged Claude lifecycle and write-conflict hooks |
The installer replaces only hooks that invoke known coord endpoints. It preserves unrelated hooks
and unrelated settings. It does not alter the target repositorys `AGENTS.md`, `CLAUDE.md`, VCS
history, or runtime database.
Codex pins hook source hashes and may prompt to trust a changed hook after an upgrade. Review and
approve it with `/hooks`; that prompt is an intentional harness security boundary.
## Everyday use
Hooks normally register the current runtime and export enough identity for commands to infer it.
Use the canonical entry point from the repository root:
```sh
.coord/coord list
.coord/coord claim tests/checks.nix -p "add cache checks"
.coord/coord send "I am changing the cache schema" --to '*'
.coord/coord release tests/checks.nix
```
Use `append` only when simultaneous additions cannot overwrite one another:
```sh
.coord/coord claim tests/checks.nix --mode append -p "append one independent check"
```
Compose low-risk coordinator actions with `:` separators:
```sh
.coord/coord batch claim docs/plan.md -p "revise plan" : send "plan claimed" --to '*'
```
The batch stops at the first failure and is deliberately not atomic. Destructive workspace and
external publication actions are excluded.
Create an isolated Jujutsu clone before writable concurrent work:
```sh
.coord/coord work create cache-audit
.coord/coord work list
.coord/coord work gc
```
`work create` prints the exact five-word work handle and path. Then use that handle for lifecycle
operations:
```sh
.coord/coord work start <work-handle> --harness codex
.coord/coord work attach <work-handle> --execute
.coord/coord work park <work-handle> --reason "waiting for review"
.coord/coord work resume <work-handle>
.coord/coord work inspect <work-handle>
```
Managed windows have short human-readable names, while mutations target immutable tmux IDs.
`work close` safely closes the recorded window without dropping the operator to an outer shell.
`work remove` is conservative: inspect its refusal, integration, gate, and confirmation
requirements before cleanup.
Run `coord COMMAND --help` or `coord work COMMAND --help` for the complete option surface. Legacy
`--sid`, `--paths`, and similar spellings remain accepted for compatibility but are hidden; new
instructions should rely on inferred identity, positional operands, and readable optional flags.
### Command map
| Command | Role |
|---|---|
| `coord hello` | Register a live runtime; normally called by lifecycle hooks |
| `coord heartbeat` | Renew runtime presence and held leases |
| `coord bye` | End a runtime attachment without ending its durable conversation |
| `coord claim` / `coord renew` / `coord release` | Manage transactional resource leases |
| `coord list` | Show live agents, leases, and optionally work records |
| `coord send` / `coord inbox` / `coord ack` | Deliver and acknowledge durable messages |
| `coord check` | Ask whether a resource is blocked; used by write hooks |
| `coord audit` | Export deterministic audit events |
| `coord batch` | Compose guarded low-risk actions separated by `:` |
| `coord remote verify` | Read and verify an authoritative remote bookmark |
| `coord work …` | Create, inspect, run, integrate, recover, and clean managed work |
## State, identity, and portability
Runtime state defaults to:
```text
$XDG_STATE_HOME/fleet-agent-coord/<project-id>.sqlite3
```
or `~/.local/state/fleet-agent-coord/…` when `XDG_STATE_HOME` is unset. The directory and SQLite
files are private to the user. `COORD_DB` selects a different database, and `COORD_PROJECT_ID`
overrides project discovery for controlled testing. The historical `fleet-agent-coord` directory
name is retained so existing deployments do not silently split their state.
The managed-clone default is `/var/tmp/fleet-audit`, retained for compatibility with the original
deployment. Use `work create --scratch-root /safe/local/path` when that directory is inappropriate.
The cleanup registry never scans arbitrary directories or guesses ownership.
Five-word handles are collision-checked aliases. Full UUIDs and full VCS object IDs remain the
authoritative stored evidence; prefixes and shortened IDs never authorize mutations.
## Skills
The canonical repository skill source is [`skills/coord/SKILL.md`](skills/coord/SKILL.md). An
installation retains it at `.coord/skills/coord/` and puts identical discovery copies under both
supported harness paths. Invoke it explicitly as `$coord`, or let its description trigger when
coordinating agents, claiming shared resources, creating isolated work, handling messages,
managing tmux sessions, or cleaning up managed work.
The skill is intentionally concise. Detailed architecture stays in `.coord/DESIGN.md`, while
repository-specific policies belong in the target repositorys own agent instructions.
## Develop and validate
This repository is a colocated Jujutsu repository. Use `jj`, not Git, for status and history:
```sh
jj st
jj diff
python3 -m unittest discover -s tests -v
python3 -m py_compile coord hook.py install session.py store.py workspace.py
python3 ~/.codex/skills/.system/skill-creator/scripts/quick_validate.py skills/coord
```
For an installer change, test both a fresh install and an in-place legacy upgrade. Do not publish
or force-update a remote without explicit authorization. No remote is configured by default.

19
install
View file

@ -17,6 +17,8 @@ from typing import Any
HERE = Path(__file__).resolve().parent
CORE_FILES = (
".gitignore",
"AGENTS.md",
"CLAUDE.md",
"DESIGN.md",
"README.md",
"coord",
@ -27,6 +29,10 @@ CORE_FILES = (
"workspace.py",
)
EXECUTABLES = {"coord", "install"}
SKILL_FILES = (
"SKILL.md",
"agents/openai.yaml",
)
PROJECT_ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}")
CLAUDE_COMMANDS = {
@ -233,6 +239,19 @@ def install(target: Path, *, requested_id: str, check: bool) -> list[str]:
name in EXECUTABLES,
)
)
for root in (
".coord/skills/coord",
".agents/skills/coord",
".claude/skills/coord",
):
for name in SKILL_FILES:
planned.append(
(
target / root / name,
(HERE / "skills" / "coord" / name).read_bytes(),
False,
)
)
planned.append(
(destination / "project-id", f"{selected_id}\n".encode(), False)
)

75
skills/coord/SKILL.md Normal file
View file

@ -0,0 +1,75 @@
---
name: coord
description: Coordinate concurrent repository agents with transactional resource leases, durable messages, isolated Jujutsu clones, managed tmux sessions, integration evidence, and ownership-checked cleanup. Use when an agent needs to inspect peer work, claim or release shared resources, message another session, create or resume isolated work, start or close an agent window, queue integration, or garbage-collect managed work.
---
# Coordinate repository agents
Use the repositorys `.coord/coord` entry point. Prefer inferred identity from hooks or a managed
shell; use `-i <exact-five-word-handle>` only for manual recovery or selection. Never use prefixes.
## Choose the boundary
- For ordinary writable implementation, create an isolated clone with
`.coord/coord work create <short-task>`. Work inside the returned path.
- For a shared external boundary, claim an explicit resource such as `integration/master`,
`push/origin/master`, or a deployment target.
- Claim files in a shared checkout only when isolation is unavailable. Use `--mode append` solely
when concurrent additions cannot overwrite one another.
Inspect peers before coordinating:
```sh
.coord/coord list
.coord/coord work list
```
## Claim and communicate
Use positional resources and concise purposes:
```sh
.coord/coord claim tests/checks.nix -p "add one cache check"
.coord/coord send "cache schema is changing" --to '*'
.coord/coord release tests/checks.nix
```
Treat a refused claim as authoritative. Message the exact holder handle or choose disjoint work;
do not bypass a lease. Acknowledge delivered messages after acting on them:
```sh
.coord/coord inbox --peek
.coord/coord ack <message-id>
```
Use `batch ACTION : ACTION...` only for its guarded low-risk commands. It is sequential, stops at
the first failure, and is not atomic.
## Manage isolated work
Use exact five-word work handles returned by the CLI:
```sh
.coord/coord work start <work-handle> --harness codex
.coord/coord work attach <work-handle> --execute
.coord/coord work park <work-handle> --reason "waiting"
.coord/coord work resume <work-handle>
.coord/coord work inspect <work-handle>
.coord/coord work gc
```
Use `work close`, not raw pane/window killing, for a managed agent. Window names are human labels;
the coordinator mutates only recorded immutable tmux IDs.
Before removal, inspect the work record. Never bypass dirty-tree, ownership, gate, or integration
refusals. Use `work adopt` only for a known independent clone with explicit recovery evidence.
## Cross shared boundaries
Acquire the exact resource before integration, push, activation, deployment, or another external
mutation. Verify the authoritative remote or target before and after the action. Never force-push.
Lock release alone is not integration proof; record the exact integrated target with
`work queue <handle> integrate --target <revision>`.
For architecture, failure modes, and recovery rules, read the repositorys `.coord/DESIGN.md`.
Repository-specific instructions in `AGENTS.md` and `CLAUDE.md` always take precedence.

View file

@ -0,0 +1,4 @@
interface:
display_name: "Agent Coordinator"
short_description: "Coordinate agents, leases, workspaces, and tmux"
default_prompt: "Use $coord to organize concurrent agent work safely."

73
tests/test_docs.py Normal file
View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Keep entry documentation, skill packaging, and the CLI surface aligned."""
from __future__ import annotations
import importlib.machinery
import importlib.util
from pathlib import Path
import subprocess
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
class DocumentationContract(unittest.TestCase):
def test_required_document_map_and_jj_policy(self) -> None:
readme = (ROOT / "README.md").read_text(encoding="utf-8")
claude = (ROOT / "CLAUDE.md").read_text(encoding="utf-8")
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
design = (ROOT / "DESIGN.md").read_text(encoding="utf-8")
self.assertIn("[`CLAUDE.md`](CLAUDE.md)", readme)
self.assertIn("[`DESIGN.md`](DESIGN.md)", readme)
self.assertIn("[`CLAUDE.md`](CLAUDE.md)", agents)
self.assertIn("Jujutsu (`jj`)", claude)
self.assertIn("Jujutsu (`jj`)", agents)
self.assertIn("Status: implemented", design)
def test_readme_names_every_public_top_level_command(self) -> None:
loader = importlib.machinery.SourceFileLoader("coord_docs_test", str(ROOT / "coord"))
spec = importlib.util.spec_from_loader(loader.name, loader)
assert spec is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
loader.exec_module(module)
parser = module.build_parser()
choices: set[str] = set()
for action in parser._actions:
if hasattr(action, "choices") and isinstance(action.choices, dict):
choices.update(action.choices)
readme = (ROOT / "README.md").read_text(encoding="utf-8")
missing = sorted(name for name in choices if f"coord {name}" not in readme)
self.assertEqual(missing, [], f"README omits public commands: {missing}")
def test_help_entrypoints_are_live(self) -> None:
for command in (
[str(ROOT / "coord"), "--help"],
[str(ROOT / "coord"), "work", "--help"],
[str(ROOT / "install"), "--help"],
):
result = subprocess.run(
command,
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("usage:", result.stdout)
def test_skill_is_concise_and_has_no_placeholders(self) -> None:
skill = (ROOT / "skills/coord/SKILL.md").read_text(encoding="utf-8")
self.assertNotIn("TODO", skill)
self.assertLess(len(skill.splitlines()), 120)
for operation in ("claim", "send", "work create", "work close", "work gc"):
self.assertIn(operation, skill)
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -41,6 +41,25 @@ class InstallBehavior(unittest.TestCase):
(target / ".coord/project-id").read_text().strip(),
"fresh-project",
)
codex_skill = target / ".agents/skills/coord/SKILL.md"
claude_skill = target / ".claude/skills/coord/SKILL.md"
canonical_skill = target / ".coord/skills/coord/SKILL.md"
self.assertEqual(codex_skill.read_bytes(), claude_skill.read_bytes())
self.assertEqual(codex_skill.read_bytes(), canonical_skill.read_bytes())
self.assertEqual(
codex_skill.read_bytes(),
(ROOT / "skills/coord/SKILL.md").read_bytes(),
)
self.assertEqual(
(target / ".agents/skills/coord/agents/openai.yaml").read_bytes(),
(
target / ".claude/skills/coord/agents/openai.yaml"
).read_bytes(),
)
self.assertTrue((target / ".coord/AGENTS.md").is_file())
self.assertTrue((target / ".coord/CLAUDE.md").is_file())
self.assertFalse((target / "AGENTS.md").exists())
self.assertFalse((target / "CLAUDE.md").exists())
checked = self.run_install(target, "--check")
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
@ -50,6 +69,11 @@ class InstallBehavior(unittest.TestCase):
legacy = target / ".claude/coord"
legacy.mkdir(parents=True)
(legacy / "project-id").write_text("legacy-project\n")
(target / "AGENTS.md").write_text("target agents\n")
(target / "CLAUDE.md").write_text("target claude\n")
unrelated_skill = target / ".claude/skills/local/SKILL.md"
unrelated_skill.parent.mkdir(parents=True)
unrelated_skill.write_text("target skill\n")
(target / ".claude/settings.json").write_text(
json.dumps(
{
@ -112,6 +136,9 @@ class InstallBehavior(unittest.TestCase):
(target / ".claude/coord/project-id").read_text().strip(),
"legacy-project",
)
self.assertEqual((target / "AGENTS.md").read_text(), "target agents\n")
self.assertEqual((target / "CLAUDE.md").read_text(), "target claude\n")
self.assertEqual(unrelated_skill.read_text(), "target skill\n")
settings = json.loads((target / ".claude/settings.json").read_text())
self.assertEqual(settings["effortLevel"], "high")
commands = [
@ -268,6 +295,9 @@ class InstallBehavior(unittest.TestCase):
sentinel = legacy / "coord"
sentinel.write_text("legacy-sentinel\n")
(target / ".claude/settings.json").write_text("{ invalid")
unrelated_skill = target / ".agents/skills/local/SKILL.md"
unrelated_skill.parent.mkdir(parents=True)
unrelated_skill.write_text("local-sentinel\n")
refused = self.run_install(target)
self.assertEqual(refused.returncode, 2)
@ -275,6 +305,7 @@ class InstallBehavior(unittest.TestCase):
self.assertEqual(sentinel.read_text(), "legacy-sentinel\n")
self.assertFalse((target / ".coord").exists())
self.assertFalse((target / ".codex").exists())
self.assertEqual(unrelated_skill.read_text(), "local-sentinel\n")
if __name__ == "__main__":