Five phases of kodr where the theme isn’t a new capability but not making me hate using it. A tool you’ve built can be powerful and still annoying to drive. This batch sands the edges - recovery, packaging, config, defaults, and the apply gate - mostly by deleting friction I’d been tolerating.
Phase 94: undo, the git-aware way
Applied runs get a recovery primitive: kodr undo (and TUI /undo) reverts the last applied run, an opt-in --commit commits exactly the applied files, and tree state (clean/dirty/not-a-repo) is recorded before every apply. The honest bit is the planning miss baked into the writeup: the original plan was to build a bespoke snapshot undo store then add git on top. Reviewing the backlog made the ordering wrong - nearly every workspace kodr targets is already a git repo where git diff is the review and git checkout is the undo, so a parallel snapshot store would’ve been dead code. The phases got merged, the superseded specs kept with banners, the miss recorded rather than erased.
The load-bearing decision: undo is backup-based, not git-based, and it hashes each applied file before reverting. So undo converts “this might silently destroy your manual fixes” into “this refuses and names the conflicting files” - the difference between a recovery primitive and a footgun. The heal loop interacts correctly for free: repairs edit applied files after the apply, so an undo after healing refuses rather than reverting to a mid-heal state.
Phase 95: the repo-map becomes a library
The structural index, ranked repo-map, and chunk selection move into a self-contained src/repomap/ with a single public entry point. The interesting cleanup was a hidden contract: the inspector attached a non-enumerable _contentLines to each file that the ranker and chunk builder silently consumed. A published library can’t ship secret handshakes, so it got renamed to a plain, documented contentLines. A dependency inversion got fixed too (“inspect a repo” no longer drags in prompt assembly), and a boundary test now statically asserts every import inside repomap/ is either a node: builtin or a sibling - proving the module could be lifted out and published without touching kodr’s app code.
Phase 96: config so I stop retyping
My realistic daily invocation was kodr run -p "task" --tools --yes --install --test "npm test" --heal --stream. Every flag, every time. Phase 96 puts them in .kodr/config.json, where each key maps 1-to-1 to a flag with the same validation, and "//" comment keys survive JSON.parse. kodr init writes an annotated starter from the resolved model and base URL. Precedence is explicit - CLI flags > env > project config > model profile > builtin - and --show-config prints every option with its resolved source. The security boundary matters because config is workspace content a cloned repo can ship: the gate keys (yes, gitCommit, installDependencies, enableHooks, apiKey) are rejected with a named error, not silently honored. A config that tries to grant apply permission is conspicuous, not sneaky.
Phase 97: auto defaults that do the obvious thing
--tools, --stream, --heal, and --inspect-context all defaulted to false - safe, but it meant typing flags for the behavior you almost always want. Phase 97 makes them tri-state 'auto': tools on when the model profile says nativeToolCalls, stream on for an interactive TTY, heal on when both --yes and --test are set, inspect-context on with graceful fallback. All overridable with --no-*, all still behind the same precedence. The sharp edge during implementation: auto-inspection ran on test temp dirs and produced all-empty plans that broke dozens of tests until a hasInspectionTargets guard stopped empty plans being prepended to prompts.
Phase 98: the apply prompt
The dry-run dead-end is gone. Before, a one-shot run that proposed writes ended with “re-run with --yes” - meaning a second multi-minute local inference for the same task, producing a proposal you couldn’t even compare to the one you just read. Now there’s an apply? [y/N] prompt at the gate, so the proposal you reviewed is the proposal that gets applied, exactly once. It reuses the phase-67 permission request shape, and an accepted prompt flows into the full install/test/heal pipeline without a new code path. It only injects for interactive TTY runs without --yes, --json, or an explicit --dry-run (a new _dryRunSet sentinel tells “skip the prompt” from “hasn’t said yes yet”). Every run records how the decision was made - flag, prompt-accepted, prompt-declined, none, late-apply - so /undo can find TUI-accepted runs too. Small change, disproportionately nicer to live with.
Links:
- Phase docs: 94, 95, 96, 97, 98
- Kodr blog: 94, 95, 96, 97, 98
- Undo: src/undo.mjs, git surface: src/git-workspace.mjs, config: src/project-config.mjs