AGENTS.md · git:20261029.45e5fd8 · 2026-10-29 · sha256 7de7624ff49f2f93

AGENTS.md git:20261029.45e5fd8A

Immutable. This exact content never changes and is served at /api/v1/blob/7de7624ff49f2f93.

# Repository Guidance

- Consult `DESIGN_PRINCIPLES.md` when changing module ownership, dependency direction, effect contracts, data scope, expected-failure handling, closed-family members, recurring boundary boilerplate, duplication consolidation or certification, an agent-facing schema or artifact, multi-step procedures, diagnostics, logging, or recovery surfaces. Apply its decision visibility, proportional-resource, outer-glue ownership, plain-function, thematic symmetry, semantic-scaffold, and fixed-point stop rules as repository constraints.
- When delivering or changing a feature, use the narrative-parity method in `DESIGN_PRINCIPLES.md`: make one representative code path and its product overview tell the same ordered story, and change either code or documentation when they disagree.
- Follow the current-contract narration rule in `DESIGN_PRINCIPLES.md` when changing source, comments, or product documentation.
- Before refining or implementing a relevant change, inspect the current limitations and reopening conditions in `ARCHITECTURE.md` and apply the limitation method in `DESIGN_PRINCIPLES.md`. Obtain explicit human approval before introducing, widening, preserving, masking, or deepening an unacknowledged material limitation; that answer authorizes only the exact architecture decision presented.
- Before broad autonomous work begins, give the human one short declarative scope confirmation that names the intended outcome, the principal surfaces expected to be read and touched, the approximate magnitude, and any surprising exclusion. This confirms the interpretation without asking again for permission already granted or exposing an internal plan; pause only when the sentence reveals a product or scope choice the human has not made.
- Use Python 3.14.x syntax only. Keep `.python-version`, `requires-python`, Ruff `target-version`, Pyrefly, CI, and the uv lock aligned with the current stable 3.14 patch release.
- Use uv as the only Python dependency, environment, build, and installed-package entry point. Keep `uv.lock` checked in. jscpd is the sole non-Python development dependency: install its pinned native binary project-locally with `npm ci`, keep `package-lock.json` checked in, and do not add other npm packages without explicit scope.
- Implement in Python with the standard library or installed packages first. Before adding another Python subprocess call or dependency, present the Python alternatives with pros and cons and obtain an explicit human decision. Git configuration shared with the pre-runtime shell launcher, and the per-user `~/.config/pinboard/hosts.config` page-size settings, are an accepted subprocess exception: keep Python Git-config effects in `adapters/files/git_config.py`, while the launcher uses bounded Git commands before Python is available. The behavioral evaluation harness is the other: its `agy`, `claude`, `codex` and `git` calls and the evaluated revision's launcher runs for `--prepare-runtime` and `init` stay in `evals/behavioral/processes.py`, and only the declared `mcp` stdio client in `evals/behavioral/board.py` starts that launcher's `--mcp` server.
- After creating a new implementation worktree, run `scripts/prepare-worktree` once before Pinboard preparation or implementation source work. The command owns `uv sync --locked` and `npm ci --prefer-offline --no-audit --no-fund` for that checkout; keep `.venv` and `node_modules` checkout-local and ignored while allowing package-manager caches to be reused. For setup failure, follow [source-worktree setup recovery](skills/pinboard/references/runtime-adapters.md#source-worktree-setup-recovery), which owns the bounded runtime permission retry and human decision branches. When changing this preparation command, a lock, package-manager policy, launcher, validator, or another tool needed to assess itself, prepare from the accepted base before editing and verify the changed candidate directly; stable installed tooling may coordinate the attempt but is not candidate evidence.
- Use `unittest` as the supported Python test runner. Selecting pytest or another runner requires explicit framework-change approval before it enters a work brief or implementation.
- Do not inject `PYTHONPATH` or another import path, invoke an ambient Python interpreter, add requirements files, or run source files as a substitute for the installed package.
- Write each fixed configuration value through the same key-level mutation when its selected config file or key is missing, then read the stored value explicitly. Never rely on an effective value that exists only as a runtime fallback; report malformed or unwritable config rather than silently choosing a value.
- For a request only to answer, explain, assess a review, or diagnose, inspect and report without implementation repository writes. An explicit human request to review a human-owned PR may record review evidence through its separate route, but does not authorize implementation edits. After the human requests an implementation change, announce before the first implementation repository write whether it uses an existing or new Pinboard item or proceeds directly outside Pinboard. This announcement grants no edit authority and requires no repeat approval for already authorized direct work. Follow `skills/pinboard/SKILL.md` for that choice and for review, checkout, and wrap-up when repository changes need human disposition.
- Match checks to the changed surface. When plugin or skill files change, run `uv run --locked python scripts/validate-metadata.py`; it is the repository-owned platform-compatible metadata check. Run any platform-supplied `quick_validate.py` through the same locked development environment. PyYAML is declared there for validation, so a missing `yaml` module is a broken environment to fix, not an expected limitation to report. Do not provision undeclared one-off dependencies for unrelated optional checks.
- Keep Ruff `UP` as the only pyupgrade authority. Do not add standalone pyupgrade, MyPy, Pyright, or redundant type-checking layers.
- Run `npm run duplication` after changing production Python. Treat a match as a refactoring prompt rather than an automatic defect: share code only when doing so gives behavior or a repeated decision one clearer owner, and do not move code across an ownership or layer boundary merely to reduce the count. Do not abstract independent tests, required boundary shapes, or exhaustive closed-family branches merely to silence the detector. CI uses the less-sensitive `npm run duplication:ci` profile, rejects new clones relative to `origin/main`, and enforces its recorded percentage ceiling. Lower that ceiling after accepted cleanup reduces the baseline; do not raise it merely to admit new duplication.
- Keep Pyrefly strict and retain its 100% strict production type coverage check. Run `scripts/check-types` for the matching local and CI sequence: Pyrefly, strict coverage, then the locked development ty through `uv check --locked`. Retain `all = "error"` and only the three documented optional `unsound-*` overrides in `pyproject.toml`; keep every other ty diagnostic visible. Do not suppress a failing baseline or run developer checks during product startup. Convert boundary values into exact validated types before domain use.
- Declare supported structured boundary shapes as frozen `msgspec.Struct` records with annotated field constraints, then decode or convert directly into the declared record. Pinboard-owned formats require `forbid_unknown_fields=True`; an external sender does not change schema ownership. At an explicitly accepted externally owned protocol boundary, ignore unrelated additive fields while preserving validation of every required field and emitting only the declared Pinboard-owned output. Claude SessionStart, SubagentStart and PreToolUse inputs, Antigravity SessionStart and PreToolUse inputs, the MCP initialize `clientInfo`, whose name selects a startup-text host profile and whose version must be nonempty, the `claude -p` stream-json event stream (whose final result record is the `--output-format json` result), the `codex exec --json` event stream and the `agy` JSON output (`agy -p --output-format json` and `agy plugin list --json`) are the current accepted cases. This applies to JSON payloads and to mapping-shaped CLI, SQLite, YAML, and TOML values. Prefer a verbose record declaration over `JsonValue` trees, mapping walkers, primitive field validators, or serialize-then-validate round trips. Put cross-field invariants that cannot be expressed declaratively in the boundary record's post-init validation so decoding and instantiation remain one operation; do not repeat them after the value becomes a production command or domain model. Schema-tagged payloads with no shared shape may use `msgspec.Raw`. Hand-written parsing is acceptable only when the format is predominantly a custom grammar or relational validation makes a declarative codec less clear; document that reason at the owner. When Codex or a project tool is the only writer of a machine-consumed structured artifact, keep strict typed JSON canonical and derive human-readable Markdown as a non-authoritative projection. Separate models, exact errors, and behavior only when each has a coherent responsibility; fold ceremonial islands into the nearest real owner.
- Keep Python types concrete. Do not use `object` as an annotation escape hatch. When a boundary is genuinely untyped, use `Any`, then validate or narrow it before domain use. Prefer explicit branches over reflective `getattr` or `setattr` loops when the concrete variants are known. Model closed vocabularies without variant-specific data as plain `Enum` values inside the domain. Model closed variants that carry different data as flat dataclass unions rather than a fieldless enum coupled to a separate payload or a polymorphic class hierarchy. Convert strings only at parsing and rendering boundaries. Handle closed branching with exhaustive `match` cases and `typing.assert_never`; write the unreachable arm as `case _ as unreachable:` so the project coverage configuration excludes the statically impossible clause. Combine alternatives with an OR-pattern when they bind equivalent values and perform identical work; remove a named case when an existing general case already owns the same outcome. Do not merge the variants themselves merely because one consumer handles them alike. Do not construct invalid variants merely to execute `assert_never`. Do not use static handler or type dictionaries when the alternatives cannot change at runtime, and do not let a catch-all `else` silently stand for a known variant. Reserve registries and polymorphism for behavior that is genuinely open or runtime-extensible. Never use `StrEnum`.
- When a closed family repeats one classification through a tag, payload presence, collection membership, or conversion table, give that classification one canonical owner. Use a vocabulary value when every alternative has the same shape, a closed variant when alternatives carry different data, and an explicit decision when validity depends on surrounding state. Preserve required wire, storage, and presentation shapes through named exhaustive boundary conversions instead of leaking their redundancy into the domain. Recompute producers and consumers after each collapse, and stop when another fold would erase a product distinction or add more conversion machinery than invalid states and repeated decisions it removes. Report core decision points, explicit boundary conversion, import volume, and source-line change separately; none is a proxy for the others.
- Treat a command route and its variant-specific arguments as one closed hierarchy. The parser must select and decode an exact leaf command before dispatch; do not carry a general-purpose argument namespace, separate command and operation discriminators, or nullable fields whose valid combinations depend on those discriminators into handlers. Represent coupled option sets as separate flat union members, and require production consumers and reviews to match the resulting union exhaustively rather than reparsing or revalidating the route late.
- When changing a command or closed-variant flow, trace one representative value from its supported entry point to its effect and simulate adding one sibling. Count the places a developer must discover and edit. Keep an exhaustive branch only where that owner distinguishes alternatives or converts an independently required boundary shape; fold route enums, conversion tables, wrapper variants, and layer-by-layer matches that merely repeat an already selected fact. Do not replace a justified closed branch with reflection, a registry, callbacks, or polymorphism solely to reduce the count. Dynamic dispatch is valid at a deliberately open boundary or when the caller should not distinguish implementations, but keep its required surface and wiring visible and never supply a fallback handler, inherited default behavior, optional callback, or permissive lookup for an unsupported case.
- Parsing, deserialization, and post-deserialization structural or canonical validation may use typed exceptions locally while values remain boundary representations. When invalid input is an advertised outcome of an installed use case, the boundary owner catches the exact parser exception and returns that use case's typed failure. Once input has become a domain model, return expected domain and state-machine rejections as explicit values rather than raising exceptions. Do not model command, proposal, dispatch, or domain rejection families as exceptions. Reviewers must reject a custom exception unless it begins at an explicitly named decoding or effect boundary, execution cannot continue as designed or explicit result propagation would materially obscure one cohesive operation, and the nearest module, protocol, or function contract declares that exceptional family. Reject any custom exception whose immediate caller catches it only to copy its code, message, or details into a returned failure. Convert an adapter exception to a typed use-case result when the receiving owner assigns effect, retry, recovery, or stable diagnostic meaning; retain tracebacks for unclassified effects and genuine invariant or programming defects. Do not let a pure-looking function hide an advertised failure or expose a filesystem path as a substitute for verified bytes. Express a concrete success value as `DecisionResult[T]`; when a caller only propagates its failure, use an early `isinstance(value, DecisionFailure)` return so static narrowing exposes `T`. Use exhaustive `match` only when distinct closed variants require distinct behavior. Use `DecisionFailure | None` when validation has no success value, and propagate it with a walrus `None` check when the failure is only returned.
- When implementing or reviewing an agent-facing operation, apply the caller-perspective outcome review in `DESIGN_PRINCIPLES.md#Leave evidence an agent can diagnose from` to its changed supported paths. When adding an agent-facing expected failure, make its installed result name the failed resource or condition, truthful effect and retry disposition, and one safe executable next step, exact permission request, or plain human diagnosis. Put that follow-up at the earliest boundary that has the facts; never suggest replay after a possible committed effect.
- Permit defaults only at an exact CLI entry boundary when they mirror optional command-line convenience. Internal DTOs, domain records, and effect records must have no field defaults, and internal function signatures must require every argument explicitly. In particular, every `FailureDetails` construction must state observations, mismatches, retry disposition, effect disposition, changed surfaces, and alternatives explicitly, including empty and unchanged values. Do not hide those decisions behind a convenience constructor, defaulted helper parameter, or fallback.
- Keep read-only ledger lookup on `LedgerSnapshot` and return `None` for absence. The decision call site owns the operation-specific failure value; lookup helpers must not combine data access with hidden control flow.
- Model stable opaque string identifiers as distinct `typing.NewType` values. Convert validated external strings once at the owning boundary; do not let storage, Markdown, JSON, CLI, or filesystem representations enter pure decision signatures.
- Name concrete values for their product role or operation, such as `snapshot_items`; do not use architecture-layer prefixes such as `domain_` merely to distinguish them from boundary values.
- Do not introduce a named helper whose only behavior is reading one public attribute or reapplying a constructor or `NewType` that does not change the runtime representation when the caller can use the expression directly. A concretely typed callback helper remains appropriate when Python cannot annotate the equivalent lambda and strict Pyrefly would otherwise infer `Any`. Retain helpers that own validation, canonicalization, meaningful composite keys, stable protocols, or reused domain operations.
- Keep unshipped behavior without an installed entry point out of `src`. When a maintained design probe still provides useful evidence, isolate it under `tests/prototypes/` with a module header that names the deferred purpose and states that it is excluded from the installed package. Move it into production only with an accepted product brief and a real production consumer; otherwise delete it when the evidence is no longer useful.
- Before adding or refactoring tests, consult [test design in CONTRIBUTING.md](CONTRIBUTING.md#design-tests-around-coherent-behavior).
- Tests must exercise observable behavior, rejection contracts, concurrency, recovery, or package/plugin integration rather than implementation constants. Do not copy production data, palettes, inventories, or configuration values into tests merely to freeze their current contents; assert the consumer-visible requirement or an independently owned external contract instead. Do not unit-test prose semantics by matching required or forbidden wording in Markdown, skill instructions, prompts, or other guidance files. Validate mechanical structure with the repository's metadata tools and assess meaning through review.
- Record a drift estimate in the brief or pull request of every guidance change: the replies it targets, the cost of a miss or an over-application, and the neighboring rules it could crowd, contradict or reorder. Recommend a behavioral comparison only when that estimate finds a plausible effect on authority, lifecycle, review, safety or other correctness-relevant behavior, or when the change rewrites or reorders existing rules; otherwise the recorded estimate and a review of the edited guidance against its neighbors are its acceptance basis.
- Evaluate how guidance changes agent replies only through the maintained behavioral harness, `uv run --locked python -m evals.behavioral` (see `evals/behavioral/README.md`), under the variability criteria in `evals/behavioral/CRITERIA.md`. A guidance change that runs a comparison cites the harness and criteria by path and revision as its acceptance check instead of copying or re-adapting them, registers its held-out scenarios by digest and declares its targeted checklist rules before its last guidance edit, and stays within the criteria's per-comparison spending cap. Never run the harness from `unittest`, the default checks or CI.
- Do not make test outcomes depend on wall-clock passage or scheduler speed. Model time with fixed timestamps or a patched clock, and coordinate concurrent scenarios with events, barriers, or controlled fakes. Use bounded timeouts only to fail deadlocks, never as the behavior under test. Use real elapsed time only when an external system's timing is itself the supported contract, and document why deterministic control cannot represent it.
- During implementation and review, require every added observable behavior, blocking product claim, or mandatory tool, threshold, platform, compatibility, or hardening obligation to trace to accepted scope, an applicable repository rule, a reviewed product authority, or a named existing production consumer. Treat unsupported discoveries as upstream scope questions rather than silently implementing, requiring, or hardening them.
- Before treating a new closed domain state or mutation family as complete, prove one representative accepted value through the application-owned persistence boundary and reload it from a fresh store. Pure decision tests and separately constructed storage fixtures do not prove cross-layer compatibility; treat a mismatch between domain values, mutation projection, stored state, and schema as an architecture defect rather than normalizing or dropping facts in an adapter.
- Support macOS and Linux. Do not add cross-platform-looking branches without contention and filesystem evidence on the claimed platform.
- Preserve the observable transaction contract: a failed transition leaves the previous valid ledger intact, and concurrent stale actions are rejected.
- Follow the trusted-local boundary and its reopening conditions in `ARCHITECTURE.md`. Keep ordinary containment at the single computed-write boundary, and do not add defensive local-filesystem hardening (such as symlink rejection or permission or mode policing) without a named supported threat or failure model and explicit human approval; follow symlinked directories canonically and fail with one actionable error naming the link, its target, and one repair step when a link is dangling or points to an unusable target.
- Treat compatibility code as release-scoped, not permanent architecture. When retiring an intake or creation shape, trace its models, helpers, branches, projections, and tests recursively. Classify code as compatibility-only when no supported current producer remains and retained persisted data or an external contract still requires it; a version number alone does not establish that status. Put that code in explicitly named compatibility modules within its existing layer. Where a required mixed boundary prevents relocation, give the compatibility-only binding an explicit compatibility name and record the boundary reason. Keep shared current code at its ordinary owner, and document the retained responsibility and the condition for removing it. When no external users depend on an older format and the user has not explicitly chosen retention, use temporary isolated tooling to migrate the known local ledgers, preserve equivalence evidence, then delete the migrator, legacy readers, compatibility branches, and superseded tests before acceptance. Retain current schema detection and clean module boundaries; add a version-specific migration only when a real supported predecessor exists.
- Keep production source, public documentation, and code comments meaningful outside their delivery history. Describe current behavior, ownership, and invariants; keep implementation-plan task or checkpoint numbers, attempt names, milestone labels, and speculative future file paths in private planning artifacts.
- Use “Pinboard” in prose and `pinboard` for technical identifiers. Do not introduce another formal project name.
- Keep `README.md` human-facing and descriptive. Include material only when it helps a prospective user understand concrete use, judge fit or a tradeoff, install and begin, or discover a useful capability or onward guide. Apply the explanation-budget method in `DESIGN_PRINCIPLES.md`; feature delivery alone does not justify a README addition. Keep essential human installation commands and skill invocation examples in the README. Put agent-operated diagnostic commands, result-field inventories, schemas, runtime internals, and operational procedures at their narrower skill or contributor owner. Put detailed setup and recovery in `INSTALL.md`, and imperative agent and contributor constraints here. When an addition's human value or placement is doubtful, ask the human before adding it and give a recommendation; continue other authorized work. Routine wording changes and existing authorized editorial work require no new approval.
- Consult `ARCHITECTURE.md` whenever work may change architectural ownership or dependency direction. Record that impact in the work brief, and include every required `ARCHITECTURE.md` correction in the same implementation candidate; never defer it to a separate documentation cleanup.