Merge remote-tracking branch 'origin/main' into explore-connection-mapping

# Conflicts:
#	packages/cli/src/connection.test.ts
This commit is contained in:
Andrey Avtomonov 2026-05-14 17:44:38 +02:00
commit 01680d7b89
70 changed files with 185 additions and 326 deletions

View file

@ -166,7 +166,6 @@ async function writeHistoricSqlProject(project: KtxLocalProject): Promise<KtxLoc
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -231,7 +230,7 @@ describe('historic-SQL local ingest retrieval acceptance', () => {
});
it('projects table and pattern evidence into semantic-layer and wiki retrieval surfaces', async () => {
const initialized = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir: join(tempDir, 'project') });
const project = await writeHistoricSqlProject(initialized);
const sqlAnalysis = acceptanceSqlAnalysis();
const agentRunner = new HistoricSqlAcceptanceAgentRunner();

View file

@ -22,7 +22,7 @@ describe('Metabase YAML source state and discovery cache', () => {
function projectWithMetabaseMappings(mappings: Record<string, unknown>) {
return {
config: {
...buildDefaultKtxProjectConfig('metabase-cache-test'),
...buildDefaultKtxProjectConfig(),
connections: {
'prod-metabase': connectionConfigSchema.parse({
driver: 'metabase',

View file

@ -16,7 +16,7 @@ describe('local ingest adapters', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-adapters-'));
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
project = await loadKtxProject({ projectDir });
});

View file

@ -314,11 +314,10 @@ describe('canonical local ingest', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-full-ingest-'));
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -443,7 +442,6 @@ describe('canonical local ingest', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -521,11 +519,10 @@ describe('canonical local ingest', () => {
it('runs historic-SQL evidence projection through the local bundle post-processor', async () => {
const projectDir = join(tempDir, 'historic-sql-project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -605,11 +602,10 @@ describe('canonical local ingest', () => {
it('rejects direct Metabase scheduled pulls before requiring a local ingest LLM provider', async () => {
const projectDir = join(tempDir, 'metabase-project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -637,7 +633,7 @@ describe('canonical local ingest', () => {
it('runs full MetricFlow local ingest from a dbt repo fixture through the canonical runner', async () => {
const projectDir = join(tempDir, 'metricflow-run-project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
const fixtureDir = join(tempDir, 'metricflow-fixture');
await mkdir(join(fixtureDir, 'models'), { recursive: true });
@ -685,7 +681,6 @@ describe('canonical local ingest', () => {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -767,7 +762,6 @@ describe('canonical local ingest', () => {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: local-mf',
'connections:',
' warehouse:',
' driver: postgres',
@ -801,11 +795,10 @@ describe('canonical local ingest', () => {
it('runs scheduled Looker ingest through the canonical local runner and records SL target evidence', async () => {
const projectDir = join(tempDir, 'looker-project');
await initKtxProject({ projectDir, projectName: 'looker-runtime' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: looker-runtime',
'connections:',
' prod-looker:',
' driver: looker',

View file

@ -24,11 +24,10 @@ describe('createLocalBundleIngestRuntime', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-bundle-runtime-'));
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -149,7 +148,6 @@ describe('createLocalBundleIngestRuntime', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',

View file

@ -17,7 +17,6 @@ async function writeWarehouseConfig(projectDir: string): Promise<void> {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -34,7 +33,6 @@ async function writeLiveDatabaseConfig(projectDir: string): Promise<void> {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -88,7 +86,7 @@ describe('local ingest', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-ingest-'));
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeWarehouseConfig(projectDir);
project = await loadKtxProject({ projectDir });
});
@ -574,7 +572,6 @@ describe('local ingest', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' notion-main:',
' driver: notion',

View file

@ -15,7 +15,7 @@ describe('EntityDetailsTool', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-entity-details-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
await seedLiveDatabaseScan();
tool = new EntityDetailsTool(() => new WarehouseCatalogService({ fileStore: project.fileStore }));
context = {

View file

@ -11,7 +11,7 @@ describe('WarehouseCatalogService', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-warehouse-catalog-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
});
afterEach(async () => {

View file

@ -194,7 +194,7 @@ describe('local KTX embedding config', () => {
it('constructs deterministic embeddings from the default project config', () => {
const createKtxEmbeddingProvider = vi.fn(() => ({}) as never);
const provider = createLocalKtxEmbeddingProviderFromConfig(
buildDefaultKtxProjectConfig('warehouse').ingest.embeddings,
buildDefaultKtxProjectConfig().ingest.embeddings,
{ createKtxEmbeddingProvider },
);

View file

@ -71,7 +71,7 @@ describe('createLocalProjectMcpContextPorts', () => {
}
it('lists local project connections from ktx.yaml', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'env:DATABASE_URL',
@ -84,7 +84,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('tests a local project connection through the native scan connector factory', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'env:DATABASE_URL',
@ -120,7 +120,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('triggers canonical bundle ingest and reads status, report, and replay through MCP ports', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
};
@ -216,7 +216,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('returns child run metadata for local Metabase fan-out triggers', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections = {
'prod-metabase': {
driver: 'metabase',
@ -339,7 +339,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('writes, reads, and searches global wiki pages', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
const ports = createLocalProjectMcpContextPorts(project);
await expect(
@ -383,7 +383,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('writes, lists, reads, and validates semantic-layer sources', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
const ports = createLocalProjectMcpContextPorts(project);
await expect(
@ -449,7 +449,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('returns semantic-layer hybrid search metadata through local project ports', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
await writeLocalSlSource(project, {
connectionId: 'warehouse',
sourceName: 'orders',
@ -518,7 +518,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('returns historic SQL usage frequency and snippet through semantic-layer list search', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
await project.fileStore.writeFile(
'semantic-layer/warehouse/_schema/public.yaml',
`tables:
@ -566,7 +566,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('uses configured local embeddings for semantic-layer search when available', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.ingest.embeddings = { backend: 'none', dimensions: 2 };
await writeLocalSlSource(project, {
connectionId: 'warehouse',
@ -607,7 +607,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('rejects path traversal keys before touching the project directory', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
const ports = createLocalProjectMcpContextPorts(project);
await expect(
@ -626,7 +626,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('uses semantic compute for validation and compile-only sl_query when supplied', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'env:DATABASE_URL',
@ -712,7 +712,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('executes local MCP sl_query when a query executor is configured', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'env:DATABASE_URL',
@ -770,7 +770,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('exposes detailed local ingest trigger and status ports when local ingest is enabled', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = { driver: 'postgres' };
project.config.ingest.adapters = ['fake'];
project.config.ingest.embeddings = {
@ -890,7 +890,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('passes local ingest pull-config options into runLocalIngest', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = { driver: 'postgres' };
project.config.ingest.adapters = ['looker'];
const runLocalIngest = vi.fn(async () => ({
@ -949,7 +949,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('triggers fetch-capable local ingest without sourceDir config', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'postgres://localhost:5432/warehouse',
@ -1024,7 +1024,7 @@ describe('createLocalProjectMcpContextPorts', () => {
});
it('lists and reads only artifacts that belong to a local scan report', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = {
driver: 'postgres',
url: 'env:DATABASE_URL',
@ -1140,6 +1140,6 @@ describe('createLocalProjectMcpContextPorts', () => {
}),
).resolves.toBeNull();
await expect(ports.scan?.listArtifacts?.({ runId: 'missing' })).resolves.toBeNull();
await expect(readFile(join(project.projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse');
await expect(readFile(join(project.projectDir, 'ktx.yaml'), 'utf-8')).resolves.not.toContain('project:');
});
});

View file

@ -168,7 +168,7 @@ describe('createKtxMcpServer', () => {
it('runs MCP memory_capture against a local project memory port', async () => {
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-mcp-local-memory-'));
try {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
const agentRunner = {
runLoop: async ({
toolSet,

View file

@ -89,7 +89,7 @@ describe('createLocalProjectMemoryCapture', () => {
});
it('captures a wiki page through the local memory agent and persists pollable status', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
const agentRunner = {
runLoop: async ({
toolSet,
@ -144,7 +144,7 @@ describe('createLocalProjectMemoryCapture', () => {
});
it('captures a semantic-layer source for a named local connection id', async () => {
const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
const project = await initKtxProject({ projectDir: tempDir });
project.config.connections.warehouse = { driver: 'postgres' };
const agentRunner = {
runLoop: async ({

View file

@ -11,7 +11,6 @@ describe('KTX project config', () => {
it.each(['status', 'replay', 'run', 'watch'])('accepts former ingest subcommand name "%s" as a connection id', (connectionId) => {
expect(
parseKtxProjectConfig(`
project: reserved-test
connections:
${connectionId}:
driver: postgres
@ -24,8 +23,7 @@ connections:
});
it('builds the default standalone project config', () => {
expect(buildDefaultKtxProjectConfig('warehouse')).toEqual({
project: 'warehouse',
expect(buildDefaultKtxProjectConfig()).toEqual({
connections: {},
storage: {
state: 'sqlite',
@ -84,15 +82,14 @@ connections:
});
it('round-trips through YAML with stable defaults', () => {
const serialized = serializeKtxProjectConfig(buildDefaultKtxProjectConfig('warehouse'));
const serialized = serializeKtxProjectConfig(buildDefaultKtxProjectConfig());
const parsed = parseKtxProjectConfig(serialized);
expect(serialized).toContain('project: warehouse');
expect(serialized).not.toContain('project:');
expect(serialized).not.toContain('live-database');
expect(serialized).toContain(
' embeddings:\n backend: deterministic\n model: deterministic\n dimensions: 8',
);
expect(parsed.project).toBe('warehouse');
expect(parsed.ingest.adapters).toEqual([]);
expect(parsed.ingest.embeddings).toEqual({
backend: 'deterministic',
@ -103,7 +100,6 @@ connections:
it('parses and serializes setup warehouse metadata without setup progress', () => {
const config = parseKtxProjectConfig(`
project: revenue
setup:
database_connection_ids:
- warehouse
@ -126,7 +122,6 @@ connections:
it('parses global direct Anthropic LLM config', () => {
const config = parseKtxProjectConfig(`
project: demo
llm:
provider:
backend: anthropic
@ -166,7 +161,6 @@ ingest:
it('parses global Vertex LLM config', () => {
const config = parseKtxProjectConfig(`
project: demo
llm:
provider:
backend: vertex
@ -188,7 +182,6 @@ llm:
it('parses gateway LLM, OpenAI scan embeddings, and sentence-transformers ingest embeddings', () => {
const config = parseKtxProjectConfig(`
project: demo
llm:
provider:
backend: gateway
@ -232,7 +225,6 @@ scan:
it('parses scan relationship settings', () => {
const config = parseKtxProjectConfig(`
project: demo
scan:
relationships:
enabled: false
@ -273,7 +265,6 @@ scan:
it('parses the scan relationship validation budget sentinel', () => {
const config = parseKtxProjectConfig(`
project: demo
scan:
relationships:
validationBudget: all
@ -285,7 +276,6 @@ scan:
it('rejects out-of-range scan relationship numeric settings', () => {
const yaml = `
project: demo
scan:
relationships:
acceptThreshold: 2
@ -316,7 +306,6 @@ scan:
it('rejects invalid scan relationship validation budget strings', () => {
const yaml = `
project: demo
scan:
relationships:
validationBudget: infinite
@ -327,7 +316,6 @@ scan:
it('rejects unsupported local LLM and embedding fields', () => {
expect(() =>
parseKtxProjectConfig(`
project: demo
ingest:
llm:
backend: anthropic
@ -336,7 +324,6 @@ ingest:
expect(() =>
parseKtxProjectConfig(`
project: demo
scan:
enrichment:
backend: gateway
@ -345,7 +332,6 @@ scan:
expect(() =>
parseKtxProjectConfig(`
project: demo
scan:
enrichment:
mode: llm
@ -356,7 +342,6 @@ scan:
expect(() =>
parseKtxProjectConfig(`
project: demo
ingest:
embeddings:
provider: gateway
@ -368,7 +353,6 @@ ingest:
it('rejects gateway embedding configs', () => {
expect(() =>
parseKtxProjectConfig(`
project: demo
ingest:
embeddings:
backend: gateway
@ -379,7 +363,6 @@ ingest:
expect(() =>
parseKtxProjectConfig(`
project: demo
scan:
enrichment:
mode: llm
@ -392,9 +375,9 @@ scan:
});
it('fills optional sections when a minimal config is loaded', () => {
const config = parseKtxProjectConfig('project: local\n');
const config = parseKtxProjectConfig('{}\n');
expect(config).toEqual(buildDefaultKtxProjectConfig('local'));
expect(config).toEqual(buildDefaultKtxProjectConfig());
expect(config.ingest.embeddings).toEqual({
backend: 'deterministic',
model: 'deterministic',
@ -406,14 +389,15 @@ scan:
expect(() => parseKtxProjectConfig('- nope\n')).toThrow('ktx.yaml must contain a YAML object');
});
it('rejects configs with a missing project name', () => {
expect(() => parseKtxProjectConfig('connections: {}\n')).toThrow('ktx.yaml field "project" is required');
it('accepts configs without a project name', () => {
expect(parseKtxProjectConfig('connections: {}\n')).toMatchObject({
connections: {},
});
});
it('rejects unknown top-level fields under strict mode', () => {
expect(() =>
parseKtxProjectConfig(`
project: demo
storrage:
state: sqlite
`),
@ -423,13 +407,12 @@ storrage:
describe('validateKtxProjectConfig', () => {
it('returns ok: true with no issues for a valid config', () => {
const result = validateKtxProjectConfig('project: warehouse\n');
const result = validateKtxProjectConfig('connections: {}\n');
expect(result).toEqual({ ok: true, issues: [] });
});
it('collects every schema issue without throwing', () => {
const result = validateKtxProjectConfig(`
project: ""
storage:
search: not-a-real-backend
scan:
@ -441,7 +424,6 @@ scan:
const paths = result.issues.map((issue) => issue.path);
expect(paths).toEqual(
expect.arrayContaining([
'project',
'storage.search',
'scan.relationships.acceptThreshold',
]),
@ -450,7 +432,6 @@ scan:
it('attaches migration hints for known deprecated keys', () => {
const result = validateKtxProjectConfig(`
project: demo
ingest:
llm:
backend: anthropic
@ -499,18 +480,15 @@ describe('generateKtxProjectConfigJsonSchema', () => {
it('exposes every top-level ktx.yaml section under properties', () => {
const properties = schema.properties as Record<string, unknown>;
expect(Object.keys(properties).sort()).toEqual(
['agent', 'connections', 'ingest', 'llm', 'memory', 'project', 'scan', 'setup', 'storage'].sort(),
);
expect(Object.keys(properties).sort()).toEqual(['agent', 'connections', 'ingest', 'llm', 'memory', 'scan', 'setup', 'storage'].sort());
});
it('marks "project" as required', () => {
expect(schema.required).toEqual(expect.arrayContaining(['project']));
it('does not require any top-level fields', () => {
expect(schema.required).toBeUndefined();
});
it('carries .describe() text on top-level fields', () => {
const properties = schema.properties as Record<string, { description?: string }>;
expect(properties.project?.description).toMatch(/Project identifier/);
expect(properties.llm?.description).toMatch(/LLM/);
expect(properties.scan?.description).toMatch(/Schema-scan/);
});

View file

@ -238,11 +238,6 @@ const memorySchema = z
const ktxProjectConfigSchema = z
.strictObject({
project: z
.string({ error: 'ktx.yaml field "project" is required' })
.trim()
.min(1, 'ktx.yaml field "project" is required')
.describe('Project identifier; used in logs, ktx state files, and as the default workspace name.'),
setup: setupSchema.optional().describe('Setup-wizard state. Written by `ktx setup`; may be omitted.'),
connections: z
.record(z.string(), connectionSchema)
@ -332,8 +327,8 @@ function formatZodError(error: z.ZodError, input: unknown): string {
.join('\n');
}
export function buildDefaultKtxProjectConfig(projectName = 'ktx-project'): KtxProjectConfig {
return ktxProjectConfigSchema.parse({ project: projectName });
export function buildDefaultKtxProjectConfig(): KtxProjectConfig {
return ktxProjectConfigSchema.parse({});
}
export function parseKtxProjectConfig(raw: string): KtxProjectConfig {

View file

@ -20,15 +20,13 @@ describe('KTX local project runtime', () => {
const result = await initKtxProject({
projectDir,
projectName: 'warehouse',
authorName: 'Agent',
authorEmail: 'agent@example.com',
});
expect(result.projectDir).toBe(projectDir);
expect(result.config.project).toBe('warehouse');
expect(result.commitHash).toMatch(/^[0-9a-f]{40}$/);
await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse');
await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.not.toContain('project:');
const gitignore = await readFile(join(projectDir, '.ktx/.gitignore'), 'utf-8');
expect(gitignore).toContain('cache/');
expect(gitignore).toContain('db.sqlite');
@ -46,7 +44,7 @@ describe('KTX local project runtime', () => {
it('loads an initialized project with a working file store', async () => {
const projectDir = join(tempDir, 'warehouse');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
const loaded = await loadKtxProject({ projectDir });
await loaded.fileStore.writeFile(
@ -57,7 +55,6 @@ describe('KTX local project runtime', () => {
'Add revenue page',
);
expect(loaded.config.project).toBe('warehouse');
await expect(loaded.fileStore.readFile('wiki/global/revenue.md')).resolves.toMatchObject({
content: '# Revenue\n',
});
@ -65,16 +62,12 @@ describe('KTX local project runtime', () => {
it('rejects reinitializing an existing project unless force is set', async () => {
const projectDir = join(tempDir, 'warehouse');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await expect(initKtxProject({ projectDir, projectName: 'warehouse' })).rejects.toThrow(
'Project already contains ktx.yaml',
);
await expect(initKtxProject({ projectDir })).rejects.toThrow('Project already contains ktx.yaml');
await expect(initKtxProject({ projectDir, projectName: 'warehouse-v2', force: true })).resolves.toMatchObject({
config: {
project: 'warehouse-v2',
},
await expect(initKtxProject({ projectDir, force: true })).resolves.toMatchObject({
configPath: join(projectDir, 'ktx.yaml'),
});
});
});

View file

@ -7,7 +7,6 @@ import { LocalGitFileStore } from './local-git-file-store.js';
export interface InitKtxProjectOptions {
projectDir: string;
projectName?: string;
force?: boolean;
authorName?: string;
authorEmail?: string;
@ -101,7 +100,7 @@ async function createRuntime(
export async function initKtxProject(options: InitKtxProjectOptions): Promise<InitKtxProjectResult> {
const projectDir = resolve(options.projectDir);
const projectName = options.projectName?.trim() || basename(projectDir) || 'ktx-project';
const projectName = basename(projectDir) || 'ktx-project';
const authorName = options.authorName ?? 'ktx';
const authorEmail = options.authorEmail ?? 'ktx@example.com';
const logger = options.logger ?? noopLogger;
@ -112,7 +111,7 @@ export async function initKtxProject(options: InitKtxProjectOptions): Promise<In
throw new Error(`Project already contains ktx.yaml: ${configPath}`);
}
const config = buildDefaultKtxProjectConfig(projectName);
const config = buildDefaultKtxProjectConfig();
const runtime = await createRuntime(projectDir, config, authorName, authorEmail, logger);
await writeProjectFile(projectDir, 'ktx.yaml', serializeKtxProjectConfig(config));

View file

@ -36,7 +36,7 @@ describe('KTX setup config helpers', () => {
});
it('sets setup database connection ids without duplicates', () => {
const config = buildDefaultKtxProjectConfig('warehouse');
const config = buildDefaultKtxProjectConfig();
const withDatabases = setKtxSetupDatabaseConnectionIds(config, ['warehouse', 'analytics', 'warehouse']);

View file

@ -231,7 +231,6 @@ describe('writeLocalScanEnrichmentArtifacts', () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-enrichment-artifacts-'));
project = await initKtxProject({
projectDir: join(tempDir, 'project'),
projectName: 'warehouse',
});
});

View file

@ -371,7 +371,7 @@ describe('local scan enrichment', () => {
},
},
relationshipSettings: {
...buildDefaultKtxProjectConfig('warehouse').scan.relationships,
...buildDefaultKtxProjectConfig().scan.relationships,
llmProposals: false,
maxLlmTablesPerBatch: 40,
},
@ -383,7 +383,7 @@ describe('local scan enrichment', () => {
it('skips relationship detection when scan relationships are disabled', async () => {
const settings = {
...buildDefaultKtxProjectConfig('warehouse').scan.relationships,
...buildDefaultKtxProjectConfig().scan.relationships,
enabled: false,
};
const result = await runLocalScanEnrichment({
@ -474,7 +474,7 @@ describe('local scan enrichment', () => {
})),
};
const settings = {
...buildDefaultKtxProjectConfig('test').scan.relationships,
...buildDefaultKtxProjectConfig().scan.relationships,
enabled: false,
};

View file

@ -515,8 +515,7 @@ export async function runLocalScanEnrichment(
const now = input.now ?? (() => new Date());
const state = completedKtxScanEnrichmentStateSummary();
const syncId = input.syncId ?? input.context.runId;
const relationshipSettings =
input.relationshipSettings ?? buildDefaultKtxProjectConfig(input.connectionId).scan.relationships;
const relationshipSettings = input.relationshipSettings ?? buildDefaultKtxProjectConfig().scan.relationships;
const inputHash = computeKtxScanEnrichmentInputHash({
snapshot,
mode: input.mode,

View file

@ -105,7 +105,6 @@ async function writeLiveDatabaseConfig(projectDir: string): Promise<void> {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -123,7 +122,6 @@ async function writeDatabaseConfigWithoutIngestAdapters(projectDir: string): Pro
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -184,7 +182,7 @@ describe('local scan', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-scan-'));
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeLiveDatabaseConfig(projectDir);
project = await loadKtxProject({ projectDir });
});
@ -1037,7 +1035,6 @@ describe('local scan', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: postgres',
@ -1393,7 +1390,6 @@ describe('local scan', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: sqlite',
@ -1425,7 +1421,6 @@ describe('local scan', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: mysql',
@ -1457,7 +1452,6 @@ describe('local scan', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: clickhouse',
@ -1492,7 +1486,6 @@ describe('local scan', () => {
await writeFile(
join(project.projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: sqlserver',

View file

@ -13,7 +13,6 @@ describe('readLocalScanStructuralSnapshot', () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-structural-artifacts-'));
project = await initKtxProject({
projectDir: join(tempDir, 'project'),
projectName: 'warehouse',
});
});

View file

@ -19,7 +19,6 @@ async function writeWarehouseConfig(projectDir: string): Promise<void> {
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: sqlite',
@ -67,7 +66,7 @@ function liveDatabaseAdapter(): SourceAdapter {
}
async function createLiveDatabaseRun(projectDir: string, runId: string) {
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeWarehouseConfig(projectDir);
const project = await loadKtxProject({ projectDir });
await runLocalStageOnlyIngest({
@ -283,7 +282,7 @@ describe('local scan relationship artifact reader', () => {
it('returns null when the scan run has no report', async () => {
const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-artifacts-missing-run-'));
try {
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
const project = await loadKtxProject({ projectDir });
await expect(readLocalScanRelationshipArtifacts(project, 'missing-run')).resolves.toBeNull();

View file

@ -243,7 +243,7 @@ function llmProvider(): KtxLlmProvider {
}
function relationshipSettings() {
return buildDefaultKtxProjectConfig('warehouse').scan.relationships;
return buildDefaultKtxProjectConfig().scan.relationships;
}
function llmOnlyRelationshipSnapshot(): KtxSchemaSnapshot {
@ -557,7 +557,7 @@ describe('production relationship discovery', () => {
`);
const settings = {
...buildDefaultKtxProjectConfig('warehouse').scan.relationships,
...buildDefaultKtxProjectConfig().scan.relationships,
acceptThreshold: 0.99,
reviewThreshold: 0.55,
};
@ -633,7 +633,7 @@ describe('production relationship discovery', () => {
schema: snapshotToKtxEnrichedSchema(richSnapshot),
context: { runId: 'candidate-cap' },
settings: {
...buildDefaultKtxProjectConfig('warehouse').scan.relationships,
...buildDefaultKtxProjectConfig().scan.relationships,
maxCandidatesPerColumn: 1,
},
});

View file

@ -202,7 +202,6 @@ async function projectWithDecisions(
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-apply-'));
const project = await initKtxProject({
projectDir: join(tempDir, 'project'),
projectName: 'warehouse',
});
await project.fileStore.writeFile(
'raw-sources/warehouse/live-database/sync-a/enrichment/relationship-review-decisions.json',

View file

@ -19,11 +19,10 @@ async function writeProjectFile(projectDir: string, relativePath: string, conten
}
async function createProject(projectDir: string): Promise<void> {
await initKtxProject({ projectDir, projectName: 'warehouse' });
await initKtxProject({ projectDir });
await writeFile(
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: sqlite',

View file

@ -241,7 +241,7 @@ describe('SQLite hybrid search backend conformance', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-search-conformance-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
dbPath = join(tempDir, '.ktx', 'db.sqlite');
});

View file

@ -13,7 +13,7 @@ describe('compileLocalSlQuery', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-query-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
project.config.connections.warehouse = { driver: 'postgres' };
await project.fileStore.writeFile(
'semantic-layer/warehouse/orders.yaml',

View file

@ -51,7 +51,7 @@ describe('local semantic-layer helpers', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-sl-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
});
afterEach(async () => {

View file

@ -169,7 +169,7 @@ describe('PGlite semantic-layer search prototype', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-sl-prototype-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
project.config.ingest.embeddings.dimensions = 3;
pgliteDataDir = join(tempDir, 'pglite-search');
port = await allocatePort();

View file

@ -11,7 +11,7 @@ describe('loadLatestSlDictionaryEntries', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-sl-dictionary-profile-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
});
afterEach(async () => {

View file

@ -28,7 +28,7 @@ describe('local knowledge helpers', () => {
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-knowledge-'));
project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' });
project = await initKtxProject({ projectDir: join(tempDir, 'project') });
});
afterEach(async () => {