canned-prompts/README.md
tegwick b3280df742 Add the catalog index, and package a real prompt collection
CANP-WP-0004. The operator asked canned-prompts to build a database of
versioned prompts recording where each came from and when — the first step
toward a platform for collaborative prompting.

The format had nowhere to put that. `provenance` records who wrote a prompt and
where the idea came from; nothing recorded how a copy arrived in a particular
store. Section 20.3 now specifies an `index.yaml` as store metadata rather than
package data: how a copy arrived differs for every consumer, and recording an
arrival must never rewrite the package that arrived.

`add`, `install` and `publish` record registry, id, version, name, source,
method, first-inclusion date, and the package's declared author, source and
licence — the last three copied so a listing is readable without opening every
package. A new `index` verb lists it. `included_at` is never overwritten; a
re-run updates `last_seen_at`, because when a package first entered a
collection is a fact about history rather than about the last command run.

Tests 84 -> 90.

Also records two findings from actually using the format:

CANP-WP-0004-T03 — inclusion has no deduplication, so a diamond dependency
renders shared content once per path. Found by composing a real collection.
Not fixed here: deduplicating means choosing which occurrence survives and
deciding what happens when two paths resolve different versions, which is
resolver behaviour that section 10.4 deliberately avoids. Handed to
CANP-WP-0005 with a leaning: document it, warn at validation time, do not
deduplicate.

The add_ons workaround in practice/pqrst-estimate is evidence about section
23's deferred "richer template syntax" — an optional appendix has to be an
input with an empty default, because CPF has no conditionals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 388925@bnt-lap001
Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
2026-09-06 17:14:21 +02:00

236 lines
8.8 KiB
Markdown

