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
This commit is contained in:
parent
f55ef13c75
commit
b3280df742
7 changed files with 386 additions and 1 deletions
|
|
@ -808,3 +808,62 @@ def test_dependency_present_but_wrong_version_is_reported(composed: Path) -> Non
|
|||
manifest = cp.validate_package(package_dir)
|
||||
manifest["dependencies"]["prompts"][0]["version"] = "9.9.9"
|
||||
assert cp.missing_dependencies(composed, manifest) == ["style/house@9.9.9"]
|
||||
|
||||
|
||||
# --- catalog index (§ 20.3) ---
|
||||
|
||||
def indexed(tmp_path: Path) -> tuple[Path, dict]:
|
||||
store = tmp_path / "catalog"
|
||||
manifest = {"id": "helix/thing", "version": "1.0.0", "name": "Thing",
|
||||
"license": "MIT", "provenance": {"author": "Ada", "source": "~/somewhere"}}
|
||||
return store, manifest
|
||||
|
||||
|
||||
def test_index_starts_empty(tmp_path: Path) -> None:
|
||||
store, _ = indexed(tmp_path)
|
||||
assert cp.read_index(store)["entries"] == []
|
||||
|
||||
|
||||
def test_record_inclusion_captures_source_and_date(tmp_path: Path) -> None:
|
||||
store, manifest = indexed(tmp_path)
|
||||
entry = cp.record_inclusion(store, "local", manifest, "/src/thing", "add")
|
||||
assert entry["id"] == "helix/thing"
|
||||
assert entry["source"] == "/src/thing"
|
||||
assert entry["method"] == "add"
|
||||
assert entry["declared_author"] == "Ada"
|
||||
assert entry["declared_source"] == "~/somewhere"
|
||||
assert entry["license"] == "MIT"
|
||||
assert entry["included_at"].endswith("Z")
|
||||
assert cp.read_index(store)["entries"] == [entry]
|
||||
|
||||
|
||||
def test_reincluding_keeps_the_original_date(tmp_path: Path) -> None:
|
||||
"""When a package first entered is a fact about history, not about reruns."""
|
||||
store, manifest = indexed(tmp_path)
|
||||
first = cp.record_inclusion(store, "local", manifest, "/src/thing", "add")
|
||||
again = cp.record_inclusion(store, "local", manifest, "/elsewhere", "install")
|
||||
assert again["included_at"] == first["included_at"]
|
||||
assert "last_seen_at" in again
|
||||
assert len(cp.read_index(store)["entries"]) == 1
|
||||
|
||||
|
||||
def test_distinct_versions_are_separate_entries(tmp_path: Path) -> None:
|
||||
store, manifest = indexed(tmp_path)
|
||||
cp.record_inclusion(store, "local", manifest, "/src", "add")
|
||||
cp.record_inclusion(store, "local", {**manifest, "version": "1.1.0"}, "/src", "add")
|
||||
assert len(cp.read_index(store)["entries"]) == 2
|
||||
|
||||
|
||||
def test_same_id_from_two_registries_is_two_entries(tmp_path: Path) -> None:
|
||||
store, manifest = indexed(tmp_path)
|
||||
cp.record_inclusion(store, "local", manifest, "/a", "add")
|
||||
cp.record_inclusion(store, "house", manifest, "/b", "install")
|
||||
assert len(cp.read_index(store)["entries"]) == 2
|
||||
|
||||
|
||||
def test_bad_index_format_is_rejected(tmp_path: Path) -> None:
|
||||
store, _ = indexed(tmp_path)
|
||||
store.mkdir(parents=True)
|
||||
(store / "index.yaml").write_text("format: something/else\nentries: []\n", encoding="utf-8")
|
||||
with pytest.raises(cp.CannedPromptError, match="unsupported index format"):
|
||||
cp.read_index(store)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue