fix(cli): treat omitted sentence-transformers base_url as managed daemon (#194)

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.
This commit is contained in:
Andrey Avtomonov 2026-05-21 02:38:34 +02:00 committed by GitHub
parent 0958bc03dc
commit 9fc715ac6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 128 additions and 3 deletions

View file

@ -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/);
});
});

View file

@ -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' };