PRD 0011: Per-file Markdown manifest #17
Reference in New Issue
Block a user
Delete Branch "md-manifest"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Specs the implementation chosen in the PR #16 closing comment: per-file MD-with-YAML-frontmatter layout for both bottles and agents, with a hand-rolled YAML subset parser (no PyYAML dependency).
Layout:
$HOME/.claude-bottle/bottles/<name>.md(home-only by filesystem layout, not by resolver check),$HOME/.claude-bottle/agents/<name>.md(home-resident agents),$CWD/.claude-bottle/agents/<name>.md(repo-supplied agents that reference home-defined bottles). The trust boundary that PRD-0011-v1 (closed PR #15) tried to enforce in code now falls out of filesystem layout —$CWD/.claude-bottle/has nobottles/subdir, the loader doesn't look there.Eight success criteria including: stdlib-only (no PyYAML), idempotent
./cli.py migrate-manifestfor existing JSON users, agent files shaped close enough to Claude Code's existing subagent spec that the same file can drop into~/.claude/agents/and be picked up as a Claude Code subagent.The YAML subset parser is bounded by what claude-bottle's frontmatter actually uses: flat keys → strings / ints / bools / lists / one-level-nested dicts. No anchors, no multi-line block scalars, no tags, no implicit type coercion (so the Norway problem and friends don't bite). Documents outside the subset die with a clear pointer at the spec; we are not building a YAML library.
PRD-only PR; no implementation in this commit. PRD slot 0011 is intentionally reused — the v1 file (PR #15) was never merged to main, so the number is free.
Companion:
docs/research/manifest-format-and-grouping.md(the analysis that led to this design).claude_bottle/yaml_subset.py — stdlib-only, ~450 lines. Parses the bounded shape claude-bottle's manifest files use: - Block mappings (top-level + nested via indentation) - Block lists (under a key, items can be scalars or block-style mappings whose keys align with the rest after the dash) - Inline lists `[a, b]` and inline dicts `{a: 1}` for one-level leaves - Quoted (single + double) and bare strings - Scalars: string, int, true/false, null/~ Rejects, each with a clear pointer at the line number: - `yes`/`no`/`on`/`off`/`Y`/`N`/`TRUE`/`FALSE` — only literal `true` / `false` are bools (the Norway problem stays solved by "quote your strings if they look like bools") - Bare strings that look like dates / octals / hex / floats - Anchors (`&`/`*`), aliases, YAML tags (`!!str`) - Multi-line block scalars (`|`, `>`) - Tabs in indentation - Nested flow style (only one level allowed) Public API: parse_yaml_subset(text) -> dict[str, object] Top level must be a mapping. parse_frontmatter(text) -> (dict, body_text) Strips `---` delimiters, parses content as YAML subset, returns the verbatim body text after the closing fence. 46 unit tests covering every construct the real manifest files use (the cred_proxy.routes structure, role-as-inline-list, nested ExtraHosts dicts) plus every rejection case listed in PRD 0011.Manifest.resolve walks $HOME/.claude-bottle/{bottles,agents}/ and $CWD/.claude-bottle/agents/ instead of reading claude-bottle.json. A bottles/ subdir under $CWD is logged as a warn and ignored — the filesystem layout IS the trust boundary, no resolver check needed. If claude-bottle.json exists alongside no .claude-bottle/ dir at either location, dies with a clear pointer at the README — the manifest format changed and we don't silently fall back. Manifest.from_md_dirs(home, cwd) is the programmatic entry point tests use to build a Manifest from fixture directories without touching os.environ. Manifest.from_json_obj is preserved for tests that still want to build manifests in-memory. Bottle / agent frontmatter goes through Bottle.from_dict / Agent.from_dict — same validators as today's JSON path. Unknown top-level frontmatter keys die with a "did you mean" pointer listing accepted keys. Filenames that don't match [a-z][a-z0-9-]* are skipped with a warn. Agent files accept the Claude Code subagent passthrough fields (name, description, model, color, memory) so the same file can drop into ~/.claude/agents/ — claude-bottle ignores them at launch but doesn't reject. The dry-run integration test ships a real MD fixture tree now; all 200 unit + 17 integration tests stay green.The "Manifest" section now describes the per-file MD layout under ~/.claude-bottle/{bottles,agents}/, the filename-as-key convention, the YAML subset constraints, and the trust boundary (bottles are home-only by filesystem layout). Includes a working bottle example with comments inside the frontmatter and a working agent example showing the Markdown body as the system prompt. Drops claude-bottle.example.json. The new examples/ tree — examples/bottles/dev.md, examples/agents/implementer.md, examples/agents/researcher.md — verifies the parser end-to-end via Manifest.from_md_dirs(examples/, None).