-
Notifications
You must be signed in to change notification settings - Fork 0
CONVENTIONS
Code style and patterns observed across src/harnessed/*.py. Follow these exactly when adding or editing source files.
Every source module opens with a triple-quoted docstring that names what the module does and what it deliberately does not do:
# src/harnessed/emit.py:1-12
"""Write the assembled artifacts into the mounted build dir (EMIT ONLY).
Pure file emission — no podman/docker, no daemon. ...
"""# src/harnessed/schema.py:1-14
"""Parse + validate recipe.yaml / stack.yaml into typed objects.
EMIT-ONLY assembler component: this module only reads files and builds in-memory
objects. It never invokes podman/docker and never writes anything.
"""The first sentence is the one-line summary; subsequent paragraphs capture constraints and design rationale.
All source files begin with:
from __future__ import annotationsThis appears at line 16 of schema.py, line 14 of emit.py, line 14 of paths.py, and line 13 of launcher.py. It is required in every new module.
Standard library → third-party → local (relative). Relative imports use from . import module for whole-module imports and from .module import Name for specific symbols:
# src/harnessed/emit.py:14-28
import json
import re
import shutil
from copy import deepcopy
from pathlib import Path
from . import paths
from .schema import McpServer, Recipe, Stack# src/harnessed/launcher.py:12-41
import json
import os
import re
import shutil
import subprocess
import sys
...
import typer
from rich.console import Console
from . import emit
from . import paths
from . import persist
from .paths import CONTAINER_HOME, instance_name, is_built, profile_dir, project_relpath
from .assemble import assemble
from .schema import (
HARNESS_CONFIG_DIR,
InitSpec,
SchemaError,
...
)Type hints are mandatory everywhere — parameters, return types, and local variables when the type is non-obvious.
- Union types use the
X | Ysyntax (notUnion[X, Y]):str | None,Path | None,dict[str, list[HookCommand]] - Generic collections use lowercase built-ins:
list[str],dict[str, str],tuple[Stack, list[Recipe]] -
Optionalfromtypingappears only in older code inlauncher.py; new code usesX | None
# src/harnessed/schema.py:30-38
def _resolve_dir(root: Path | None, kind: str, name: str) -> Path: ...
def load_stack_with_recipes(
root: Path | None, stack_name: str, *, strict: bool = False
) -> tuple[Stack, list[Recipe]]: ...All typed value objects use @dataclass. Mutable defaults always use field(default_factory=...):
# src/harnessed/schema.py:88-110
@dataclass
class McpServer:
name: str
command: str | None = None
args: list[str] = field(default_factory=list)
transport: str = "stdio"
url: str | None = None
env: dict = field(default_factory=dict)
raw: dict = field(default_factory=dict)Every dataclass that carries parsed YAML includes a raw: dict field (forward-compatibility, design D-14):
# src/harnessed/schema.py:584-603
@dataclass
class Recipe:
name: str
description: str = ""
servers: list[McpServer] = field(default_factory=list)
...
raw: dict = field(default_factory=dict)Properties on dataclasses are used for derived values:
# src/harnessed/schema.py:108-110
@property
def is_stdio_child(self) -> bool:
"""A stdio server hatago must bake + spawn (vs a network-native URL proxy)."""
return self.transport == "stdio" and self.command is not NoneCustom exceptions subclass from a base SchemaError:
# src/harnessed/schema.py:65-75
class SchemaError(Exception):
"""A recipe/stack manifest is missing a required field or is malformed."""
class RecipeLintError(SchemaError):
"""A recipe uses raw npm/npx instead of the pnpm equivalent (BLD-03 supply-chain lint)."""
class PinValidationError(SchemaError):
"""A floating ref (--branch main/master, :latest, @latest) was detected."""Exception messages are always actionable — they state what is wrong, what the valid options are, and include the offending value with !r:
# src/harnessed/schema.py:296-301
if scope in _PERSIST_RESERVED_SCOPES:
raise SchemaError(
f"persist entry [{i}]: scope: {scope!r} is reserved for a future release "
"and not yet implemented"
)
if scope not in _PERSIST_VALID_SCOPES:
raise SchemaError(
f"persist entry [{i}]: unknown scope {scope!r} — "
f"valid values: {', '.join(sorted(_PERSIST_VALID_SCOPES))}"
)When a typo is the likely cause, the error suggests the intended field (did you mean ...?):
# src/harnessed/schema.py:745-752
described = [
f"{f!r}" + (f" (did you mean {s!r}?)" if (s := _suggest_field(f)) else "")
for f in unknown
]
raise SchemaError(
f"{manifest}: unknown recipe field(s) in --strict mode: {', '.join(described)}. ..."
)Public constants: UPPER_CASE. Private module-level values: _lower_case with a leading underscore.
# src/harnessed/paths.py:17-19
CONTAINER_HOME = Path("/home/harnessed")
HATAGO_PORT = 3535
# src/harnessed/schema.py:162-173
_PERSIST_NAME_COMPONENT_RE = re.compile(r"^[A-Za-z0-9._-]+$")
_PERSIST_VALID_SCOPES = {"workspace", "project", "global"}
_PERSIST_RESERVED_SCOPES = {"repo"}Use frozenset for immutable sets of valid values:
# src/harnessed/schema.py:509-518
_VALID_HOOK_EVENTS = frozenset({
"SessionStart", "Setup", "SessionEnd",
"UserPromptSubmit", ...
})
# src/harnessed/schema.py:423
_INIT_VALID_SCOPES = frozenset({"workspace", "project"})All helper functions that are not part of the public API are prefixed with _:
# src/harnessed/schema.py
def _load_yaml(path: Path) -> dict: ... # private
def _parse_persist(raw_persist) -> PersistSpec: ... # private
def load_recipe(recipe_dir: Path, *, strict: bool = False) -> Recipe: ... # publicFlags and options that change behavior use keyword-only syntax (* separator):
# src/harnessed/schema.py:756
def load_recipe(recipe_dir: Path, *, strict: bool = False) -> Recipe: ...
# src/harnessed/emit.py:237
def write_derived_dockerfile(
profile_dir: Path, stack: Stack, recipes: list[Recipe], *, with_scan: bool = True
) -> Path: ...Functions that may produce non-fatal warnings accept an optional warn callable rather than logging directly. The caller decides what to do with warnings:
# src/harnessed/emit.py:120-142
def read_baked_settings(text: str | None, *, warn=None) -> dict | None:
...
_warn = warn or (lambda _m: None)
...
_warn("image settings.json is not valid JSON — keeping harnessed's default")In tests this is captured with warns.append:
# tests/test_emit.py:163-165
warns: list[str] = []
assert read_baked_settings(None, warn=warns.append) is None
assert warns == []subprocess.run is called with explicit capture_output=True, text=True, check=True. Exception handling always catches the tuple (subprocess.CalledProcessError, FileNotFoundError, OSError):
# src/harnessed/paths.py:146-155
try:
result = subprocess.run(
["git", "-C", str(project_path), "rev-parse", "--path-format=absolute", "--git-common-dir"],
capture_output=True,
text=True,
check=True,
)
p = Path(result.stdout.strip())
return p if p.exists() else None
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
return NoneBoolean probes (image exists, container running) use returncode == 0:
# src/harnessed/launcher.py:81-85
def _image_exists(rt: str, image: str) -> bool:
return subprocess.run(
[rt, "image", "inspect", image],
capture_output=True,
).returncode == 0No logging module. All output goes through rich.console.Console. Two module-level instances in launcher.py:
# src/harnessed/launcher.py:49-50
_out = Console()
_err = Console(stderr=True)Errors use Rich markup: [bold red]error:[/bold red]. Exit with raise typer.Exit(1) — never sys.exit() directly.
A single module-level YAML instance with safe type and pure=True:
# src/harnessed/schema.py:27
_yaml = YAML(typ="safe", pure=True)All YAML loads go through _load_yaml(path) which enforces that the result is a dict and returns {} for empty files:
# src/harnessed/schema.py:77-84
def _load_yaml(path: Path) -> dict:
with path.open("r", encoding="utf-8") as fh:
data = _yaml.load(fh)
if data is None:
return {}
if not isinstance(data, dict):
raise SchemaError(f"{path}: expected a YAML mapping at the top level")
return dataAll JSON files are emitted through _write_json which creates parent dirs, indents with 2 spaces, and appends a trailing newline:
# src/harnessed/emit.py:42-44
def _write_json(path: Path, data: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")Non-obvious decisions reference the design doc code inline. This keeps the comment localized to the code it explains:
# src/harnessed/schema.py:596
# GAP 2: declarative Claude Code hooks, merged into settings.json by emit.py. {EventName: [...]}.
# src/harnessed/emit.py:29
# hatago's single Streamable-HTTP endpoint (design D-04; default port 3535, `HATAGO_PORT`
# overridable). Single source: `paths.hatago_endpoint()`. The harness `.mcp.json` points ONLY
# here — never at a stdio server directly.Do not use vague comments like # fix. Use the design/plan code the relevant decision lives in (design §N, plan N-N, BLD-03, T-08-01, etc.).
Start Here
Guides
- Recipe authoring
- Service authoring
- Stacks
- Extending stacks (proposed)
- Recipe catalog
- System prompt & rules (proposed)
- Secrets
- AWS SSO
- Pulumi (host login forwarding)
- Egress & exposing services
- Container filesystem
- Git hooks
- Troubleshooting
- Pin management (harnessed update)
Codebase Map
Planning & Roadmap
- open work: GitHub Issues
Research & Prompts
- research/ (home-folder requirements per harness, browse in-repo)
- prompts/ (reusable prompt templates, browse in-repo)