caokit 0.3.0.dev0__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.
- caokit/__init__.py +3 -0
- caokit/adapters.py +259 -0
- caokit/cli.py +704 -0
- caokit/company.py +115 -0
- caokit/config.py +191 -0
- caokit/data/adapters/antigravity.toml +35 -0
- caokit/data/adapters/claude.toml +38 -0
- caokit/data/adapters/codex.toml +33 -0
- caokit/data/roles/engineer.md +90 -0
- caokit/data/roles/general.md +177 -0
- caokit/data/roles/inspector.md +55 -0
- caokit/data/roles/scout.md +77 -0
- caokit/data/scaffold/company/BACKLOG.md +5 -0
- caokit/data/scaffold/company/COMPANY.md +28 -0
- caokit/data/scaffold/company/DECISIONS.md +5 -0
- caokit/data/scaffold/company/README.md +16 -0
- caokit/data/scaffold/company/TEAM.md +7 -0
- caokit/data/scaffold/company/contracts/ARTIFACT_CONTRACT.md +44 -0
- caokit/data/scaffold/company/contracts/COMMUNICATION_CONTRACT.md +38 -0
- caokit/data/scaffold/company/contracts/COMPANY_CONTRACT.md +42 -0
- caokit/data/scaffold/company/contracts/REVIEW_CONTRACT.md +36 -0
- caokit/data/scaffold/company/contracts/SAFETY_CONTRACT.md +40 -0
- caokit/data/scaffold/company/contracts/STATE_CONTRACT.md +31 -0
- caokit/data/scaffold/company/contracts/TASK_CONTRACT.md +26 -0
- caokit/data/scaffold/company/prompts/first-run.md +18 -0
- caokit/data/scaffold/company/tasks/TASK-0001-smoke-test.md +109 -0
- caokit/data/scaffold/company/tasks/TASK_TEMPLATE.md +73 -0
- caokit/doctor.py +287 -0
- caokit/gitignore.py +36 -0
- caokit/init_project.py +183 -0
- caokit/install.py +242 -0
- caokit/launch.py +102 -0
- caokit/project.py +359 -0
- caokit/render.py +129 -0
- caokit/reset.py +208 -0
- caokit/roster.py +95 -0
- caokit/schedule.py +104 -0
- caokit/stack.py +117 -0
- caokit/tasks.py +180 -0
- caokit/worktrees.py +107 -0
- caokit-0.3.0.dev0.dist-info/METADATA +382 -0
- caokit-0.3.0.dev0.dist-info/RECORD +46 -0
- caokit-0.3.0.dev0.dist-info/WHEEL +5 -0
- caokit-0.3.0.dev0.dist-info/entry_points.txt +2 -0
- caokit-0.3.0.dev0.dist-info/licenses/LICENSE +21 -0
- caokit-0.3.0.dev0.dist-info/top_level.txt +1 -0
caokit/company.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""`caokit company create`: build, check and write caokit.toml."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .adapters import ROLES, AdapterError, Runner, default_runner, load_adapter, validate_model
|
|
7
|
+
from .config import Binding, Config, default_path, dump_config
|
|
8
|
+
from .roster import check_roster
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CompanyError(Exception):
|
|
12
|
+
"""Raised when a company cannot be created (bad roster, bad model, file exists)."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_binding(spec: str) -> Binding:
|
|
16
|
+
"""Parse a ``tool`` or ``tool:model`` command-line spec.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
spec: For example ``claude`` or ``antigravity:gemini-4-argon-high``.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The binding; ``model`` is None when no ``:model`` part is given.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
CompanyError: The tool part is empty.
|
|
26
|
+
"""
|
|
27
|
+
# partition on the first ":" only, so model IDs may not contain one but tools never do.
|
|
28
|
+
tool, _, model = spec.partition(":")
|
|
29
|
+
if not tool:
|
|
30
|
+
raise CompanyError(f"empty tool in {spec!r}")
|
|
31
|
+
return Binding(tool, model or None)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_company(
|
|
35
|
+
bindings: dict[str, Binding],
|
|
36
|
+
allow_soft_engineer: bool = False,
|
|
37
|
+
allow_single_provider: bool = False,
|
|
38
|
+
allow_soft_general: bool = False,
|
|
39
|
+
runner: Runner = default_runner,
|
|
40
|
+
out: Callable[[str], None] = print,
|
|
41
|
+
) -> Config:
|
|
42
|
+
"""Build a config from role bindings and check every guardrail.
|
|
43
|
+
|
|
44
|
+
Roster rules and model validation both run before anything is returned, so
|
|
45
|
+
all problems are reported together. Overriding a recommended model only
|
|
46
|
+
warns; an unlisted or unverifiable model is an error.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
bindings: Role name to binding; every role in ``ROLES`` must be present.
|
|
50
|
+
allow_soft_engineer: Accept a soft-enforcement tool as engineer.
|
|
51
|
+
allow_single_provider: Downgrade the cross-provider rule to a warning.
|
|
52
|
+
allow_soft_general: Accept a soft-enforcement tool as the General.
|
|
53
|
+
runner: Command runner used to validate models (injectable for tests).
|
|
54
|
+
out: Sink for warnings and recommendation notes.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The validated :class:`Config` (not yet written to disk).
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
CompanyError: A role is missing, the roster breaks a guardrail, or a
|
|
61
|
+
model ID is rejected. The message lists every problem.
|
|
62
|
+
"""
|
|
63
|
+
cfg = Config(
|
|
64
|
+
roles=dict(bindings),
|
|
65
|
+
allow_soft_engineer=allow_soft_engineer,
|
|
66
|
+
allow_single_provider=allow_single_provider,
|
|
67
|
+
allow_soft_general=allow_soft_general,
|
|
68
|
+
)
|
|
69
|
+
errors: list[str] = [f"role {role} has no tool" for role in ROLES if role not in cfg.roles]
|
|
70
|
+
if errors:
|
|
71
|
+
raise CompanyError("\n".join(errors)) # cannot check a partial roster
|
|
72
|
+
r = check_roster(cfg)
|
|
73
|
+
errors += r.errors
|
|
74
|
+
for role in ROLES:
|
|
75
|
+
b = cfg.roles[role]
|
|
76
|
+
try:
|
|
77
|
+
a = load_adapter(b.tool)
|
|
78
|
+
except AdapterError:
|
|
79
|
+
continue # already reported by check_roster as an unknown tool
|
|
80
|
+
rec = a.roles[role].recommended_model
|
|
81
|
+
if b.model:
|
|
82
|
+
e, w = validate_model(a, b.model, runner)
|
|
83
|
+
errors += [f"roles.{role}: {x}" for x in e]
|
|
84
|
+
r.warnings += [f"roles.{role}: {x}" for x in w]
|
|
85
|
+
if rec and b.model != rec: # advice only: the user's choice wins
|
|
86
|
+
r.warnings.append(f"roles.{role}: {b.model} is not the recommended model ({rec})")
|
|
87
|
+
elif rec:
|
|
88
|
+
out(f" note: roles.{role} has no model set; recommended for {a.id} is {rec}")
|
|
89
|
+
for warning in r.warnings:
|
|
90
|
+
out("WARNING: " + warning)
|
|
91
|
+
if errors:
|
|
92
|
+
raise CompanyError("\n".join(errors))
|
|
93
|
+
return cfg
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def write_company(cfg: Config, path: Path | None = None, force: bool = False) -> Path:
|
|
97
|
+
"""Write ``cfg`` as caokit.toml, creating parent directories.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
cfg: The validated configuration.
|
|
101
|
+
path: Destination (default: :func:`caokit.config.default_path`).
|
|
102
|
+
force: Overwrite an existing file.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The path written.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
CompanyError: The file exists and ``force`` is False.
|
|
109
|
+
"""
|
|
110
|
+
path = Path(path) if path else default_path()
|
|
111
|
+
if path.exists() and not force:
|
|
112
|
+
raise CompanyError(f"{path} already exists (use --force to overwrite)")
|
|
113
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
path.write_text(dump_config(cfg), encoding="utf-8")
|
|
115
|
+
return path
|
caokit/config.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""caokit.toml: load, validate and dump the company configuration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import tomllib
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .adapters import ROLES
|
|
12
|
+
from .render import validate_project_name
|
|
13
|
+
|
|
14
|
+
SCHEMA = 1
|
|
15
|
+
POLICY_KEYS = ("allow_soft_engineer", "allow_soft_general", "allow_single_provider")
|
|
16
|
+
CAO_VERSION = "2.5.4" # the only CAO version this kit is validated against
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConfigError(Exception):
|
|
20
|
+
"""Raised for any problem reading or validating ``caokit.toml``."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Binding:
|
|
25
|
+
"""The tool (and optionally model) assigned to one role.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
tool: Adapter ID, e.g. ``claude``.
|
|
29
|
+
model: Model ID, or None to use the tool's own default.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
tool: str
|
|
33
|
+
model: str | None = None # None = use the tool's own default model
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Config:
|
|
38
|
+
"""The whole company: one binding per role plus policy switches.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
roles: Binding for each of the four roles.
|
|
42
|
+
allow_soft_engineer: Lets a soft-enforcement tool be the engineer.
|
|
43
|
+
allow_soft_general: Lets a soft-enforcement tool be the General.
|
|
44
|
+
allow_single_provider: Downgrades the cross-provider rule to a warning.
|
|
45
|
+
expected_cao_version: CAO version the kit expects; doctor warns otherwise.
|
|
46
|
+
project: Project slug when this is a project-scoped (effective) config; its
|
|
47
|
+
profiles are named ``<project>_<role>-<tool>``. None for the user-level config.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
roles: dict[str, Binding]
|
|
51
|
+
allow_soft_engineer: bool = False # lets a soft-enforcement tool be the engineer
|
|
52
|
+
allow_soft_general: bool = False # lets a soft-enforcement tool be the General
|
|
53
|
+
allow_single_provider: bool = False # downgrades the cross-provider rule to a warning
|
|
54
|
+
expected_cao_version: str = CAO_VERSION
|
|
55
|
+
project: str | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def default_path() -> Path:
|
|
59
|
+
"""Return the default config path.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
``$CAOKIT_HOME/caokit.toml`` if set, else ``~/.config/caokit/caokit.toml``.
|
|
63
|
+
"""
|
|
64
|
+
home = os.environ.get("CAOKIT_HOME")
|
|
65
|
+
base = Path(home) if home else Path.home() / ".config" / "caokit"
|
|
66
|
+
return base / "caokit.toml"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _only(d: dict[str, Any], allowed: Iterable[str], where: str) -> None:
|
|
70
|
+
"""Reject keys not in ``allowed`` so typos fail loudly instead of being ignored."""
|
|
71
|
+
allowed = set(allowed)
|
|
72
|
+
for k in d:
|
|
73
|
+
if k not in allowed:
|
|
74
|
+
raise ConfigError(f"unknown key {where}.{k}" if where else f"unknown key {k}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _parse_project_name(raw: Any) -> str | None:
|
|
78
|
+
"""Validate the optional ``[project] name`` table, returning the slug or None."""
|
|
79
|
+
if raw is None:
|
|
80
|
+
return None
|
|
81
|
+
_only(raw, {"name"}, "project")
|
|
82
|
+
name = raw.get("name")
|
|
83
|
+
if not isinstance(name, str):
|
|
84
|
+
raise ConfigError("project.name must be a string")
|
|
85
|
+
try:
|
|
86
|
+
return validate_project_name(name)
|
|
87
|
+
except ValueError as e:
|
|
88
|
+
raise ConfigError(str(e)) from e
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def parse_config(d: dict[str, Any]) -> Config:
|
|
92
|
+
"""Validate a parsed TOML dict and build a :class:`Config`.
|
|
93
|
+
|
|
94
|
+
Tool names are not checked here; the roster rules report unknown or parked
|
|
95
|
+
tools with a clearer message.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
d: The dict produced by parsing ``caokit.toml``.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The validated configuration.
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
ConfigError: Wrong schema, unknown/missing role, unknown key, or bad type.
|
|
105
|
+
"""
|
|
106
|
+
_only(d, {"schema", "cao", "policy", "roles", "project"}, "")
|
|
107
|
+
if d.get("schema") != SCHEMA:
|
|
108
|
+
raise ConfigError(f"schema must be {SCHEMA} (got {d.get('schema')!r})")
|
|
109
|
+
cao = d.get("cao", {})
|
|
110
|
+
_only(cao, {"expected_version"}, "cao")
|
|
111
|
+
pol = d.get("policy", {})
|
|
112
|
+
_only(pol, set(POLICY_KEYS), "policy")
|
|
113
|
+
raw = d.get("roles", {})
|
|
114
|
+
for r in raw:
|
|
115
|
+
if r not in ROLES:
|
|
116
|
+
raise ConfigError(f"unknown role: roles.{r} (valid: {', '.join(ROLES)})")
|
|
117
|
+
roles: dict[str, Binding] = {}
|
|
118
|
+
for r in ROLES:
|
|
119
|
+
if r not in raw:
|
|
120
|
+
raise ConfigError(f"missing role: roles.{r}")
|
|
121
|
+
b = raw[r]
|
|
122
|
+
_only(b, {"tool", "model"}, "roles." + r)
|
|
123
|
+
if not isinstance(b.get("tool"), str) or not b["tool"]:
|
|
124
|
+
raise ConfigError(f"roles.{r}.tool must be a non-empty string")
|
|
125
|
+
m = b.get("model")
|
|
126
|
+
if m is not None and (not isinstance(m, str) or not m):
|
|
127
|
+
raise ConfigError(f"roles.{r}.model must be a non-empty string")
|
|
128
|
+
roles[r] = Binding(b["tool"], m)
|
|
129
|
+
for k in POLICY_KEYS:
|
|
130
|
+
if not isinstance(pol.get(k, False), bool):
|
|
131
|
+
raise ConfigError(f"policy.{k} must be true or false")
|
|
132
|
+
return Config(
|
|
133
|
+
roles=roles,
|
|
134
|
+
allow_soft_engineer=pol.get("allow_soft_engineer", False),
|
|
135
|
+
allow_soft_general=pol.get("allow_soft_general", False),
|
|
136
|
+
allow_single_provider=pol.get("allow_single_provider", False),
|
|
137
|
+
expected_cao_version=cao.get("expected_version", CAO_VERSION),
|
|
138
|
+
project=_parse_project_name(d.get("project")),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def load_config(path: Path | None = None) -> Config:
|
|
143
|
+
"""Read and validate the config file.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
path: File to read; the default location if None.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The validated configuration.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ConfigError: The file is missing, is not valid TOML, or fails validation.
|
|
153
|
+
"""
|
|
154
|
+
path = Path(path) if path else default_path()
|
|
155
|
+
if not path.is_file():
|
|
156
|
+
raise ConfigError(f"config not found: {path} (run `caokit company create`)")
|
|
157
|
+
try:
|
|
158
|
+
return parse_config(tomllib.loads(path.read_text(encoding="utf-8")))
|
|
159
|
+
except ValueError as e: # TOMLDecodeError subclasses ValueError
|
|
160
|
+
raise ConfigError(f"invalid TOML in {path}: {e}") from e
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def dump_config(c: Config) -> str:
|
|
164
|
+
"""Serialise a config to TOML text (hand-rolled: stdlib can only read TOML).
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
c: The configuration to write.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
TOML text that :func:`parse_config` round-trips.
|
|
171
|
+
"""
|
|
172
|
+
q = json.dumps # JSON string escaping is valid TOML basic-string escaping
|
|
173
|
+
out = [
|
|
174
|
+
f"schema = {SCHEMA}",
|
|
175
|
+
"",
|
|
176
|
+
"[cao]",
|
|
177
|
+
f"expected_version = {q(c.expected_cao_version)}",
|
|
178
|
+
"",
|
|
179
|
+
"[policy]",
|
|
180
|
+
f"allow_soft_engineer = {str(c.allow_soft_engineer).lower()}",
|
|
181
|
+
f"allow_soft_general = {str(c.allow_soft_general).lower()}",
|
|
182
|
+
f"allow_single_provider = {str(c.allow_single_provider).lower()}",
|
|
183
|
+
]
|
|
184
|
+
if c.project:
|
|
185
|
+
out[2:2] = ["[project]", f"name = {q(c.project)}", ""]
|
|
186
|
+
for r in ROLES:
|
|
187
|
+
b = c.roles[r]
|
|
188
|
+
out += ["", f"[roles.{r}]", f"tool = {q(b.tool)}"]
|
|
189
|
+
if b.model: # omit entirely when unset so the tool default applies
|
|
190
|
+
out.append(f"model = {q(b.model)}")
|
|
191
|
+
return "\n".join(out) + "\n"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id = "antigravity"
|
|
2
|
+
cao_provider = "antigravity_cli"
|
|
3
|
+
enforcement = "soft"
|
|
4
|
+
status = "active"
|
|
5
|
+
default_vendor = "google"
|
|
6
|
+
|
|
7
|
+
[detect]
|
|
8
|
+
binary = "agy"
|
|
9
|
+
version_cmd = ["agy", "--version"]
|
|
10
|
+
auth_cmd = ["agy", "models"] # succeeds only when logged in
|
|
11
|
+
|
|
12
|
+
[models]
|
|
13
|
+
source = "cmd"
|
|
14
|
+
list_cmd = ["agy", "models"] # first tab-separated column is the model ID
|
|
15
|
+
vendor_map = { "gemini-" = "google", "claude-" = "anthropic", "gpt-" = "openai" }
|
|
16
|
+
|
|
17
|
+
[frontmatter]
|
|
18
|
+
|
|
19
|
+
[roles.general]
|
|
20
|
+
can_fill = "yes" # soft enforcement still requires --allow-soft-general
|
|
21
|
+
|
|
22
|
+
[roles.engineer]
|
|
23
|
+
can_fill = "yes" # soft enforcement still requires --allow-soft-engineer
|
|
24
|
+
recommended_model = "gemini-4-argon-high"
|
|
25
|
+
|
|
26
|
+
[roles.scout]
|
|
27
|
+
can_fill = "yes"
|
|
28
|
+
recommended_model = "gemini-3.8-flash-medium"
|
|
29
|
+
|
|
30
|
+
[roles.inspector]
|
|
31
|
+
can_fill = "yes"
|
|
32
|
+
recommended_model = "gemini-4-argon-high"
|
|
33
|
+
|
|
34
|
+
[notes]
|
|
35
|
+
text = "CAO launches Antigravity with `--dangerously-skip-permissions`, so the tool limits in this profile are advisory only. Follow the boundaries above exactly; nothing enforces them."
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
id = "claude"
|
|
2
|
+
cao_provider = "claude_code"
|
|
3
|
+
enforcement = "native"
|
|
4
|
+
status = "active"
|
|
5
|
+
default_vendor = "anthropic"
|
|
6
|
+
|
|
7
|
+
[detect]
|
|
8
|
+
binary = "claude"
|
|
9
|
+
version_cmd = ["claude", "--version"]
|
|
10
|
+
auth_cmd = [] # no cheap non-interactive auth check
|
|
11
|
+
|
|
12
|
+
[models]
|
|
13
|
+
source = "shape" # no `claude models` command exists
|
|
14
|
+
aliases = ["sonnet", "opus", "haiku", "fable"]
|
|
15
|
+
shape = "^claude-[A-Za-z0-9.\\[\\]-]+$"
|
|
16
|
+
vendor_map = { "" = "anthropic" }
|
|
17
|
+
|
|
18
|
+
[frontmatter]
|
|
19
|
+
permissionMode = "auto"
|
|
20
|
+
|
|
21
|
+
[roles.general]
|
|
22
|
+
can_fill = "yes"
|
|
23
|
+
recommended_model = "sonnet"
|
|
24
|
+
|
|
25
|
+
[roles.engineer]
|
|
26
|
+
can_fill = "yes"
|
|
27
|
+
recommended_model = "opus"
|
|
28
|
+
|
|
29
|
+
[roles.scout]
|
|
30
|
+
can_fill = "yes"
|
|
31
|
+
recommended_model = "sonnet"
|
|
32
|
+
|
|
33
|
+
[roles.inspector]
|
|
34
|
+
can_fill = "yes"
|
|
35
|
+
recommended_model = "sonnet"
|
|
36
|
+
|
|
37
|
+
[notes]
|
|
38
|
+
text = ""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
id = "codex"
|
|
2
|
+
cao_provider = "codex"
|
|
3
|
+
enforcement = "soft"
|
|
4
|
+
status = "parked" # not selectable; kept as metadata only
|
|
5
|
+
default_vendor = "openai"
|
|
6
|
+
|
|
7
|
+
[detect]
|
|
8
|
+
binary = "codex"
|
|
9
|
+
version_cmd = ["codex", "--version"]
|
|
10
|
+
auth_cmd = ["codex", "login", "status"]
|
|
11
|
+
|
|
12
|
+
[models]
|
|
13
|
+
source = "shape"
|
|
14
|
+
aliases = []
|
|
15
|
+
shape = "^[A-Za-z0-9.-]+$"
|
|
16
|
+
vendor_map = { "" = "openai" }
|
|
17
|
+
|
|
18
|
+
[frontmatter]
|
|
19
|
+
|
|
20
|
+
[roles.general]
|
|
21
|
+
can_fill = "no"
|
|
22
|
+
|
|
23
|
+
[roles.engineer]
|
|
24
|
+
can_fill = "no"
|
|
25
|
+
|
|
26
|
+
[roles.scout]
|
|
27
|
+
can_fill = "no"
|
|
28
|
+
|
|
29
|
+
[roles.inspector]
|
|
30
|
+
can_fill = "no"
|
|
31
|
+
|
|
32
|
+
[notes]
|
|
33
|
+
text = ""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Engineer: scoped implementation, tests, debugging; runs only the authorised commands"
|
|
3
|
+
role: developer
|
|
4
|
+
skills: []
|
|
5
|
+
allowedTools:
|
|
6
|
+
- "fs_read"
|
|
7
|
+
- "fs_list"
|
|
8
|
+
- "fs_write"
|
|
9
|
+
- "execute_bash"
|
|
10
|
+
- "@cao-mcp-server"
|
|
11
|
+
mcpServers:
|
|
12
|
+
cao-mcp-server:
|
|
13
|
+
type: stdio
|
|
14
|
+
command: cao-mcp-server
|
|
15
|
+
args: []
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Engineer
|
|
19
|
+
|
|
20
|
+
## Identity
|
|
21
|
+
|
|
22
|
+
You are the implementation engineer. The General owns delegation and the human owns final approval. Any source change you make is reviewed by the `inspector`, which runs on a different tool (see `.company/ROSTER.md`).
|
|
23
|
+
|
|
24
|
+
## Before working
|
|
25
|
+
|
|
26
|
+
1. Read `.company/COMPANY.md`.
|
|
27
|
+
2. Read `.company/contracts/`.
|
|
28
|
+
3. Read the exact task file named in the assignment.
|
|
29
|
+
4. Restate internally the allowed paths, forbidden paths, deliverables, and acceptance checks.
|
|
30
|
+
5. If the task is ambiguous or exceeds its boundary, stop and message the General.
|
|
31
|
+
|
|
32
|
+
## Engineering rules
|
|
33
|
+
|
|
34
|
+
- Inspect before editing.
|
|
35
|
+
- Change only the assigned scope.
|
|
36
|
+
- Prefer the smallest coherent change.
|
|
37
|
+
- Add or update tests when source changes are authorized.
|
|
38
|
+
- Run the task's "Authorised validation commands" exactly as written, and record each command with its real output. Do not skip an authorised check out of caution.
|
|
39
|
+
- Run no other commands for validation or tidying: no `rm`, no cleanup of generated files, no git commands that change state (except the commit in Worktree mode below). If an authorised command leaves files behind (for example `__pycache__/`), leave them and report it, so the General can fix the task's command.
|
|
40
|
+
- Never deploy, merge, push, rewrite Git history, alter production data, read secrets, or contact external systems.
|
|
41
|
+
- Do not install packages, change lockfiles, or use network access unless the task explicitly authorizes it.
|
|
42
|
+
- Do not create or delegate to another agent, including Claude Code's own subagent tool.
|
|
43
|
+
- Do not claim a command passed unless you actually ran it and saw a successful result.
|
|
44
|
+
|
|
45
|
+
## Worktree mode
|
|
46
|
+
|
|
47
|
+
You are in worktree mode when your working directory contains `/.cao/worktrees/`. The project root is the part of the path before `/.cao/worktrees/`.
|
|
48
|
+
|
|
49
|
+
- Your working directory is a private checkout on its own branch. Edit source only there.
|
|
50
|
+
- `.company/` is git-ignored and does not exist in your checkout. Read the contracts and the task file, and write your artifact, under the project root's `.company/` (absolute paths), never under your checkout.
|
|
51
|
+
- Before editing, run `git rev-parse HEAD` and record it in your artifact as **Base commit**. Read the contracts, the task file and the source with your file tools, not with `ls`, `cat` or `pwd`; those are outside the authorised commands.
|
|
52
|
+
- When your work is done and the authorised checks pass, run `git add -A` and then `git commit -m "<TASK-ID>: <task title>"`, then `git rev-parse HEAD` and record the hash in your artifact as **Commit**. CAO discards uncommitted work when your terminal is deleted. Commit again, never amend, when asked to rework.
|
|
53
|
+
- These are the only state-changing git commands you may run, and only here. Never push, merge, rebase, reset, checkout another branch or touch other worktrees.
|
|
54
|
+
- Report `BLOCKED` instead of `COMPLETED` if the commit fails.
|
|
55
|
+
|
|
56
|
+
## Smoke-test restriction
|
|
57
|
+
|
|
58
|
+
For TASK-0001 (smoke test), do not modify application source. Write only the contracted artifact under `.company/artifacts/`.
|
|
59
|
+
|
|
60
|
+
## Required completion artifact
|
|
61
|
+
|
|
62
|
+
Unless the task says otherwise, write:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
.company/artifacts/<TASK-ID>-engineer.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Include:
|
|
69
|
+
|
|
70
|
+
- scope inspected,
|
|
71
|
+
- architecture summary,
|
|
72
|
+
- files or modules examined,
|
|
73
|
+
- work performed,
|
|
74
|
+
- every command you ran, each with its real output (a **Commands run** section),
|
|
75
|
+
- changed files,
|
|
76
|
+
- risks,
|
|
77
|
+
- unresolved questions,
|
|
78
|
+
- recommended next action.
|
|
79
|
+
|
|
80
|
+
## Callback
|
|
81
|
+
|
|
82
|
+
When assigned asynchronously, call `send_message` without `receiver_id` so CAO routes the callback to the assigning General. Send only:
|
|
83
|
+
|
|
84
|
+
- task ID,
|
|
85
|
+
- `COMPLETED`, `BLOCKED`, or `FAILED`,
|
|
86
|
+
- absolute artifact path,
|
|
87
|
+
- up to five findings,
|
|
88
|
+
- blocker or decision required.
|
|
89
|
+
|
|
90
|
+
Do not paste the full artifact into the message.
|