PRD 0011: Per-file Markdown manifest #17

Merged
didericis merged 5 commits from md-manifest into main 2026-05-24 22:43:44 -04:00
Owner

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 no bottles/ subdir, the loader doesn't look there.

Eight success criteria including: stdlib-only (no PyYAML), idempotent ./cli.py migrate-manifest for 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).

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 no `bottles/` subdir, the loader doesn't look there. Eight success criteria including: stdlib-only (no PyYAML), idempotent `./cli.py migrate-manifest` for 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).
didericis added 1 commit 2026-05-24 21:40:23 -04:00
docs: add PRD 0011 — per-file Markdown manifest
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 22s
894bdea288
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).

Layout:
- $HOME/.claude-bottle/bottles/<name>.md   (home-only)
- $HOME/.claude-bottle/agents/<name>.md    (home agents)
- $CWD/.claude-bottle/agents/<name>.md     (repo-supplied agents)

The trust boundary that PRD-0011-v1 (closed PR #15) tried to
enforce in the resolver now falls out of filesystem layout —
$CWD/.claude-bottle/ has no bottles/ subdir, the loader doesn't
look there. Filesystem layout IS the enforcement.

Eight success criteria, including: stdlib-only (no new runtime
dep), idempotent migration command, agent files shaped close to
Claude Code's existing subagent spec so the same file can drop
into ~/.claude/agents/.

PRD-only; no implementation in this commit. PRD slot 0011 is
intentionally reused — the v1 file was never merged to main.
didericis added 1 commit 2026-05-24 21:46:26 -04:00
docs(prd-0011): drop the migration command requirement
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 23s
afa8ca67a4
claude-bottle has a single primary user today; an automated
JSON → MD migration tool is overkill. Hand-rewriting one file
is the migration cost. The resolver still dies with a pointer
at the README's manifest section if a stale claude-bottle.json
is found alongside no .claude-bottle/ directory, so the breaking
change isn't silent.

Drops: SC #6 (migration tool), the "Migration command" In Scope
sub-bullet, the migrate_manifest.py / cli wiring entries from
Existing code touched, the tests/integration/test_migrate_manifest.py
entry from Tests, the destructive-vs-additive open question.
Renumbers the remaining success criteria 6, 7 (formerly 7, 8).
Backward-compat section rewritten around hand-rewrite.
didericis added 1 commit 2026-05-24 21:59:37 -04:00
feat(yaml_subset): hand-rolled YAML-subset + frontmatter parser
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 25s
8c1e4d0220
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.
didericis added 1 commit 2026-05-24 22:15:04 -04:00
feat(manifest): per-file MD directory loader (PRD 0011)
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 22s
6ba5f9a9d3
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.
didericis added 1 commit 2026-05-24 22:19:46 -04:00
docs: rewrite README manifest section + ship MD examples (PRD 0011)
test / unit (pull_request) Successful in 12s
test / integration (pull_request) Successful in 22s
958a8845a6
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).
didericis merged commit b0581e60d7 into main 2026-05-24 22:43:44 -04:00
Sign in to join this conversation.