canned-prompts/reference/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

103 lines
4.1 KiB
Markdown

# canned-prompts reference CLI
This is intentionally a **small reference implementation**, not the intended final architecture.
It demonstrates nine verbs:
```text
add PATH
search QUERY
show ID
resolve ID --set key=value
render ID --set key=value
eval ID
index [ID]
install ID [--version VERSION]
publish PATH
```
The implementation uses a local catalog plus a filesystem registry and performs no model calls.
`resolve` and `render` are separate because the specification separates them
(§ 5.1): resolution decides each value and may be non-deterministic, rendering
substitutes and always is. `resolve` prints where every value came from —
supplied, default, or fallback — before any prompt is produced.
## Stores
Default locations:
```text
~/.canned-prompts/catalog
~/.canned-prompts/registry
```
A **registry** stores packages flat, because an id is unambiguous within one
registry:
```text
<registry>/<id path>/<version>/...
```
A **catalog** is namespaced by registry, because identity is registry-scoped
(§ 3.2) and the same id may be installed from more than one place:
```text
<catalog>/<registry name>/<id path>/<version>/...
```
For example:
```text
~/.canned-prompts/catalog/house/practice/pqrst-estimate/0.1.0/
~/.canned-prompts/catalog/local/practice/pqrst-estimate/0.1.0/
```
A registry's name comes from its optional `registry.yaml`, and otherwise from
its directory basename. `add` takes a package from a path rather than a
registry, so it files it under `local` (override with `--as`).
Commands that take an ID accept a bare id or a qualified `<registry>:<id>`.
A bare id installed from more than one registry is reported as ambiguous
rather than resolved by guessing.
## Design choices
- YAML manifest via PyYAML.
- `{{ name }}` template substitution only.
- No arbitrary expression/code execution.
- Published versions are immutable by default.
- `add`, `publish` and `install` copy only reserved paths and manifest-
referenced files. A working directory's `.git`, virtualenv or scratch files
never ship; whatever is left out is named on stderr.
- Version precedence follows SemVer: a prerelease ranks below its release, and
`any`, `newest` and `>=` skip prereleases entirely. Only an exact pin
selects one.
- `install` copies from registry to catalog.
- `add` copies a package directly to catalog.
- `search`, `show`, `resolve`, and `render` operate on catalog packages, and
print qualified `<registry>:<id>` references.
- `include` defaults are satisfied (inclusion is deterministic); `derive`
defaults are reported, not run. Inclusion cycles are detected and named.
- `eval` runs the deterministic render checks of any eval declaring the
`canned-prompts/eval-rubric/v0.1` schema, and reports output criteria as
declared but not run. Unrecognized schemas are skipped, not rejected. A
failed render check exits non-zero.
- `resolve` lists required capabilities and context dependencies, which this
tool cannot verify, rather than implying it checked them.
- `add` and `install` name declared prompt dependencies the catalog cannot
satisfy. They do not fetch them — resolution is the consumer's job (§ 10) —
but a package that looks installed and cannot render should say so.
- `add`, `install` and `publish` record an entry in the store's `index.yaml`:
source, method, first-inclusion date, and the package's declared author,
source and licence. `index` lists it. Re-adding keeps the original
`included_at` and updates `last_seen_at`.
- An optional `registry.yaml` names a registry and records namespace claims.
`publish` warns when a namespace is declared `closed` — it cannot
authenticate a publisher, and says so rather than implying it checked.
- Static input defaults are applied; **derived** defaults (§ 6.1) are not. This
tool never calls a model, so a derived default is satisfied only by its
static fallback `value`. Without one, `resolve` reports the input as
unresolved and `render` refuses rather than substituting empty text.
Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.