diff --git a/site/src/content/blog/adopt-dont-rebuild.md b/site/src/content/blog/adopt-dont-rebuild.md index c18cf96..e8efb01 100644 --- a/site/src/content/blog/adopt-dont-rebuild.md +++ b/site/src/content/blog/adopt-dont-rebuild.md @@ -1,138 +1,137 @@ ---- -title: "Adopt, don't rebuild" -description: "create-vivary adopt brings Vivary to a repo you already have. Dry-run by default, an explicit write gate, and an invariant we tried hard to break before shipping it: it only ever adds files." -date: 2026-07-10 -author: "Jeff Kazzee" -tags: ["adopt", "release", "trust"] -draft: true ---- - -The single scariest thing a scaffolding tool can do is touch a file you -already had. Not create a new one, not fail loudly, touch one of yours and get -it wrong. That's the failure mode that makes people afraid to run a tool -against a real project instead of a throwaway folder. - -`create-vivary adopt` exists to bring Vivary to a repo, vault, or notes folder -you already have, without becoming that failure mode. The product promise is -one sentence: **adopt only adds files.** It never moves, renames, edits, or -overwrites anything that was already there. If a file it would have created -already exists (`README.md`, `AGENTS.md`, `CLAUDE.md`, anything), it's -skipped and reported "exists, kept." - -A promise like that is worth exactly as much as the testing behind it, so -here's what actually backs it up. - -## Dry-run first, write only on request - -```bash -create-vivary adopt . --json # analyze only, writes nothing -create-vivary adopt . --yes # apply the plan -``` - -Unlike `create-vivary init`, which writes immediately, `adopt` defaults to -dry-run. You get a full plan: what would be created, what already exists and -gets kept, follow-ups (like privacy lines missing from an existing -`.gitignore`), candidate modules it found, and any collisions it's skipping. -Nothing touches disk until you pass `--yes`. That's the write gate, and it's -not a suggestion, it's the only way any file gets written. - -## What "only adds" actually covers - -Three cases had to hold, not just the easy one: - -**Existing top-level files.** Point `adopt` at a repo that already has a -`README.md`. It doesn't append to it, doesn't rewrite it to match Vivary's -template, doesn't touch it at all. It shows up in the plan as "exists, kept." - -**Pre-existing content inside Vivary's own graph folders.** This is the -sneaky one. Say your repo already has a `decisions/` folder with your own -markdown files in it, written before you'd ever heard of Vivary, and `adopt` -wants to treat `decisions/` as a typed graph folder. Those pre-existing files -get excluded from the typed graph by exact path in `tropo.toml`, reported -under `excluded_pre_existing`, so `tropo check` doesn't flag your old notes as -malformed typed documents. They stay exactly as they were; they're just not -graph-typed until you deliberately add frontmatter and remove the exclude -later. - -**Module collisions.** Say a brownfield directory's name collides with a -module the chosen preset already owns: you have a `codebase/` folder, and the -`coding` preset wants to create its own `codebase` module. `adopt` doesn't -create a router for the existing one and doesn't let the preset's starter -module get silently overwritten either. It reports the collision under -`skipped_module_collisions` and moves on. - -## How we tried to break it - -An "only adds" claim is easy to state and easy to get subtly wrong, so the -adversarial pass for this release specifically went after it: hostile -fixtures designed to catch exactly the kind of bug that a promise like this -tends to hide. Read-only files in the target tree. Files at paths that would -collide with generated ones. Before/after byte comparisons on anything adopt -was supposed to leave alone, not just "did it run without crashing." - -That review found real gaps, and they got fixed before merge, not after. That -part matters more to me than the clean version of the story would: the point -of an adversarial pass isn't to confirm everything's fine, it's to find the -places it isn't, while there's still time to fix them. If I only told you "we -tested it and it's great," you'd have no way to tell that from marketing. The -honest version is: we went looking for the ways this breaks, found some, and -closed them before anyone outside the review saw the code. - -Same hardening also handles the obvious hostile inputs: symlinked targets and -paths that resolve outside the workspace root both get refused, the same -protection `init --force` already had. - -The result, per this release's verification: `create-vivary` full suite green -post-merge, `init` byte-parity against 0.2.8 verified across five flag -configurations, and only-adds/dry-run purity verified against those hostile -fixtures. `vivary-tropo` sits at 83 of 83 tests passing on Python 3.11 and -3.14. None of that is a guarantee nothing will ever surprise us. It's the -difference between "should work" and "we tried to break it and here's what we -found." - -## What adopt actually writes - -When you do run `--yes`, here's the shape of what lands: - -- `AGENTS.md`, `STATE.md`, and the rest of the agent-OS shell, only for pieces - that don't already exist. -- A `tropo.toml` with an `exclude` list widened to cover your pre-existing - brownfield content, so adoption doesn't immediately fail its own `tropo - check`. -- Thin `modules//index.md` routers for markdown-heavy directories (5+ - files, no existing `index.md`/`README.md`) it finds during analysis. The - directory itself is never touched, just a router pointing at it. -- A `.gitignore` follow-up if one already exists: `adopt` leaves it alone and - lists the privacy lines it's missing instead of editing it for you. - -An adopted workspace passes `create-vivary doctor` and `tropo check` the same -way a freshly-scaffolded one does, with one honest exception: if your repo -already had a `.gitignore` missing Vivary's privacy lines, `adopt` won't edit -a file you own, so it reports the exact lines you need as a follow-up instead. -Add them and `doctor` passes clean. That was the deliberate bar: adoption -isn't a lesser path, but it also won't silently rewrite your files to hit a -green check. - -## Greenfield or brownfield: the decision is simple - -If the folder is empty or new, you want `init`. If it already has content, you -want `adopt`. `create-vivary` can even make that call for you: point the -[setup prompt](/getting-started/) at an agent and it checks the folder first. -Doing it by hand is one look at whether `ls` returns anything: - -```bash -# Empty or new folder -create-vivary init my-workspace --preset coding - -# Already has content -create-vivary adopt . --json # see the plan -create-vivary adopt . --yes # apply it -``` - -`adopt` also auto-detects a preset if you don't pass one (`coding` for a -code-file majority, `second-brain` for a markdown-file majority), and tells -you which one it picked and why, in both the human and `--json` output. - -Want to see what `adopt` is looking at before it plans anything? That's -[tropo map](/blog/tropo-map-read-the-shape/), the read-only step that comes -before this one. Full flag reference is in [commands](/commands/#adopt--point-vivary-at-your-mess). +--- +title: "Adopt, don't rebuild" +description: "create-vivary adopt brings Vivary to a repo you already have. Dry-run by default, an explicit write gate, and an invariant we tried hard to break before shipping it: it only ever adds files." +date: 2026-07-10 +author: "Jeff Kazzee" +tags: ["adopt", "release", "trust"] +draft: false--- + +The single scariest thing a scaffolding tool can do is touch a file you +already had. Not create a new one, not fail loudly, touch one of yours and get +it wrong. That's the failure mode that makes people afraid to run a tool +against a real project instead of a throwaway folder. + +`create-vivary adopt` exists to bring Vivary to a repo, vault, or notes folder +you already have, without becoming that failure mode. The product promise is +one sentence: **adopt only adds files.** It never moves, renames, edits, or +overwrites anything that was already there. If a file it would have created +already exists (`README.md`, `AGENTS.md`, `CLAUDE.md`, anything), it's +skipped and reported "exists, kept." + +A promise like that is worth exactly as much as the testing behind it, so +here's what actually backs it up. + +## Dry-run first, write only on request + +```bash +create-vivary adopt . --json # analyze only, writes nothing +create-vivary adopt . --yes # apply the plan +``` + +Unlike `create-vivary init`, which writes immediately, `adopt` defaults to +dry-run. You get a full plan: what would be created, what already exists and +gets kept, follow-ups (like privacy lines missing from an existing +`.gitignore`), candidate modules it found, and any collisions it's skipping. +Nothing touches disk until you pass `--yes`. That's the write gate, and it's +not a suggestion, it's the only way any file gets written. + +## What "only adds" actually covers + +Three cases had to hold, not just the easy one: + +**Existing top-level files.** Point `adopt` at a repo that already has a +`README.md`. It doesn't append to it, doesn't rewrite it to match Vivary's +template, doesn't touch it at all. It shows up in the plan as "exists, kept." + +**Pre-existing content inside Vivary's own graph folders.** This is the +sneaky one. Say your repo already has a `decisions/` folder with your own +markdown files in it, written before you'd ever heard of Vivary, and `adopt` +wants to treat `decisions/` as a typed graph folder. Those pre-existing files +get excluded from the typed graph by exact path in `tropo.toml`, reported +under `excluded_pre_existing`, so `tropo check` doesn't flag your old notes as +malformed typed documents. They stay exactly as they were; they're just not +graph-typed until you deliberately add frontmatter and remove the exclude +later. + +**Module collisions.** Say a brownfield directory's name collides with a +module the chosen preset already owns: you have a `codebase/` folder, and the +`coding` preset wants to create its own `codebase` module. `adopt` doesn't +create a router for the existing one and doesn't let the preset's starter +module get silently overwritten either. It reports the collision under +`skipped_module_collisions` and moves on. + +## How we tried to break it + +An "only adds" claim is easy to state and easy to get subtly wrong, so the +adversarial pass for this release specifically went after it: hostile +fixtures designed to catch exactly the kind of bug that a promise like this +tends to hide. Read-only files in the target tree. Files at paths that would +collide with generated ones. Before/after byte comparisons on anything adopt +was supposed to leave alone, not just "did it run without crashing." + +That review found real gaps, and they got fixed before merge, not after. That +part matters more to me than the clean version of the story would: the point +of an adversarial pass isn't to confirm everything's fine, it's to find the +places it isn't, while there's still time to fix them. If I only told you "we +tested it and it's great," you'd have no way to tell that from marketing. The +honest version is: we went looking for the ways this breaks, found some, and +closed them before anyone outside the review saw the code. + +Same hardening also handles the obvious hostile inputs: symlinked targets and +paths that resolve outside the workspace root both get refused, the same +protection `init --force` already had. + +The result, per this release's verification: `create-vivary` full suite green +post-merge, `init` byte-parity against 0.2.8 verified across five flag +configurations, and only-adds/dry-run purity verified against those hostile +fixtures. `vivary-tropo` sits at 83 of 83 tests passing on Python 3.11 and +3.14. None of that is a guarantee nothing will ever surprise us. It's the +difference between "should work" and "we tried to break it and here's what we +found." + +## What adopt actually writes + +When you do run `--yes`, here's the shape of what lands: + +- `AGENTS.md`, `STATE.md`, and the rest of the agent-OS shell, only for pieces + that don't already exist. +- A `tropo.toml` with an `exclude` list widened to cover your pre-existing + brownfield content, so adoption doesn't immediately fail its own `tropo + check`. +- Thin `modules//index.md` routers for markdown-heavy directories (5+ + files, no existing `index.md`/`README.md`) it finds during analysis. The + directory itself is never touched, just a router pointing at it. +- A `.gitignore` follow-up if one already exists: `adopt` leaves it alone and + lists the privacy lines it's missing instead of editing it for you. + +An adopted workspace passes `create-vivary doctor` and `tropo check` the same +way a freshly-scaffolded one does, with one honest exception: if your repo +already had a `.gitignore` missing Vivary's privacy lines, `adopt` won't edit +a file you own, so it reports the exact lines you need as a follow-up instead. +Add them and `doctor` passes clean. That was the deliberate bar: adoption +isn't a lesser path, but it also won't silently rewrite your files to hit a +green check. + +## Greenfield or brownfield: the decision is simple + +If the folder is empty or new, you want `init`. If it already has content, you +want `adopt`. `create-vivary` can even make that call for you: point the +[setup prompt](/getting-started/) at an agent and it checks the folder first. +Doing it by hand is one look at whether `ls` returns anything: + +```bash +# Empty or new folder +create-vivary init my-workspace --preset coding + +# Already has content +create-vivary adopt . --json # see the plan +create-vivary adopt . --yes # apply it +``` + +`adopt` also auto-detects a preset if you don't pass one (`coding` for a +code-file majority, `second-brain` for a markdown-file majority), and tells +you which one it picked and why, in both the human and `--json` output. + +Want to see what `adopt` is looking at before it plans anything? That's +[tropo map](/blog/tropo-map-read-the-shape/), the read-only step that comes +before this one. Full flag reference is in [commands](/commands/#adopt--point-vivary-at-your-mess). diff --git a/site/src/content/blog/the-adoption-line.md b/site/src/content/blog/the-adoption-line.md index aee8b04..35f9d12 100644 --- a/site/src/content/blog/the-adoption-line.md +++ b/site/src/content/blog/the-adoption-line.md @@ -4,8 +4,7 @@ description: "Vivary only ever worked on an empty folder. That was a demo, not a date: 2026-07-06 author: "Jeff Kazzee" tags: ["release", "adoption", "adopt"] -draft: true ---- +draft: false--- For the first few months, Vivary only worked if you started from nothing. diff --git a/site/src/content/blog/tropo-map-read-the-shape.md b/site/src/content/blog/tropo-map-read-the-shape.md index 2e4a62d..cd5c997 100644 --- a/site/src/content/blog/tropo-map-read-the-shape.md +++ b/site/src/content/blog/tropo-map-read-the-shape.md @@ -1,133 +1,132 @@ ---- -title: "Read the shape of a repo before you read the repo" -description: "Agents burn their context finding things. tropo map gives an agent the shape of a repo in one read-only command, before it opens a single file." -date: 2026-07-08 -author: "Jeff Kazzee" -tags: ["tropo", "context", "guide"] -draft: true ---- - -Drop an agent into an unfamiliar repo and watch what it does first. It lists a -directory. It opens a file. It greps for something. It opens three more files -because the first one referenced them. Twenty tool calls later it has a mental -model of maybe a third of the project, and it spent most of its context budget -building that model instead of doing the task you asked for. - -That's the tax nobody prices in. Every "let me look around first" is tokens -that aren't going toward your actual change. The bigger the repo, the worse the -tax, and the worse agents get at paying it, because a large context window -isn't the same thing as a well-organized one. - -`tropo map` is a read-only fix for exactly that first move. One command, no -config required, gives you the shape of a tree: how big, what's dense, what -already has a map, and what doesn't. - -## What it looks like - -```bash -$ tropo map --root . --depth 2 -``` - -``` -# tropo map: repo - -163 file(s), 65 director(y/ies), depth ≤ 2 - -## Directories - -| Path | Depth | Files | Size | Dominant extensions | Index? | -|---|---|---|---|---|---| -| . | 0 | 163 | 1.6MB | .md (89), .py (14) | yes | -| docs | 1 | 22 | 574.0KB | .md (18), .webp (4) | yes | -| packages/tropo | 2 | 6 | 128.4KB | .py (2), .md (2) | no | - -## File extensions (top 10) -... - -## Likely modules without an index - -Directories at depth 1-2 with >= 5 files (recursive) and no `index.md`/`README.md`: - -- packages/tropo -``` - -That's the whole idea in one screen. Root has an index (`README.md`), `docs/` -has one, and `packages/tropo` doesn't, despite having six files under it. If -you're an agent about to work in `packages/tropo`, that last line just told -you there's no router waiting for you: you're going in cold, so budget more -reads there and consider dropping an `index.md` when you leave. - -The counts in the summary sections (totals, extensions, largest files, -missing-index detection) always cover the whole tree, even when `--depth` -limits which rows show up in the directory table. `--depth` trims what you -have to read, not what gets counted. - -## No `tropo.toml` required - -This is the part I'd get wrong if I were skimming: `map` doesn't need a Vivary -workspace at all. Run it against any repo, any notes vault, any docs tree, -adopted or not. It skips the usual junk (`.git`, `node_modules`, -`__pycache__`, `.venv`, `venv`, `dist`, `build`, `.astro`, `.next`, `target`), -and if it finds a `tropo.toml` by walking up from wherever you pointed it, it -also honors that config's `exclude` patterns. If it doesn't find one, it just -runs anyway. A missing or broken config never blocks the map. - -That matters for the adoption story. Before you decide whether a repo should -become a `coding` or `second-brain` preset, before you run `create-vivary -adopt`, you can run `map` against it with nothing installed but the map -command itself. It's the look-before-you-touch step. More on the actual -touching in a follow-up post, Adopt, don't rebuild. - -## `--json` is deterministic on purpose - -```bash -tropo map --root . --json -``` - -Every key is sorted, ordering is stable, and the output is meant to be diffed. -That's not a throwaway detail. Say an agent is going to make a decision based -on the map: "there are 40 markdown files under `notes/` with no index, propose -a router." The map needs to be something you can run twice and get the same -answer, and something you can put in a test fixture without it flaking on key -order. Deterministic JSON is what makes `map` usable as a build block for -other tools instead of just a pretty printout for humans. - -## Privacy details that are easy to miss - -Two things about what actually gets printed, both intentional: - -- The `root` field, and the markdown heading, show the mapped directory's - **basename only.** If you run `tropo map --root - /Users/jeff/client-work/acme-contract`, the report says `acme-contract`, not - the absolute path. Every other path in the output is root-relative with - forward slashes, even on Windows. -- **Excludes are honored at the file level, not just the directory level**, - and they get rebased when your map root sits below the config root. If a - `tropo.toml` up the tree says `exclude = ["docs/private"]`, and you run - `tropo map docs`, that exclude still applies to `private/` even though - you're mapping from one level down. - -Neither of those is exciting until you're about to paste a map's output into a -chat with an agent, or a support ticket, or a README, and you realize it would -have leaked your home directory's username. It doesn't. - -## Junction cycles don't lie to you - -The other trust detail: directory junctions and symlink cycles are pruned by -real path. A repo with a symlink that loops back on itself, or a Windows -junction pointing somewhere it shouldn't, doesn't inflate your file count or -send `map` into a loop. This sounds like an edge case until you've actually -hit a real one in a monorepo with vendored submodules, at which point it's the -difference between a report you can trust and a report that tells you there -are eleven million files in your project. - -## When to reach for it - -Anytime an agent (or you) are about to work in a tree you don't already have a -mental model of. Before adoption. Before a big refactor. Before you ask an -agent to "go find where X lives" in a codebase it's never seen, hand it a -`tropo map` first, and it spends its budget on your actual question instead -of on orientation. - -See the [command reference](/commands/) for the full flag list, or -[getting started](/getting-started/) if you haven't set up Vivary at all yet. +--- +title: "Read the shape of a repo before you read the repo" +description: "Agents burn their context finding things. tropo map gives an agent the shape of a repo in one read-only command, before it opens a single file." +date: 2026-07-08 +author: "Jeff Kazzee" +tags: ["tropo", "context", "guide"] +draft: false--- + +Drop an agent into an unfamiliar repo and watch what it does first. It lists a +directory. It opens a file. It greps for something. It opens three more files +because the first one referenced them. Twenty tool calls later it has a mental +model of maybe a third of the project, and it spent most of its context budget +building that model instead of doing the task you asked for. + +That's the tax nobody prices in. Every "let me look around first" is tokens +that aren't going toward your actual change. The bigger the repo, the worse the +tax, and the worse agents get at paying it, because a large context window +isn't the same thing as a well-organized one. + +`tropo map` is a read-only fix for exactly that first move. One command, no +config required, gives you the shape of a tree: how big, what's dense, what +already has a map, and what doesn't. + +## What it looks like + +```bash +$ tropo map --root . --depth 2 +``` + +``` +# tropo map: repo + +163 file(s), 65 director(y/ies), depth ≤ 2 + +## Directories + +| Path | Depth | Files | Size | Dominant extensions | Index? | +|---|---|---|---|---|---| +| . | 0 | 163 | 1.6MB | .md (89), .py (14) | yes | +| docs | 1 | 22 | 574.0KB | .md (18), .webp (4) | yes | +| packages/tropo | 2 | 6 | 128.4KB | .py (2), .md (2) | no | + +## File extensions (top 10) +... + +## Likely modules without an index + +Directories at depth 1-2 with >= 5 files (recursive) and no `index.md`/`README.md`: + +- packages/tropo +``` + +That's the whole idea in one screen. Root has an index (`README.md`), `docs/` +has one, and `packages/tropo` doesn't, despite having six files under it. If +you're an agent about to work in `packages/tropo`, that last line just told +you there's no router waiting for you: you're going in cold, so budget more +reads there and consider dropping an `index.md` when you leave. + +The counts in the summary sections (totals, extensions, largest files, +missing-index detection) always cover the whole tree, even when `--depth` +limits which rows show up in the directory table. `--depth` trims what you +have to read, not what gets counted. + +## No `tropo.toml` required + +This is the part I'd get wrong if I were skimming: `map` doesn't need a Vivary +workspace at all. Run it against any repo, any notes vault, any docs tree, +adopted or not. It skips the usual junk (`.git`, `node_modules`, +`__pycache__`, `.venv`, `venv`, `dist`, `build`, `.astro`, `.next`, `target`), +and if it finds a `tropo.toml` by walking up from wherever you pointed it, it +also honors that config's `exclude` patterns. If it doesn't find one, it just +runs anyway. A missing or broken config never blocks the map. + +That matters for the adoption story. Before you decide whether a repo should +become a `coding` or `second-brain` preset, before you run `create-vivary +adopt`, you can run `map` against it with nothing installed but the map +command itself. It's the look-before-you-touch step. More on the actual +touching in a follow-up post, Adopt, don't rebuild. + +## `--json` is deterministic on purpose + +```bash +tropo map --root . --json +``` + +Every key is sorted, ordering is stable, and the output is meant to be diffed. +That's not a throwaway detail. Say an agent is going to make a decision based +on the map: "there are 40 markdown files under `notes/` with no index, propose +a router." The map needs to be something you can run twice and get the same +answer, and something you can put in a test fixture without it flaking on key +order. Deterministic JSON is what makes `map` usable as a build block for +other tools instead of just a pretty printout for humans. + +## Privacy details that are easy to miss + +Two things about what actually gets printed, both intentional: + +- The `root` field, and the markdown heading, show the mapped directory's + **basename only.** If you run `tropo map --root + /Users/jeff/client-work/acme-contract`, the report says `acme-contract`, not + the absolute path. Every other path in the output is root-relative with + forward slashes, even on Windows. +- **Excludes are honored at the file level, not just the directory level**, + and they get rebased when your map root sits below the config root. If a + `tropo.toml` up the tree says `exclude = ["docs/private"]`, and you run + `tropo map docs`, that exclude still applies to `private/` even though + you're mapping from one level down. + +Neither of those is exciting until you're about to paste a map's output into a +chat with an agent, or a support ticket, or a README, and you realize it would +have leaked your home directory's username. It doesn't. + +## Junction cycles don't lie to you + +The other trust detail: directory junctions and symlink cycles are pruned by +real path. A repo with a symlink that loops back on itself, or a Windows +junction pointing somewhere it shouldn't, doesn't inflate your file count or +send `map` into a loop. This sounds like an edge case until you've actually +hit a real one in a monorepo with vendored submodules, at which point it's the +difference between a report you can trust and a report that tells you there +are eleven million files in your project. + +## When to reach for it + +Anytime an agent (or you) are about to work in a tree you don't already have a +mental model of. Before adoption. Before a big refactor. Before you ask an +agent to "go find where X lives" in a codebase it's never seen, hand it a +`tropo map` first, and it spends its budget on your actual question instead +of on orientation. + +See the [command reference](/commands/) for the full flag list, or +[getting started](/getting-started/) if you haven't set up Vivary at all yet.