/** * v0.20.0 Cathedral II Layer 2 (1a) — file-classifier widening. * * Codex F1 caught that v0.19.0's sync.ts classified only 9 extensions as * code, so B1's "165 languages" claim was aspirational — anything beyond * TS/JS/Python/Ruby/Go dropped on the sync floor. Layer 2 widens the * classifier so every extension the chunker knows about reaches the * chunker during normal sync. * * Layer 2 also ships resolveSlugForPath — a central dispatcher that picks * between slugifyCodePath and pathToSlug based on isCodeFilePath. Sync * delete/rename paths now go through this so widening the classifier * doesn't break deletes (SP-5). * * Layer 2 additionally adds a setLanguageFallback hook on * chunkers/code.ts that Layer 9 (B2 Magika) will wire in. This test * covers the hook contract. */ import { describe, test, expect, afterEach } from 'bun:test'; import { isCodeFilePath, resolveSlugForPath, slugifyCodePath, slugifyPath } from '../src/core/sync.ts'; import { detectCodeLanguage, setLanguageFallback, type SupportedCodeLanguage } from '../src/core/chunkers/code.ts'; describe('Layer 2 — isCodeFilePath widening', () => { test('v0.19.0 floor still classified as code', () => { // The 9 extensions that v0.19.0 shipped with. for (const ext of ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.rb', '.go']) { expect(isCodeFilePath('foo' + ext)).toBe(true); } }); test('Rust now classified as code (codex F1)', () => { expect(isCodeFilePath('src/main.rs')).toBe(true); }); test('Java now classified as code (codex F1)', () => { expect(isCodeFilePath('src/Main.java')).toBe(true); }); test('C# now classified as code (codex F1)', () => { expect(isCodeFilePath('src/Main.cs')).toBe(true); }); test('C++ variants now classified as code', () => { expect(isCodeFilePath('src/main.cpp')).toBe(true); expect(isCodeFilePath('src/main.cc')).toBe(true); expect(isCodeFilePath('src/main.hpp')).toBe(true); expect(isCodeFilePath('src/main.h')).toBe(true); }); test('Swift / Kotlin / Scala / PHP now classified as code', () => { expect(isCodeFilePath('ios/App.swift')).toBe(true); expect(isCodeFilePath('android/Main.kt')).toBe(true); expect(isCodeFilePath('src/Main.scala')).toBe(true); expect(isCodeFilePath('web/index.php')).toBe(true); }); test('Shell / Lua / Elixir / Dart / Zig / Solidity now classified as code', () => { expect(isCodeFilePath('scripts/deploy.sh')).toBe(true); expect(isCodeFilePath('scripts/deploy.bash')).toBe(true); expect(isCodeFilePath('src/init.lua')).toBe(true); expect(isCodeFilePath('lib/worker.ex')).toBe(true); expect(isCodeFilePath('lib/test.exs')).toBe(true); expect(isCodeFilePath('flutter/main.dart')).toBe(true); expect(isCodeFilePath('src/main.zig')).toBe(true); expect(isCodeFilePath('contracts/Token.sol')).toBe(true); }); test('Web + config extensions (CSS, HTML, Vue, JSON, YAML, TOML)', () => { expect(isCodeFilePath('src/app.css')).toBe(true); expect(isCodeFilePath('public/index.html')).toBe(true); expect(isCodeFilePath('src/App.vue')).toBe(true); expect(isCodeFilePath('package.json')).toBe(true); expect(isCodeFilePath('config.yaml')).toBe(true); expect(isCodeFilePath('config.yml')).toBe(true); expect(isCodeFilePath('Cargo.toml')).toBe(true); }); // v0.41 D2 wave (#1173): SQL via tree-sitter-sql. test('SQL classified as code (#1173)', () => { expect(isCodeFilePath('migrations/001_init.sql')).toBe(true); expect(isCodeFilePath('schema.sql')).toBe(true); expect(isCodeFilePath('Schema.SQL')).toBe(true); // case-insensitive }); test('markdown is NOT classified as code', () => { expect(isCodeFilePath('docs/README.md')).toBe(false); expect(isCodeFilePath('docs/note.mdx')).toBe(false); }); test('extensionless files are NOT classified as code via name alone (Magika fallback, Layer 9)', () => { // Layer 2's classifier is extension-based only. Layer 9 wires up // setLanguageFallback for extensionless-via-content classification. expect(isCodeFilePath('Dockerfile')).toBe(false); expect(isCodeFilePath('Makefile')).toBe(false); expect(isCodeFilePath('.envrc')).toBe(false); }); }); describe('Layer 2 — resolveSlugForPath dispatches by extension (SP-5)', () => { test('markdown path → markdown-style slug (slugifyPath)', () => { expect(resolveSlugForPath('people/alice-smith.md')) .toBe(slugifyPath('people/alice-smith.md').toLowerCase()); }); test('code path → code-style slug (slugifyCodePath)', () => { // slugifyCodePath replaces dots with hyphens and flattens // slashes to hyphens: src/core/sync.ts → src-core-sync-ts expect(resolveSlugForPath('src/core/sync.ts')) .toBe(slugifyCodePath('src/core/sync.ts').toLowerCase()); }); test('Rust file uses code-slug (previously fell through to markdown, SP-5)', () => { expect(resolveSlugForPath('crates/worker/src/main.rs')) .toBe(slugifyCodePath('crates/worker/src/main.rs').toLowerCase()); }); test('repoPrefix is applied before lowercasing', () => { expect(resolveSlugForPath('src/foo.ts', 'my-repo')) .toMatch(/^my-repo\//); }); test('round-trip: same path in → same slug out', () => { const path = 'crates/worker/src/lib.rs'; expect(resolveSlugForPath(path)).toBe(resolveSlugForPath(path)); }); }); describe('Layer 2 — detectCodeLanguage Magika fallback hook', () => { afterEach(() => { setLanguageFallback(null); }); test('no fallback installed + extensionless path → null', () => { expect(detectCodeLanguage('Dockerfile')).toBe(null); expect(detectCodeLanguage('Dockerfile', '# syntax=docker/dockerfile:1')).toBe(null); }); test('fallback fires only on unknown-extension + content provided', () => { let calls = 0; setLanguageFallback((path, content) => { calls++; if (path === 'Dockerfile' && content.includes('FROM')) return 'bash'; return null; }); // Known extension — fallback NOT consulted. expect(detectCodeLanguage('app.ts', 'const x = 1')).toBe('typescript'); expect(calls).toBe(0); // Unknown extension + no content — fallback NOT consulted. expect(detectCodeLanguage('Dockerfile')).toBe(null); expect(calls).toBe(0); // Unknown extension + content — fallback consulted. expect(detectCodeLanguage('Dockerfile', 'FROM alpine')).toBe('bash'); expect(calls).toBe(1); }); test('fallback throw is swallowed → null result', () => { setLanguageFallback(() => { throw new Error('Magika model load failed'); }); expect(detectCodeLanguage('Dockerfile', 'FROM alpine')).toBe(null); }); test('fallback returning null passes through as null', () => { setLanguageFallback(() => null); expect(detectCodeLanguage('weird.file.xyz', 'some content')).toBe(null); }); test('fallback returning a supported language passes through', () => { const targetLang: SupportedCodeLanguage = 'python'; setLanguageFallback(() => targetLang); expect(detectCodeLanguage('run', '#!/usr/bin/env python3\nprint("hi")')).toBe('python'); }); });