# canned-prompts seed
> **Collect, reuse and share prompts and prompt templates.**
This bundle contains a first project seed for `canned-prompts`:
- [`INTENT.md`](INTENT.md) — project mission, boundaries, principles, and success criteria.
- [`CannedPromptFormat.md`](CannedPromptFormat.md) — the package-format specification, currently revision v0.2.
- [`reference/`](reference/) — deliberately small Python CLI implementing the basic lifecycle.
- [`examples/pqrst-estimate/`](examples/pqrst-estimate/) — a real package that can be used to exercise the implementation.
- [`examples/house-style/`](examples/house-style/) — a `type: fragment` package that `pqrst-estimate` composes.
## Try the reference implementation
```bash
cd reference
python -m venv .venv
. .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# add the included examples to your local catalog
python canned_prompts.py add ../examples/house-style
python canned_prompts.py add ../examples/pqrst-estimate
# find and inspect it
python canned_prompts.py search pqrst
python canned_prompts.py show practice/pqrst-estimate
# see how each input and parameter resolves, and where the value came from
python canned_prompts.py resolve practice/pqrst-estimate \
--set session_summary="Implemented feature X, read unfamiliar code, added tests."
# render it
python canned_prompts.py render practice/pqrst-estimate \
--set session_summary="Implemented feature X, read unfamiliar code, added tests."
# publish it to the local filesystem registry
python canned_prompts.py publish ../examples/pqrst-estimate
# remove the local catalog if you want to simulate another machine, then install
python canned_prompts.py install practice/pqrst-estimate --version 0.1.0
```
Packages added from a path are filed under the registry name `local`; packages
installed from a registry are filed under that registry's name. `search` prints
qualified references:
```text
house:practice/pqrst-estimate@0.1.0 PQRST Estimate
local:practice/pqrst-estimate@0.1.0 PQRST Estimate
```
By default the reference tool uses:
```text
~/.canned-prompts/catalog
~/.canned-prompts/registry
```
Override them with:
```text
CANNED_PROMPTS_HOME=/some/path
```
or command-level `--catalog` / `--registry` options.
## Resolution vs rendering
The format separates the two steps (`CannedPromptFormat.md` § 5.1).
Resolution decides a value for every input and parameter and may be
non-deterministic; rendering substitutes those values and always is. An input
may declare a default that is either a static value or a *derived* one — a
prompt that a capable consumer may run to produce the value, declared without
naming any resolver or model.
This reference tool never calls a model, so it resolves supplied values and
static defaults only, and reports anything it cannot derive instead of
rendering a prompt with a silent hole in it.
## Composition
A package composes another by declaring it as a dependency and binding it to an
input default. There are two kinds:
- **`include`** — inline the other package's *rendered template* as text.
Deterministic, needs no model, and the reference CLI performs it.
- **`derive`** — use the other package's *result*, obtained by running it.
Only a consumer able to run it can supply one.
`examples/pqrst-estimate` includes `examples/house-style`, so a shared style
block is a versioned package rather than copied text:
```yaml
dependencies:
prompts:
- id: practice/house-style
version: ">= 0.1.0"
requirement: required
inputs:
- name: house_style
required: false
default:
include: practice/house-style
```
A dependency pins an exact version by default; `any`, `newest` and a `>= X.Y.Z`
lower bound are explicit opt-ins. These are per-dependency selectors, not
version ranges — there is no solver, and constraint resolution across a
dependency graph remains a non-goal.
There is no template inheritance. A package never extends another or overrides
its parts; composition is by reference only, so a package's content stays
readable without chasing ancestors.
## Evals
An eval file declares what to assess. `evals/` used to hold whatever an author
put there; it now has one schema the tooling understands, split along the same
line as composition:
- **render checks** — deterministic assertions about the *rendered prompt*
(`contains`, `not_contains`, `resolves_all`). No model needed, so the
reference CLI runs them.
- **output criteria** — statements about a good *result*. Declared, not run.
```bash
python canned_prompts.py eval practice/pqrst-estimate
```
```text
local:practice/pqrst-estimate@0.2.0
evals/quality.yaml (pqrst-estimate-quality)
render PASS contains "must sum to exactly 100%"
render PASS resolves_all
output -- 4 criteria declared (not run: judging output needs a model)
```
An eval declares assessment, never results. Results are run evidence and live
outside the immutable package.
## Required versus compatible
Two fields used to say overlapping things about capabilities. They now differ
in one word each:
| Field | Meaning | Absence means |
|-------|---------|---------------|
| `dependencies` | **required** — does not work without it | a consumer should warn or refuse |
| `compatibility` | **observed** — known to work with it | nothing; it is information |
Dependencies come in three kinds, separated by what the format can do about
them: `prompts` are packages it resolves by id and version, `context` names
things it does not package at all (an information space, an API, a corpus the
caller supplies), and `capabilities` are what the environment must be able to
do. `resolve` lists the last two, since the reference tool cannot verify
either:
```text
requires (this tool cannot verify these):
capability web-search
context repository-tree — A listing of the repository under review.
```
## Registries and identity
An id names a package *within a registry* (`CannedPromptFormat.md`
§ 3.2). The same id obtained from two registries may be two different
packages, so the catalog keeps them apart and a bare id that matches more than
one is reported as ambiguous rather than guessed. Qualify it when you need to:
```bash
python canned_prompts.py render house:practice/pqrst-estimate --set ...
```
A registry may describe itself with an optional `registry.yaml` naming it and
recording which namespaces are claimed and under what policy. Those claims are
descriptive: a filesystem registry cannot authenticate a publisher, and
signing and trust scoring are explicit non-goals. Ownership lives with the
registry rather than in the package, so no package carries an unverifiable
assertion of authority.
## The index
Every store keeps an `index.yaml` recording which package versions entered it,
from where, and when:
```bash
python canned_prompts.py index
```
```text
local:helix/repo-orient@0.1.0 2026-09-06T13:31:02Z add
from /home/worsch/helix-forge/prompts/repo-orient
declares source personal prompt collection, contributed 2026-09-06
```
This is store metadata, not package data. `provenance` records who wrote a
prompt; the index records how a copy arrived *here* — which differs for every
consumer, and which must never rewrite the package it describes. `included_at`
is first arrival and is never overwritten; a re-run updates `last_seen_at`.
## Dependencies are reported, not fetched
Installing a package that composes another warns when the dependency is
absent, including when it is present but no version matches:
```text
declared dependencies not in this catalog: practice/house-style@>= 0.1.0
— install them, or composition referencing them will not resolve
```
Nothing is fetched automatically. Dependency resolution is left to the
consumer, but a package that installs cleanly and then cannot render is worse
than one that says what it is waiting for.
## Packaging and versions
`add`, `publish` and `install` copy the reserved paths (`prompt.yaml`,
`prompt.md`, `README.md`, `LICENSE`, `examples/`, `evals/`, `assets/`) plus
anything a manifest field references — and nothing else, as § 2 requires. What
was left behind is reported rather than silently dropped:
```text
not packaged (not a reserved path, not referenced by the manifest): .git/, .venv/, notes.txt
```
Version precedence follows SemVer, so `1.0.0` outranks `1.0.0-rc1`, and
`any`, `newest` and `>= X.Y.Z` skip prereleases entirely. Publishing a release
candidate never changes what existing consumers resolve to; name it exactly to
use it.
## Deliberate limitations
This seed has no hosted registry, model execution, authentication, network access, dependency resolver, or social features. `publish` and `install` operate on a filesystem registry so that the package semantics can be tested before infrastructure is built around them.