docs(prd-0011): drop the migration command requirement
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 23s

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.
This commit is contained in:
2026-05-24 21:46:22 -04:00
parent 894bdea288
commit afa8ca67a4
+19 -53
View File
@@ -87,21 +87,13 @@ Each test runs against a temporary `$HOME` and a temporary `$CWD`:
parser test. Frontmatter parsing is hand-rolled against the parser test. Frontmatter parsing is hand-rolled against the
declared YAML subset. declared YAML subset.
6. **Migration tool converts existing JSON to per-file MD.** 6. **Existing tests pass against the new layout.** Tests today
`./cli.py migrate-manifest` reads `$HOME/claude-bottle.json`
(and `$CWD/claude-bottle.json` if present), writes a tree of
per-file MD docs to the new locations, then prints what was
moved. Idempotent: rerunning is a no-op when the new layout
already exists. Does not delete the old JSON files
automatically (user-driven cleanup).
7. **Existing tests pass against the new layout.** Tests today
build manifests via JSON literals against `Manifest.from_json_obj`. build manifests via JSON literals against `Manifest.from_json_obj`.
That entry point keeps working for tests (used to construct That entry point keeps working for tests (used to construct
manifests programmatically); production resolution flows manifests programmatically); production resolution flows
through the new directory-globbing loader. through the new directory-globbing loader.
8. **Agent files double as Claude Code subagent files.** The 7. **Agent files double as Claude Code subagent files.** The
`name`, `description`, `model`, `color`, and `memory` fields `name`, `description`, `model`, `color`, and `memory` fields
from Claude Code's existing subagent spec are accepted in from Claude Code's existing subagent spec are accepted in
our frontmatter alongside our own fields. Copying an agent our frontmatter alongside our own fields. Copying an agent
@@ -119,11 +111,11 @@ Each test runs against a temporary `$HOME` and a temporary `$CWD`:
pointer at the spec. We are not building a YAML library. pointer at the spec. We are not building a YAML library.
- **Compatibility with the old JSON layout at runtime.** The - **Compatibility with the old JSON layout at runtime.** The
resolver no longer reads `claude-bottle.json` files. The resolver no longer reads `claude-bottle.json` files. This is
migration tool is the bridge; after migration the JSON file a breaking change; existing users hand-rewrite their JSON
is stale (and the user removes it). This is a breaking into the new per-file layout (claude-bottle has a single
change for v1 users; the migration cost is one command + a primary user today, so the migration is one person rewriting
manual delete. one file). Documented as part of the README rewrite.
- **`$HOME/.claude/agents/` integration on the input side.** We - **`$HOME/.claude/agents/` integration on the input side.** We
don't read agent files out of Claude Code's directory. Our don't read agent files out of Claude Code's directory. Our
@@ -208,25 +200,6 @@ Each test runs against a temporary `$HOME` and a temporary `$CWD`:
5. Warn if `$CWD/.claude-bottle/bottles/` exists with files. 5. Warn if `$CWD/.claude-bottle/bottles/` exists with files.
6. Return Manifest dataclass — same shape as today. 6. Return Manifest dataclass — same shape as today.
- **Migration command.** `./cli.py migrate-manifest`:
- Reads `$HOME/claude-bottle.json` and (if present)
`$CWD/claude-bottle.json`.
- Creates `$HOME/.claude-bottle/{bottles,agents}/` dirs.
- For each `bottles[<name>]`, writes
`$HOME/.claude-bottle/bottles/<name>.md` with frontmatter
rendered from the bottle dict, body empty (or a one-line
"Migrated from claude-bottle.json on <date>" stub).
- For each home `agents[<name>]`, writes
`$HOME/.claude-bottle/agents/<name>.md` with frontmatter
(bottle, skills, etc.) and body = `prompt`.
- For each cwd `agents[<name>]` (if cwd JSON existed),
writes `$CWD/.claude-bottle/agents/<name>.md`.
- Refuses to overwrite existing MD files; if a target
already exists, prints what would have been written and
bails on that file (continues with the others).
- Prints a summary at the end: N bottles written, M agents
written, what was skipped.
- **Docs.** README's manifest section rewrites against the new - **Docs.** README's manifest section rewrites against the new
layout. `claude-bottle.example.json` becomes layout. `claude-bottle.example.json` becomes
`examples/bottles/dev.md` + `examples/agents/implementer.md`. `examples/bottles/dev.md` + `examples/agents/implementer.md`.
@@ -238,9 +211,7 @@ Each test runs against a temporary `$HOME` and a temporary `$CWD`:
- `tests/unit/test_yaml_subset_parser.py` — the parser - `tests/unit/test_yaml_subset_parser.py` — the parser
itself, including all the rejection cases listed above. itself, including all the rejection cases listed above.
- `tests/unit/test_manifest_md_load.py` — directory-globbing - `tests/unit/test_manifest_md_load.py` — directory-globbing
+ assembly, the 8 success criteria. + assembly, the seven success criteria.
- `tests/integration/test_migrate_manifest.py` — round-trip
JSON → MD; idempotency.
- Existing integration tests keep working (the only public - Existing integration tests keep working (the only public
entry points they hit are `Manifest.resolve` and entry points they hit are `Manifest.resolve` and
`Manifest.from_json_obj`). `Manifest.from_json_obj`).
@@ -248,7 +219,9 @@ Each test runs against a temporary `$HOME` and a temporary `$CWD`:
### Out of scope ### Out of scope
- Watching the directory for changes mid-session. - Watching the directory for changes mid-session.
- A migration tool for moving back (MD → JSON). - An automated migration command. Existing JSON users
hand-rewrite into the new layout. The README rewrite
documents the new shape; that's the migration surface.
- Validating that frontmatter `name:` matches the filename. - Validating that frontmatter `name:` matches the filename.
Soft check via a warn log if mismatched, but not enforced. Soft check via a warn log if mismatched, but not enforced.
- A bottle/agent dependency graph beyond the existing `bottle:` - A bottle/agent dependency graph beyond the existing `bottle:`
@@ -378,9 +351,6 @@ def parse_frontmatter(text: str) -> tuple[dict[str, object], str]:
a programmatic entry point (used by tests). New a programmatic entry point (used by tests). New
`Manifest.from_md_dirs(home_dir, cwd_dir)` for the loader. `Manifest.from_md_dirs(home_dir, cwd_dir)` for the loader.
- **`claude_bottle/yaml_subset.py`** — new. The parser. - **`claude_bottle/yaml_subset.py`** — new. The parser.
- **`claude_bottle/cli/migrate_manifest.py`** — new. The
migration command.
- **`claude_bottle/cli/__init__.py`** — wire the new subcommand.
- **`README.md`** — manifest section rewritten against the new - **`README.md`** — manifest section rewritten against the new
layout. layout.
- **`claude-bottle.example.json`** — removed; replaced by an - **`claude-bottle.example.json`** — removed; replaced by an
@@ -396,14 +366,15 @@ etc. all stay the same shape. Only the loader changes.
### Backward compatibility ### Backward compatibility
This is a breaking change for v1 users. Mitigations: This is a breaking change for v1 users. claude-bottle has a
single primary user today, so migration is one person rewriting
one file — no automated migration command is in scope.
- `./cli.py migrate-manifest` does the heavy lifting in one If `claude-bottle.json` exists in `$HOME` or `$CWD` *and* the
command. new `.claude-bottle/` directory does not exist, the resolver
- If `claude-bottle.json` exists in `$HOME` or `$CWD` *and* the dies with a clear pointer at the README's manifest section —
new directories don't exist, the resolver dies with a clear not silently merging formats, not silently dropping the JSON
pointer at the migration command — not silently merging content.
formats, not silently dropping the JSON content.
## Open questions ## Open questions
@@ -427,11 +398,6 @@ This is a breaking change for v1 users. Mitigations:
pattern will be "every bottle allows api.anthropic.com and pattern will be "every bottle allows api.anthropic.com and
github.com"; do we want a way to share the list? Default no github.com"; do we want a way to share the list? Default no
for v1; revisit if it bites. for v1; revisit if it bites.
- **Migration tool destructive vs additive.** Default
additive (writes new files, leaves old JSON in place). If
users find the half-migrated state confusing, switch to
printing a "delete claude-bottle.json now" reminder at the
end of the migration.
## References ## References