Register with Custodian State Hub and seed format open-questions workplan
Register canned-prompts under agents / practice (topic
c1d199b6-55ee-4db6-b49e-257a9f0f15ac, workplan prefix CANP-WP) via
`statehub register`, then replace the generated placeholders with
repo-specific facts.
- SCOPE.md: real boundaries drawn from INTENT.md's deliberate boundary,
current state (spec v0.1 + reference CLI, 3/3 tests pass, example
round-trips), and the developer workflow.
- AGENTS.md: drop the unresolved {CREDENTIAL_ROUTING} template token left
by the generator.
- CANP-WP-0001: bootstrap tasks closed.
- CANP-WP-0002: new workplan carrying the five § 23 open questions promoted
from "experience will decide" to "decide for v0.2" — optional-input
defaults (static or derived), registry namespaces/ownership, prompt
composition, canonical eval schemas, typed context/dependency contracts —
plus two reference-implementation conformance defects found in review
(prerelease versions sort as newest; copy_immutable packages the whole
source directory).
Also lands the previously untracked seed: INTENT.md, the CPF v0.1 spec,
the reference CLI, and examples/pqrst-estimate.
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
This commit is contained in:
parent
985b41dc87
commit
dc615ef530
19 changed files with 2130 additions and 2 deletions
27
.custodian-brief.md
Normal file
27
.custodian-brief.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!-- custodian-brief: generated by statehub register; fix-consistency may replace this file -->
|
||||||
|
# Custodian Brief - canned-prompts
|
||||||
|
|
||||||
|
**Project:** canned-prompts
|
||||||
|
**Domain:** agents
|
||||||
|
**State Hub:** http://127.0.0.1:8000
|
||||||
|
**Topic ID:** `c1d199b6-55ee-4db6-b49e-257a9f0f15ac`
|
||||||
|
|
||||||
|
## Open Workplans
|
||||||
|
|
||||||
|
### Bootstrap State Hub integration
|
||||||
|
|
||||||
|
Workplan file: `workplans/CANP-WP-0001-statehub-bootstrap.md`
|
||||||
|
|
||||||
|
Open tasks:
|
||||||
|
- T01 - Review generated integration files
|
||||||
|
- T02 - Verify local developer workflow
|
||||||
|
- T03 - Seed first real workplan
|
||||||
|
|
||||||
|
## Session Start
|
||||||
|
|
||||||
|
1. Read `INTENT.md`, `SCOPE.md`, and `AGENTS.md`.
|
||||||
|
2. Check inbox: `GET /messages/?to_agent=canned-prompts&unread_only=true`.
|
||||||
|
3. Scan `workplans/`.
|
||||||
|
4. Update task statuses in workplan files as work progresses.
|
||||||
|
|
||||||
|
Last generated: 2026-09-06
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# state-hub: track .claude/rules
|
||||||
|
# Claude Code local state (track shared rules; ignore machine-specific files)
|
||||||
|
.claude/*
|
||||||
|
!.claude/rules/
|
||||||
|
!.claude/rules/*.md
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
203
AGENTS.md
Normal file
203
AGENTS.md
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
# canned-prompts — Agent Instructions
|
||||||
|
|
||||||
|
## Repo Identity
|
||||||
|
|
||||||
|
**Purpose:** Portable package format, spec and reference CLI for reusable prompt artifacts (Canned Prompt Format v0.1).
|
||||||
|
|
||||||
|
**Domain:** agents
|
||||||
|
**Repo slug:** canned-prompts
|
||||||
|
**Topic ID:** `c1d199b6-55ee-4db6-b49e-257a9f0f15ac`
|
||||||
|
**Workplan prefix:** `CANP-WP-`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Hub Integration
|
||||||
|
|
||||||
|
The Custodian State Hub tracks work across all domains. Codex uses HTTP REST and
|
||||||
|
the `statehub` CLI by default. MCP is opt-in because the current Codex MCP bridge
|
||||||
|
adds severe call latency; the full administrative MCP surface remains available
|
||||||
|
to clients that need it.
|
||||||
|
|
||||||
|
| Context | URL |
|
||||||
|
|---------|-----|
|
||||||
|
| Local workstation | `http://127.0.0.1:8000` |
|
||||||
|
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||||
|
| Optional local edge relay | http://127.0.0.1:18080 |
|
||||||
|
|
||||||
|
When an operator has enabled the edge relay, set API_BASE to the relay URL.
|
||||||
|
Queueable writes return an explicit queued receipt if the central hub is
|
||||||
|
unreachable. Treat that as pending local evidence, then ask the operator to run
|
||||||
|
statehub outbox status/replay after connectivity returns.
|
||||||
|
|
||||||
|
Codex workspace-write sandboxes need network access enabled to reach the host's
|
||||||
|
loopback listener. Bootstrap this once with `make -C ~/state-hub configure-codex`
|
||||||
|
and restart Codex. The canonical REST health endpoint is `/state/health`, not
|
||||||
|
`/health`. If a sandboxed loopback probe fails, retry it with escalated execution
|
||||||
|
before declaring State Hub unavailable; a managed Codex permission profile may
|
||||||
|
still enforce isolated networking. Experimental MCP can be enabled explicitly
|
||||||
|
with `make -C ~/state-hub configure-codex WITH_MCP=1`.
|
||||||
|
|
||||||
|
### Orient at session start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Offline brief — works without hub connection
|
||||||
|
cat .custodian-brief.md
|
||||||
|
|
||||||
|
# Active workplans for this domain
|
||||||
|
curl -s "http://127.0.0.1:8000/workplans/?topic_id=c1d199b6-55ee-4db6-b49e-257a9f0f15ac&status=active" \
|
||||||
|
| python3 -m json.tool
|
||||||
|
|
||||||
|
# Check inbox
|
||||||
|
curl -s "http://127.0.0.1:8000/messages/?to_agent=canned-prompts&unread_only=true" \
|
||||||
|
| python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
Mark a message read:
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||||
|
-H "Content-Type: application/json" -d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log progress (required at session close)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"summary": "what was done",
|
||||||
|
"event_type": "note",
|
||||||
|
"author": "codex",
|
||||||
|
"workplan_id": "<uuid>",
|
||||||
|
"task_id": "<uuid>"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Omit `workplan_id` / `task_id` when not applicable.
|
||||||
|
|
||||||
|
### Update task status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"status": "progress"}'
|
||||||
|
# values: wait | todo | progress | done | cancel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flag a task for human review
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"needs_human": true, "intervention_note": "reason"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Protocol
|
||||||
|
|
||||||
|
**Start:**
|
||||||
|
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
|
||||||
|
2. Check inbox: `GET /messages/?to_agent=canned-prompts&unread_only=true`; mark read
|
||||||
|
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
||||||
|
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
|
||||||
|
|
||||||
|
**During work:**
|
||||||
|
- Update task statuses in workplan files as tasks progress
|
||||||
|
- Record significant decisions via `POST /decisions/`
|
||||||
|
|
||||||
|
**Close:**
|
||||||
|
1. Update workplan file task statuses to reflect progress
|
||||||
|
2. If finishing a workplan: hand off **residuals** as live work records first
|
||||||
|
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
|
||||||
|
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
|
||||||
|
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
|
||||||
|
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
|
||||||
|
4. After workplan file changes, run:
|
||||||
|
```bash
|
||||||
|
uv run --project ~/repo-manager rmgr sync --path . --push
|
||||||
|
```
|
||||||
|
This assigns only missing deterministic identifiers, verifies the pushed
|
||||||
|
Forgejo commit and `primary/railliance01`, then requests one central
|
||||||
|
reconciliation. A queued receipt is pending evidence; rerun after
|
||||||
|
connectivity returns. Use `statehub fix-consistency` for a separate deep audit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||||
|
<!-- Append repo-specific agent instructions below this marker.
|
||||||
|
The state-hub template sync preserves content after this line. -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
|
Work items originate as files in this repo — not in the hub. The hub is a
|
||||||
|
read/cache/index layer that rebuilds from files.
|
||||||
|
|
||||||
|
**File location:** `workplans/CANP-WP-NNNN-<slug>.md`
|
||||||
|
|
||||||
|
**Archived location:** finished workplans may move to
|
||||||
|
`workplans/archived/YYMMDD-CANP-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
|
||||||
|
the completion/archive date; the frontmatter `id` does not change.
|
||||||
|
|
||||||
|
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||||
|
`workplans/ADHOC-YYYY-MM-DD.md`, workplan id
|
||||||
|
`CANP-WP-ADHOC-YYYY-MM-DD`, and task ids
|
||||||
|
`CANP-WP-ADHOC-YYYY-MM-DD-T01`, etc. `CANP-WP` includes its final `-WP`
|
||||||
|
token. Unqualified historic `ADHOC-*` ids are grandfathered and must not be
|
||||||
|
copied into new records. Use this only for low-risk work completed directly;
|
||||||
|
create a normal workplan for anything needing analysis, design, approval,
|
||||||
|
dependencies, or multiple phases.
|
||||||
|
|
||||||
|
**Frontmatter:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
id: CANP-WP-NNNN
|
||||||
|
type: workplan
|
||||||
|
title: "..."
|
||||||
|
domain: agents
|
||||||
|
repo: canned-prompts
|
||||||
|
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||||
|
owner: codex
|
||||||
|
topic_slug: ...
|
||||||
|
created: "YYYY-MM-DD"
|
||||||
|
updated: "YYYY-MM-DD"
|
||||||
|
state_hub_workstream_id: "<uuid>" # deterministic UUIDv5; managed by Repo Manager
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `proposed` for a new draft, `ready` after review against current repo
|
||||||
|
state, and `finished` after implementation. `stalled` and `needs_review` are
|
||||||
|
derived health labels, not frontmatter statuses.
|
||||||
|
|
||||||
|
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
|
||||||
|
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
|
||||||
|
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
|
||||||
|
|
||||||
|
**Task block format** (one per `##` section):
|
||||||
|
|
||||||
|
```
|
||||||
|
## Task Title
|
||||||
|
|
||||||
|
` ` `task
|
||||||
|
id: CANP-WP-NNNN-T01
|
||||||
|
status: wait | todo | progress | done | cancel
|
||||||
|
priority: high | medium | low
|
||||||
|
state_hub_task_id: "<uuid>" # deterministic UUIDv5; managed by Repo Manager
|
||||||
|
` ` `
|
||||||
|
|
||||||
|
Task description text.
|
||||||
|
```
|
||||||
|
|
||||||
|
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
|
||||||
|
|
||||||
|
**Residuals when finishing:** actionable leftovers become live work records
|
||||||
|
before `status: finished` — usually an intake (`origin: residual`,
|
||||||
|
`origin_ref: CANP-WP-NNNN`) or a spawned workplan. Residual is a *role*,
|
||||||
|
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
|
||||||
|
|
||||||
|
To create a new workplan:
|
||||||
|
1. Write the file following the format above
|
||||||
|
2. Run `uv run --project ~/repo-manager rmgr sync --path . --push`.
|
||||||
|
3. Run `statehub fix-consistency` only when a separate deep audit is needed.
|
||||||
640
CannedPromptFormat-v0.1.md
Normal file
640
CannedPromptFormat-v0.1.md
Normal file
|
|
@ -0,0 +1,640 @@
|
||||||
|
# Canned Prompt Format v0.1
|
||||||
|
|
||||||
|
**Status:** Seed specification / experimental
|
||||||
|
**Project:** `canned-prompts`
|
||||||
|
**Purpose:** Portable packaging of reusable prompts and prompt templates.
|
||||||
|
|
||||||
|
## 1. Goals
|
||||||
|
|
||||||
|
The Canned Prompt Format (CPF) defines the smallest practical contract for a reusable prompt artifact.
|
||||||
|
|
||||||
|
A conforming package should be:
|
||||||
|
|
||||||
|
- human-readable;
|
||||||
|
- filesystem-portable;
|
||||||
|
- provider-neutral;
|
||||||
|
- inspectable before use;
|
||||||
|
- parameterizable where useful;
|
||||||
|
- versionable;
|
||||||
|
- extensible with examples, evals, dependencies, and provenance.
|
||||||
|
|
||||||
|
CPF v0.1 specifies the **artifact format**. It intentionally does not specify model execution, agent orchestration, dependency resolution, registry transport, or evaluation engines.
|
||||||
|
|
||||||
|
## 2. Package layout
|
||||||
|
|
||||||
|
The minimum valid package is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
my-prompt/
|
||||||
|
├── prompt.yaml
|
||||||
|
└── prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
A richer package may contain:
|
||||||
|
|
||||||
|
```text
|
||||||
|
my-prompt/
|
||||||
|
├── prompt.yaml
|
||||||
|
├── prompt.md
|
||||||
|
├── README.md
|
||||||
|
├── examples/
|
||||||
|
│ ├── basic.yaml
|
||||||
|
│ └── edge-case.yaml
|
||||||
|
├── evals/
|
||||||
|
│ └── quality.yaml
|
||||||
|
└── assets/
|
||||||
|
└── rubric.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reserved paths
|
||||||
|
|
||||||
|
| Path | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `prompt.yaml` | Required package manifest |
|
||||||
|
| `prompt.md` | Default prompt template unless overridden by `template` |
|
||||||
|
| `README.md` | Optional human documentation |
|
||||||
|
| `examples/` | Optional examples/fixtures |
|
||||||
|
| `evals/` | Optional evaluation specifications |
|
||||||
|
| `assets/` | Optional supporting text/data artifacts |
|
||||||
|
|
||||||
|
Tools MUST ignore unknown non-reserved files unless a manifest field explicitly references them.
|
||||||
|
|
||||||
|
## 3. Manifest
|
||||||
|
|
||||||
|
The canonical manifest is UTF-8 YAML named `prompt.yaml`.
|
||||||
|
|
||||||
|
### 3.1 Minimal manifest
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: review/code-review
|
||||||
|
name: Code Review
|
||||||
|
version: 1.0.0
|
||||||
|
summary: Review a change for correctness and maintainability.
|
||||||
|
template: prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Required fields are:
|
||||||
|
|
||||||
|
- `format`
|
||||||
|
- `id`
|
||||||
|
- `name`
|
||||||
|
- `version`
|
||||||
|
- `summary`
|
||||||
|
- `template`
|
||||||
|
|
||||||
|
### 3.2 Package identity
|
||||||
|
|
||||||
|
#### `format`
|
||||||
|
|
||||||
|
MUST be exactly:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
for this specification.
|
||||||
|
|
||||||
|
#### `id`
|
||||||
|
|
||||||
|
A stable, registry-independent logical identifier.
|
||||||
|
|
||||||
|
Recommended syntax:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<namespace>/<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```text
|
||||||
|
review/code-review
|
||||||
|
engineering/architecture-review
|
||||||
|
practice/pqrst-estimate
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules for v0.1:
|
||||||
|
|
||||||
|
- lowercase ASCII is RECOMMENDED;
|
||||||
|
- `/`, `-`, `_`, and `.` MAY be used;
|
||||||
|
- whitespace MUST NOT be used;
|
||||||
|
- the ID MUST NOT contain `..` path traversal segments;
|
||||||
|
- registry implementations MUST treat the ID as logical metadata rather than an unchecked filesystem path.
|
||||||
|
|
||||||
|
#### `name`
|
||||||
|
|
||||||
|
Human-readable display name.
|
||||||
|
|
||||||
|
#### `version`
|
||||||
|
|
||||||
|
A package version. Semantic Versioning (`MAJOR.MINOR.PATCH`) is RECOMMENDED and used by the reference implementation.
|
||||||
|
|
||||||
|
Behavior-changing edits SHOULD create a new version rather than overwrite a published package.
|
||||||
|
|
||||||
|
#### `summary`
|
||||||
|
|
||||||
|
A short description of the intended purpose. A consumer SHOULD be able to decide whether a package is potentially relevant from `name` + `summary` alone.
|
||||||
|
|
||||||
|
#### `template`
|
||||||
|
|
||||||
|
Relative path to the primary prompt template inside the package. The path MUST remain within the package directory.
|
||||||
|
|
||||||
|
## 4. Complete v0.1 manifest surface
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: review/code-review
|
||||||
|
name: Code Review
|
||||||
|
version: 1.2.0
|
||||||
|
summary: >
|
||||||
|
Review a change for correctness, maintainability,
|
||||||
|
security and test coverage.
|
||||||
|
|
||||||
|
type: template
|
||||||
|
template: prompt.md
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- name: change
|
||||||
|
type: content
|
||||||
|
required: true
|
||||||
|
description: The code, diff, or change to review.
|
||||||
|
|
||||||
|
- name: repository_context
|
||||||
|
type: content
|
||||||
|
required: false
|
||||||
|
description: Optional surrounding repository context.
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
depth:
|
||||||
|
type: enum
|
||||||
|
values: [quick, normal, thorough]
|
||||||
|
default: normal
|
||||||
|
description: Desired review depth.
|
||||||
|
|
||||||
|
include_security:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: markdown
|
||||||
|
description: A structured review with findings and recommendations.
|
||||||
|
|
||||||
|
compatibility:
|
||||||
|
capabilities:
|
||||||
|
- code-analysis
|
||||||
|
models: []
|
||||||
|
providers: []
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
prompts: []
|
||||||
|
context: []
|
||||||
|
capabilities: []
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- examples/basic.yaml
|
||||||
|
|
||||||
|
evals:
|
||||||
|
- evals/review-quality.yaml
|
||||||
|
|
||||||
|
license: CC-BY-4.0
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- code-review
|
||||||
|
- engineering
|
||||||
|
|
||||||
|
provenance:
|
||||||
|
author: Example Author
|
||||||
|
source: https://example.invalid/original
|
||||||
|
derived_from: []
|
||||||
|
|
||||||
|
extensions: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
All fields other than the required fields in section 3.1 are optional.
|
||||||
|
|
||||||
|
## 5. Prompt template syntax
|
||||||
|
|
||||||
|
CPF v0.1 uses deliberately small placeholder semantics:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{{ variable_name }}
|
||||||
|
```
|
||||||
|
|
||||||
|
A placeholder name MUST correspond to either:
|
||||||
|
|
||||||
|
- a declared `input`, or
|
||||||
|
- a declared `parameter`.
|
||||||
|
|
||||||
|
Whitespace immediately inside `{{` and `}}` is insignificant.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Review the following change at {{ depth }} depth.
|
||||||
|
|
||||||
|
{{ change }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.1 Rendering rules
|
||||||
|
|
||||||
|
1. Call-supplied values override defaults.
|
||||||
|
2. A declared parameter default is used when no call value is supplied.
|
||||||
|
3. A required input without a value is an error.
|
||||||
|
4. A placeholder with no resolved value is an error.
|
||||||
|
5. Values are substituted as text in v0.1.
|
||||||
|
6. Template evaluation MUST NOT execute arbitrary code.
|
||||||
|
|
||||||
|
CPF v0.1 does not define conditionals, loops, filters, or functions. Implementations MAY offer richer rendering modes only when explicitly declared by an extension; they MUST NOT silently reinterpret a v0.1 template as executable code.
|
||||||
|
|
||||||
|
## 6. Inputs
|
||||||
|
|
||||||
|
`inputs` is an optional ordered list.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
inputs:
|
||||||
|
- name: document
|
||||||
|
type: content
|
||||||
|
required: true
|
||||||
|
description: Document to summarize.
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
| Field | Required | Meaning |
|
||||||
|
|---|---:|---|
|
||||||
|
| `name` | yes | Placeholder/input identifier |
|
||||||
|
| `type` | no | Suggested semantic type; defaults to `content` |
|
||||||
|
| `required` | no | Whether a caller must supply it; defaults to `false` |
|
||||||
|
| `description` | no | Human-readable explanation |
|
||||||
|
|
||||||
|
Recommended v0.1 input types are:
|
||||||
|
|
||||||
|
- `content`
|
||||||
|
- `text`
|
||||||
|
- `url`
|
||||||
|
- `path`
|
||||||
|
- `json`
|
||||||
|
|
||||||
|
These are descriptive hints in v0.1. A runtime MAY use them for validation or adapters.
|
||||||
|
|
||||||
|
## 7. Parameters
|
||||||
|
|
||||||
|
`parameters` is an optional mapping keyed by parameter name.
|
||||||
|
|
||||||
|
Supported descriptive parameter types:
|
||||||
|
|
||||||
|
- `string`
|
||||||
|
- `integer`
|
||||||
|
- `number`
|
||||||
|
- `boolean`
|
||||||
|
- `enum`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
parameters:
|
||||||
|
tone:
|
||||||
|
type: enum
|
||||||
|
values: [neutral, friendly, formal]
|
||||||
|
default: neutral
|
||||||
|
|
||||||
|
max_items:
|
||||||
|
type: integer
|
||||||
|
default: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
A tool SHOULD validate enum values. Other type validation is RECOMMENDED but not mandatory for a minimal implementation.
|
||||||
|
|
||||||
|
## 8. Output contract
|
||||||
|
|
||||||
|
`output` describes the intended result, not an execution protocol.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
output:
|
||||||
|
format: markdown
|
||||||
|
description: Concise structured findings.
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested `format` values include:
|
||||||
|
|
||||||
|
- `text`
|
||||||
|
- `markdown`
|
||||||
|
- `json`
|
||||||
|
- `yaml`
|
||||||
|
- `xml`
|
||||||
|
- `code`
|
||||||
|
|
||||||
|
Registries MAY index output format for discovery.
|
||||||
|
|
||||||
|
## 9. Compatibility
|
||||||
|
|
||||||
|
`compatibility` records known requirements or observations without binding the package to one runtime.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
compatibility:
|
||||||
|
capabilities:
|
||||||
|
- code-analysis
|
||||||
|
- long-context
|
||||||
|
models:
|
||||||
|
- example/model-family
|
||||||
|
providers: []
|
||||||
|
```
|
||||||
|
|
||||||
|
Semantics:
|
||||||
|
|
||||||
|
- `capabilities`: abstract capabilities expected from the execution environment;
|
||||||
|
- `models`: model identifiers known to be compatible or evaluated;
|
||||||
|
- `providers`: provider identifiers when provider-specific behavior matters.
|
||||||
|
|
||||||
|
An empty list means "not constrained/unspecified", not "compatible with nothing".
|
||||||
|
|
||||||
|
## 10. Dependencies
|
||||||
|
|
||||||
|
Dependencies describe external artifacts or capabilities expected by the prompt.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
- id: context/repository-summary
|
||||||
|
version: 1.0.0
|
||||||
|
requirement: optional
|
||||||
|
|
||||||
|
context:
|
||||||
|
- id: policy/security
|
||||||
|
requirement: required
|
||||||
|
|
||||||
|
capabilities:
|
||||||
|
- web-search
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended requirement values:
|
||||||
|
|
||||||
|
- `required`
|
||||||
|
- `optional`
|
||||||
|
- `generate`
|
||||||
|
|
||||||
|
`generate` means that a resolver MAY satisfy a missing dependency by invoking an appropriate generation process. **CPF v0.1 does not define how generation or dependency resolution works.**
|
||||||
|
|
||||||
|
This allows richer systems to integrate prompt resolution without forcing simple tools to implement an agent runtime.
|
||||||
|
|
||||||
|
## 11. Examples
|
||||||
|
|
||||||
|
`examples` is a list of relative paths.
|
||||||
|
|
||||||
|
An example file is not normative but SHOULD make intended usage obvious.
|
||||||
|
|
||||||
|
Suggested YAML shape:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: basic review
|
||||||
|
values:
|
||||||
|
change: |
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
|
depth: quick
|
||||||
|
```
|
||||||
|
|
||||||
|
Tools MAY render examples directly.
|
||||||
|
|
||||||
|
## 12. Evals
|
||||||
|
|
||||||
|
`evals` is a list of relative paths to evaluation specifications.
|
||||||
|
|
||||||
|
CPF v0.1 deliberately does not standardize a universal evaluation language. Eval files SHOULD therefore declare their own evaluator or schema.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema: canned-prompts/eval-rubric/v0.1
|
||||||
|
name: code-review-quality
|
||||||
|
criteria:
|
||||||
|
- identifies correctness risks
|
||||||
|
- distinguishes blocking from advisory findings
|
||||||
|
- avoids inventing repository facts
|
||||||
|
```
|
||||||
|
|
||||||
|
A registry may associate externally collected run/eval evidence with `<id>@<version>` without mutating the package.
|
||||||
|
|
||||||
|
## 13. Provenance and lineage
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
provenance:
|
||||||
|
author: Ada Example
|
||||||
|
source: https://example.invalid/source
|
||||||
|
derived_from:
|
||||||
|
- id: review/code-review
|
||||||
|
version: 1.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The field is descriptive in v0.1. Registries SHOULD preserve provenance when publishing or mirroring packages.
|
||||||
|
|
||||||
|
## 14. Licensing
|
||||||
|
|
||||||
|
A package MAY declare an SPDX license identifier or other clear license expression:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
license: CC-BY-4.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Absence of a license MUST NOT be interpreted as permission to redistribute or modify the package.
|
||||||
|
|
||||||
|
Tools SHOULD surface licensing metadata during publishing and installation.
|
||||||
|
|
||||||
|
## 15. Tags
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tags:
|
||||||
|
- architecture
|
||||||
|
- review
|
||||||
|
- agentic-coding
|
||||||
|
```
|
||||||
|
|
||||||
|
Tags are free-form discovery hints. Registries MAY normalize or enrich tags while preserving package metadata.
|
||||||
|
|
||||||
|
## 16. Extensions
|
||||||
|
|
||||||
|
`extensions` is the designated escape hatch for experimental or implementation-specific metadata.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extensions:
|
||||||
|
org.example.canned-prompts:
|
||||||
|
maturity: experimental
|
||||||
|
```
|
||||||
|
|
||||||
|
Extension keys SHOULD be namespaced to avoid collisions.
|
||||||
|
|
||||||
|
A consumer MUST ignore unknown extension entries unless it explicitly claims support for them.
|
||||||
|
|
||||||
|
## 17. Immutability and versioning
|
||||||
|
|
||||||
|
Published `<id>@<version>` pairs SHOULD be immutable.
|
||||||
|
|
||||||
|
A registry SHOULD reject publication of a package when the same ID/version already exists with different contents unless an explicit administrative override mechanism exists.
|
||||||
|
|
||||||
|
Suggested versioning guidance:
|
||||||
|
|
||||||
|
- PATCH: wording/metadata correction with intended behavior unchanged;
|
||||||
|
- MINOR: backward-compatible behavior or parameter additions;
|
||||||
|
- MAJOR: changed contract, renamed/removed inputs, or materially different intended behavior.
|
||||||
|
|
||||||
|
This guidance is intentionally advisory because prompt behavior is probabilistic and cannot be versioned as mechanically as an API.
|
||||||
|
|
||||||
|
## 18. Package validation
|
||||||
|
|
||||||
|
A v0.1 validator SHOULD verify at least:
|
||||||
|
|
||||||
|
1. `prompt.yaml` exists and parses as YAML;
|
||||||
|
2. required manifest fields exist;
|
||||||
|
3. `format == canned-prompt/v0.1`;
|
||||||
|
4. `id` is non-empty and contains no path traversal;
|
||||||
|
5. `version` is non-empty;
|
||||||
|
6. `template` resolves to a regular file inside the package;
|
||||||
|
7. referenced example/eval paths do not escape the package;
|
||||||
|
8. required inputs and parameter names are unique;
|
||||||
|
9. every template placeholder resolves to a declared input or parameter;
|
||||||
|
10. no required value is silently omitted during rendering.
|
||||||
|
|
||||||
|
## 19. Security requirements
|
||||||
|
|
||||||
|
Prompt packages are content, not trusted code.
|
||||||
|
|
||||||
|
Implementations MUST NOT:
|
||||||
|
|
||||||
|
- execute code merely because it appears in a package;
|
||||||
|
- treat template expressions as arbitrary code;
|
||||||
|
- interpolate environment variables or credentials implicitly;
|
||||||
|
- follow paths outside the package without explicit user action;
|
||||||
|
- embed or require secrets in published package metadata.
|
||||||
|
|
||||||
|
Implementations SHOULD:
|
||||||
|
|
||||||
|
- inspect all referenced paths for traversal;
|
||||||
|
- make package contents visible before execution;
|
||||||
|
- surface provenance and license metadata;
|
||||||
|
- treat remote content referenced by a package as untrusted input;
|
||||||
|
- separate package installation from model/tool authorization.
|
||||||
|
|
||||||
|
## 20. Registry model
|
||||||
|
|
||||||
|
CPF v0.1 does not mandate registry transport.
|
||||||
|
|
||||||
|
A valid registry may be:
|
||||||
|
|
||||||
|
- a filesystem directory;
|
||||||
|
- a Git repository;
|
||||||
|
- an object store;
|
||||||
|
- an HTTP service;
|
||||||
|
- a federated catalog.
|
||||||
|
|
||||||
|
Conceptually, a registry stores immutable package versions keyed by:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<id>@<version>
|
||||||
|
```
|
||||||
|
|
||||||
|
The reference implementation uses the filesystem layout:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registry/
|
||||||
|
└── <id path>/
|
||||||
|
└── <version>/
|
||||||
|
├── prompt.yaml
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
registry/
|
||||||
|
└── practice/
|
||||||
|
└── pqrst-estimate/
|
||||||
|
└── 0.1.0/
|
||||||
|
├── prompt.yaml
|
||||||
|
└── prompt.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 21. Reference CLI semantics
|
||||||
|
|
||||||
|
The v0.1 reference tool uses two stores:
|
||||||
|
|
||||||
|
- **catalog** — packages locally available for search/show/render;
|
||||||
|
- **registry** — packages available for publish/install.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
```text
|
||||||
|
add PATH validate and copy a package into the local catalog
|
||||||
|
search QUERY search locally installed package metadata
|
||||||
|
show ID display one installed package manifest
|
||||||
|
render ID render an installed prompt with supplied values
|
||||||
|
publish PATH validate and copy a package into a filesystem registry
|
||||||
|
install ID copy a package version from the registry into the catalog
|
||||||
|
```
|
||||||
|
|
||||||
|
These semantics are illustrative, not mandatory for other implementations.
|
||||||
|
|
||||||
|
## 22. Worked example
|
||||||
|
|
||||||
|
`prompt.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: practice/pqrst-estimate
|
||||||
|
name: PQRST Estimate
|
||||||
|
version: 0.1.0
|
||||||
|
summary: Estimate how session effort was distributed across PQRST categories.
|
||||||
|
template: prompt.md
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- name: session_summary
|
||||||
|
type: content
|
||||||
|
required: true
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
include_rationale:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: markdown
|
||||||
|
|
||||||
|
tags: [retrospective, agentic-coding, pqrst]
|
||||||
|
```
|
||||||
|
|
||||||
|
`prompt.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Review the following coding-session summary and estimate the distribution of
|
||||||
|
session effort across PQRST. Percentages must sum to 100%.
|
||||||
|
|
||||||
|
P = main problem
|
||||||
|
Q = quality and tests
|
||||||
|
R = research and context clarification
|
||||||
|
S = security and credentials
|
||||||
|
T = task organization
|
||||||
|
|
||||||
|
Session:
|
||||||
|
|
||||||
|
{{ session_summary }}
|
||||||
|
|
||||||
|
Include rationale: {{ include_rationale }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 23. Open questions for v0.2+
|
||||||
|
|
||||||
|
Experience should determine whether later revisions standardize:
|
||||||
|
|
||||||
|
- typed context/dependency contracts;
|
||||||
|
- content macros;
|
||||||
|
- prompt composition and inheritance;
|
||||||
|
- registry namespaces and ownership;
|
||||||
|
- cryptographic integrity/signing;
|
||||||
|
- canonical evaluation schemas;
|
||||||
|
- model capability vocabularies;
|
||||||
|
- run manifests and evidence formats;
|
||||||
|
- deterministic compilation manifests;
|
||||||
|
- trust/reputation signals;
|
||||||
|
- federated discovery;
|
||||||
|
- richer template syntax.
|
||||||
|
|
||||||
|
Until practical usage forces these decisions, v0.1 should remain intentionally small.
|
||||||
247
INTENT.md
Normal file
247
INTENT.md
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
# canned-prompts — INTENT
|
||||||
|
|
||||||
|
> **Collect, reuse and share prompts and prompt templates.**
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
|
||||||
|
`canned-prompts` exists to make prompts that are worth using more than once easy to package, understand, find, reuse, version, evaluate, and share.
|
||||||
|
|
||||||
|
The project treats a reusable prompt as a small software-like artifact with an explicit contract rather than as an anonymous block of copied text.
|
||||||
|
|
||||||
|
A canned prompt can therefore carry not only prompt text, but also its purpose, inputs, parameters, expected output, examples, compatibility information, provenance, and quality evidence.
|
||||||
|
|
||||||
|
## Core proposition
|
||||||
|
|
||||||
|
> **The valuable unit is not prompt text. It is a reusable prompt contract.**
|
||||||
|
|
||||||
|
A copied prompt is easy to produce but hard to govern. A reusable prompt should be independently understandable and usable by another human, tool, or agent without requiring hidden knowledge from its author.
|
||||||
|
|
||||||
|
## Primary user loop
|
||||||
|
|
||||||
|
```text
|
||||||
|
write
|
||||||
|
↓
|
||||||
|
package
|
||||||
|
↓
|
||||||
|
publish
|
||||||
|
↓
|
||||||
|
discover
|
||||||
|
↓
|
||||||
|
configure
|
||||||
|
↓
|
||||||
|
render / invoke
|
||||||
|
↓
|
||||||
|
observe
|
||||||
|
↓
|
||||||
|
improve
|
||||||
|
↓
|
||||||
|
publish a new revision
|
||||||
|
```
|
||||||
|
|
||||||
|
The first implementation should make this loop cheap enough that packaging a useful prompt feels easier than leaving it in an arbitrary notes file.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
`canned-prompts` owns the **artifact and ecosystem around reusable prompts**.
|
||||||
|
|
||||||
|
It should establish:
|
||||||
|
|
||||||
|
1. a portable package format for prompts and prompt templates;
|
||||||
|
2. a catalog/registry model for discovery and distribution;
|
||||||
|
3. explicit parameters and context requirements;
|
||||||
|
4. versioning and provenance;
|
||||||
|
5. examples and evaluation hooks;
|
||||||
|
6. compatibility metadata for models, tools, agents, or required capabilities;
|
||||||
|
7. composition metadata for prompts that depend on other reusable prompt/context artifacts;
|
||||||
|
8. lightweight tooling to add, inspect, search, install, render, and publish packages.
|
||||||
|
|
||||||
|
## Deliberate boundary
|
||||||
|
|
||||||
|
> **canned-prompts standardizes reusable prompt artifacts, not agent execution.**
|
||||||
|
|
||||||
|
The project may provide thin execution adapters for convenience, but it should not become a general agent runtime, workflow engine, model router, memory system, or orchestration framework.
|
||||||
|
|
||||||
|
Execution systems should be able to consume canned prompt packages without being forced to adopt a particular agent architecture.
|
||||||
|
|
||||||
|
A useful conceptual separation is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
canned-prompts
|
||||||
|
|
|
||||||
|
| discover / retrieve
|
||||||
|
v
|
||||||
|
PromptPackage / PromptTemplate
|
||||||
|
|
|
||||||
|
v
|
||||||
|
resolver / compiler / agent runtime
|
||||||
|
|
|
||||||
|
v
|
||||||
|
CompiledPrompt
|
||||||
|
|
|
||||||
|
v
|
||||||
|
PromptRun
|
||||||
|
|
|
||||||
|
v
|
||||||
|
result + run evidence
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design principles
|
||||||
|
|
||||||
|
### 1. Text first
|
||||||
|
|
||||||
|
A package should remain readable and editable with ordinary text tools. Markdown plus a small machine-readable manifest is the preferred baseline.
|
||||||
|
|
||||||
|
### 2. Portable by default
|
||||||
|
|
||||||
|
The package format must not depend on one model provider, IDE, agent framework, or hosted registry.
|
||||||
|
|
||||||
|
### 3. Explicit over magical
|
||||||
|
|
||||||
|
Inputs, parameters, dependencies, and expectations should be declared where practical. Hidden context is the enemy of reuse.
|
||||||
|
|
||||||
|
### 4. Useful before sophisticated
|
||||||
|
|
||||||
|
A prompt consisting only of `prompt.yaml` and `prompt.md` should already be a valid package. Examples, evals, dependencies, and richer metadata are progressive enhancements.
|
||||||
|
|
||||||
|
### 5. Version prompt behavior
|
||||||
|
|
||||||
|
Changes that materially alter intended behavior should create a new package version. Consumers should be able to pin versions when reproducibility matters.
|
||||||
|
|
||||||
|
### 6. Preserve provenance
|
||||||
|
|
||||||
|
Authorship, source, derivation, and lineage should be representable. Forking and adaptation are expected rather than treated as exceptional.
|
||||||
|
|
||||||
|
### 7. Quality should become evidence-backed
|
||||||
|
|
||||||
|
Popularity is not the same as quality. The ecosystem should make room for evals, run evidence, model/context compatibility, and adoption signals without requiring them in the minimum package.
|
||||||
|
|
||||||
|
### 8. Safe to inspect
|
||||||
|
|
||||||
|
Prompt packages must never require embedded secrets or credentials. Consumers should be able to inspect the complete artifact before execution.
|
||||||
|
|
||||||
|
### 9. Composition without capture
|
||||||
|
|
||||||
|
Packages may declare dependencies on prompts, context generators, policies, or information spaces, but the format should describe those requirements without dictating one resolver implementation.
|
||||||
|
|
||||||
|
### 10. Local-first, registry-ready
|
||||||
|
|
||||||
|
The first useful implementation should work entirely on a filesystem. Hosted or federated registries can be layered on later without changing the core package semantics.
|
||||||
|
|
||||||
|
## Canonical concepts
|
||||||
|
|
||||||
|
### PromptPackage
|
||||||
|
|
||||||
|
The distributable directory containing a manifest, template text, and optional supporting artifacts.
|
||||||
|
|
||||||
|
### PromptTemplate
|
||||||
|
|
||||||
|
The reusable prompt text plus its declared substitution/context contract.
|
||||||
|
|
||||||
|
### Input
|
||||||
|
|
||||||
|
Content supplied by a caller for a specific use, such as source code, a document, or a question.
|
||||||
|
|
||||||
|
### Parameter
|
||||||
|
|
||||||
|
A named configuration choice that modifies how the prompt behaves, preferably with type, allowed values, and defaults.
|
||||||
|
|
||||||
|
### Dependency
|
||||||
|
|
||||||
|
Another named artifact or capability that a package expects to be available. Dependency resolution is outside the core package format.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
A small reproducible usage fixture showing representative inputs and/or rendered output.
|
||||||
|
|
||||||
|
### Eval
|
||||||
|
|
||||||
|
A machine- or human-readable specification for assessing whether the prompt behaves as intended.
|
||||||
|
|
||||||
|
### Provenance
|
||||||
|
|
||||||
|
Metadata describing origin, authorship, source, derivation, and lineage.
|
||||||
|
|
||||||
|
### RunEvidence
|
||||||
|
|
||||||
|
Optional external evidence produced by use of a prompt version. Run evidence is not embedded into the immutable package by default, but may be linked to it by registries or evaluation systems.
|
||||||
|
|
||||||
|
## Initial capability surface
|
||||||
|
|
||||||
|
A minimal reference tool should support:
|
||||||
|
|
||||||
|
```text
|
||||||
|
canned-prompts add <package-path>
|
||||||
|
canned-prompts search <query>
|
||||||
|
canned-prompts show <id>
|
||||||
|
canned-prompts render <id> --set name=value
|
||||||
|
canned-prompts install <id>
|
||||||
|
canned-prompts publish <package-path>
|
||||||
|
```
|
||||||
|
|
||||||
|
The reference implementation may use a filesystem-backed local catalog and filesystem-backed registry. Network services are explicitly unnecessary for v0.1.
|
||||||
|
|
||||||
|
## Non-goals for v0.1
|
||||||
|
|
||||||
|
The first version does **not** need to solve:
|
||||||
|
|
||||||
|
- hosted social features;
|
||||||
|
- model execution or billing;
|
||||||
|
- autonomous dependency generation;
|
||||||
|
- semantic version range resolution;
|
||||||
|
- trust/reputation scoring;
|
||||||
|
- cryptographic package signing;
|
||||||
|
- cross-registry federation;
|
||||||
|
- secrets management;
|
||||||
|
- universal prompt evaluation;
|
||||||
|
- a universal agent or workflow specification.
|
||||||
|
|
||||||
|
These may become separate modules or later layers if usage demonstrates the need.
|
||||||
|
|
||||||
|
## Success criteria for the first practical release
|
||||||
|
|
||||||
|
The project is useful when a user can:
|
||||||
|
|
||||||
|
1. take a prompt worth keeping and package it in a few minutes;
|
||||||
|
2. understand an unfamiliar package without reading external documentation;
|
||||||
|
3. search a local catalog and discover a suitable prompt;
|
||||||
|
4. render a parameterized prompt deterministically;
|
||||||
|
5. publish a version to a registry and install it elsewhere;
|
||||||
|
6. keep multiple versions without destructive overwrite;
|
||||||
|
7. attach examples or eval specifications without changing the core format;
|
||||||
|
8. use the package from another tool without importing the reference runtime.
|
||||||
|
|
||||||
|
## Evolution hypothesis
|
||||||
|
|
||||||
|
If the simple package loop proves useful, `canned-prompts` can evolve from a prompt library into a package ecosystem for reusable cognitive procedures.
|
||||||
|
|
||||||
|
A mature feedback loop could be:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PromptPackage
|
||||||
|
↓
|
||||||
|
Use
|
||||||
|
↓
|
||||||
|
Run evidence
|
||||||
|
↓
|
||||||
|
Evaluation
|
||||||
|
↓
|
||||||
|
Improved package
|
||||||
|
↓
|
||||||
|
New version
|
||||||
|
↓
|
||||||
|
Adoption / comparison
|
||||||
|
```
|
||||||
|
|
||||||
|
That later ecosystem should be able to answer questions such as:
|
||||||
|
|
||||||
|
- Which prompt versions perform better for a defined task?
|
||||||
|
- Under which models, contexts, or capabilities?
|
||||||
|
- Which forks outperform their ancestors?
|
||||||
|
- Which dependencies reliably improve outcomes?
|
||||||
|
- When has a prompt's behavior drifted enough to require a new version?
|
||||||
|
|
||||||
|
These are long-term opportunities, not reasons to complicate the initial format.
|
||||||
|
|
||||||
|
## Working motto
|
||||||
|
|
||||||
|
> **Package what is worth prompting twice.**
|
||||||
56
README.md
56
README.md
|
|
@ -1,3 +1,55 @@
|
||||||
# canned-prompts
|
# canned-prompts seed
|
||||||
|
|
||||||
Collect, reuse and share prompts and prompt templates
|
> **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-v0.1.md`](CannedPromptFormat-v0.1.md) — experimental package-format specification.
|
||||||
|
- [`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.
|
||||||
|
|
||||||
|
## 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 example to your local catalog
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
||||||
66
SCOPE.md
Normal file
66
SCOPE.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# SCOPE
|
||||||
|
|
||||||
|
## One-liner
|
||||||
|
|
||||||
|
Portable package format, spec and reference CLI for reusable prompt artifacts
|
||||||
|
(Canned Prompt Format v0.1).
|
||||||
|
|
||||||
|
## Core Idea
|
||||||
|
|
||||||
|
The valuable unit is not prompt text but a **reusable prompt contract**: a
|
||||||
|
filesystem-portable, provider-neutral package (`prompt.yaml` + `prompt.md`) that
|
||||||
|
declares its purpose, inputs, parameters, output, compatibility, provenance and
|
||||||
|
evidence, and is inspectable before use. See `INTENT.md`.
|
||||||
|
|
||||||
|
## In Scope
|
||||||
|
|
||||||
|
- `CannedPromptFormat-v0.1.md` — the package-format specification.
|
||||||
|
- `reference/` — a deliberately small Python CLI (`add`, `search`, `show`,
|
||||||
|
`render`, `install`, `publish`) over a filesystem catalog and registry.
|
||||||
|
- `examples/` — real packages that exercise the format and the implementation.
|
||||||
|
- Format evolution: manifest surface, rendering rules, validation rules,
|
||||||
|
registry semantics, provenance and eval hooks.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
Per `INTENT.md` § Deliberate boundary — canned-prompts standardizes reusable
|
||||||
|
prompt artifacts, **not agent execution**:
|
||||||
|
|
||||||
|
- model execution, routing, billing, or authentication;
|
||||||
|
- agent runtimes, workflow/orchestration engines, memory systems;
|
||||||
|
- hosted or federated registries and network transport (v0.1 is filesystem-only);
|
||||||
|
- dependency resolution, semver range resolution, package signing;
|
||||||
|
- trust/reputation scoring and social features.
|
||||||
|
|
||||||
|
Thin execution adapters are permitted; a general runtime is not.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
- **Seed / experimental.** Spec v0.1 is written; the reference CLI implements the
|
||||||
|
full local lifecycle and its 3 tests pass. One example package
|
||||||
|
(`examples/pqrst-estimate`) round-trips add → search → show → render → publish
|
||||||
|
→ install.
|
||||||
|
- The reference implementation is spec-conformant on the points it covers; the
|
||||||
|
known open design questions are tracked in `workplans/CANP-WP-0002-*.md`.
|
||||||
|
|
||||||
|
## Developer Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd reference
|
||||||
|
python3 -m venv .venv && . .venv/bin/activate
|
||||||
|
pip install -r requirements-dev.txt
|
||||||
|
|
||||||
|
python3 -m pytest -q # tests
|
||||||
|
CANNED_PROMPTS_HOME=/tmp/cp \
|
||||||
|
python3 canned_prompts.py add ../examples/pqrst-estimate # smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no lint or build step yet; `pyproject.toml` declares the
|
||||||
|
`canned-prompts` console script but the module is normally run directly.
|
||||||
|
|
||||||
|
## Getting Oriented
|
||||||
|
|
||||||
|
- Intent and principles: `INTENT.md`
|
||||||
|
- Format specification: `CannedPromptFormat-v0.1.md`
|
||||||
|
- Agent instructions: `AGENTS.md`
|
||||||
|
- Workplans: `workplans/`
|
||||||
11
examples/pqrst-estimate/README.md
Normal file
11
examples/pqrst-estimate/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# PQRST Estimate example
|
||||||
|
|
||||||
|
This package is included as a first non-trivial example of Canned Prompt Format v0.1.
|
||||||
|
|
||||||
|
It demonstrates:
|
||||||
|
|
||||||
|
- one required content input;
|
||||||
|
- one boolean parameter with a default;
|
||||||
|
- a structured output expectation;
|
||||||
|
- discovery tags;
|
||||||
|
- a prompt that carries terminology and interpretation rules, not merely prose.
|
||||||
8
examples/pqrst-estimate/examples/basic.yaml
Normal file
8
examples/pqrst-estimate/examples/basic.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
name: feature implementation with unfamiliar codebase
|
||||||
|
values:
|
||||||
|
session_summary: |
|
||||||
|
The session traced an unfamiliar request path through the repository,
|
||||||
|
implemented a new validation rule, added unit and integration tests, fixed
|
||||||
|
two edge cases discovered during testing, and updated the implementation
|
||||||
|
after finding a conflicting assumption in an internal helper.
|
||||||
|
include_rationale: true
|
||||||
47
examples/pqrst-estimate/prompt.md
Normal file
47
examples/pqrst-estimate/prompt.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
Review the coding session described below and produce a **PQRST Estimate** of
|
||||||
|
where effort was spent.
|
||||||
|
|
||||||
|
Use these categories:
|
||||||
|
|
||||||
|
- **P — Main problem:** implementing or directly solving the requested deliverable.
|
||||||
|
- **Q — Quality and tests:** tests, verification, edge cases, maintainability,
|
||||||
|
error handling, cleanup, and production-quality hardening.
|
||||||
|
- **R — Research and context clarification:** reading the codebase or docs,
|
||||||
|
tracing behavior, investigating unknowns, reconciling requirements, and
|
||||||
|
establishing missing context.
|
||||||
|
- **S — Security and credentials:** authentication, authorization, secrets,
|
||||||
|
credentials, trust boundaries, security validation, and security-specific
|
||||||
|
handling.
|
||||||
|
- **T — Task organization:** planning, decomposition, todo management,
|
||||||
|
sequencing, coordination, and overhead required to keep the work organized.
|
||||||
|
|
||||||
|
Treat this as a **post-session audit, not a planning estimate**. Estimate
|
||||||
|
relative cognitive/work effort rather than tokens or wall-clock time. The five
|
||||||
|
percentages **must sum to exactly 100%**.
|
||||||
|
|
||||||
|
Where activities overlap, assign effort according to the primary purpose of the
|
||||||
|
activity. Do not inflate a category merely because it was important; estimate
|
||||||
|
how much effort it actually consumed.
|
||||||
|
|
||||||
|
Session material:
|
||||||
|
|
||||||
|
{{ session_summary }}
|
||||||
|
|
||||||
|
Return:
|
||||||
|
|
||||||
|
```text
|
||||||
|
P: NN%
|
||||||
|
Q: NN%
|
||||||
|
R: NN%
|
||||||
|
S: NN%
|
||||||
|
T: NN%
|
||||||
|
Total: 100%
|
||||||
|
```
|
||||||
|
|
||||||
|
Then provide:
|
||||||
|
|
||||||
|
1. **Primary effort driver** — one sentence naming what dominated the session.
|
||||||
|
2. **Interpretation** — what the distribution says about the session's shape.
|
||||||
|
3. **Signal** — one notable imbalance, if any, that may be worth learning from.
|
||||||
|
|
||||||
|
Include rationale: {{ include_rationale }}
|
||||||
38
examples/pqrst-estimate/prompt.yaml
Normal file
38
examples/pqrst-estimate/prompt.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: practice/pqrst-estimate
|
||||||
|
name: PQRST Estimate
|
||||||
|
version: 0.1.0
|
||||||
|
summary: 'Produce a post-session estimate of effort distributed across the PQRST categories
|
||||||
|
for an agentic coding session.
|
||||||
|
|
||||||
|
'
|
||||||
|
type: template
|
||||||
|
template: prompt.md
|
||||||
|
inputs:
|
||||||
|
- name: session_summary
|
||||||
|
type: content
|
||||||
|
required: true
|
||||||
|
description: 'Session transcript, summary, or sufficiently detailed account of the
|
||||||
|
work performed during the coding session.
|
||||||
|
|
||||||
|
'
|
||||||
|
parameters:
|
||||||
|
include_rationale:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description: Explain the evidence behind the estimate.
|
||||||
|
output:
|
||||||
|
format: markdown
|
||||||
|
description: A 100% PQRST effort allocation with concise interpretation.
|
||||||
|
compatibility:
|
||||||
|
capabilities:
|
||||||
|
- session-review
|
||||||
|
tags:
|
||||||
|
- pqrst
|
||||||
|
- retrospective
|
||||||
|
- agentic-coding
|
||||||
|
- effort-estimation
|
||||||
|
provenance:
|
||||||
|
author: canned-prompts seed
|
||||||
|
examples:
|
||||||
|
- examples/basic.yaml
|
||||||
49
reference/README.md
Normal file
49
reference/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# canned-prompts reference CLI
|
||||||
|
|
||||||
|
This is intentionally a **small reference implementation**, not the intended final architecture.
|
||||||
|
|
||||||
|
It demonstrates six verbs:
|
||||||
|
|
||||||
|
```text
|
||||||
|
add PATH
|
||||||
|
search QUERY
|
||||||
|
show ID
|
||||||
|
render ID --set key=value
|
||||||
|
install ID [--version VERSION]
|
||||||
|
publish PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
The implementation uses a local catalog plus a filesystem registry and performs no model calls.
|
||||||
|
|
||||||
|
## Stores
|
||||||
|
|
||||||
|
Default locations:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.canned-prompts/catalog
|
||||||
|
~/.canned-prompts/registry
|
||||||
|
```
|
||||||
|
|
||||||
|
Catalog and registry both store packages as:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<store>/<id path>/<version>/...
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.canned-prompts/catalog/practice/pqrst-estimate/0.1.0/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design choices
|
||||||
|
|
||||||
|
- YAML manifest via PyYAML.
|
||||||
|
- `{{ name }}` template substitution only.
|
||||||
|
- No arbitrary expression/code execution.
|
||||||
|
- Published versions are immutable by default.
|
||||||
|
- `install` copies from registry to catalog.
|
||||||
|
- `add` copies a package directly to catalog.
|
||||||
|
- `search`, `show`, and `render` operate on catalog packages.
|
||||||
|
|
||||||
|
Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.
|
||||||
418
reference/canned_prompts.py
Executable file
418
reference/canned_prompts.py
Executable file
|
|
@ -0,0 +1,418 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Tiny reference CLI for Canned Prompt Format v0.1.
|
||||||
|
|
||||||
|
This implementation intentionally favors readability over features. It uses a
|
||||||
|
filesystem-backed local catalog and registry and never calls a model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Iterable
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
FORMAT = "canned-prompt/v0.1"
|
||||||
|
PLACEHOLDER_RE = re.compile(r"{{\s*([A-Za-z_][A-Za-z0-9_.-]*)\s*}}")
|
||||||
|
SEMVER_RE = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+].*)?$")
|
||||||
|
REQUIRED_FIELDS = ("format", "id", "name", "version", "summary", "template")
|
||||||
|
|
||||||
|
|
||||||
|
class CannedPromptError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def home_dir() -> Path:
|
||||||
|
return Path(os.environ.get("CANNED_PROMPTS_HOME", Path.home() / ".canned-prompts"))
|
||||||
|
|
||||||
|
|
||||||
|
def default_catalog() -> Path:
|
||||||
|
return home_dir() / "catalog"
|
||||||
|
|
||||||
|
|
||||||
|
def default_registry() -> Path:
|
||||||
|
return home_dir() / "registry"
|
||||||
|
|
||||||
|
|
||||||
|
def read_manifest(package_dir: Path) -> dict[str, Any]:
|
||||||
|
manifest_path = package_dir / "prompt.yaml"
|
||||||
|
if not manifest_path.is_file():
|
||||||
|
raise CannedPromptError(f"missing manifest: {manifest_path}")
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
raise CannedPromptError(f"invalid YAML in {manifest_path}: {exc}") from exc
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise CannedPromptError("prompt.yaml must contain a mapping")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def safe_relative_file(package_dir: Path, relative: str, field: str) -> Path:
|
||||||
|
if not isinstance(relative, str) or not relative.strip():
|
||||||
|
raise CannedPromptError(f"{field} must be a non-empty relative path")
|
||||||
|
candidate = (package_dir / relative).resolve()
|
||||||
|
root = package_dir.resolve()
|
||||||
|
try:
|
||||||
|
candidate.relative_to(root)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise CannedPromptError(f"{field} escapes the package: {relative}") from exc
|
||||||
|
if not candidate.is_file():
|
||||||
|
raise CannedPromptError(f"{field} does not reference a file: {relative}")
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
|
def declared_names(manifest: dict[str, Any]) -> set[str]:
|
||||||
|
names: set[str] = set()
|
||||||
|
inputs = manifest.get("inputs") or []
|
||||||
|
if not isinstance(inputs, list):
|
||||||
|
raise CannedPromptError("inputs must be a list")
|
||||||
|
for item in inputs:
|
||||||
|
if not isinstance(item, dict) or not isinstance(item.get("name"), str):
|
||||||
|
raise CannedPromptError("each input must be a mapping with a string name")
|
||||||
|
name = item["name"]
|
||||||
|
if name in names:
|
||||||
|
raise CannedPromptError(f"duplicate input/parameter name: {name}")
|
||||||
|
names.add(name)
|
||||||
|
|
||||||
|
parameters = manifest.get("parameters") or {}
|
||||||
|
if not isinstance(parameters, dict):
|
||||||
|
raise CannedPromptError("parameters must be a mapping")
|
||||||
|
for name, spec in parameters.items():
|
||||||
|
if not isinstance(name, str) or not isinstance(spec, dict):
|
||||||
|
raise CannedPromptError("parameters must map names to mappings")
|
||||||
|
if name in names:
|
||||||
|
raise CannedPromptError(f"duplicate input/parameter name: {name}")
|
||||||
|
names.add(name)
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def validate_package(package_dir: Path) -> dict[str, Any]:
|
||||||
|
package_dir = package_dir.resolve()
|
||||||
|
if not package_dir.is_dir():
|
||||||
|
raise CannedPromptError(f"package directory not found: {package_dir}")
|
||||||
|
|
||||||
|
manifest = read_manifest(package_dir)
|
||||||
|
missing = [field for field in REQUIRED_FIELDS if field not in manifest]
|
||||||
|
if missing:
|
||||||
|
raise CannedPromptError("missing required fields: " + ", ".join(missing))
|
||||||
|
|
||||||
|
if manifest["format"] != FORMAT:
|
||||||
|
raise CannedPromptError(f"unsupported format: {manifest['format']!r}")
|
||||||
|
|
||||||
|
package_id = manifest["id"]
|
||||||
|
if not isinstance(package_id, str) or not package_id.strip():
|
||||||
|
raise CannedPromptError("id must be a non-empty string")
|
||||||
|
if any(part in ("", ".", "..") for part in package_id.split("/")):
|
||||||
|
raise CannedPromptError("id contains an invalid path segment")
|
||||||
|
if re.search(r"[^A-Za-z0-9._/-]", package_id):
|
||||||
|
raise CannedPromptError("id contains unsupported characters")
|
||||||
|
|
||||||
|
version = manifest["version"]
|
||||||
|
if not isinstance(version, str) or not SEMVER_RE.match(version):
|
||||||
|
raise CannedPromptError("version must be semantic-version-like, e.g. 1.2.0")
|
||||||
|
|
||||||
|
template_path = safe_relative_file(package_dir, manifest["template"], "template")
|
||||||
|
|
||||||
|
for field in ("examples", "evals"):
|
||||||
|
refs = manifest.get(field) or []
|
||||||
|
if not isinstance(refs, list):
|
||||||
|
raise CannedPromptError(f"{field} must be a list")
|
||||||
|
for relative in refs:
|
||||||
|
safe_relative_file(package_dir, relative, field)
|
||||||
|
|
||||||
|
names = declared_names(manifest)
|
||||||
|
template = template_path.read_text(encoding="utf-8")
|
||||||
|
placeholders = set(PLACEHOLDER_RE.findall(template))
|
||||||
|
undeclared = sorted(placeholders - names)
|
||||||
|
if undeclared:
|
||||||
|
raise CannedPromptError(
|
||||||
|
"template contains undeclared placeholders: " + ", ".join(undeclared)
|
||||||
|
)
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
def id_path(store: Path, package_id: str) -> Path:
|
||||||
|
parts = package_id.split("/")
|
||||||
|
if any(part in ("", ".", "..") for part in parts):
|
||||||
|
raise CannedPromptError("unsafe package id")
|
||||||
|
return store.joinpath(*parts)
|
||||||
|
|
||||||
|
|
||||||
|
def package_path(store: Path, package_id: str, version: str) -> Path:
|
||||||
|
return id_path(store, package_id) / version
|
||||||
|
|
||||||
|
|
||||||
|
def parse_semver(value: str) -> tuple[int, int, int, str]:
|
||||||
|
match = SEMVER_RE.match(value)
|
||||||
|
if not match:
|
||||||
|
return (-1, -1, -1, value)
|
||||||
|
return (int(match.group(1)), int(match.group(2)), int(match.group(3)), value)
|
||||||
|
|
||||||
|
|
||||||
|
def versions_for(store: Path, package_id: str) -> list[str]:
|
||||||
|
base = id_path(store, package_id)
|
||||||
|
if not base.is_dir():
|
||||||
|
return []
|
||||||
|
versions = [p.name for p in base.iterdir() if p.is_dir() and (p / "prompt.yaml").is_file()]
|
||||||
|
return sorted(versions, key=parse_semver, reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_installed(store: Path, package_id: str, version: str | None) -> Path:
|
||||||
|
if version:
|
||||||
|
path = package_path(store, package_id, version)
|
||||||
|
if not path.is_dir():
|
||||||
|
raise CannedPromptError(f"package not found: {package_id}@{version}")
|
||||||
|
return path
|
||||||
|
versions = versions_for(store, package_id)
|
||||||
|
if not versions:
|
||||||
|
raise CannedPromptError(f"package not found: {package_id}")
|
||||||
|
return package_path(store, package_id, versions[0])
|
||||||
|
|
||||||
|
|
||||||
|
def copy_immutable(src: Path, dst: Path, what: str) -> None:
|
||||||
|
if dst.exists():
|
||||||
|
raise CannedPromptError(f"{what} already exists: {dst}")
|
||||||
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copytree(src, dst)
|
||||||
|
|
||||||
|
|
||||||
|
def iter_packages(store: Path) -> Iterable[tuple[Path, dict[str, Any]]]:
|
||||||
|
if not store.exists():
|
||||||
|
return
|
||||||
|
for manifest_path in store.rglob("prompt.yaml"):
|
||||||
|
package_dir = manifest_path.parent
|
||||||
|
try:
|
||||||
|
manifest = validate_package(package_dir)
|
||||||
|
except CannedPromptError:
|
||||||
|
continue
|
||||||
|
yield package_dir, manifest
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_add(args: argparse.Namespace) -> None:
|
||||||
|
src = Path(args.path)
|
||||||
|
manifest = validate_package(src)
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
dst = package_path(catalog, manifest["id"], manifest["version"])
|
||||||
|
copy_immutable(src.resolve(), dst, "catalog package")
|
||||||
|
print(f"added {manifest['id']}@{manifest['version']} -> {dst}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_publish(args: argparse.Namespace) -> None:
|
||||||
|
src = Path(args.path)
|
||||||
|
manifest = validate_package(src)
|
||||||
|
registry = Path(args.registry).expanduser()
|
||||||
|
dst = package_path(registry, manifest["id"], manifest["version"])
|
||||||
|
copy_immutable(src.resolve(), dst, "published package")
|
||||||
|
print(f"published {manifest['id']}@{manifest['version']} -> {dst}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_install(args: argparse.Namespace) -> None:
|
||||||
|
registry = Path(args.registry).expanduser()
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
src = resolve_installed(registry, args.id, args.version)
|
||||||
|
manifest = validate_package(src)
|
||||||
|
dst = package_path(catalog, manifest["id"], manifest["version"])
|
||||||
|
copy_immutable(src, dst, "catalog package")
|
||||||
|
print(f"installed {manifest['id']}@{manifest['version']} -> {dst}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_search(args: argparse.Namespace) -> None:
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
query = args.query.lower()
|
||||||
|
matches: list[dict[str, str]] = []
|
||||||
|
for _, manifest in iter_packages(catalog):
|
||||||
|
haystack = " ".join(
|
||||||
|
[
|
||||||
|
str(manifest.get("id", "")),
|
||||||
|
str(manifest.get("name", "")),
|
||||||
|
str(manifest.get("summary", "")),
|
||||||
|
" ".join(str(tag) for tag in (manifest.get("tags") or [])),
|
||||||
|
]
|
||||||
|
).lower()
|
||||||
|
if query in haystack:
|
||||||
|
matches.append(
|
||||||
|
{
|
||||||
|
"id": manifest["id"],
|
||||||
|
"version": manifest["version"],
|
||||||
|
"name": manifest["name"],
|
||||||
|
"summary": manifest["summary"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
matches.sort(key=lambda m: (m["id"], parse_semver(m["version"])), reverse=False)
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(matches, indent=2, ensure_ascii=False))
|
||||||
|
return
|
||||||
|
if not matches:
|
||||||
|
print("no matches")
|
||||||
|
return
|
||||||
|
for item in matches:
|
||||||
|
print(f"{item['id']}@{item['version']} {item['name']}")
|
||||||
|
print(f" {item['summary']}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_show(args: argparse.Namespace) -> None:
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
package_dir = resolve_installed(catalog, args.id, args.version)
|
||||||
|
manifest = validate_package(package_dir)
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(manifest, indent=2, ensure_ascii=False))
|
||||||
|
else:
|
||||||
|
print(yaml.safe_dump(manifest, sort_keys=False, allow_unicode=True).rstrip())
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_value(raw: str, spec: dict[str, Any]) -> Any:
|
||||||
|
kind = spec.get("type", "string")
|
||||||
|
if kind == "boolean":
|
||||||
|
lowered = raw.lower()
|
||||||
|
if lowered in {"true", "1", "yes", "on"}:
|
||||||
|
return True
|
||||||
|
if lowered in {"false", "0", "no", "off"}:
|
||||||
|
return False
|
||||||
|
raise CannedPromptError(f"cannot parse boolean value: {raw}")
|
||||||
|
if kind == "integer":
|
||||||
|
try:
|
||||||
|
return int(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise CannedPromptError(f"cannot parse integer value: {raw}") from exc
|
||||||
|
if kind == "number":
|
||||||
|
try:
|
||||||
|
return float(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise CannedPromptError(f"cannot parse numeric value: {raw}") from exc
|
||||||
|
if kind == "enum":
|
||||||
|
values = spec.get("values") or []
|
||||||
|
if raw not in values:
|
||||||
|
raise CannedPromptError(f"invalid enum value {raw!r}; expected one of {values}")
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
def supplied_values(pairs: list[str]) -> dict[str, str]:
|
||||||
|
values: dict[str, str] = {}
|
||||||
|
for pair in pairs:
|
||||||
|
if "=" not in pair:
|
||||||
|
raise CannedPromptError(f"--set expects name=value, got: {pair}")
|
||||||
|
name, value = pair.split("=", 1)
|
||||||
|
if not name:
|
||||||
|
raise CannedPromptError("--set name cannot be empty")
|
||||||
|
values[name] = value
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_values(manifest: dict[str, Any], raw_values: dict[str, str]) -> dict[str, Any]:
|
||||||
|
resolved: dict[str, Any] = {}
|
||||||
|
known: set[str] = set()
|
||||||
|
|
||||||
|
inputs = manifest.get("inputs") or []
|
||||||
|
for item in inputs:
|
||||||
|
name = item["name"]
|
||||||
|
known.add(name)
|
||||||
|
if name in raw_values:
|
||||||
|
resolved[name] = raw_values[name]
|
||||||
|
elif item.get("required", False):
|
||||||
|
raise CannedPromptError(f"missing required input: {name}")
|
||||||
|
|
||||||
|
parameters = manifest.get("parameters") or {}
|
||||||
|
for name, spec in parameters.items():
|
||||||
|
known.add(name)
|
||||||
|
if name in raw_values:
|
||||||
|
resolved[name] = coerce_value(raw_values[name], spec)
|
||||||
|
elif "default" in spec:
|
||||||
|
resolved[name] = spec["default"]
|
||||||
|
|
||||||
|
unknown = sorted(set(raw_values) - known)
|
||||||
|
if unknown:
|
||||||
|
raise CannedPromptError("unknown values: " + ", ".join(unknown))
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
def render_template(template: str, values: dict[str, Any]) -> str:
|
||||||
|
def replace(match: re.Match[str]) -> str:
|
||||||
|
name = match.group(1)
|
||||||
|
if name not in values:
|
||||||
|
raise CannedPromptError(f"unresolved placeholder: {name}")
|
||||||
|
value = values[name]
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return "true" if value else "false"
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
return json.dumps(value, ensure_ascii=False)
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
return PLACEHOLDER_RE.sub(replace, template)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_render(args: argparse.Namespace) -> None:
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
package_dir = resolve_installed(catalog, args.id, args.version)
|
||||||
|
manifest = validate_package(package_dir)
|
||||||
|
template_path = safe_relative_file(package_dir, manifest["template"], "template")
|
||||||
|
raw = supplied_values(args.set_values)
|
||||||
|
values = resolve_values(manifest, raw)
|
||||||
|
rendered = render_template(template_path.read_text(encoding="utf-8"), values)
|
||||||
|
print(rendered, end="" if rendered.endswith("\n") else "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(prog="canned-prompts", description=__doc__)
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
add = sub.add_parser("add", help="add a package directory to the local catalog")
|
||||||
|
add.add_argument("path")
|
||||||
|
add.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
add.set_defaults(func=cmd_add)
|
||||||
|
|
||||||
|
search = sub.add_parser("search", help="search the local catalog")
|
||||||
|
search.add_argument("query")
|
||||||
|
search.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
search.add_argument("--json", action="store_true")
|
||||||
|
search.set_defaults(func=cmd_search)
|
||||||
|
|
||||||
|
show = sub.add_parser("show", help="show an installed package manifest")
|
||||||
|
show.add_argument("id")
|
||||||
|
show.add_argument("--version")
|
||||||
|
show.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
show.add_argument("--json", action="store_true")
|
||||||
|
show.set_defaults(func=cmd_show)
|
||||||
|
|
||||||
|
render = sub.add_parser("render", help="render an installed prompt template")
|
||||||
|
render.add_argument("id")
|
||||||
|
render.add_argument("--version")
|
||||||
|
render.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
render.add_argument("--set", dest="set_values", action="append", default=[], metavar="NAME=VALUE")
|
||||||
|
render.set_defaults(func=cmd_render)
|
||||||
|
|
||||||
|
install = sub.add_parser("install", help="install a package from a filesystem registry")
|
||||||
|
install.add_argument("id")
|
||||||
|
install.add_argument("--version")
|
||||||
|
install.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
install.add_argument("--registry", default=str(default_registry()))
|
||||||
|
install.set_defaults(func=cmd_install)
|
||||||
|
|
||||||
|
publish = sub.add_parser("publish", help="publish a package to a filesystem registry")
|
||||||
|
publish.add_argument("path")
|
||||||
|
publish.add_argument("--registry", default=str(default_registry()))
|
||||||
|
publish.set_defaults(func=cmd_publish)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
try:
|
||||||
|
args.func(args)
|
||||||
|
return 0
|
||||||
|
except CannedPromptError as exc:
|
||||||
|
print(f"error: {exc}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
16
reference/pyproject.toml
Normal file
16
reference/pyproject.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "canned-prompts-reference"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Tiny filesystem reference CLI for Canned Prompt Format v0.1"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = ["PyYAML>=6.0,<7"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
canned-prompts = "canned_prompts:main"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
py-modules = ["canned_prompts"]
|
||||||
2
reference/requirements-dev.txt
Normal file
2
reference/requirements-dev.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
-r requirements.txt
|
||||||
|
pytest>=8,<9
|
||||||
1
reference/requirements.txt
Normal file
1
reference/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
PyYAML>=6.0,<7
|
||||||
53
reference/tests/test_canned_prompts.py
Normal file
53
reference/tests/test_canned_prompts.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import canned_prompts as cp
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def package(tmp_path: Path) -> Path:
|
||||||
|
pkg = tmp_path / "pkg"
|
||||||
|
pkg.mkdir()
|
||||||
|
(pkg / "prompt.yaml").write_text(
|
||||||
|
"""\
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: demo/hello
|
||||||
|
name: Hello
|
||||||
|
version: 1.0.0
|
||||||
|
summary: Say hello.
|
||||||
|
template: prompt.md
|
||||||
|
inputs:
|
||||||
|
- name: person
|
||||||
|
required: true
|
||||||
|
parameters:
|
||||||
|
tone:
|
||||||
|
type: enum
|
||||||
|
values: [warm, formal]
|
||||||
|
default: warm
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(pkg / "prompt.md").write_text(
|
||||||
|
"Say hello to {{ person }} in a {{ tone }} tone.\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
return pkg
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_and_render(package: Path) -> None:
|
||||||
|
manifest = cp.validate_package(package)
|
||||||
|
values = cp.resolve_values(manifest, {"person": "Ada"})
|
||||||
|
rendered = cp.render_template((package / "prompt.md").read_text(), values)
|
||||||
|
assert rendered == "Say hello to Ada in a warm tone.\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_required_input_fails(package: Path) -> None:
|
||||||
|
manifest = cp.validate_package(package)
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="missing required input"):
|
||||||
|
cp.resolve_values(manifest, {})
|
||||||
|
|
||||||
|
|
||||||
|
def test_undeclared_placeholder_fails(package: Path) -> None:
|
||||||
|
(package / "prompt.md").write_text("{{ missing }}\n", encoding="utf-8")
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="undeclared placeholders"):
|
||||||
|
cp.validate_package(package)
|
||||||
57
workplans/CANP-WP-0001-statehub-bootstrap.md
Normal file
57
workplans/CANP-WP-0001-statehub-bootstrap.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
id: CANP-WP-0001
|
||||||
|
type: workplan
|
||||||
|
title: "Bootstrap State Hub integration"
|
||||||
|
domain: agents
|
||||||
|
repo: canned-prompts
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: practice
|
||||||
|
created: "2026-09-06"
|
||||||
|
updated: "2026-09-06"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bootstrap State Hub integration
|
||||||
|
|
||||||
|
Portable package format, spec and reference CLI for reusable prompt artifacts (Canned Prompt Format v0.1).
|
||||||
|
|
||||||
|
## Review Generated Integration Files
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0001-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Review `INTENT.md`, `SCOPE.md`, `AGENTS.md`, and `.custodian-brief.md`.
|
||||||
|
Replace generated placeholders with repo-specific facts where needed.
|
||||||
|
|
||||||
|
## Verify Local Developer Workflow
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0001-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Identify the repo's install, test, lint, build, and run commands. Add or refine
|
||||||
|
those commands in the agent instructions so future coding sessions can verify
|
||||||
|
changes confidently.
|
||||||
|
|
||||||
|
## Seed First Real Workplan
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0001-T03
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Done: `workplans/CANP-WP-0002-format-open-questions.md`.
|
||||||
|
|
||||||
|
Create the first implementation workplan for the repository's most important
|
||||||
|
next change. After workplan file updates, run the sync locally from this repo
|
||||||
|
checkout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
statehub fix-consistency
|
||||||
|
```
|
||||||
183
workplans/CANP-WP-0002-format-open-questions.md
Normal file
183
workplans/CANP-WP-0002-format-open-questions.md
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
---
|
||||||
|
id: CANP-WP-0002
|
||||||
|
type: workplan
|
||||||
|
title: "Resolve CPF v0.1 open questions promoted for v0.2"
|
||||||
|
domain: agents
|
||||||
|
repo: canned-prompts
|
||||||
|
status: ready
|
||||||
|
owner: codex
|
||||||
|
topic_slug: practice
|
||||||
|
created: "2026-09-06"
|
||||||
|
updated: "2026-09-06"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Resolve CPF v0.1 open questions promoted for v0.2
|
||||||
|
|
||||||
|
`CannedPromptFormat-v0.1.md` § 23 currently lists twelve items as "experience
|
||||||
|
should determine". A review of the seed on 2026-09-06 (spec + reference CLI +
|
||||||
|
`examples/pqrst-estimate`; 3/3 tests pass, full local lifecycle smokes clean)
|
||||||
|
plus an operator interview promoted five of them from "wait and see" to
|
||||||
|
"decide, with a stated leaning". This workplan carries those decisions.
|
||||||
|
|
||||||
|
Unpromoted § 23 items stay deferred and unchanged: content macros, cryptographic
|
||||||
|
integrity/signing, model capability vocabularies, run manifests and evidence
|
||||||
|
formats, deterministic compilation manifests, trust/reputation signals,
|
||||||
|
federated discovery, richer template syntax.
|
||||||
|
|
||||||
|
**Guardrail for every task below:** `INTENT.md` § Deliberate boundary. The
|
||||||
|
format may *declare* a requirement; it must not specify the resolver, runtime,
|
||||||
|
or execution engine that satisfies it.
|
||||||
|
|
||||||
|
## Optional inputs need defaults
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T01
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gap found in review.** Rendering rule § 5.1(4) makes any unresolved
|
||||||
|
placeholder an error, and § 6 gives `inputs` no `default` field. An input
|
||||||
|
declared `required: false` and referenced from the template therefore makes
|
||||||
|
`render` fail unless a value is supplied — optional inputs are effectively
|
||||||
|
unusable. The spec's own § 4 manifest example hits this with
|
||||||
|
`repository_context`. `reference/canned_prompts.py` is conformant here; the
|
||||||
|
defect is in the spec.
|
||||||
|
|
||||||
|
**Decision (operator, 2026-09-06):** add a `default` field to `inputs`, and
|
||||||
|
allow that default to be either
|
||||||
|
|
||||||
|
- a **static** value, or
|
||||||
|
- a **derived** default — a prompt that produces the value from available
|
||||||
|
context.
|
||||||
|
|
||||||
|
Unresolved-with-no-default remains an error.
|
||||||
|
|
||||||
|
Work:
|
||||||
|
|
||||||
|
1. Extend § 6 with `default`, and state the two default kinds.
|
||||||
|
2. Specify the derived-default declaration form so it stays inert data: the
|
||||||
|
package declares *what* to derive and the prompt to derive it with; it never
|
||||||
|
names or requires a specific resolver, model, or runtime. A consumer that
|
||||||
|
cannot derive must report the input as unresolved rather than guess.
|
||||||
|
3. Reconcile § 5.1: rules 1–2 gain input defaults; rule 4 keeps unresolved as
|
||||||
|
an error; § 5.1's "no conditionals, loops, filters, or functions" and
|
||||||
|
§ 19's "MUST NOT execute code merely because it appears in a package" must
|
||||||
|
survive the change — a derived default is a *request to a consumer*, not
|
||||||
|
template-embedded execution. Say so explicitly.
|
||||||
|
4. Extend § 18 validation accordingly.
|
||||||
|
5. Implement static defaults in `reference/canned_prompts.py` `resolve_values`
|
||||||
|
and add tests. Derived defaults: validate and surface them; the reference
|
||||||
|
CLI never calls a model, so it reports them as unresolved-by-design.
|
||||||
|
|
||||||
|
## Registry namespaces and ownership
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T02
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
An id like `practice/pqrst-estimate` has a namespace prefix with no owner. Two
|
||||||
|
authors publishing `practice/…` into one registry collide today; `add` and
|
||||||
|
`publish` only refuse an exact `id@version` that already exists.
|
||||||
|
|
||||||
|
Decide, for § 3.2 and § 20: what a namespace means, who may claim one, how a
|
||||||
|
filesystem registry records the claim, and what a consumer does on conflict.
|
||||||
|
Leaning: keep v0.1's local-first stance — namespace ownership is a *registry
|
||||||
|
policy*, declared by the registry rather than by the package — so package
|
||||||
|
semantics do not change when a hosted registry appears later.
|
||||||
|
Signing and trust scoring stay out of scope (§ 23, INTENT non-goals).
|
||||||
|
|
||||||
|
## Prompt composition and inheritance
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T03
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Largest gap between `INTENT.md` and the spec. Principle 9 ("composition without
|
||||||
|
capture") and the `dependencies.prompts` field both promise composition, but
|
||||||
|
v0.1 defines no mechanism — `dependencies` is a declared field with no
|
||||||
|
semantics.
|
||||||
|
|
||||||
|
Decide what composition means at the *artifact* level: how one package
|
||||||
|
references another, whether references are includes, extends, or plain
|
||||||
|
declared prerequisites, and how versions are pinned. Leaning: declaration only
|
||||||
|
— a package states what it needs, and resolution stays with the consumer, per
|
||||||
|
the INTENT boundary. Do not introduce range resolution (INTENT non-goal).
|
||||||
|
|
||||||
|
## Canonical eval schemas
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T04
|
||||||
|
status: todo
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
Cheapest item to pin down. `evals/` is a reserved path and `evals:` is a
|
||||||
|
manifest list, but § 12 defines no schema, so an eval file is an unvalidated
|
||||||
|
blob that no tool can act on.
|
||||||
|
|
||||||
|
Define a minimal eval-spec schema: identity, what is being asserted, the
|
||||||
|
fixture it runs against, and how a result is reported. Keep it declarative and
|
||||||
|
engine-neutral — "universal prompt evaluation" is an explicit INTENT non-goal,
|
||||||
|
so this specifies the *file*, not an evaluation engine. Extend § 18 to validate
|
||||||
|
eval files that declare the schema, and add one eval to
|
||||||
|
`examples/pqrst-estimate` as a worked case.
|
||||||
|
|
||||||
|
## Typed context and dependency contracts
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T05
|
||||||
|
status: todo
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
`dependencies.context` and `dependencies.capabilities` appear in the § 4
|
||||||
|
manifest surface with no semantics whatsoever in v0.1, and § 9
|
||||||
|
`compatibility.capabilities` overlaps them without a stated relationship.
|
||||||
|
|
||||||
|
Decide: what a context dependency declares, how it differs from an input, how
|
||||||
|
it relates to `compatibility.capabilities`, and whether capability names are
|
||||||
|
free strings in v0.2 (model capability vocabularies stay deferred). Coordinate
|
||||||
|
with T01 — a derived default is a consumer-resolved context requirement, and
|
||||||
|
the two mechanisms must not describe the same thing twice.
|
||||||
|
|
||||||
|
## Rewrite specification section 23
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T06
|
||||||
|
status: todo
|
||||||
|
priority: medium
|
||||||
|
```
|
||||||
|
|
||||||
|
After T01–T05 land, replace § 23's flat twelve-item list with two sections:
|
||||||
|
questions **being decided for v0.2** (each with its scoped question and stated
|
||||||
|
leaning, referencing this workplan) and questions **still deferred** (the eight
|
||||||
|
unpromoted items listed at the top of this file). Bump the spec status line if
|
||||||
|
the format revision warrants it.
|
||||||
|
|
||||||
|
## Reference implementation conformance fixes
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: CANP-WP-0002-T07
|
||||||
|
status: todo
|
||||||
|
priority: low
|
||||||
|
```
|
||||||
|
|
||||||
|
Two defects found in the same review, independent of the format questions:
|
||||||
|
|
||||||
|
1. **Prerelease versions sort as newest.** `parse_semver` in
|
||||||
|
`reference/canned_prompts.py` returns `(major, minor, patch, raw_string)`,
|
||||||
|
so `1.0.0-rc1` and `1.0.0` tie on the numeric fields and then compare as
|
||||||
|
strings — `"1.0.0-rc1" > "1.0.0"`. `resolve_installed` with no `--version`
|
||||||
|
therefore selects a prerelease over its own release. Order prerelease below
|
||||||
|
release, or state in § 17 that v0.1 ignores prerelease ordering.
|
||||||
|
2. **`copy_immutable` copies everything.** `add`/`publish` use
|
||||||
|
`shutil.copytree` over the whole source directory, so a stray `.git`,
|
||||||
|
`.venv`, or scratch file lands in the catalog and registry. § 2 says tools
|
||||||
|
MUST ignore unknown non-reserved files unless a manifest field references
|
||||||
|
them. Decide whether packaging is reserved-paths-only or an explicit
|
||||||
|
ignore list, then align the implementation and § 2.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue