From 31c3ccea9da9d212412699353856c8406a2b9382 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Thu, 21 May 2026 02:37:33 +0200 Subject: [PATCH] fix(cli): treat omitted sentence-transformers base_url as managed daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After PR #184 and #192 moved managed-embeddings URL resolution to the CLI project boundary and made `ktx setup` persist `ktx.yaml` without a `base_url`, the status command still treated the empty value as misconfiguration and printed "no base_url configured", dragging the verdict down to "Partially ready — embedding credentials missing". Update `buildEmbeddingsStatus` to recognize the managed-daemon convention and report it as ok. Add a `status-project.test.ts` covering the explicit-url, omitted, empty-string, and openai-missing-key paths. --- packages/cli/src/status-project.test.ts | 126 ++++++++++++++++++++++++ packages/cli/src/status-project.ts | 5 +- 2 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/status-project.test.ts diff --git a/packages/cli/src/status-project.test.ts b/packages/cli/src/status-project.test.ts new file mode 100644 index 00000000..749af664 --- /dev/null +++ b/packages/cli/src/status-project.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from 'vitest'; +import { buildDefaultKtxProjectConfig, type KtxLocalProject, type KtxProjectConfig } from '@ktx/context/project'; +import { buildProjectStatus } from './status-project.js'; + +function projectWithConfig(config: KtxProjectConfig): KtxLocalProject { + return { + projectDir: '/work/proj', + configPath: '/work/proj/ktx.yaml', + config, + coreConfig: {} as KtxLocalProject['coreConfig'], + git: {} as KtxLocalProject['git'], + fileStore: {} as KtxLocalProject['fileStore'], + }; +} + +function withEmbeddings( + config: KtxProjectConfig, + embeddings: KtxProjectConfig['ingest']['embeddings'], +): KtxProjectConfig { + return { + ...config, + ingest: { ...config.ingest, embeddings }, + scan: { ...config.scan, enrichment: { ...config.scan.enrichment, embeddings } }, + }; +} + +function withClaudeCodeLlm(config: KtxProjectConfig): KtxProjectConfig { + return { + ...config, + llm: { + ...config.llm, + provider: { backend: 'claude-code' }, + models: { ...config.llm.models, default: 'sonnet' }, + }, + }; +} + +function baseProjectConfig(): KtxProjectConfig { + return withClaudeCodeLlm(buildDefaultKtxProjectConfig()); +} + +const stubClaudeCodeAuthProbe = async () => ({ ok: true as const }); + +describe('buildProjectStatus embeddings', () => { + it('reports sentence-transformers with explicit base_url as ok', async () => { + const project = projectWithConfig( + withEmbeddings(baseProjectConfig(), { + backend: 'sentence-transformers', + model: 'all-MiniLM-L6-v2', + dimensions: 384, + sentenceTransformers: { base_url: 'http://my-st:8080', pathPrefix: '' }, + }), + ); + + const status = await buildProjectStatus(project, { + claudeCodeAuthProbe: stubClaudeCodeAuthProbe, + }); + + expect(status.embeddings).toMatchObject({ + backend: 'sentence-transformers', + status: 'ok', + detail: 'service: http://my-st:8080', + }); + }); + + it('reports sentence-transformers with omitted base_url as managed daemon (ok)', async () => { + const project = projectWithConfig( + withEmbeddings(baseProjectConfig(), { + backend: 'sentence-transformers', + model: 'all-MiniLM-L6-v2', + dimensions: 384, + }), + ); + + const status = await buildProjectStatus(project, { + claudeCodeAuthProbe: stubClaudeCodeAuthProbe, + }); + + expect(status.embeddings).toMatchObject({ + backend: 'sentence-transformers', + status: 'ok', + detail: 'managed local embeddings daemon', + }); + expect(status.verdictReason).not.toMatch(/embedding credentials missing/); + }); + + it('reports sentence-transformers with empty base_url string as managed daemon (ok)', async () => { + const project = projectWithConfig( + withEmbeddings(baseProjectConfig(), { + backend: 'sentence-transformers', + model: 'all-MiniLM-L6-v2', + dimensions: 384, + sentenceTransformers: { base_url: '', pathPrefix: '' }, + }), + ); + + const status = await buildProjectStatus(project, { + claudeCodeAuthProbe: stubClaudeCodeAuthProbe, + }); + + expect(status.embeddings).toMatchObject({ + backend: 'sentence-transformers', + status: 'ok', + detail: 'managed local embeddings daemon', + }); + }); + + it('reports openai backend with missing key as warn', async () => { + const project = projectWithConfig( + withEmbeddings(baseProjectConfig(), { + backend: 'openai', + model: 'text-embedding-3-small', + dimensions: 1536, + openai: { api_key: 'env:OPENAI_API_KEY' }, // pragma: allowlist secret + }), + ); + + const status = await buildProjectStatus(project, { + env: {}, + claudeCodeAuthProbe: stubClaudeCodeAuthProbe, + }); + + expect(status.embeddings.status).toBe('warn'); + expect(status.verdictReason).toMatch(/embedding credentials missing/); + }); +}); diff --git a/packages/cli/src/status-project.ts b/packages/cli/src/status-project.ts index 297229de..8e8662dd 100644 --- a/packages/cli/src/status-project.ts +++ b/packages/cli/src/status-project.ts @@ -267,9 +267,8 @@ function buildEmbeddingsStatus(config: KtxProjectEmbeddingConfig, env: NodeJS.Pr backend, model, dimensions, - status: 'warn', - detail: 'no base_url configured', - fix: 'Rerun `ktx setup`', + status: 'ok', + detail: 'managed local embeddings daemon', }; } return { backend, model, dimensions, status: 'warn', detail: 'unknown embedding backend' };