feat(cli): harden and polish loci init command#9
Conversation
Revamp the `loci init` experience across several axes:
UI
- New startup banner: pagga half-block figlet rendering "codeatrium"
with a blue vertical gradient, ⦿ mascot, tagline, and version —
framed by a rich Panel. Fits in 80 cols with no wrapping.
Interactive prompts
- Re-prompt on invalid input instead of silent fallback (previously
"y" for Yes was treated as No because only "2" worked).
- Accept y/n/yes/no (case/whitespace tolerant) for the distill-now
prompt.
- Range-validate custom exchange counts (1..total) and custom
min_chars (>= 0).
Resilience
- Wrap the execution phase in try/except with shutil.rmtree cleanup
on failure or KeyboardInterrupt — retrying `loci init` is safe
again (previously a half-initialized DB blocked re-init).
- Per-file try/except around index_file loop so one bad .jsonl
logs a warning and doesn't abort the whole run.
- git_root catches FileNotFoundError (handles missing git binary).
- parse_exchanges returns [] for missing files instead of raising.
- find_project_root no longer traverses parents outside a git repo
(avoids picking up an unrelated .codeatrium/).
Automation
- Auto-install Claude Code hooks at the end of init. Opt out with
--no-hooks. Hook failure is warn-only; DB is preserved.
Embedder fail-fast
- New EmbedderSetupError distinguishes environment-level failures
(numpy/pyarrow binary mismatch) from per-row errors.
- distill_all re-raises EmbedderSetupError to stop the loop early.
- init's distill handler prints a single friendly message with
remediation hints ("pip install 'numpy<2' or pip install -U
pyarrow") instead of a full traceback followed by N per-row
error lines.
Tests
- 12 new tests covering invalid-input reprompts, cleanup on
failure, KeyboardInterrupt, per-file resilience, hook install
paths (default / --no-hooks / failure), and EmbedderSetupError
wrapping + friendly message. All 142 tests pass.
- Add autouse fixture in test_init.py that redirects $HOME to
tmp_path so tests don't touch the real ~/.claude/settings.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Code Review One issue found. Bug: failure-cleanup can delete pre-existing File: The guard at line 88 only checks Concrete scenario: a user pre-creates The inconsistency is clear: line 127 goes out of its way not to overwrite a pre-existing Suggested fix: capture whether |
- assets/banner.svg generated via Freeze (charmbracelet/tap/freeze) so the font glyphs are embedded as <path> elements and the rendering is consistent across GitHub and local browsers — no fallback-font seams or row-boundary lines. - scripts/generate-banner.sh reproducibly regenerates the SVG from the live `loci` banner. - README.md / README.ja.md embed the banner above the H1 and are updated to reflect the new init behavior: hook auto-install (with --no-hooks opt-out), y/n prompt aliases, re-prompt on invalid input, and automatic cleanup on failure. - Swap the tagline mascot from ⦿ to ● (guaranteed single-width in JetBrains Mono) so the rich Panel border stays flush. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
de059e0 to
ad17a48
Compare
|
@claude |
|
Claude finished @senna-lang's task in 1m 46s —— View job Fixed: pre-existing
|
Capture `dir_preexisted` before the execution-phase `try` block and guard both `shutil.rmtree` cleanup calls with `if not dir_preexisted`. Previously, a mid-init failure (exception or Ctrl-C) would unconditionally delete the entire `.codeatrium/` directory, destroying files the user had created before running `loci init` (e.g. a custom `config.toml`). This was inconsistent with the config-write path, which already skips overwriting pre-existing `config.toml`. Also adds a regression test `test_init_preserves_preexisting_dir_on_failure` to verify the directory and its contents survive an execution-phase failure. Co-authored-by: sena inomata <senna-lang@users.noreply.github.com>
- pyproject.toml / __init__.py: 0.1.0 → 0.2.0 - CHANGELOG.md: add 0.2.0 section documenting init auto-hook-install, --no-hooks flag, invalid-input re-prompts, cleanup-on-failure, per-file resilience, EmbedderSetupError, and the SVG README banner. - Regenerate assets/banner.svg so the embedded version string matches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
This PR hardens
loci initacross multiple dimensions — UI, prompt robustness, resilience to failures, automatic hook installation, and better error messages for environment-level problems. Re-runningloci initafter a failure is now safe.Changes
UI: richer startup banner
codeatriumin the pagga half-block figlet font with a blue vertical gradient (#7bb8ff → #1b45a8),⦿mascot glyph, tagline, and version — framed by a richPanel. Fits in 80 cols without wrapping.Interactive prompts: no more silent fallback
Previously, invalid prompt input silently defaulted —
"y"for Yes was treated as No because only"2"worked. Now:1/2/y/n/yes/no(case/whitespace tolerant) for the "run distillation now?" prompt.1..total; custommin_charsmust be>= 0.Resilience: safe failure & interrupt
.codeatrium/is removed (shutil.rmtree) so re-runningloci initis safe. Previously the half-created DB was picked up by theAlready initializedfast-path and blocked retry..jsonlno longer aborts the indexing loop — it logs⚠ skip <file>: <err>and continues.git_rootcatchesFileNotFoundError: no crash whengitisn't on$PATH.parse_exchangesreturns[]for missing files instead of raising.find_project_rootno longer traverses parents when outside a git repo (prevents picking up an unrelated.codeatrium/).Automation: hooks auto-installed
loci initnow callsinstall_hooksat the end (writes to~/.claude/settings.json).loci init+loci hook install→ justloci init.--no-hooks.loci hook install.Embedder fail-fast with remediation hints
Previously a broken
sentence_transformersimport (common cause:numpy 2.x+ olderpyarrow) produced a full traceback followed by 242 per-row error lines. Now:EmbedderSetupErrordistinguishes environment-level failures from per-row errors._ensure_modelwraps import/init failures with a remediation-aware message (pip install 'numpy<2' or pip install -U pyarrow).distill_allre-raisesEmbedderSetupErrorto stop the loop on first occurrence.loci init's distill handler catches it specifically and prints a single clean message — no traceback, no per-row spam.Test plan
y/naliases accepted; custom count range-validated.codeatrium/cleanupKeyboardInterruptexits 130 with cleanup--no-hooksskips installEmbedderSetupErrorwraps import failureEmbedderSetupError, preserves DBpytest,ruff check,pyrightclean)🤖 Generated with Claude Code