Release v0.26.0
[0.26.0] - 2026-06-01
Theme: dogfood-driven CLI bug sweep. Five user-reported issues from fabricOS dogfood — mostly UX cliffs in init and list-dirs on non-Python projects, plus a navigation-correctness bug in nav-level README_AI.md. One contract change in init --yes so first scan-all --ai works out of the box.
Changed
codeindex init --yesnow seeds the recommendedai_command(GH #75): the generated.codeindex.yamlincludesai_command: 'claude -p "{prompt}" --model haiku --allowedTools "Read"'so the firstcodeindex scan-all --aiworks without "AI not configured" — the path thecodeindex:indexskill recommends by default. The documented default inDEFAULT_CONFIG_TEMPLATEand the actually-written value now share a single source of truth (config.RECOMMENDED_AI_COMMAND). Contract change vs pre-0.26: AI used to be opt-in at both layers (yaml absent AND--aiflag required); now opt-in lives only at the CLI flag —--aiis still required to spend tokens, but the yaml no longer blocks first-try success. The pre-existing regression testtest_generated_config_no_ai_commandwas renamed totest_generated_config_seeds_recommended_ai_commandand inverted to lock the new contract.
Fixed
codeindex initCLAUDE.md injection drops hardcoded.py / .php / .javaexamples and alternate-backend boilerplate (GH #77, partial): the injected## codeindexsection used to instruct users to "read the actual.py/.php/.java/ etc." — wrong on TS / Swift / Go projects — and listedopencode run/gemini -pas ai_command alternatives in every project's CLAUDE.md regardless of which backend the user actually chose. Template now uses language-neutral wording ("read the source files") and links the backend-swap recipe tocodeindex --helpinstead of inlining all options. Two regression tests (test_build_section_does_not_hardcode_language_extensions,test_build_section_does_not_advertise_alternative_backends) lock these absences. Deferred to a follow-up: zh/en locale detection (#77 also asked for the injection to match the host CLAUDE.md's language; that's a distinct design question — heuristic vs--langflag vs dual templates — and out of this PR's scope).codeindex list-dirsno longer silently returns empty when configuredlanguagesdoesn't match present files (GH #74): previously,list-dirsprinted nothing and exited 0 whenever the language filter excluded everything — indistinguishable from "nothing to index" and the single worst UX class (agents loop through--help/docs/doctor/pipx listdebugging; humans give up). Newscanner.diagnose_language_mismatchhelper walks the include roots, counts actual file extensions, and identifies which codeindex-supported languages would cover them. Whenlist-dirsreturns empty AND files-with-known-extensions are present, it now raises aClickExceptionto stderr with the configuredlanguages, the top file types seen, and the specific languages to add (e.g. "addtypescript / javascriptto.codeindex.yamllanguages:"). Truly empty include roots stay silent + exit 0 — preserving scripts that pipelist-dirsas a "anything to index?" probe.codeindex initnow detects TypeScript / JavaScript projects (GH #73):init_wizard.pycarried a stale localLANGUAGE_EXTENSIONSmap listing only Python / PHP / Java behind a "no parser yet" comment, despitescanner.pyhaving gained TS/JS parsers (and full parser modules existing undersrc/codeindex/parsers/typescript/). On a TS-only repo,detect_languages()returned[]→ nolanguages:block was written → at scan time the runtime defaulted toDEFAULT_LANGUAGES=["python"]→ 0 files matched → silentlist-dirsempty (the user-visible symptom). Fix importsLANGUAGE_EXTENSIONSfromscanner.pyas the single source of truth, so any language the scanner supports is now also detectable by init. Verified end-to-end on a TS fixture (init --yeswriteslanguages: [typescript],list-dirsreturns the include root). A new structural testtest_init_language_set_covers_scanner_supported_setlocks the invariant — adding a language to scanner.py will fail this test until init can also see it, preventing the drift class from recurring.- Navigation-level
README_AI.mdno longer flattens descendant files into## Files(GH #76): nav-level scans are recursive (per the GH #45 stats note), soparse_resultsincludes every descendant.NavigationGeneratorwas iterating them withresult.path.nameonly — dropping the path prefix — so a subdir file likesrc/components/ui/badge.tsxshowed up insrc/README_AI.md's## Filesas barebadge.tsx. Any agent readingsrc/README_AI.mdthen issuingRead src/badge.tsxgot a 404 (or worse: a confident wrong-file Read on a name collision). Fix filtersparse_resultstor.path.parent == dir_pathbefore grouping. Each subdir's files remain available via that subdir's ownREADME_AI.md. Verified on fabricOS:src/README_AI.md ## Filesshrank from 33 flattened entries to the single real direct childmain.tsx. Test helpers_make_result/_create_mock_parse_resultgained an optionalparent_dirkwarg so fixtures pass through the new filter when atmp_pathis the dir under test. - Structural rewrites no longer wipe AI enrichment (GH #38): a
scan-all --airun injects an<!-- enrichment: ok -->marker and a> descriptionblockquote, but any later structural-only write — a post-commit hook runningscan-allwithout--ai, or Phase 1 of the next--airun — used to erase both. The idempotent cache then went cold, so the nextscan-all --aire-paid the full N AI calls instead of restoring from cache.SmartWriter.write_readmenow captures the ok marker + blockquote before overwriting and re-injects them, so the cache stays warm across non-AI invocations. Unenriched READMEs are untouched (no fabricated marker).