2026-05-10 23:12:26 +02:00
import { mkdtemp , rm , writeFile } from 'node:fs/promises' ;
import { tmpdir } from 'node:os' ;
import { join } from 'node:path' ;
import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
2026-05-10 23:51:24 +02:00
import type { KtxEmbeddingConfig , KtxEmbeddingHealthCheckOptions , KtxEmbeddingHealthCheckResult } from '@ktx/llm' ;
2026-05-10 23:12:26 +02:00
import {
formatDoctorReport ,
2026-05-10 23:51:24 +02:00
runKtxDoctor ,
2026-05-10 23:12:26 +02:00
runSetupDoctorChecks ,
type DoctorCheck ,
} from './doctor.js' ;
function makeIo() {
let stdout = '' ;
let stderr = '' ;
return {
io : {
stdout : {
write : ( chunk : string ) = > {
stdout += chunk ;
} ,
} ,
stderr : {
write : ( chunk : string ) = > {
stderr += chunk ;
} ,
} ,
} ,
stdout : ( ) = > stdout ,
stderr : ( ) = > stderr ,
} ;
}
type EmbeddingHealthCheck = (
2026-05-10 23:51:24 +02:00
config : KtxEmbeddingConfig ,
options? : KtxEmbeddingHealthCheckOptions ,
) = > Promise < KtxEmbeddingHealthCheckResult > ;
2026-05-10 23:12:26 +02:00
async function writeProjectConfig ( projectDir : string , embeddingLines : string [ ] ) : Promise < void > {
await writeFile (
2026-05-10 23:51:24 +02:00
join ( projectDir , 'ktx.yaml' ) ,
2026-05-10 23:12:26 +02:00
[
'project: warehouse' ,
'connections:' ,
' warehouse:' ,
' driver: sqlite' ,
' path: ./warehouse.db' ,
'ingest:' ,
' adapters:' ,
' - live-database' ,
' embeddings:' ,
. . . embeddingLines . map ( ( line ) = > ` ${ line } ` ) ,
'' ,
] . join ( '\n' ) ,
'utf-8' ,
) ;
}
describe ( 'formatDoctorReport' , ( ) = > {
2026-05-13 15:21:57 -07:00
it ( 'shows the failing check and its fix in plain output' , ( ) = > {
2026-05-10 23:12:26 +02:00
const checks : DoctorCheck [ ] = [
2026-05-13 15:21:57 -07:00
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' , group : 'toolchain' } ,
2026-05-10 23:12:26 +02:00
{
id : 'native-sqlite' ,
label : 'Native SQLite' ,
status : 'fail' ,
detail : 'Cannot load better-sqlite3' ,
fix : 'Run: pnpm run native:rebuild' ,
2026-05-13 15:21:57 -07:00
group : 'toolchain' ,
2026-05-10 23:12:26 +02:00
} ,
] ;
2026-05-13 15:21:57 -07:00
const output = formatDoctorReport ( { title : 'KTX status' , checks } ) ;
expect ( output ) . toContain ( 'KTX status' ) ;
expect ( output ) . toContain ( '✗ Environment' ) ;
expect ( output ) . toContain ( '1 of 2 need attention' ) ;
expect ( output ) . toContain ( '✗ Native SQLite: Cannot load better-sqlite3' ) ;
expect ( output ) . toContain ( '→ Run: pnpm run native:rebuild' ) ;
expect ( output ) . toContain ( '1 issue to fix.' ) ;
} ) ;
it ( 'lists what was checked when a group has all passing checks' , ( ) = > {
const checks : DoctorCheck [ ] = [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0' , group : 'toolchain' } ,
{ id : 'pnpm' , label : 'pnpm 10.20+' , status : 'pass' , detail : '10.28.0' , group : 'toolchain' } ,
] ;
const output = formatDoctorReport ( { title : 'KTX status' , checks } ) ;
expect ( output ) . toContain ( '✓ Environment' ) ;
expect ( output ) . toContain ( 'Node 22+ · pnpm 10.20+' ) ;
expect ( output ) . not . toContain ( 'v22.16.0' ) ;
expect ( output ) . toContain ( 'Everything ready.' ) ;
} ) ;
it ( 'shows the underlying detail for a single-check group on the group line' , ( ) = > {
const checks : DoctorCheck [ ] = [
{
id : 'semantic-search-embeddings' ,
label : 'Semantic search embeddings' ,
status : 'pass' ,
detail : 'openai/text-embedding-3-small (1536d) probe succeeded' ,
group : 'search' ,
} ,
] ;
const output = formatDoctorReport ( { title : 'KTX status' , checks } ) ;
expect ( output ) . toContain ( '✓ Semantic search' ) ;
expect ( output ) . toContain ( 'openai/text-embedding-3-small (1536d) probe succeeded' ) ;
} ) ;
it ( 'lists every check in verbose mode' , ( ) = > {
const checks : DoctorCheck [ ] = [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0' , group : 'toolchain' } ,
] ;
const output = formatDoctorReport ( { title : 'KTX status' , checks } , { verbose : true } ) ;
expect ( output ) . toContain ( '✓ Node 22+: v22.16.0' ) ;
2026-05-10 23:12:26 +02:00
} ) ;
} ) ;
describe ( 'runSetupDoctorChecks' , ( ) = > {
it ( 'returns pass checks when injected commands and file checks succeed' , async ( ) = > {
const checks = await runSetupDoctorChecks ( {
env : { PATH : '/bin' } ,
2026-05-10 23:51:24 +02:00
workspaceRoot : '/workspace/ktx' ,
2026-05-10 23:12:26 +02:00
execText : async ( command , args ) = > {
if ( command === 'pnpm' && args [ 0 ] === '--version' ) return '10.28.0' ;
if ( command === 'corepack' && args [ 0 ] === '--version' ) return '0.32.0' ;
if ( command === 'uv' && args [ 0 ] === '--version' ) return 'uv 0.9.5' ;
2026-05-10 23:51:24 +02:00
if ( command === process . execPath && args . includes ( '--version' ) ) return '@ktx/cli 0.0.0-private' ;
2026-05-10 23:12:26 +02:00
throw new Error ( ` ${ command } ${ args . join ( ' ' ) } ` ) ;
} ,
pathExists : async ( ) = > true ,
importBetterSqlite3 : async ( ) = > ( { default : function Database() { } } ) ,
} ) ;
expect ( checks . map ( ( check ) = > [ check . id , check . status ] ) ) . toEqual ( [
[ 'node' , 'pass' ] ,
[ 'pnpm' , 'pass' ] ,
[ 'corepack' , 'pass' ] ,
[ 'uv' , 'pass' ] ,
[ 'native-sqlite' , 'pass' ] ,
[ 'package-build' , 'pass' ] ,
[ 'workspace-cli' , 'pass' ] ,
] ) ;
} ) ;
it ( 'returns exact fixes when setup checks fail' , async ( ) = > {
const checks = await runSetupDoctorChecks ( {
env : { } ,
2026-05-10 23:51:24 +02:00
workspaceRoot : '/workspace/ktx' ,
2026-05-10 23:12:26 +02:00
execText : async ( command ) = > {
throw new Error ( ` ${ command } not found ` ) ;
} ,
pathExists : async ( ) = > false ,
importBetterSqlite3 : async ( ) = > {
throw new Error ( 'Cannot find module better-sqlite3' ) ;
} ,
} ) ;
expect ( checks ) . toContainEqual ( {
id : 'pnpm' ,
label : 'pnpm 10.20+' ,
status : 'fail' ,
detail : 'pnpm not found' ,
fix : 'Run: corepack enable && corepack prepare pnpm@10.28.0 --activate' ,
2026-05-13 15:21:57 -07:00
group : 'toolchain' ,
2026-05-10 23:12:26 +02:00
} ) ;
expect ( checks ) . toContainEqual ( {
id : 'package-build' ,
label : 'TypeScript package build' ,
status : 'fail' ,
detail : 'Missing packages/cli/dist/bin.js' ,
fix : 'Run: pnpm run build' ,
2026-05-13 15:21:57 -07:00
group : 'toolchain' ,
2026-05-10 23:12:26 +02:00
} ) ;
} ) ;
it ( 'treats missing corepack as a warning so setup doctor can still pass' , async ( ) = > {
const checks = await runSetupDoctorChecks ( {
env : { PATH : '/bin' } ,
2026-05-10 23:51:24 +02:00
workspaceRoot : '/workspace/ktx' ,
2026-05-10 23:12:26 +02:00
execText : async ( command , args ) = > {
if ( command === 'pnpm' && args [ 0 ] === '--version' ) return '10.28.0' ;
if ( command === 'corepack' && args [ 0 ] === '--version' ) throw new Error ( 'spawn corepack ENOENT' ) ;
if ( command === 'uv' && args [ 0 ] === '--version' ) return 'uv 0.9.5' ;
2026-05-10 23:51:24 +02:00
if ( command === process . execPath && args . includes ( '--version' ) ) return '@ktx/cli 0.0.0-private' ;
2026-05-10 23:12:26 +02:00
throw new Error ( ` ${ command } ${ args . join ( ' ' ) } ` ) ;
} ,
pathExists : async ( ) = > true ,
importBetterSqlite3 : async ( ) = > ( { default : function Database() { } } ) ,
} ) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-13 15:21:57 -07:00
runKtxDoctor (
{ command : 'setup' , outputMode : 'plain' , inputMode : 'disabled' , verbose : true } ,
testIo . io ,
{ runSetupChecks : async ( ) = > checks } ,
) ,
2026-05-10 23:12:26 +02:00
) . resolves . toBe ( 0 ) ;
expect ( checks ) . toContainEqual ( {
id : 'corepack' ,
label : 'Corepack' ,
status : 'warn' ,
detail : 'spawn corepack ENOENT' ,
fix : 'Run: corepack enable' ,
2026-05-13 15:21:57 -07:00
group : 'toolchain' ,
2026-05-10 23:12:26 +02:00
} ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . toContain ( '⚠ Corepack: spawn corepack ENOENT' ) ;
2026-05-10 23:12:26 +02:00
expect ( testIo . stderr ( ) ) . toBe ( '' ) ;
} ) ;
} ) ;
2026-05-10 23:51:24 +02:00
describe ( 'runKtxDoctor' , ( ) = > {
2026-05-10 23:12:26 +02:00
let tempDir : string ;
beforeEach ( async ( ) = > {
2026-05-10 23:51:24 +02:00
tempDir = await mkdtemp ( join ( tmpdir ( ) , 'ktx-doctor-' ) ) ;
2026-05-10 23:12:26 +02:00
} ) ;
afterEach ( async ( ) = > {
await rm ( tempDir , { recursive : true , force : true } ) ;
} ) ;
it ( 'prints setup report and exits nonzero when a check fails' , async ( ) = > {
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'setup' , outputMode : 'plain' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
{
id : 'package-build' ,
label : 'TypeScript package build' ,
status : 'fail' ,
detail : 'Missing packages/cli/dist/bin.js' ,
fix : 'Run: pnpm run build' ,
} ,
] ,
} ,
) ,
) . resolves . toBe ( 1 ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . toContain ( 'KTX status' ) ;
expect ( testIo . stdout ( ) ) . toContain ( 'No project here yet.' ) ;
expect ( testIo . stdout ( ) ) . toContain ( 'Before you can run' ) ;
expect ( testIo . stdout ( ) ) . toContain ( '✗ TypeScript package build: Missing packages/cli/dist/bin.js' ) ;
expect ( testIo . stdout ( ) ) . toContain ( '→ Run: pnpm run build' ) ;
2026-05-10 23:12:26 +02:00
expect ( testIo . stderr ( ) ) . toBe ( '' ) ;
} ) ;
2026-05-13 15:21:57 -07:00
it ( 'leads with `ktx setup` and hides toolchain warnings when no project exists' , async ( ) = > {
const testIo = makeIo ( ) ;
await expect (
runKtxDoctor (
{ command : 'setup' , outputMode : 'plain' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0' , group : 'toolchain' } ,
{
id : 'corepack' ,
label : 'Corepack' ,
status : 'warn' ,
detail : 'spawn corepack ENOENT' ,
fix : 'Run: corepack enable' ,
group : 'toolchain' ,
} ,
] ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
const out = testIo . stdout ( ) ;
expect ( out ) . toContain ( 'No project here yet.' ) ;
expect ( out ) . toContain ( 'Run' ) ;
expect ( out ) . toContain ( 'ktx setup' ) ;
expect ( out ) . not . toContain ( 'Corepack' ) ;
expect ( out ) . not . toContain ( 'Node 22+' ) ;
} ) ;
2026-05-10 23:12:26 +02:00
it ( 'prints JSON setup report' , async ( ) = > {
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'setup' , outputMode : 'json' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
expect ( JSON . parse ( testIo . stdout ( ) ) ) . toEqual ( {
2026-05-13 15:21:57 -07:00
title : 'KTX status' ,
2026-05-10 23:12:26 +02:00
checks : [ { id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ] ,
} ) ;
} ) ;
2026-05-10 23:51:24 +02:00
it ( 'runs project checks against a valid ktx.yaml' , async ( ) = > {
2026-05-10 23:12:26 +02:00
await writeFile (
2026-05-10 23:51:24 +02:00
join ( tempDir , 'ktx.yaml' ) ,
2026-05-10 23:12:26 +02:00
[
'project: warehouse' ,
'connections:' ,
' warehouse:' ,
' driver: sqlite' ,
' path: ./warehouse.db' ,
'ingest:' ,
' adapters:' ,
' - live-database' ,
'' ,
] . join ( '\n' ) ,
'utf-8' ,
) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'project' , projectDir : tempDir , outputMode : 'plain' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . toContain ( 'KTX status' ) ;
expect ( testIo . stdout ( ) ) . toContain ( '· warehouse' ) ;
expect ( testIo . stdout ( ) ) . toContain ( '✓ Project' ) ;
2026-05-10 23:12:26 +02:00
} ) ;
it ( 'includes Postgres historic-SQL readiness in project doctor output' , async ( ) = > {
await writeFile (
2026-05-10 23:51:24 +02:00
join ( tempDir , 'ktx.yaml' ) ,
2026-05-10 23:12:26 +02:00
[
'project: warehouse' ,
'connections:' ,
' warehouse:' ,
' driver: postgres' ,
' url: env:WAREHOUSE_DATABASE_URL' ,
' historicSql:' ,
' enabled: true' ,
' dialect: postgres' ,
'ingest:' ,
' adapters:' ,
' - live-database' ,
' - historic-sql' ,
'' ,
] . join ( '\n' ) ,
'utf-8' ,
) ;
const testIo = makeIo ( ) ;
const runHistoricSqlDoctorChecks = vi . fn ( async ( ) = > [
{
id : 'historic-sql-postgres-warehouse' ,
label : 'Postgres Historic SQL (warehouse)' ,
2026-05-11 19:27:57 +02:00
status : 'pass' as const ,
2026-05-10 23:12:26 +02:00
detail :
2026-05-11 19:27:57 +02:00
'pg_stat_statements ready (PostgreSQL 16.4); info: pg_stat_statements.max is 1000; set it to at least 5000 to reduce query-template eviction churn' ,
2026-05-10 23:12:26 +02:00
} ,
] ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-13 15:21:57 -07:00
{ command : 'project' , projectDir : tempDir , outputMode : 'plain' , inputMode : 'disabled' , verbose : true } ,
2026-05-10 23:12:26 +02:00
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
runHistoricSqlDoctorChecks ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
expect ( runHistoricSqlDoctorChecks ) . toHaveBeenCalledTimes ( 1 ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . toContain ( '✓ Postgres Historic SQL (warehouse): pg_stat_statements ready' ) ;
2026-05-11 19:27:57 +02:00
expect ( testIo . stdout ( ) ) . toContain ( 'info: pg_stat_statements.max is 1000' ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . not . toContain ( '→ Update the Postgres parameter group or config' ) ;
2026-05-10 23:12:26 +02:00
} ) ;
it ( 'warns when semantic-search embeddings are not configured' , async ( ) = > {
await writeProjectConfig ( tempDir , [ 'backend: deterministic' , 'model: deterministic' , 'dimensions: 8' ] ) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'project' , projectDir : tempDir , outputMode : 'plain' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
2026-05-13 15:21:57 -07:00
expect ( testIo . stdout ( ) ) . toContain ( '⚠ Semantic search' ) ;
expect ( testIo . stdout ( ) ) . toContain ( 'ingest.embeddings.backend is deterministic.' ) ;
2026-05-10 23:12:26 +02:00
expect ( testIo . stdout ( ) ) . toContain (
'Semantic lane will be skipped; lexical, dictionary, and token lanes remain available.' ,
) ;
expect ( testIo . stdout ( ) ) . toContain (
2026-05-13 15:21:57 -07:00
` → Run: ktx setup --project-dir ${ tempDir } --no-input ` ,
2026-05-10 23:12:26 +02:00
) ;
} ) ;
it ( 'probes configured semantic-search embeddings for project doctor' , async ( ) = > {
await writeProjectConfig ( tempDir , [
'backend: sentence-transformers' ,
'model: all-MiniLM-L6-v2' ,
'dimensions: 384' ,
'sentenceTransformers:' ,
' base_url: http://127.0.0.1:8765' ,
" pathPrefix: ''" ,
] ) ;
const healthCheck = vi . fn < EmbeddingHealthCheck > ( async ( ) = > ( { ok : true } ) ) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-13 15:21:57 -07:00
{ command : 'project' , projectDir : tempDir , outputMode : 'plain' , inputMode : 'disabled' , verbose : true } ,
2026-05-10 23:12:26 +02:00
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
embeddingHealthCheck : healthCheck ,
embeddingProbeTimeoutMs : 1234 ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
expect ( healthCheck ) . toHaveBeenCalledWith (
{
backend : 'sentence-transformers' ,
model : 'all-MiniLM-L6-v2' ,
dimensions : 384 ,
sentenceTransformers : { baseURL : 'http://127.0.0.1:8765' , pathPrefix : '' } ,
} ,
2026-05-10 23:51:24 +02:00
{ text : 'KTX semantic search doctor probe' , timeoutMs : 1234 } ,
2026-05-10 23:12:26 +02:00
) ;
expect ( testIo . stdout ( ) ) . toContain (
2026-05-13 15:21:57 -07:00
'✓ Semantic search embeddings: sentence-transformers/all-MiniLM-L6-v2 (384d) probe succeeded' ,
2026-05-10 23:12:26 +02:00
) ;
} ) ;
it ( 'allows local sentence-transformers semantic-search probes enough time for cold start' , async ( ) = > {
await writeProjectConfig ( tempDir , [
'backend: sentence-transformers' ,
'model: all-MiniLM-L6-v2' ,
'dimensions: 384' ,
'sentenceTransformers:' ,
' base_url: http://127.0.0.1:8765' ,
" pathPrefix: ''" ,
] ) ;
const healthCheck = vi . fn < EmbeddingHealthCheck > ( async ( ) = > ( { ok : true } ) ) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'project' , projectDir : tempDir , outputMode : 'plain' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
embeddingHealthCheck : healthCheck ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
expect ( healthCheck ) . toHaveBeenCalledWith (
expect . objectContaining ( {
backend : 'sentence-transformers' ,
model : 'all-MiniLM-L6-v2' ,
dimensions : 384 ,
} ) ,
2026-05-10 23:51:24 +02:00
{ text : 'KTX semantic search doctor probe' , timeoutMs : 120_000 } ,
2026-05-10 23:12:26 +02:00
) ;
} ) ;
it ( 'reports unhealthy semantic-search embeddings as a warning in JSON output' , async ( ) = > {
await writeProjectConfig ( tempDir , [
'backend: sentence-transformers' ,
'model: all-MiniLM-L6-v2' ,
'dimensions: 384' ,
'sentenceTransformers:' ,
' base_url: http://127.0.0.1:8765' ,
" pathPrefix: ''" ,
] ) ;
const healthCheck = vi . fn < EmbeddingHealthCheck > ( async ( ) = > ( {
ok : false ,
message : 'connect ECONNREFUSED 127.0.0.1:8765' ,
} ) ) ;
const testIo = makeIo ( ) ;
await expect (
2026-05-10 23:51:24 +02:00
runKtxDoctor (
2026-05-10 23:12:26 +02:00
{ command : 'project' , projectDir : tempDir , outputMode : 'json' , inputMode : 'disabled' } ,
testIo . io ,
{
runSetupChecks : async ( ) = > [
{ id : 'node' , label : 'Node 22+' , status : 'pass' , detail : 'v22.16.0 ABI 127' } ,
] ,
embeddingHealthCheck : healthCheck ,
} ,
) ,
) . resolves . toBe ( 0 ) ;
const report = JSON . parse ( testIo . stdout ( ) ) as {
checks : Array < { id : string ; label : string ; status : string ; detail : string ; fix? : string } > ;
} ;
expect ( report . checks ) . toContainEqual ( {
id : 'semantic-search-embeddings' ,
label : 'Semantic search embeddings' ,
status : 'warn' ,
detail :
'sentence-transformers/all-MiniLM-L6-v2 (384d) probe failed: connect ECONNREFUSED 127.0.0.1:8765. Semantic lane will be skipped; lexical, dictionary, and token lanes remain available.' ,
2026-05-10 23:51:24 +02:00
fix : ` Run: ktx setup --project-dir ${ tempDir } --no-input ` ,
2026-05-13 15:21:57 -07:00
group : 'search' ,
2026-05-10 23:12:26 +02:00
} ) ;
} ) ;
} ) ;