diff --git a/TODO.md b/TODO.md index febc9440..751b3cf0 100644 --- a/TODO.md +++ b/TODO.md @@ -298,6 +298,39 @@ pane belonging to the folder you left; and the breadcrumb opted out of that last restored. Deliberate as written — a dashboard can hold two browsers and one URL cannot serve both — but worth re-deciding rather than inheriting. +## Affordances (2026-08-19) + +Agreed and written down in `plugins/CLAUDE.md`; **nothing is built**. An affordance is what the surface +on the other end offers the agent — declared by the code that implements it, read at runtime. The first +two are image rendering (core) and gitea (`git` for git, `tea` for everything else). + +To build: + +- `capabilities/affordances/` created by the setup script, with `officer/core/` populated from an + officer-capabilities repository. The clone is future work; the directory is not. +- A compiler: parse the tree, take each body under its `name` as a heading, produce one small block. + Per session, not per boot — `capabilities/` is already read fresh each agent session, and a plugin + installed mid-day should be live on the next message. +- Delivery. Claude sessions take `systemPrompt: { type: 'preset', preset: 'claude_code', append }`, + which nothing passes today. **Opencode has its own driver and would need the same**, or the affordance + is a claim that only holds on one harness — which is the drift this exists to remove. A bare `claude` + run in a terminal on the host gets neither unless a file is also written. +- Plugin contribution: an `affordances/` directory in the plugin repository, copied to + `capabilities/affordances///` on install and removed on uninstall. Same shape as + `assets/` and `publishAssets`. + +Open, and deliberately deferred: + +- **Scoping.** Everything is loaded into every conversation. Fine at two files, not at twenty. A + `surface` field was drafted and dropped because it was doing two jobs at once: for image rendering it + means _what the client renders_ (a phone, a web bubble, a cron run with nobody watching), for gitea + _what tooling exists on this machine_. Those are different axes and one field cannot be both. Decide + the axis before adding the field — and note this codebase has already been bitten by a field that + validated itself and was never consumed (`unauthenticatedPaths`, see `plugins/public-paths.ts`). +- **Drift.** The rule is "declared where implemented", but a hand-authored markdown file in a repository + is not that — `ChatImage` could stop rendering images and no file would notice. Accepted for now + because prose that reaches every harness beats a registry that reaches one; revisit if it bites. + ## Plugin ecosystem — before it opens to third parties (2026-08-18) Not now. Months away. Recorded so it is not re-derived. diff --git a/plugins/CLAUDE.md b/plugins/CLAUDE.md index f031ba9f..4526df43 100644 --- a/plugins/CLAUDE.md +++ b/plugins/CLAUDE.md @@ -71,6 +71,71 @@ hand-written `CLAUDE.md` problem again with a better name — and this time with Same posture as `permissions/registry.ts`, and for the same reason: one enumeration, owned by the code it describes, which cannot quietly disagree with reality. +### The file + +Markdown with YAML frontmatter, like every other item in `capabilities/`. Frontmatter is for the +PLATFORM; the body is for the agent. + +```markdown +--- +name: Image rendering +description: Use when you have an image to show the user in chat. +version: 1 +--- + +`![alt](https://…)` renders as an image. **The user's device fetches the URL**, so it must be +reachable from there — a local path, `file://`, or anything behind authentication renders nothing, +and fails silently. +``` + +| field | | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | becomes the heading in the compiled block, so the agent can index it | +| `description` | when to reach for it. Costs the agent nothing — it is not sent | +| `version` | an INTEGER, not semver, matching `capabilities/agents/` (`agent-files.ts` parses it that way). A plugin-contributed affordance sits near two version numbers and they mean different things: this integer is the revision of _this text_, the plugin's semver is the plugin's. Do not reconcile them | + +**Keep the body short.** Every affordance is loaded into every conversation, so it is charged to the +context budget of every turn. State the constraint, name the silent failures, say what to do instead. + +There is deliberately no `surface` field yet. It was drafted and dropped: for the image affordance it +would mean _what the client renders_, and for the gitea one below _what tooling exists on the machine_ — +two different axes wearing one name. Everything is loaded unconditionally until that is worked out, +which is fine at two files and will not be at twenty. See `TODO.md`. + +### Where they live + +``` +capabilities/affordances/ + officer/core/ ← core, installed by the setup script + image-rendering.md + officer/gitea/ ← contributed by the gitea plugin on install + gitea.md +``` + +`/`, with `core` occupying the plugin slot for the platform's own. A plugin ships an +`affordances/` directory in its repository; installing copies it in, uninstalling removes it — the same +shape as `assets/` and `publishAssets`. + +### A plugin-contributed one, for reference + +This is the gitea plugin's, and it shows what these are for: the agent cannot discover from the +filesystem that `tea` is authenticated, or that it should prefer `git` for the things git already does. + +```markdown +--- +name: Gitea +description: Use when working with git repositories, or with Gitea issues and pull requests. +version: 1 +--- + +For anything git already does — clone, fetch, pull, push, branches, tags — use plain `git`. It talks +to the server directly. + +For anything git does not — pull requests, issues, comments, releases, creating a repository — use +`tea`. It is already logged in as the current user, with that user's own permissions. Do not +configure it, and do not ask the user for a token. +``` + ### On the word `capability` was taken — `CAPABILITIES` is the permission registry. `skill` was taken twice: by