refactor: enforce ktx naming and AGENTS.md compliance sweep (#289)

Align the tree with AGENTS.md/CLAUDE.md conventions:

- Rewrite user-facing strings, docs, and tests to lowercase `ktx`
  (no bare uppercase `KTX` tokens remain outside literal identifiers).
- Drop the legacy `historicSql` migration path and its now-unused
  helpers, per the no-backward-compat rule.
- Remove `as unknown as` / `any` casts: narrow `BaseTool` generics to
  `z.ZodObject`, add a typed `createLookerClient`, and delete the dead
  `getParametersSchema`/`toAnthropicFormat` pre-AI-SDK helpers.
- Use `InvalidArgumentError` for Commander parse failures.
- Finish the adapter→connector prose conversion in the `ktx.yaml` docs
  while keeping the literal `adapters` config key.
This commit is contained in:
Andrey Avtomonov 2026-06-11 13:49:45 +02:00 committed by GitHub
parent 005c5fc860
commit 00cdf2de90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
237 changed files with 844 additions and 974 deletions

View file

@ -82,7 +82,7 @@ describe('admin Commander tree', () => {
try {
await expect(runKtxCli(['admin', 'init', projectDir], testIo.io)).resolves.toBe(0);
expect(testIo.stdout()).toContain(`Initialized KTX project at ${projectDir}`);
expect(testIo.stdout()).toContain(`Initialized ktx project at ${projectDir}`);
await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.not.toContain('project:');
expect(testIo.stderr()).toBe('');
} finally {
@ -103,14 +103,14 @@ describe('admin Commander tree', () => {
runKtxCli(['--project-dir', projectDir, 'admin', 'init'], testIo.io),
).resolves.toBe(0);
expect(testIo.stdout()).toContain(`Initialized KTX project at ${projectDir}`);
expect(testIo.stdout()).toContain(`Initialized ktx project at ${projectDir}`);
expect(testIo.stderr()).toBe('');
} finally {
await rm(tempDir, { recursive: true, force: true });
}
});
it('prints config schema without requiring a KTX project directory', async () => {
it('prints config schema without requiring a ktx project directory', async () => {
const { mkdtemp, rm } = await import('node:fs/promises');
const { tmpdir } = await import('node:os');
const { join } = await import('node:path');

View file

@ -47,7 +47,7 @@ describe('walkCommandTree', () => {
it('captures required, optional, and variadic arguments', () => {
const command = new Command('scan')
.argument('<connectionId>', 'KTX connection id')
.argument('<connectionId>', 'ktx connection id')
.argument('[schemas...]', 'Schemas');
expect(walkCommandTree(command).arguments).toEqual(['<connectionId>', '[schemas...]']);
@ -56,7 +56,7 @@ describe('walkCommandTree', () => {
it('walks registered commands without applying hidden-command policy', () => {
const root = new Command('ktx');
root.command('scan', { hidden: true }).description('Run a standalone connection scan');
const ingest = root.command('ingest').description('Build or inspect KTX context');
const ingest = root.command('ingest').description('Build or inspect ktx context');
ingest.command('run', { hidden: true }).description('Run local ingest by adapter');
ingest.command('watch', { hidden: true }).description('Open a stored visual report');
ingest.command('status').description('Print status');

View file

@ -51,7 +51,7 @@ describe('registerMcpCommands', () => {
registerMcpCommands(program, context);
await expect(program.parseAsync(['mcp', 'start', '--host', '0.0.0.0'], { from: 'user' })).rejects.toThrow(
'Binding KTX MCP to 0.0.0.0 requires --token or KTX_MCP_TOKEN',
'Binding ktx MCP to 0.0.0.0 requires --token or KTX_MCP_TOKEN',
);
expect(startDaemon).not.toHaveBeenCalled();
});
@ -80,11 +80,11 @@ describe('registerMcpCommands', () => {
expect(startDaemon).toHaveBeenCalledTimes(1);
expect(context.io.stdout.write).toHaveBeenCalledWith(
[
'KTX MCP daemon already running: http://127.0.0.1:7878/mcp',
'ktx MCP daemon already running: http://127.0.0.1:7878/mcp',
'',
'KTX is ready for configured agents.',
'Open your agent for this KTX project and ask a data question, for example:',
' "Use KTX to show me the available tables and metrics."',
'ktx is ready for configured agents.',
'Open your agent for this ktx project and ask a data question, for example:',
' "Use ktx to show me the available tables and metrics."',
'',
].join('\n'),
);
@ -112,10 +112,10 @@ describe('registerMcpCommands', () => {
await program.parseAsync(['--project-dir', '/tmp/ktx-started', 'mcp', 'start'], { from: 'user' });
expect(context.io.stdout.write).toHaveBeenCalledWith(
expect.stringContaining('KTX MCP daemon started: http://127.0.0.1:7878/mcp\n\nKTX is ready for configured agents.'),
expect.stringContaining('ktx MCP daemon started: http://127.0.0.1:7878/mcp\n\nktx is ready for configured agents.'),
);
expect(context.io.stdout.write).toHaveBeenCalledWith(
expect.stringContaining('"Use KTX to show me the available tables and metrics."'),
expect.stringContaining('"Use ktx to show me the available tables and metrics."'),
);
});

View file

@ -13,7 +13,7 @@ describe('KtxClickHouseDialect', () => {
expect(dialect.formatTableName({ catalog: null, db: null, name: 'events' })).toBe('`events`');
});
it('maps nullable and low-cardinality ClickHouse types to KTX dimension types', () => {
it('maps nullable and low-cardinality ClickHouse types to ktx dimension types', () => {
expect(dialect.mapToDimensionType('Nullable(DateTime64(3))')).toBe('time');
expect(dialect.mapToDimensionType('LowCardinality(Nullable(String))')).toBe('string');
expect(dialect.mapToDimensionType('UInt64')).toBe('number');

View file

@ -13,7 +13,7 @@ describe('KtxMysqlDialect', () => {
expect(dialect.formatTableName({ catalog: null, db: null, name: 'orders' })).toBe('`orders`');
});
it('maps native MySQL types to KTX dimension types', () => {
it('maps native MySQL types to ktx dimension types', () => {
expect(dialect.mapToDimensionType('tinyint(1)')).toBe('boolean');
expect(dialect.mapToDimensionType('int')).toBe('number');
expect(dialect.mapToDimensionType('decimal(10,2)')).toBe('number');

View file

@ -10,7 +10,7 @@ describe('KtxPostgresDialect', () => {
expect(dialect.formatTableName({ catalog: null, db: null, name: 'orders' })).toBe('"orders"');
});
it('maps native PostgreSQL types to KTX dimension types', () => {
it('maps native PostgreSQL types to ktx dimension types', () => {
expect(dialect.mapToDimensionType('timestamp with time zone')).toBe('time');
expect(dialect.mapToDimensionType('numeric(12,2)')).toBe('number');
expect(dialect.mapToDimensionType('uuid')).toBe('string');

View file

@ -10,7 +10,7 @@ describe('KtxSqliteDialect', () => {
expect(dialect.formatTableName({ catalog: 'ignored', db: 'ignored', name: 'orders' })).toBe('"orders"');
});
it('maps native SQLite types to KTX dimension types', () => {
it('maps native SQLite types to ktx dimension types', () => {
expect(dialect.mapToDimensionType('INTEGER')).toBe('number');
expect(dialect.mapToDimensionType('numeric(10,2)')).toBe('number');
expect(dialect.mapToDimensionType('timestamp')).toBe('time');

View file

@ -13,7 +13,7 @@ describe('KtxSqlServerDialect', () => {
expect(dialect.formatTableName({ catalog: null, db: null, name: 'events' })).toBe('[events]');
});
it('maps SQL Server types to KTX dimension types', () => {
it('maps SQL Server types to ktx dimension types', () => {
expect(dialect.mapToDimensionType('datetime2')).toBe('time');
expect(dialect.mapToDimensionType('decimal(18, 2)')).toBe('number');
expect(dialect.mapToDimensionType('bigint')).toBe('number');

View file

@ -98,7 +98,7 @@ describe('extractProgressMessage', () => {
});
it('returns null for non-progress output', () => {
expect(extractProgressMessage('KTX scan completed\n')).toBeNull();
expect(extractProgressMessage('ktx scan completed\n')).toBeNull();
});
});
@ -165,7 +165,7 @@ describe('renderContextBuildView', () => {
]);
const output = renderContextBuildView(state, { styled: false });
expect(output).toContain('Building KTX context');
expect(output).toContain('Building ktx context');
expect(output).toContain('(0/2)');
expect(output).toContain('○');
expect(output).toContain('Databases:');
@ -271,7 +271,7 @@ describe('renderContextBuildView', () => {
const output = renderContextBuildView(state, { styled: false });
const lines = output.split('\n');
const headerLine = lines.find((l) => l.includes('Building KTX context'))!;
const headerLine = lines.find((l) => l.includes('Building ktx context'))!;
const separatorLine = lines.find((l) => /^─+$/.test(l))!;
expect(separatorLine.length).toBeGreaterThanOrEqual(headerLine.length);
});
@ -394,11 +394,11 @@ describe('renderContextBuildView', () => {
{ connectionId: 'warehouse', driver: 'postgres', operation: 'database-ingest', debugCommand: '', steps: ['database-schema'] },
]);
state.primarySources[0].status = 'failed';
state.primarySources[0].failureText = 'KTX lost its connection to PostgreSQL while reading schema for warehouse.';
state.primarySources[0].failureText = 'ktx lost its connection to PostgreSQL while reading schema for warehouse.';
const output = renderContextBuildView(state, { styled: false });
expect(output).toContain('✗');
expect(output).toContain('KTX lost its connection to PostgreSQL while reading schema for warehouse.');
expect(output).toContain('ktx lost its connection to PostgreSQL while reading schema for warehouse.');
});
it('omits empty groups', () => {
@ -897,7 +897,7 @@ describe('runContextBuild', () => {
);
expect(result).toEqual({ exitCode: 1 });
expect(io.stdout()).toContain('KTX lost its connection to PostgreSQL while reading schema for warehouse.');
expect(io.stdout()).toContain('ktx lost its connection to PostgreSQL while reading schema for warehouse.');
expect(io.stdout()).toContain('network address unavailable (EADDRNOTAVAIL)');
expect(io.stdout()).toContain('Retry: ktx setup --project-dir /tmp/project');
expect(io.stdout()).not.toContain('BoundPool');
@ -931,11 +931,11 @@ describe('runContextBuild', () => {
expect(result).toEqual({ exitCode: 1 });
expect(io.stdout()).toContain(
'KTX could not reach the local SQL analysis runtime while processing query history for warehouse.',
'ktx could not reach the local SQL analysis runtime while processing query history for warehouse.',
);
expect(io.stdout()).toContain('connection refused (ECONNREFUSED)');
expect(io.stdout()).toContain('Retry: ktx setup --project-dir /tmp/project');
expect(io.stdout()).not.toContain('KTX lost its connection to PostgreSQL');
expect(io.stdout()).not.toContain('ktx lost its connection to PostgreSQL');
});
it('uses captured query-history stderr instead of generic failed-at detail', async () => {
@ -944,11 +944,11 @@ describe('runContextBuild', () => {
warehouse: { driver: 'postgres', context: { queryHistory: { enabled: true } } },
});
const executeTarget = vi.fn(async (target, _args, targetIo) => {
targetIo.stdout.write('KTX scan completed\n');
targetIo.stdout.write('ktx scan completed\n');
targetIo.stdout.write('Mode: enriched\n');
targetIo.stderr.write('Missing bundled Python runtime manifest: /tmp/assets/python/manifest.json\n');
targetIo.stderr.write('In a source checkout, build the local runtime assets with: pnpm run artifacts:build\n');
targetIo.stderr.write('Then retry the runtime-backed KTX command.\n');
targetIo.stderr.write('Then retry the runtime-backed ktx command.\n');
return {
connectionId: target.connectionId,
driver: target.driver,
@ -976,7 +976,7 @@ describe('runContextBuild', () => {
expect(result).toEqual({ exitCode: 1 });
expect(io.stdout()).toContain('Missing bundled Python runtime manifest: /tmp/assets/python/manifest.json.');
expect(io.stdout()).toContain('Retry: ktx ingest warehouse --project-dir /tmp/project --query-history');
expect(io.stdout()).not.toContain('Then retry the runtime-backed KTX command');
expect(io.stdout()).not.toContain('Then retry the runtime-backed ktx command');
expect(io.stdout()).not.toContain('warehouse failed at query-history');
expect(io.stdout().match(/Retry: /g)).toHaveLength(1);
});
@ -999,7 +999,7 @@ describe('runContextBuild', () => {
);
expect(result).toEqual({ exitCode: 1 });
expect(io.stdout()).toContain('KTX lost its connection to PostgreSQL while reading schema for warehouse.');
expect(io.stdout()).toContain('ktx lost its connection to PostgreSQL while reading schema for warehouse.');
expect(io.stdout()).toContain('connection reset (ECONNRESET)');
});
@ -1149,7 +1149,7 @@ describe('runContextBuild', () => {
);
const output = io.stdout();
expect(output).toContain('Building KTX context');
expect(output).toContain('Building ktx context');
expect(output).toContain('Project: /tmp/project');
expect(output).toContain('Databases:');
expect(output).toContain('warehouse');
@ -1397,7 +1397,7 @@ describe('viewStateFromSourceProgress', () => {
);
const output = renderContextBuildView(state, { styled: false });
expect(output).toContain('Building KTX context');
expect(output).toContain('Building ktx context');
expect(output).toContain('Databases:');
expect(output).toContain('warehouse');
expect(output).toContain('42 tables');

View file

@ -4,7 +4,7 @@ import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { resolveKtxConfigReference, resolveKtxHomePath } from '../../../src/context/core/config-reference.js';
describe('KTX config references', () => {
describe('ktx config references', () => {
it('resolves env references without returning empty values', () => {
expect(resolveKtxConfigReference('env:AI_GATEWAY_API_KEY', { AI_GATEWAY_API_KEY: ' gateway-key ' })).toBe(
'gateway-key',

View file

@ -165,7 +165,7 @@ async function writeHistoricSqlProject(project: KtxLocalProject): Promise<KtxLoc
' state: sqlite',
' search: sqlite-fts5',
' git:',
' author: KTX Test <system@ktx.local>',
' author: ktx Test <system@ktx.local>',
'',
].join('\n'),
'utf-8',
@ -193,7 +193,7 @@ async function writeHistoricSqlProject(project: KtxLocalProject): Promise<KtxLoc
},
},
}),
'KTX Test',
'ktx Test',
'system@ktx.local',
'Seed schema shard',
);

View file

@ -29,7 +29,7 @@ function sdk(): LookerSdkPort {
}
describe('DefaultLookerConnectionClientFactory', () => {
it('resolves credentials by Looker connection id and creates a KTX Looker client', async () => {
it('resolves credentials by Looker connection id and creates a ktx Looker client', async () => {
const fakeSdk = sdk();
const resolver: LookerCredentialResolver = {
resolve: vi.fn().mockResolvedValue({

View file

@ -69,7 +69,7 @@ describe('discoverLookerConnections', () => {
});
describe('looker dialect and target validation helpers', () => {
it('maps Looker dialect names to KTX connection types', () => {
it('maps Looker dialect names to ktx connection types', () => {
expect(lookerDialectToConnectionType('bigquery_standard_sql')).toBe('BIGQUERY');
expect(lookerDialectToConnectionType('postgres')).toBe('POSTGRESQL');
expect(lookerDialectToConnectionType('mssql')).toBeNull();
@ -224,7 +224,7 @@ describe('computeLookerMappingDrift and validateLookerMappings', () => {
).toEqual({
ok: false,
errors: [
{ key: 'b2b_sandbox_bq', reason: 'KTX connection missing does not exist' },
{ key: 'b2b_sandbox_bq', reason: 'ktx connection missing does not exist' },
{
key: 'pg_runtime',
reason: 'Connection type LOOKER cannot be used as a Looker warehouse mapping target',
@ -259,7 +259,7 @@ describe('collectExploreParseItems and projectParsedIdentifier', () => {
});
});
it('projects successful and failed parser rows into KTX parsed target tables', () => {
it('projects successful and failed parser rows into ktx parsed target tables', () => {
expect(
projectParsedIdentifier({
ok: true,

View file

@ -255,7 +255,7 @@ describe('Looker staged runtime schemas', () => {
});
});
it('accepts slug-shaped connection ids inside KTX Looker runtime schemas', () => {
it('accepts slug-shaped connection ids inside ktx Looker runtime schemas', () => {
const parsedTargetTable = {
ok: true as const,
catalog: 'proj',
@ -313,7 +313,7 @@ describe('Looker staged runtime schemas', () => {
});
});
it('rejects unsafe KTX Looker connection ids', () => {
it('rejects unsafe ktx Looker connection ids', () => {
expect(() =>
parseLookerPullConfig({
lookerConnectionId: '../prod-looker',

View file

@ -9,8 +9,8 @@ async function readMetabaseFile(name: string): Promise<string> {
return readFile(join(metabaseDir, name), 'utf-8');
}
describe('KTX Metabase client boundary', () => {
it('keeps NestJS, server data-source base classes, and server-relative imports out of the KTX client', async () => {
describe('ktx Metabase client boundary', () => {
it('keeps NestJS, server data-source base classes, and server-relative imports out of the ktx client', async () => {
const client = await readMetabaseFile('client.ts');
expect(client).not.toContain(`@${'nestjs'}`);
expect(client).not.toContain(`DataSource${'Client'}`);
@ -19,7 +19,7 @@ describe('KTX Metabase client boundary', () => {
expect(client).not.toContain('../../types/brand');
});
it('keeps proxy implementation code out of the KTX v1 client', async () => {
it('keeps proxy implementation code out of the ktx v1 client', async () => {
const client = await readMetabaseFile('client.ts');
expect(client).not.toContain(`network-${'proxy'}`);
expect(client).not.toContain(`ssh${'2'}`);

View file

@ -232,7 +232,7 @@ describe('MetabaseClient admin auth helpers', () => {
);
await expect(client.getPermissionGroups()).resolves.toEqual([{ id: 2, name: 'Administrators' }]);
await expect(client.createApiKey({ name: 'KTX CLI test', groupId: 2 })).resolves.toBe(mintedMetabaseCredential);
await expect(client.createApiKey({ name: 'ktx CLI test', groupId: 2 })).resolves.toBe(mintedMetabaseCredential);
expect(fetchMock).toHaveBeenNthCalledWith(
1,
@ -247,7 +247,7 @@ describe('MetabaseClient admin auth helpers', () => {
'https://metabase.example.test/api/api-key',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ name: 'KTX CLI test', group_id: 2 }),
body: JSON.stringify({ name: 'ktx CLI test', group_id: 2 }),
}),
);
});

View file

@ -57,7 +57,7 @@ describe('metabaseRuntimeConfigFromLocalConnection', () => {
};
expect(() => metabaseRuntimeConfigFromLocalConnection('prod-metabase', connection)).toThrow(
'Standalone KTX does not support proxy-bearing Metabase connections yet',
'Standalone ktx does not support proxy-bearing Metabase connections yet',
);
});

View file

@ -87,7 +87,7 @@ describe('validateMetabaseMappings', () => {
}),
).toEqual({
ok: false,
errors: [{ key: '2', reason: 'KTX connection missing-target does not exist' }],
errors: [{ key: '2', reason: 'ktx connection missing-target does not exist' }],
});
});
});
@ -149,7 +149,7 @@ describe('validateMappingPhysicalMatch', () => {
).toBeNull();
});
it('returns null for unknown engines because KTX cannot validate them', () => {
it('returns null for unknown engines because ktx cannot validate them', () => {
expect(
validateMappingPhysicalMatch(
{ metabaseEngine: 'unknown-engine', metabaseDbName: 'X', metabaseHost: 'host' },
@ -177,7 +177,7 @@ describe('computeMetabaseMappingPhysicalMismatches', () => {
).toEqual([
{
mappingId: 'mapping-bad',
reason: "Metabase database 'app' does not match KTX connection database 'other_app'",
reason: "Metabase database 'app' does not match ktx connection database 'other_app'",
},
]);
});
@ -216,7 +216,7 @@ describe('refreshMetabaseMapping', () => {
physicalMismatches: [
{
mappingId: '2',
reason: "Metabase database 'analytics' does not match KTX connection database 'wrong_database'",
reason: "Metabase database 'analytics' does not match ktx connection database 'wrong_database'",
},
],
});
@ -282,7 +282,7 @@ describe('findBestMatch', () => {
});
describe('METABASE_ENGINE_TO_CONNECTION_TYPE', () => {
it('keeps the server-supported Metabase engine table in KTX', () => {
it('keeps the server-supported Metabase engine table in ktx', () => {
expect(METABASE_ENGINE_TO_CONNECTION_TYPE).toMatchObject({
postgres: 'POSTGRESQL',
bigquery: 'BIGQUERY',

View file

@ -213,7 +213,7 @@ function makeDeps(
lockingService: { withLock: vi.fn(async (_key, fn) => fn()) },
storage: {
homeDir: join(runtime.configDir, '.ktx'),
systemGitAuthor: { name: 'KTX Test', email: 'system@ktx.local' },
systemGitAuthor: { name: 'ktx Test', email: 'system@ktx.local' },
resolveUploadDir: (id) => join(runtime.homeDir, 'upload', id),
resolvePullDir: (id) => join(runtime.homeDir, 'pull', id),
resolveTranscriptDir: (id) => join(runtime.configDir, '.ktx/ingest-transcripts', id),
@ -308,7 +308,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/custom-isolated.md'],
'custom wiki',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -395,7 +395,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/legacy-isolated.md'],
'legacy isolated wiki',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -486,7 +486,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/good.yaml'],
'test: add good source',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -504,7 +504,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/bad.yaml'],
'test: add bad source',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -606,7 +606,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
[`wiki/global/${sourceKey}-isolated.md`],
`${sourceKey} wiki`,
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -682,7 +682,7 @@ describe('IngestBundleRunner isolated diff path', () => {
'---\nsummary: Account segments\nusage_mode: auto\nsl_refs:\n - mart_account_segments\n---\n\nARR is `mart_account_segments.total_contract_arr_cents`.\n',
);
currentSession.actions.push({ target: 'wiki', type: 'created', key: 'account-segments', detail: 'Account segments' });
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'wu wiki', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'wu wiki', 'ktx Test', 'system@ktx.local');
}
if (params.telemetryTags.unitKey === 'card-source') {
await writeFile(
@ -697,7 +697,7 @@ describe('IngestBundleRunner isolated diff path', () => {
detail: 'Dollar measure',
targetConnectionId: 'warehouse',
});
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/mart_account_segments.yaml'], 'wu source', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/mart_account_segments.yaml'], 'wu source', 'ktx Test', 'system@ktx.local');
}
return { stopReason: 'natural' };
}) as never;
@ -740,7 +740,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await runtime.git.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml', 'wiki/global/account-segments.md'],
'seed existing wiki body ref',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
const preRunHead = await runtime.git.revParseHead();
@ -773,7 +773,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
'wu source rename',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -903,7 +903,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await mkdir(join(root, 'wiki/global'), { recursive: true });
await writeFile(join(root, `wiki/global/${unitKey}.md`), `---\nsummary: ${unitKey}\nusage_mode: auto\n---\n\n${unitKey}\n`);
currentSession.actions.push({ target: 'wiki', type: 'created', key: unitKey, detail: unitKey });
await currentSession.gitService.commitFiles([`wiki/global/${unitKey}.md`], `wu ${unitKey}`, 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles([`wiki/global/${unitKey}.md`], `wu ${unitKey}`, 'ktx Test', 'system@ktx.local');
return { stopReason: 'natural' };
}) as never;
const runner = new IngestBundleRunner(deps);
@ -950,7 +950,7 @@ describe('IngestBundleRunner isolated diff path', () => {
);
addTouchedSlSource(currentSession.touchedSlSources, 'warehouse', 'orders');
currentSession.actions.push({ target: 'sl', type: 'updated', key: 'orders', detail: suffix, targetConnectionId: 'warehouse' });
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/orders.yaml'], `wu ${suffix}`, 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/orders.yaml'], `wu ${suffix}`, 'ktx Test', 'system@ktx.local');
return { stopReason: 'natural' };
}) as never;
const runner = new IngestBundleRunner(deps);
@ -1006,7 +1006,7 @@ describe('IngestBundleRunner isolated diff path', () => {
'---\nsummary: Projected orders\nusage_mode: auto\nsl_refs:\n - mart_account_segments\n---\n\nARR `mart_account_segments.total_contract_arr`.\n',
);
currentSession.actions.push({ target: 'wiki', type: 'created', key: 'projected-orders', detail: 'Projected orders' });
await currentSession.gitService.commitFiles(['wiki/global/projected-orders.md'], 'wu projected wiki', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/projected-orders.md'], 'wu projected wiki', 'ktx Test', 'system@ktx.local');
return { stopReason: 'natural' };
}) as never;
const runner = new IngestBundleRunner(deps);
@ -1042,7 +1042,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await mkdir(join(root, 'wiki/global'), { recursive: true });
await writeFile(join(root, 'wiki/global/notion-page.md'), '---\nsummary: Notion page\nusage_mode: auto\nsl_refs:\n - missing_source\n---\n\nBody\n');
currentSession.actions.push({ target: 'wiki', type: 'created', key: 'notion-page', detail: 'Notion page' });
await currentSession.gitService.commitFiles(['wiki/global/notion-page.md'], 'wu notion', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/notion-page.md'], 'wu notion', 'ktx Test', 'system@ktx.local');
return { stopReason: 'natural' };
}) as never;
const runner = new IngestBundleRunner(deps);
@ -1088,7 +1088,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
'wu source',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
} else {
@ -1104,7 +1104,7 @@ describe('IngestBundleRunner isolated diff path', () => {
detail: 'Stale reconcile wiki page',
rawPaths: ['cards/source.json'],
});
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'reconcile wiki', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'reconcile wiki', 'ktx Test', 'system@ktx.local');
}
return { stopReason: 'natural' };
}) as never;
@ -1167,7 +1167,7 @@ describe('IngestBundleRunner isolated diff path', () => {
detail: 'Account segments',
rawPaths: ['cards/wiki.json'],
});
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'wu wiki', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/account-segments.md'], 'wu wiki', 'ktx Test', 'system@ktx.local');
}
if (params.telemetryTags.unitKey === 'card-source') {
await mkdir(join(root, 'semantic-layer/warehouse'), { recursive: true });
@ -1187,7 +1187,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
'wu source',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -1302,7 +1302,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml', 'wiki/global/account-segments.md'],
'valid artifacts with invalid provenance',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -1444,7 +1444,7 @@ describe('IngestBundleRunner isolated diff path', () => {
'name: orders\ngrain: [id]\ncolumns: [{name: id, type: string}]\njoins: []\nmeasures: []\n',
);
currentSession.actions.push({ target: 'sl', type: 'created', key: 'orders', detail: 'forbidden', targetConnectionId: 'warehouse' });
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/orders.yaml'], 'forbidden sl', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['semantic-layer/warehouse/orders.yaml'], 'forbidden sl', 'ktx Test', 'system@ktx.local');
return { stopReason: 'natural' };
}) as never;
const runner = new IngestBundleRunner(deps);
@ -1469,7 +1469,7 @@ describe('IngestBundleRunner isolated diff path', () => {
join(runtime.configDir, 'wiki/global/source-page.md'),
'---\nsummary: Source page\nusage_mode: auto\n---\n\nSource page\n',
);
await runtime.git.commitFiles(['wiki/global/source-page.md'], 'seed source page', 'KTX Test', 'system@ktx.local');
await runtime.git.commitFiles(['wiki/global/source-page.md'], 'seed source page', 'ktx Test', 'system@ktx.local');
const preRunHead = await runtime.git.revParseHead();
const { deps, adapter } = makeDeps(runtime);
adapter.chunk.mockResolvedValue({
@ -1501,7 +1501,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/account-segments.md'],
'wu page ref',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -1517,7 +1517,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/source-page.md'],
'wu delete source page',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -1582,7 +1582,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await runtime.git.commitFiles(
['wiki/global/source-page.md', 'wiki/global/account-segments.md'],
'seed inbound wiki refs',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
const preRunHead = await runtime.git.revParseHead();
@ -1613,7 +1613,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/source-page.md'],
'wu delete target page',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -1751,7 +1751,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/finance/orders.yaml'],
'wu unauthorized target',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -1822,7 +1822,7 @@ describe('IngestBundleRunner isolated diff path', () => {
detail: 'Valid page',
rawPaths: ['pages/source.json'],
});
await currentSession.gitService.commitFiles(['wiki/global/valid-page.md'], 'wu valid page', 'KTX Test', 'system@ktx.local');
await currentSession.gitService.commitFiles(['wiki/global/valid-page.md'], 'wu valid page', 'ktx Test', 'system@ktx.local');
} else {
await mkdir(join(root, 'semantic-layer/finance'), { recursive: true });
await writeFile(
@ -1841,7 +1841,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/finance/reconcile_orders.yaml'],
'reconcile unauthorized target',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
}
@ -1959,7 +1959,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
`wu ${params.telemetryTags.unitKey}`,
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' };
@ -2025,7 +2025,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await runtime.git.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml', 'wiki/global/account-segments.md'],
'seed stale wiki body ref',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
@ -2073,7 +2073,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
'wu source rename',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' as const };
@ -2128,7 +2128,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await runtime.git.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml', 'wiki/global/account-segments.md'],
'seed stale wiki body ref',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
const preRunHead = await runtime.git.revParseHead();
@ -2168,7 +2168,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['semantic-layer/warehouse/mart_account_segments.yaml'],
'wu source rename',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' as const };
@ -2302,7 +2302,7 @@ describe('IngestBundleRunner isolated diff path', () => {
await currentSession.gitService.commitFiles(
['wiki/global/orders.md'],
'wu orders',
'KTX Test',
'ktx Test',
'system@ktx.local',
);
return { stopReason: 'natural' as const };

View file

@ -261,7 +261,7 @@ const buildRunner = (deps: ReturnType<typeof makeDeps> = makeDeps(), overrides:
lockingService: deps.lockingService as any,
storage: {
homeDir: '/tmp/ktx-test',
systemGitAuthor: { name: 'KTX Test', email: 'system@ktx.local' },
systemGitAuthor: { name: 'ktx Test', email: 'system@ktx.local' },
resolveUploadDir: (uploadId) => `/tmp/ktx-test/ingest-uploads/${uploadId}`,
resolvePullDir: (jobId) => `/tmp/ktx-test/ingest-pulls/${jobId}`,
resolveTranscriptDir: (jobId) => `/tmp/ktx-test/run/wu-transcripts/${jobId}`,
@ -1518,7 +1518,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => {
...(buildRunner(deps) as any).deps,
storage: {
homeDir: tempRoot,
systemGitAuthor: { name: 'KTX Test', email: 'system@ktx.local' },
systemGitAuthor: { name: 'ktx Test', email: 'system@ktx.local' },
resolveUploadDir: (uploadId: string) => join(tempRoot, 'ingest-uploads', uploadId),
resolvePullDir: (jobId: string) => join(tempRoot, 'ingest-pulls', jobId),
resolveTranscriptDir: (jobId: string) => join(tempRoot, 'run', 'wu-transcripts', jobId),

View file

@ -18,14 +18,14 @@ describe('ingest prompt assets', () => {
expect(prompt).toContain('Do not create a duplicate contested artifact');
});
it('uses product-neutral KTX runtime wording', async () => {
it('uses product-neutral ktx runtime wording', async () => {
const prompt = await readFile(
new URL('../../../src/prompts/memory_agent_bundle_ingest_work_unit.md', import.meta.url),
'utf-8',
);
expect(prompt).toContain('KTX semantic-layer sources and/or knowledge wiki pages');
expect(prompt).toContain('maps cleanly to KTX');
expect(prompt).toContain('ktx semantic-layer sources and/or knowledge wiki pages');
expect(prompt).toContain('maps cleanly to ktx');
expect(prompt).not.toMatch(forbiddenProductPattern());
});

View file

@ -34,7 +34,7 @@ function forbiddenProductPattern() {
}
describe('ingest runtime assets', () => {
it('resolves every reusable ingest skill from packaged KTX assets without server fallback', async () => {
it('resolves every reusable ingest skill from packaged ktx assets without server fallback', async () => {
const registry = new SkillsRegistryService({ skillsDir });
const expected = [...new Set([...adapterSkillNames, ...adapterReconcileSkillNames])].sort();
@ -48,7 +48,7 @@ describe('ingest runtime assets', () => {
}
});
it('loads page-triage and light-extraction prompts from packaged KTX prompt assets', async () => {
it('loads page-triage and light-extraction prompts from packaged ktx prompt assets', async () => {
const prompts = new PromptService({ promptsDir, partials: [] });
for (const promptName of pageTriagePromptNames) {
@ -61,7 +61,7 @@ describe('ingest runtime assets', () => {
await expect(prompts.loadPrompt('skills/light_extraction')).resolves.toContain('# Light Context Extraction');
});
it('packages historic-SQL table digest guidance from KTX assets', async () => {
it('packages historic-SQL table digest guidance from ktx assets', async () => {
const registry = new SkillsRegistryService({ skillsDir });
const skills = await registry.listSkills(['historic_sql_table_digest'], 'memory_agent');
@ -77,7 +77,7 @@ describe('ingest runtime assets', () => {
expect(body).not.toMatch(forbiddenProductPattern());
});
it('packages historic-SQL patterns guidance from KTX assets', async () => {
it('packages historic-SQL patterns guidance from ktx assets', async () => {
const registry = new SkillsRegistryService({ skillsDir });
const skills = await registry.listSkills(['historic_sql_patterns'], 'memory_agent');

View file

@ -49,7 +49,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree: vi.fn().mockResolvedValue(undefined),
slDisallowed: false,
allowedTargetConnectionIds: new Set(['c1']),
@ -82,7 +82,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree: vi.fn().mockRejectedValue(new Error('final artifact gates failed')),
slDisallowed: false,
allowedTargetConnectionIds: new Set(['c1']),
@ -117,7 +117,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree: vi.fn().mockResolvedValue(undefined),
slDisallowed: true,
allowedTargetConnectionIds: new Set(['c1']),
@ -158,7 +158,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree: vi.fn().mockResolvedValue(undefined),
slDisallowed: false,
allowedTargetConnectionIds: new Set(['warehouse']),
@ -325,7 +325,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree,
slDisallowed: false,
allowedTargetConnectionIds: new Set(['c1']),
@ -380,7 +380,7 @@ describe('integrateWorkUnitPatch', () => {
patchPath,
integrationGit: git,
trace,
author: { name: 'KTX Test', email: 'system@ktx.local' },
author: { name: 'ktx Test', email: 'system@ktx.local' },
validateAppliedTree: vi.fn().mockRejectedValue(new Error('final artifact gates failed')),
slDisallowed: false,
allowedTargetConnectionIds: new Set(['c1']),

View file

@ -65,7 +65,7 @@ describe('runIsolatedWorkUnit', () => {
run: async (child) => {
await mkdir(join(child.workdir, 'wiki/global'), { recursive: true });
await writeFile(join(child.workdir, 'wiki/global/a.md'), '---\nsummary: A\nusage_mode: auto\n---\n\nBody\n');
await child.git.commitFiles(['wiki/global/a.md'], 'test: write wiki', 'KTX Test', 'system@ktx.local');
await child.git.commitFiles(['wiki/global/a.md'], 'test: write wiki', 'ktx Test', 'system@ktx.local');
return {
unitKey: 'wu-1',
status: 'success',

View file

@ -521,7 +521,7 @@ describe('canonical local ingest', () => {
' state: sqlite',
' search: sqlite-fts5',
' git:',
' author: KTX Test <system@ktx.local>',
' author: ktx Test <system@ktx.local>',
'',
].join('\n'),
'utf-8',
@ -530,7 +530,7 @@ describe('canonical local ingest', () => {
await historicProject.fileStore.writeFile(
'semantic-layer/warehouse/_schema/public.yaml',
YAML.stringify({ tables: { orders: { table: 'public.orders', columns: [{ name: 'id', type: 'string' }] } } }),
'KTX Test',
'ktx Test',
'system@ktx.local',
'Seed schema shard',
);
@ -682,7 +682,7 @@ describe('canonical local ingest', () => {
' state: sqlite',
' search: sqlite-fts5',
' git:',
' author: KTX Test <system@ktx.local>',
' author: ktx Test <system@ktx.local>',
'',
].join('\n'),
'utf-8',
@ -765,7 +765,7 @@ describe('canonical local ingest', () => {
' state: sqlite',
' search: sqlite-fts5',
' git:',
' author: KTX Test <system@ktx.local>',
' author: ktx Test <system@ktx.local>',
'',
].join('\n'),
'utf-8',
@ -808,7 +808,7 @@ describe('canonical local ingest', () => {
' state: sqlite',
' search: sqlite-fts5',
' git:',
' author: KTX Test <system@ktx.local>',
' author: ktx Test <system@ktx.local>',
'',
].join('\n'),
'utf-8',

View file

@ -596,7 +596,7 @@ describe('local ingest', () => {
});
});
it('includes upload-capable KTX adapters in default local ingest adapters', () => {
it('includes upload-capable ktx adapters in default local ingest adapters', () => {
expect(createDefaultLocalIngestAdapters(project).map((adapter) => adapter.source)).toEqual(
expect.arrayContaining(['dbt', 'metricflow', 'notion']),
);

View file

@ -17,7 +17,7 @@ describe('memory-flow acceptance scenarios', () => {
it('renders a completed replay with a clear saved-memory completion line', () => {
const output = renderScenario(successfulReplayScenario());
expect(output).toContain('KTX memory flow warehouse/metricflow done');
expect(output).toContain('ktx memory flow warehouse/metricflow done');
expect(output).toContain('Saved 3 memories from 4 raw files: 2 wiki pages, 1 SL updates.');
expect(output).toContain('Commit: abc12345 Run: run-success Report: ingest-report.json');
});
@ -48,7 +48,7 @@ describe('memory-flow acceptance scenarios', () => {
it('renders no ANSI color codes in the text fallback for terminals without color support', () => {
const output = renderScenario(successfulReplayScenario(), 80);
expect(output).toContain('KTX memory flow warehouse/metricflow done');
expect(output).toContain('ktx memory flow warehouse/metricflow done');
expect(output).not.toMatch(/\u001b\[[0-9;]*m/);
});

View file

@ -13,7 +13,7 @@ import type { MemoryFlowInteractionState, MemoryFlowViewModel } from '../../../.
function view(): MemoryFlowViewModel {
return {
title: 'KTX memory flow warehouse/metricflow running',
title: 'ktx memory flow warehouse/metricflow running',
subtitle: 'Run run-1 Sync sync-1',
status: 'running',
activeLine: 'active: WorkUnit orders step 2/4',

View file

@ -5,7 +5,7 @@ import type { MemoryFlowViewModel } from '../../../../src/context/ingest/memory-
function view(): MemoryFlowViewModel {
return {
title: 'KTX memory flow warehouse/metricflow done',
title: 'ktx memory flow warehouse/metricflow done',
subtitle: 'Run run-1 Sync sync-1',
status: 'done',
activeLine: 'active: complete',
@ -128,7 +128,7 @@ describe('renderMemoryFlowInteractive', () => {
const output = renderMemoryFlowInteractive(view(), state, { terminalWidth: 140 });
expect(output).toContain('KTX memory flow warehouse/metricflow done');
expect(output).toContain('ktx memory flow warehouse/metricflow done');
expect(output).toContain('OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED');
expect(output).toContain('[WORKUNITS]');
expect(output).toContain('> orders');

View file

@ -4,7 +4,7 @@ import { renderMemoryFlowReplay } from '../../../../src/context/ingest/memory-fl
function view(): MemoryFlowViewModel {
return {
title: 'KTX memory flow warehouse/metricflow done',
title: 'ktx memory flow warehouse/metricflow done',
subtitle: 'Run run-1 Sync sync-1',
status: 'done',
activeLine: 'active: complete',
@ -79,7 +79,7 @@ describe('renderMemoryFlowReplay', () => {
'OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED',
);
expect(renderMemoryFlowReplay(view(), { terminalWidth: 140 })).toMatchInlineSnapshot(`
"KTX memory flow warehouse/metricflow done
"ktx memory flow warehouse/metricflow done
active: complete
Run run-1 Sync sync-1
OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED

View file

@ -93,7 +93,7 @@ describe('buildMemoryFlowViewModel', () => {
it('builds six readable columns from replay events', () => {
const view = buildMemoryFlowViewModel(replayInput());
expect(view.title).toBe('KTX memory flow warehouse/metricflow done');
expect(view.title).toBe('ktx memory flow warehouse/metricflow done');
expect(view.activeLine).toBe('active: complete');
expect(view.columns.map((column) => column.id)).toEqual([
'source',
@ -179,7 +179,7 @@ describe('buildMemoryFlowViewModel', () => {
details: { actions: [], provenance: [], transcripts: [] },
});
expect(view.title).toBe('KTX memory flow Warehouse + dbt + BI + Docs done');
expect(view.title).toBe('ktx memory flow Warehouse + dbt + BI + Docs done');
expect(view.columns.find((column) => column.id === 'source')?.counters[0]).toBe('Warehouse, dbt, BI, Docs');
expect(view.completionLine).toContain('Saved 16 memories from 29 raw files');
});

View file

@ -11,7 +11,7 @@ function viewWithStatuses(statuses: Array<'waiting' | 'active' | 'complete' | 'w
const ids = ['source', 'chunks', 'workUnits', 'actions', 'gates', 'saved'] as const;
return {
title: 'KTX memory flow warehouse/metricflow running',
title: 'ktx memory flow warehouse/metricflow running',
subtitle: 'Run run-1 Sync sync-1',
status: 'running',
activeLine: 'active: WorkUnit orders',

View file

@ -198,7 +198,7 @@ describe('PageTriageService', () => {
'Reusable outbound sequence:',
'',
'- Ask about current customer success expansion workflow.',
'- Position KTX as AI search visibility for CS teams.',
'- Position ktx as AI search visibility for CS teams.',
'- Close with a discovery call request.',
].join('\n'),
'utf-8',
@ -233,7 +233,7 @@ describe('PageTriageService', () => {
{
candidateKey: 'cold-call-script',
topic: 'Cold Call Script',
assertion: 'Cold call outreach should position KTX around AI search visibility for CS teams.',
assertion: 'Cold call outreach should position ktx around AI search visibility for CS teams.',
rationale: 'The script gives a reusable outbound call sequence and positioning language.',
evidenceChunkIds: ['00000000-0000-0000-0000-000000000101'],
suggestedPageKey: 'cold-call-script',

View file

@ -106,7 +106,7 @@ describe('repo-fetch', () => {
const cacheGit = createSimpleGit(cacheDir);
await cacheGit.addConfig('user.email', 'test@ktx.local');
await cacheGit.addConfig('user.name', 'KTX Test');
await cacheGit.addConfig('user.name', 'ktx Test');
await writeFile(join(cacheDir, 'local-only.txt'), 'local commit\n', 'utf-8');
await cacheGit.add('.');
await cacheGit.commit('local-only divergent commit');

View file

@ -362,7 +362,7 @@ describe('ClaudeCodeKtxLlmRuntime', () => {
});
});
it('registers only exact KTX MCP tool ids and denies non-KTX tools', async () => {
it('registers only exact ktx MCP tool ids and denies non-ktx tools', async () => {
const query = vi.fn((_input: any) =>
stream([
initMessage({ tools: ['mcp__ktx__load_skill'], mcp_servers: [{ name: 'ktx', status: 'connected' }] }),
@ -487,7 +487,7 @@ describe('ClaudeCodeKtxLlmRuntime', () => {
);
});
it('allows host-discovered context during agent loops while requiring exact KTX MCP tools and servers', async () => {
it('allows host-discovered context during agent loops while requiring exact ktx MCP tools and servers', async () => {
const query = vi.fn((_input: any) =>
stream([
initMessage({
@ -553,7 +553,7 @@ describe('ClaudeCodeKtxLlmRuntime', () => {
});
});
it('still rejects unexpected tools, missing KTX tools, plugins, and non-KTX MCP servers from init messages', async () => {
it('still rejects unexpected tools, missing ktx tools, plugins, and non-ktx MCP servers from init messages', async () => {
const query = vi.fn((_input: any) =>
stream([
initMessage({

View file

@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest';
import { KtxIngestEmbeddingPortAdapter, KtxScanEmbeddingPortAdapter } from '../../../src/context/llm/embedding-port.js';
describe('KTX embedding port adapters', () => {
describe('ktx embedding port adapters', () => {
it('adapts LLM modules embeddings to ingest embedding port shape', async () => {
const provider = {
dimensions: 3,

View file

@ -12,7 +12,7 @@ import {
resolveLocalKtxLlmConfig,
} from '../../../src/context/llm/local-config.js';
describe('local KTX LLM config', () => {
describe('local ktx LLM config', () => {
it('resolves env and file references into a KtxLlmConfig', () => {
const config: KtxProjectLlmConfig = {
provider: {
@ -190,7 +190,7 @@ describe('local KTX LLM config', () => {
});
});
describe('local KTX embedding config', () => {
describe('local ktx embedding config', () => {
it('resolves sentence-transformers config', () => {
const config: KtxProjectEmbeddingConfig = {
backend: 'sentence-transformers',

View file

@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest';
import { createLocalKtxLlmProviderFromConfig, createLocalKtxLlmRuntimeFromConfig } from '../../../src/context/llm/local-config.js';
describe('local KTX LLM runtime config', () => {
describe('local ktx LLM runtime config', () => {
it('creates a Claude Code runtime for claude-code backend without creating an AI SDK provider', () => {
const runtime = createLocalKtxLlmRuntimeFromConfig(
{

View file

@ -2,7 +2,7 @@
{
"name": "connection_list",
"title": "Connection List",
"description": "List configured read-only data connections available to this KTX project. Use this before connection-scoped tools when the project may have multiple warehouses.",
"description": "List configured read-only data connections available to this ktx project. Use this before connection-scoped tools when the project may have multiple warehouses.",
"inputSchema": {
"type": "object",
"properties": {},
@ -54,7 +54,7 @@
{
"name": "wiki_search",
"title": "Wiki Search",
"description": "Search KTX wiki pages for reusable business context. Example: wiki_search({ query: \"revenue recognition\", limit: 5 }).",
"description": "Search ktx wiki pages for reusable business context. Example: wiki_search({ query: \"revenue recognition\", limit: 5 }).",
"inputSchema": {
"type": "object",
"properties": {
@ -181,7 +181,7 @@
{
"name": "wiki_read",
"title": "Wiki Read",
"description": "Read a KTX wiki page by key returned from wiki_search. Example: wiki_read({ key: \"global/revenue\" }).",
"description": "Read a ktx wiki page by key returned from wiki_search. Example: wiki_read({ key: \"global/revenue\" }).",
"inputSchema": {
"type": "object",
"properties": {
@ -1227,7 +1227,7 @@
{
"name": "discover_data",
"title": "Discover Data",
"description": "Search across KTX wiki pages, semantic-layer sources, measures, dimensions, raw tables, and columns. Example: discover_data({ query: \"monthly orders by customer\", connectionId: \"warehouse\", kinds: [\"sl_source\", \"table\"] }).",
"description": "Search across ktx wiki pages, semantic-layer sources, measures, dimensions, raw tables, and columns. Example: discover_data({ query: \"monthly orders by customer\", connectionId: \"warehouse\", kinds: [\"sl_source\", \"table\"] }).",
"inputSchema": {
"type": "object",
"properties": {
@ -1398,7 +1398,7 @@
{
"name": "sql_execution",
"title": "SQL Execution",
"description": "Execute one parser-validated read-only SQL query against a configured KTX connection. Example: sql_execution({ connectionId: \"warehouse\", sql: \"select count(*) from public.orders\", maxRows: 100 }).",
"description": "Execute one parser-validated read-only SQL query against a configured ktx connection. Example: sql_execution({ connectionId: \"warehouse\", sql: \"select count(*) from public.orders\", maxRows: 100 }).",
"inputSchema": {
"type": "object",
"properties": {
@ -1472,7 +1472,7 @@
{
"name": "memory_ingest",
"title": "Memory Ingest",
"description": "Ingest free-form markdown knowledge into durable KTX memory. Use this for business rules, metric definitions, schema gotchas, recurring findings, or explicit user requests to remember something. Example: memory_ingest({ connectionId: \"warehouse\", content: \"ARR is reported in cents in this warehouse.\" }).",
"description": "Ingest free-form markdown knowledge into durable ktx memory. Use this for business rules, metric definitions, schema gotchas, recurring findings, or explicit user requests to remember something. Example: memory_ingest({ connectionId: \"warehouse\", content: \"ARR is reported in cents in this warehouse.\" }).",
"inputSchema": {
"type": "object",
"properties": {

View file

@ -987,7 +987,7 @@ describe('createKtxMcpServer', () => {
expect(ingest.ingest).toHaveBeenCalledWith({
userId: 'mcp-user',
chatId: expect.stringMatching(/^mcp-/),
userMessage: 'Ingest external knowledge into KTX memory.',
userMessage: 'Ingest external knowledge into ktx memory.',
assistantMessage: content,
connectionId: '00000000-0000-4000-8000-000000000001',
sourceType: 'external_ingest',
@ -996,7 +996,7 @@ describe('createKtxMcpServer', () => {
const cliEquivalentInput: MemoryAgentInput = {
userId: 'mcp-user',
chatId: 'cli-text-ingest-test-1',
userMessage: 'Ingest external text artifact "orders lookml" into KTX memory.',
userMessage: 'Ingest external text artifact "orders lookml" into ktx memory.',
assistantMessage: content,
connectionId: '00000000-0000-4000-8000-000000000001',
sourceType: 'external_ingest',
@ -1101,7 +1101,7 @@ describe('createKtxMcpServer', () => {
expect(ingestSpy).toHaveBeenCalledWith({
userId: 'local',
chatId: expect.stringMatching(/^mcp-/),
userMessage: 'Ingest external knowledge into KTX memory.',
userMessage: 'Ingest external knowledge into ktx memory.',
assistantMessage: 'Revenue means paid order value.',
connectionId: 'warehouse',
sourceType: 'external_ingest',
@ -1127,7 +1127,7 @@ describe('createKtxMcpServer', () => {
}
});
it('registers KTX context MCP tools when context ports are supplied', async () => {
it('registers ktx context MCP tools when context ports are supplied', async () => {
const fake = makeFakeServer();
const contextTools: KtxMcpContextPorts = {
connections: {
@ -1298,7 +1298,7 @@ describe('createKtxMcpServer', () => {
expect(jsonToolResult({ ok: true }).structuredContent).toEqual({ ok: true });
if (false) {
// @ts-expect-error bare arrays are not valid MCP structuredContent objects in KTX
// @ts-expect-error bare arrays are not valid MCP structuredContent objects in ktx
jsonToolResult([]);
}
});

View file

@ -20,9 +20,9 @@ const expectedAdapterSkillHeadings: Record<string, string> = {
historic_sql_table_digest: '# Historic SQL Table Digest',
live_database_ingest: '# Live Database Ingest',
looker_ingest: '# Looker Runtime Ingest',
lookml_ingest: '# LookML to KTX Semantic Layer',
metabase_ingest: '# Metabase to KTX Semantic Layer',
metricflow_ingest: '# MetricFlow to KTX Semantic Layer',
lookml_ingest: '# LookML to ktx Semantic Layer',
metabase_ingest: '# Metabase to ktx Semantic Layer',
metricflow_ingest: '# MetricFlow to ktx Semantic Layer',
};
const verificationWriterSkills = [
'notion_synthesize',
@ -125,7 +125,7 @@ describe('memory runtime assets', () => {
it('ships Metabase guidance that avoids invalid joins for SQL-only card outputs', async () => {
const body = await readFile(join(skillsDir, 'metabase_ingest', 'SKILL.md'), 'utf-8');
expect(body).toContain('Do not declare a KTX join just because the card SQL joins that table internally');
expect(body).toContain('Do not declare a ktx join just because the card SQL joins that table internally');
expect(body).toContain('only when the card output exposes a local key that matches the target source grain');
expect(body).toContain('If `sl_discover` resolves the table, it is not outside the manifest');
expect(body).toContain('reason: "parse_error"');
@ -167,7 +167,7 @@ describe('memory runtime assets', () => {
}
});
it('ships only the KTX connectionId sql_execution call shape in writer guidance', async () => {
it('ships only the ktx connectionId sql_execution call shape in writer guidance', async () => {
const shared = await readFile(join(skillsDir, '_shared', 'identifier-verification.md'), 'utf-8');
const bodies = [{ name: '_shared/identifier-verification.md', body: shared }];

View file

@ -9,7 +9,7 @@ import {
const removedAutoCommitKey = ['auto', 'commit'].join('_');
describe('KTX project config', () => {
describe('ktx project config', () => {
it.each(['status', 'replay', 'run', 'watch'])('accepts former ingest subcommand name "%s" as a connection id', (connectionId) => {
expect(
parseKtxProjectConfig(`

View file

@ -5,7 +5,7 @@ import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { initKtxProject, loadKtxProject } from '../../../src/context/project/project.js';
describe('KTX local project runtime', () => {
describe('ktx local project runtime', () => {
let tempDir: string;
beforeEach(async () => {

View file

@ -10,7 +10,7 @@ import {
setKtxSetupDatabaseConnectionIds,
} from '../../../src/context/project/setup-config.js';
describe('KTX setup config helpers', () => {
describe('ktx setup config helpers', () => {
let tempDir: string;
beforeEach(async () => {

View file

@ -9,7 +9,7 @@ import {
} from '../../../src/context/scan/credentials.js';
import type { KtxCredentialEnvelope, KtxScanReport, KtxScanWarning } from '../../../src/context/scan/types.js';
describe('KTX scan credential redaction', () => {
describe('ktx scan credential redaction', () => {
it('keeps credential references inspectable', () => {
const envReference: KtxCredentialEnvelope = { kind: 'env', name: 'DATABASE_URL' };
const fileReference: KtxCredentialEnvelope = { kind: 'file', path: '~/.config/ktx/warehouse' };

View file

@ -7,7 +7,7 @@ import {
const defaultPatterns = defaultKtxDataDictionarySettings.excludePatterns;
describe('KTX scan data dictionary policy', () => {
describe('ktx scan data dictionary policy', () => {
it('includes text-like and boolean categorical types', () => {
expect(isKtxDataDictionaryCandidate('varchar(50)', 'status', defaultPatterns)).toBe(true);
expect(isKtxDataDictionaryCandidate('VARCHAR', 'category', defaultPatterns)).toBe(true);

View file

@ -90,7 +90,7 @@ function createConnector(): KtxScanConnector {
};
}
describe('KTX description prompt builders', () => {
describe('ktx description prompt builders', () => {
it('builds column prompts with sample values, source descriptions, and nested BigQuery guidance', () => {
const { system, user } = buildKtxColumnDescriptionPrompt({
columnName: 'payload',

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { buildKtxColumnEmbeddingText } from '../../../src/context/scan/embedding-text.js';
describe('KTX scan embedding text', () => {
describe('ktx scan embedding text', () => {
it('builds column embedding text with table, description, FK, and sample-value context', () => {
expect(
buildKtxColumnEmbeddingText({

View file

@ -5,7 +5,7 @@ import {
skippedKtxScanEnrichmentSummary,
} from '../../../src/context/scan/enrichment-summary.js';
describe('KTX scan enrichment summaries', () => {
describe('ktx scan enrichment summaries', () => {
it('keeps structural scans skipped when no enrichment was requested', () => {
expect(failedKtxScanEnrichmentSummary('structural', false)).toEqual(skippedKtxScanEnrichmentSummary);
});

View file

@ -11,7 +11,7 @@ import type {
KtxStructuralSyncPlan,
} from '../../../src/context/scan/enrichment-types.js';
describe('KTX scan enrichment contracts', () => {
describe('ktx scan enrichment contracts', () => {
it('models an enriched schema with reusable table, column, and relationship metadata', () => {
const schema: KtxEnrichedSchema = {
connectionId: 'warehouse',

View file

@ -373,7 +373,7 @@ describe('local scan enrichment', () => {
expect(result.summary.statisticalValidation).toBe('skipped');
expect(result.warnings).toContainEqual({
code: 'relationship_validation_failed',
message: 'KTX scan connector advertises readOnlySql but does not expose executeReadOnly',
message: 'ktx scan connector advertises readOnlySql but does not expose executeReadOnly',
recoverable: true,
metadata: { capability: 'readOnlySql' },
});

View file

@ -1644,7 +1644,7 @@ describe('local scan', () => {
expect(result.report.warnings).toEqual([
{
code: 'enrichment_failed',
message: 'KTX scan enrichment failed after structural scan completed: embedding service timed out',
message: 'ktx scan enrichment failed after structural scan completed: embedding service timed out',
recoverable: true,
metadata: {
mode: 'enriched',

View file

@ -258,7 +258,7 @@ describe('relationship benchmark report', () => {
}),
);
expect(markdown).toContain('# KTX Relationship Discovery Benchmark Evidence');
expect(markdown).toContain('# ktx Relationship Discovery Benchmark Evidence');
expect(markdown).toContain(
'| demo_b2b_no_declared_constraints | smoke | declared_pks_and_declared_fks_removed | run | no | 0.500 | 0.000 | 0.000 | 0 |',
);

View file

@ -279,7 +279,7 @@ describe('relationship diagnostics artifacts', () => {
warnings: [
{
code: 'connector_capability_missing',
message: 'KTX scan connector cannot run standalone statistical relationship validation',
message: 'ktx scan connector cannot run standalone statistical relationship validation',
recoverable: true,
metadata: { capability: 'readOnlySql' },
},

View file

@ -449,7 +449,7 @@ describe('production relationship discovery', () => {
});
expect(result.warnings).toContainEqual({
code: 'connector_capability_missing',
message: 'KTX scan connector cannot run read-only SQL relationship validation',
message: 'ktx scan connector cannot run read-only SQL relationship validation',
recoverable: true,
metadata: { capability: 'readOnlySql' },
});

View file

@ -146,12 +146,12 @@ describe('relationship LLM proposals', () => {
expect(runtime.generateObject).toHaveBeenCalledWith(
expect.objectContaining({
role: 'candidateExtraction',
system: expect.stringContaining('You are helping KTX review possible SQL relationships'),
system: expect.stringContaining('You are helping ktx review possible SQL relationships'),
prompt: expect.stringContaining('"tables"'),
}),
);
const call = vi.mocked(runtime.generateObject).mock.calls[0]?.[0];
expect(call?.prompt).not.toContain('You are helping KTX review possible SQL relationships');
expect(call?.prompt).not.toContain('You are helping ktx review possible SQL relationships');
});
it('skips when no runtime is configured', async () => {
@ -207,7 +207,7 @@ describe('relationship LLM proposals', () => {
expect(failed).toMatchObject({ candidates: [], llmCalls: 1, summary: 'failed' });
expect(failed.warnings[0]).toMatchObject({
code: 'relationship_llm_proposal_failed',
message: 'KTX relationship LLM proposal failed: model unavailable',
message: 'ktx relationship LLM proposal failed: model unavailable',
recoverable: true,
});
});

View file

@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { inferKtxDimensionType, ktxColumnTypeMappingFromNative, normalizeKtxNativeType } from '../../../src/context/scan/type-normalization.js';
describe('KTX scan type normalization', () => {
describe('ktx scan type normalization', () => {
it('normalizes native database type strings', () => {
expect(normalizeKtxNativeType(' NUMERIC(12, 2) ')).toBe('numeric');
expect(normalizeKtxNativeType('TIMESTAMP WITH TIME ZONE')).toBe('timestamp with time zone');

View file

@ -17,7 +17,7 @@ import {
type KtxSchemaSnapshot,
} from '../../../src/context/scan/types.js';
describe('KTX scan contract types', () => {
describe('ktx scan contract types', () => {
it('defaults to structural-only connector capabilities', () => {
expect(createKtxConnectorCapabilities()).toEqual({
structuralIntrospection: true,

View file

@ -47,7 +47,7 @@ const baseTable: SemanticLayerSource = {
};
describe('listConnectionIdsWithNames', () => {
it('discovers local KTX connection ids from semantic-layer directories', async () => {
it('discovers local ktx connection ids from semantic-layer directories', async () => {
const configService = {
listFiles: vi.fn().mockResolvedValue({
files: [

View file

@ -20,7 +20,7 @@ export async function makeLocalGitRepo(fixtureDir: string, destRoot: string): Pr
await git.init();
await git.raw(['checkout', '-B', 'main']);
await git.addConfig('user.email', 'test@ktx.local');
await git.addConfig('user.name', 'KTX Test');
await git.addConfig('user.name', 'ktx Test');
await git.add('.');
await git.commit('initial');
const commit = async (message: string): Promise<string> => {

View file

@ -68,8 +68,8 @@ describe('formatDoctorReport', () => {
},
];
const output = formatDoctorReport({ title: 'KTX status', checks });
expect(output).toContain('KTX status');
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');
@ -83,7 +83,7 @@ describe('formatDoctorReport', () => {
{ id: 'pnpm', label: 'pnpm 10.20+', status: 'pass', detail: '10.28.0', group: 'toolchain' },
];
const output = formatDoctorReport({ title: 'KTX status', checks });
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');
@ -106,7 +106,7 @@ describe('formatDoctorReport', () => {
},
];
const output = formatDoctorReport({ title: 'KTX status', checks });
const output = formatDoctorReport({ title: 'ktx status', checks });
expect(output).toContain('✓ Semantic search');
expect(output).toContain('openai/text-embedding-3-small (1536d) probe succeeded');
});
@ -116,7 +116,7 @@ describe('formatDoctorReport', () => {
{ id: 'node', label: 'Node 22+', status: 'pass', detail: 'v22.16.0', group: 'toolchain' },
];
const output = formatDoctorReport({ title: 'KTX status', checks }, { verbose: true });
const output = formatDoctorReport({ title: 'ktx status', checks }, { verbose: true });
expect(output).toContain('✓ Node 22+: v22.16.0');
});
});
@ -249,7 +249,7 @@ describe('runKtxDoctor', () => {
),
).resolves.toBe(1);
expect(testIo.stdout()).toContain('KTX status');
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');
@ -304,7 +304,7 @@ describe('runKtxDoctor', () => {
).resolves.toBe(0);
expect(JSON.parse(testIo.stdout())).toEqual({
title: 'KTX status',
title: 'ktx status',
checks: [{ id: 'node', label: 'Node 22+', status: 'pass', detail: 'v22.16.0 ABI 127' }],
});
});
@ -323,8 +323,8 @@ describe('runKtxDoctor', () => {
).resolves.toBe(1);
const out = testIo.stdout();
expect(out).toContain('KTX status');
expect(out).toContain('No KTX project here yet.');
expect(out).toContain('ktx status');
expect(out).toContain('No ktx project here yet.');
expect(out).toContain('ktx setup');
expect(out).toContain('KTX_PROJECT_DIR');
expect(out).not.toContain('ENOENT');
@ -377,7 +377,7 @@ describe('runKtxDoctor', () => {
).resolves.toBe(1);
const out = testIo.stdout();
expect(out).toContain('KTX status');
expect(out).toContain('ktx status');
expect(out).toContain('Config');
expect(out).toContain('Unsupported storrage: unknown field');
expect(out).toContain('Unsupported ingest.llm: unknown field');
@ -479,7 +479,7 @@ describe('runKtxDoctor', () => {
).resolves.toBe(0);
const out = testIo.stdout();
expect(out).toContain('KTX status');
expect(out).toContain('ktx status');
expect(out).toContain(`· ${basename(tempDir)}`);
expect(out).toContain('Connections (1)');
expect(out).toContain('LLM');
@ -759,7 +759,7 @@ describe('runKtxDoctor', () => {
).resolves.toBe(0);
const out = testIo.stdout();
expect(out).toContain('KTX status');
expect(out).toContain('ktx status');
expect(out).toContain('Config');
expect(out).toContain('ktx.yaml schema valid');
expect(out).not.toContain('LLM');
@ -855,7 +855,7 @@ describe('runKtxDoctor', () => {
),
).resolves.toBe(1);
expect(testIo.stdout()).toContain('No KTX project here yet.');
expect(testIo.stdout()).toContain('No ktx project here yet.');
});
it('does not invoke the Postgres query-history probe in validate mode', async () => {

View file

@ -126,7 +126,7 @@ describe('runKtxCli', () => {
await expect(runKtxCli(['--help'], testIo.io)).resolves.toBe(0);
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
expect(testIo.stdout()).toContain('KTX data agent context layer CLI');
expect(testIo.stdout()).toContain('ktx data agent context layer CLI');
for (const command of ['setup', 'connection', 'ingest', 'wiki', 'sl', 'status', 'admin']) {
expect(testIo.stdout()).toContain(`${command}`);
}
@ -407,7 +407,7 @@ describe('runKtxCli', () => {
await expect(runKtxCli(['admin', 'runtime', 'stop', '--help'], testIo.io)).resolves.toBe(0);
expect(testIo.stdout()).toContain('--all');
expect(testIo.stdout()).toContain('Stop all KTX daemon processes recorded or discoverable');
expect(testIo.stdout()).toContain('Stop all ktx daemon processes recorded or discoverable');
expect(testIo.stdout()).toContain('on this machine');
expect(testIo.stderr()).toBe('');
});
@ -913,7 +913,7 @@ describe('runKtxCli', () => {
await expect(runKtxCli(['ingest', '--help'], testIo.io, { publicIngest })).resolves.toBe(0);
expect(testIo.stdout()).toContain('Usage: ktx ingest');
expect(testIo.stdout()).toContain('Build or inspect KTX context');
expect(testIo.stdout()).toContain('Build or inspect ktx context');
expect(testIo.stdout()).toContain('--all');
expect(testIo.stdout()).toContain('--query-history');
expect(testIo.stdout()).toContain('--no-query-history');

View file

@ -90,8 +90,8 @@ describe('runKtxIngest viz and replay', () => {
expect(runLocal).toHaveBeenCalledWith(expect.objectContaining({ memoryFlow: expect.any(Object) }));
expect(io.stdout()).toContain('\u001b[2J\u001b[H');
expect((io.stdout().match(/KTX memory flow/g) ?? []).length).toBeGreaterThan(1);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect((io.stdout().match(/ktx memory flow/g) ?? []).length).toBeGreaterThan(1);
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).toContain('fake-orders');
expect(io.stderr()).toBe('');
});
@ -145,7 +145,7 @@ describe('runKtxIngest viz and replay', () => {
expect(liveSession.update).toHaveBeenCalled();
expect(liveSession.close).toHaveBeenCalledTimes(1);
expect(io.stdout()).not.toContain('\u001b[2J\u001b[H');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toBe('');
});
@ -303,7 +303,7 @@ describe('runKtxIngest viz and replay', () => {
expect(startLiveMemoryFlow).toHaveBeenCalledTimes(1);
expect(io.stdout()).toContain('\u001b[2J\u001b[H');
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
});
it('falls back to text live rendering when TUI startup fails with a redacted warning', async () => {
@ -345,7 +345,7 @@ describe('runKtxIngest viz and replay', () => {
).resolves.toBe(0);
expect(io.stderr()).toContain('TUI visualization unavailable: Failed [redacted-url] [redacted]');
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).toContain('\u001b[2J\u001b[H');
});
@ -384,7 +384,7 @@ describe('runKtxIngest viz and replay', () => {
expect(startLiveMemoryFlow).not.toHaveBeenCalled();
expect(runLocal).toHaveBeenCalledWith(expect.not.objectContaining({ memoryFlow: expect.anything() }));
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
});
it('attaches a plain progress memory-flow sink for interactive plain run output', async () => {
@ -416,7 +416,7 @@ describe('runKtxIngest viz and replay', () => {
expect(io.stderr()).toContain('[5%] Fetching source files for warehouse/fake');
expect(io.stdout()).toContain('Job: plain-run');
expect(io.stdout()).not.toContain('[5%]');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
});
it('falls back to plain run output for run --viz when stdout is not interactive', async () => {
@ -447,7 +447,7 @@ describe('runKtxIngest viz and replay', () => {
).resolves.toBe(0);
expect(io.stdout()).toContain('Job: non-tty-viz-run');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but stdout is not an interactive terminal; printing plain output.',
);
@ -493,7 +493,7 @@ describe('runKtxIngest viz and replay', () => {
expect(io.stderr()).toContain('[5%] Fetching source files for warehouse/fake');
expect(io.stdout()).toContain('Job: raw-missing-viz-run');
expect(io.stdout()).not.toContain('[5%]');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but stdin raw mode is unavailable; printing plain output.',
);
@ -547,7 +547,7 @@ describe('runKtxIngest viz and replay', () => {
runKtxIngest({ command: 'watch', projectDir, outputMode: 'viz', inputMode: 'disabled' }, io.io),
).resolves.toBe(0);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).toContain('Run: run-watch-latest');
expect(io.stderr()).toBe('');
});
@ -572,7 +572,7 @@ describe('runKtxIngest viz and replay', () => {
),
).resolves.toBe(0);
expect(io.stdout()).toContain('KTX memory flow warehouse/metabase done');
expect(io.stdout()).toContain('ktx memory flow warehouse/metabase done');
expect(io.stdout()).toContain('Saved 2 memories from 2 raw files');
expect(io.stdout()).toContain('Commit: abc12345 Run: run-1 Report: report-1');
expect(io.stdout()).toContain('SOURCE');
@ -667,7 +667,7 @@ describe('runKtxIngest viz and replay', () => {
),
).resolves.toBe(0);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).toContain('SOURCE');
expect(io.stdout()).toContain('CHUNKS');
expect(io.stdout()).toContain('WORKUNITS');
@ -736,7 +736,7 @@ describe('runKtxIngest viz and replay', () => {
).resolves.toBe(0);
expect(renderStoredMemoryFlow).toHaveBeenCalledTimes(1);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
});
it('does not use TUI for stored --viz when input is disabled', async () => {
@ -766,7 +766,7 @@ describe('runKtxIngest viz and replay', () => {
).resolves.toBe(0);
expect(renderStoredMemoryFlow).not.toHaveBeenCalled();
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
});
it('falls back to plain status for stored --viz when stdin raw mode is unavailable', async () => {
@ -797,7 +797,7 @@ describe('runKtxIngest viz and replay', () => {
expect(renderStoredMemoryFlow).not.toHaveBeenCalled();
expect(io.stdout()).toContain('Run: run-raw-missing-stored-viz-run');
expect(io.stdout()).toContain('Job: raw-missing-stored-viz-run');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but stdin raw mode is unavailable; printing plain output.',
);
@ -826,7 +826,7 @@ describe('runKtxIngest viz and replay', () => {
),
).resolves.toBe(0);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).not.toContain('\u001b[2J\u001b[H');
expect(io.stderr()).toBe('');
});
@ -854,7 +854,7 @@ describe('runKtxIngest viz and replay', () => {
),
).resolves.toBe(0);
expect(io.stdout()).toContain('KTX memory flow warehouse/fake done');
expect(io.stdout()).toContain('ktx memory flow warehouse/fake done');
expect(io.stdout()).not.toContain('\u001b[2J\u001b[H');
expect(io.stderr()).toBe('');
});
@ -884,7 +884,7 @@ describe('runKtxIngest viz and replay', () => {
expect(io.stdout()).toContain('Run: run-redirected-no-input-viz-run');
expect(io.stdout()).toContain('Job: redirected-no-input-viz-run');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but stdout is not an interactive terminal; printing plain output.',
);
@ -910,7 +910,7 @@ describe('runKtxIngest viz and replay', () => {
expect(io.stdout()).toContain('Run: run-dumb-terminal-viz-run');
expect(io.stdout()).toContain('Job: dumb-terminal-viz-run');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but TERM=dumb does not support the visual renderer; printing plain output.',
);
@ -932,7 +932,7 @@ describe('runKtxIngest viz and replay', () => {
expect(io.stdout()).toContain('Run: run-viz-run-2');
expect(io.stdout()).toContain('Job: viz-run-2');
expect(io.stdout()).not.toContain('KTX memory flow');
expect(io.stdout()).not.toContain('ktx memory flow');
expect(io.stderr()).toContain(
'Visualization requested but stdout is not an interactive terminal; printing plain output.',
);

View file

@ -1507,7 +1507,7 @@ describe('runKtxIngest', () => {
);
});
it('passes KTX daemon options to adapters and pull-config options when no explicit daemon URL is set', async () => {
it('passes ktx daemon options to adapters and pull-config options when no explicit daemon URL is set', async () => {
const projectDir = join(tempDir, 'managed-daemon-ingest-project');
await initKtxProject({ projectDir });
await writeWarehouseConfig(projectDir);

View file

@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest';
import { runKtxEmbeddingHealthCheck } from '../../src/llm/embedding-health.js';
describe('KTX embedding health check', () => {
describe('ktx embedding health check', () => {
it('runs a one-shot OpenAI embedding check through the configured provider', async () => {
const createOpenAIClient = vi.fn(() => ({
embeddings: {

View file

@ -12,7 +12,7 @@ describe('createKtxEmbeddingProvider', () => {
}),
) as KtxEmbeddingConfig;
expect(() => createKtxEmbeddingProvider(config)).toThrow('Unsupported KTX embedding backend: deterministic');
expect(() => createKtxEmbeddingProvider(config)).toThrow('Unsupported ktx embedding backend: deterministic');
});
it('rejects gateway embeddings', () => {
@ -25,7 +25,7 @@ describe('createKtxEmbeddingProvider', () => {
}),
) as KtxEmbeddingConfig;
expect(() => createKtxEmbeddingProvider(config)).toThrow('Unsupported KTX embedding backend: gateway');
expect(() => createKtxEmbeddingProvider(config)).toThrow('Unsupported ktx embedding backend: gateway');
});
it('uses OpenAI embeddings with configured dimensions', async () => {

View file

@ -4,7 +4,7 @@ import { runKtxLlmHealthCheck } from '../../src/llm/model-health.js';
const anthropicModel = { modelId: 'claude-sonnet-4-6' } as never;
describe('KTX LLM health check', () => {
describe('ktx LLM health check', () => {
it('runs a minimal non-streaming model call through the configured provider', async () => {
const generateText = vi.fn(async () => ({ text: 'ok' }));
const createAnthropic = vi.fn(() => vi.fn(() => anthropicModel));

View file

@ -95,7 +95,7 @@ function daemonResult(status: 'started' | 'reused' = 'reused'): ManagedPythonDae
}
describe('managedLocalEmbeddingHealthConfig', () => {
it('uses the active KTX daemon URL for the immediate health check', () => {
it('uses the active ktx daemon URL for the immediate health check', () => {
expect(
managedLocalEmbeddingHealthConfig({
baseUrl: 'http://127.0.0.1:61234',
@ -112,7 +112,7 @@ describe('managedLocalEmbeddingHealthConfig', () => {
});
describe('ensureManagedLocalEmbeddingsDaemon', () => {
it('ensures the local-embeddings feature and starts the KTX daemon', async () => {
it('ensures the local-embeddings feature and starts the ktx daemon', async () => {
const io = makeIo();
const ensureRuntime = vi.fn(async () => runtime());
const startDaemon = vi.fn(async () => daemonResult('started'));
@ -144,7 +144,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
features: ['local-embeddings'],
force: false,
});
expect(io.stderr()).toContain('Started KTX daemon: http://127.0.0.1:61234');
expect(io.stderr()).toContain('Started ktx daemon: http://127.0.0.1:61234');
});
it('reuses an already running daemon without reporting a new start', async () => {
@ -159,7 +159,7 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
startDaemon: vi.fn(async () => daemonResult('reused')),
});
expect(io.stderr()).toContain('Using KTX daemon: http://127.0.0.1:61234');
expect(io.stderr()).toContain('Using ktx daemon: http://127.0.0.1:61234');
});
});

View file

@ -157,9 +157,9 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
layout: { versionDir: '/runtime/0.2.0' },
});
expect(io.stderr()).toContain('Installing KTX Python runtime (local-embeddings) with uv...');
expect(io.stderr()).toContain('KTX Python runtime ready: /runtime/0.2.0');
expect(io.stderr().match(/Installing KTX Python runtime/g)).toHaveLength(1);
expect(io.stderr()).toContain('Installing ktx Python runtime (local-embeddings) with uv...');
expect(io.stderr()).toContain('ktx Python runtime ready: /runtime/0.2.0');
expect(io.stderr().match(/Installing ktx Python runtime/g)).toHaveLength(1);
});
it('shows runtime installation progress with the CLI spinner', async () => {
@ -181,8 +181,8 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
});
expect(events).toEqual([
'start:Installing KTX Python runtime (local-embeddings) with uv...',
'stop:KTX Python runtime ready: /runtime/0.2.0',
'start:Installing ktx Python runtime (local-embeddings) with uv...',
'stop:ktx Python runtime ready: /runtime/0.2.0',
]);
});
@ -221,7 +221,7 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
readStatus: vi.fn(async () => missingStatus()),
installRuntime,
}),
).rejects.toThrow('KTX Python runtime is required for this command. Run: ktx admin runtime install --yes');
).rejects.toThrow('ktx Python runtime is required for this command. Run: ktx admin runtime install --yes');
expect(installRuntime).not.toHaveBeenCalled();
});
@ -251,8 +251,8 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
force: false,
});
expect(events).toEqual([
'start:Installing KTX Python runtime (core) with uv...',
'stop:KTX Python runtime ready: /runtime/0.2.0',
'start:Installing ktx Python runtime (core) with uv...',
'stop:ktx Python runtime ready: /runtime/0.2.0',
]);
});
@ -274,7 +274,7 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
});
expect(confirmInstall).toHaveBeenCalledWith(
'KTX needs to install the core Python runtime. This downloads Python dependencies with uv. Continue?',
'ktx needs to install the core Python runtime. This downloads Python dependencies with uv. Continue?',
io.io,
);
expect(installRuntime).toHaveBeenCalledWith({
@ -282,7 +282,7 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
features: ['core'],
force: false,
});
expect(events).toContainEqual('start:Installing KTX Python runtime (core) with uv...');
expect(events).toContainEqual('start:Installing ktx Python runtime (core) with uv...');
});
it('uses injected runtime confirmation instead of reading process TTY directly', async () => {
@ -306,10 +306,10 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
).resolves.toBe(compute);
expect(confirmInstall).toHaveBeenCalledWith(
'KTX needs to install the core Python runtime. This downloads Python dependencies with uv. Continue?',
'ktx needs to install the core Python runtime. This downloads Python dependencies with uv. Continue?',
io.io,
);
expect(events).toContainEqual('start:Installing KTX Python runtime (core) with uv...');
expect(events).toContainEqual('start:Installing ktx Python runtime (core) with uv...');
});
it('can decide default runtime prompting from injected io capabilities', async () => {
@ -325,6 +325,6 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
installRuntime: vi.fn(),
createPythonCompute: () => ({ query: vi.fn(), validateSources: vi.fn(), generateSources: vi.fn() }),
}),
).rejects.toThrow('KTX Python runtime installation was cancelled');
).rejects.toThrow('ktx Python runtime installation was cancelled');
});
});

View file

@ -126,7 +126,7 @@ function daemonOptionsBase(root: string) {
} as const;
}
describe('KTX daemon lifecycle', () => {
describe('ktx daemon lifecycle', () => {
let tempDir: string;
beforeEach(async () => {

View file

@ -57,7 +57,7 @@ describe('createManagedPythonDaemonBaseUrlResolver', () => {
features: ['core'],
force: false,
});
expect(testIo.stderr()).toContain('Started KTX daemon: http://127.0.0.1:61234');
expect(testIo.stderr()).toContain('Started ktx daemon: http://127.0.0.1:61234');
});
it('reports daemon reuse without reinstalling after the first resolved URL', async () => {
@ -86,7 +86,7 @@ describe('createManagedPythonDaemonBaseUrlResolver', () => {
expect(ensureRuntime).toHaveBeenCalledTimes(1);
expect(startDaemon).toHaveBeenCalledTimes(1);
expect(testIo.stderr()).toContain('Using existing KTX daemon: http://127.0.0.1:61234');
expect(testIo.stderr()).toContain('Using existing ktx daemon: http://127.0.0.1:61234');
});
});
@ -104,8 +104,8 @@ describe('createManagedDaemonHttpJsonRunner', () => {
});
});
describe('KTX daemon ingest ports', () => {
it('creates a Looker table parser backed by the KTX daemon runner', async () => {
describe('ktx daemon ingest ports', () => {
it('creates a Looker table parser backed by the ktx daemon runner', async () => {
const requestJson = vi.fn(async () => ({
results: {
'model.explore': {
@ -135,7 +135,7 @@ describe('KTX daemon ingest ports', () => {
});
});
it('creates a SQL analysis port backed by the KTX daemon runner', async () => {
it('creates a SQL analysis port backed by the ktx daemon runner', async () => {
const requestJson = vi.fn(async () => ({
fingerprint: 'select-orders',
normalized_sql: 'SELECT * FROM public.orders WHERE id = ?',
@ -157,7 +157,7 @@ describe('KTX daemon ingest ports', () => {
});
});
it('routes SQL batch analysis through the KTX daemon runner', async () => {
it('routes SQL batch analysis through the ktx daemon runner', async () => {
const requestJson = vi.fn(async () => ({
results: {
orders: {

View file

@ -41,7 +41,7 @@ describe('buildMcpSecurityConfig', () => {
allowedHosts: [],
allowedOrigins: [],
}),
).toThrow('Binding KTX MCP to 0.0.0.0 requires --token or KTX_MCP_TOKEN');
).toThrow('Binding ktx MCP to 0.0.0.0 requires --token or KTX_MCP_TOKEN');
});
it('validates allowed origins as full origins', () => {
@ -88,7 +88,7 @@ describe('isMcpRequestAuthorized', () => {
{ path: '/health', headers: { host: 'evil.example.test' } },
config,
),
).toEqual({ ok: false, status: 403, message: 'Host header is not allowed for KTX MCP.' });
).toEqual({ ok: false, status: 403, message: 'Host header is not allowed for ktx MCP.' });
});
it('rejects browser origins unless explicitly allowed', () => {
@ -100,7 +100,7 @@ describe('isMcpRequestAuthorized', () => {
},
config,
),
).toEqual({ ok: false, status: 403, message: 'Origin header is not allowed for KTX MCP.' });
).toEqual({ ok: false, status: 403, message: 'Origin header is not allowed for ktx MCP.' });
});
it('requires bearer auth on /mcp when token auth is enabled', () => {
@ -109,7 +109,7 @@ describe('isMcpRequestAuthorized', () => {
{ path: '/mcp', headers: { host: 'mcp.example.test', authorization: 'Bearer wrong' } },
config,
),
).toEqual({ ok: false, status: 401, message: 'Missing or invalid KTX MCP bearer token.' });
).toEqual({ ok: false, status: 401, message: 'Missing or invalid ktx MCP bearer token.' });
});
it('does not require bearer auth on /health', () => {

View file

@ -186,7 +186,7 @@ describe('createKtxMcpServerFactory', () => {
factory();
expect(io.stderr.write).toHaveBeenCalledWith(
'KTX MCP memory_ingest disabled: missing local memory prerequisites\n',
'ktx MCP memory_ingest disabled: missing local memory prerequisites\n',
);
expect(createDefaultKtxMcpServer).toHaveBeenCalledWith(
expect.objectContaining({

View file

@ -102,7 +102,7 @@ describe('sanitizeMemoryFlowTuiError', () => {
});
describe('MemoryFlowTuiApp', () => {
it('always shows the KTX logo', () => {
it('always shows the ktx logo', () => {
const { lastFrame } = renderInkTest(<MemoryFlowTuiApp input={replayInput()} terminalWidth={120} onExit={vi.fn()} showBoot={false} />);
expect(lastFrame()).toContain('╚███╔╝');
});
@ -197,7 +197,7 @@ describe('MemoryFlowTuiApp', () => {
expect(frame).toContain('Created so far:');
expect(frame).toContain('order lifecycle');
expect(frame).toContain('customer metrics');
expect(frame).toContain('KTX finished ingesting your data');
expect(frame).toContain('ktx finished ingesting your data');
expect(frame).toContain('ktx sl');
expect(frame).toContain('ktx wiki');
expect(frame).not.toContain('ktx serve --mcp stdio --user-id local');
@ -253,7 +253,7 @@ describe('MemoryFlowTuiApp', () => {
it('hides completion while running', () => {
const { lastFrame } = renderInkTest(<MemoryFlowTuiApp input={runningReplayInput()} terminalWidth={120} onExit={vi.fn()} showBoot={false} />);
expect(lastFrame()).not.toContain('KTX finished ingesting');
expect(lastFrame()).not.toContain('ktx finished ingesting');
});
});

View file

@ -6,7 +6,7 @@ import {
formatSetupNextStepLines,
} from '../src/next-steps.js';
describe('KTX demo next steps', () => {
describe('ktx demo next steps', () => {
it('uses supported context-build commands before agent usage', () => {
expect(KTX_CONTEXT_BUILD_COMMANDS).toEqual([
{
@ -48,8 +48,8 @@ describe('KTX demo next steps', () => {
it('explains what the next-step commands are for', () => {
const rendered = formatNextStepLines().join('\n');
expect(rendered).toContain('KTX context is ready for agents.');
expect(rendered).toContain('KTX project directory');
expect(rendered).toContain('ktx context is ready for agents.');
expect(rendered).toContain('ktx project directory');
expect(rendered).toContain('ask a data question');
expect(rendered).toContain('Verify with:');
expect(rendered).not.toContain('this directory');
@ -82,7 +82,7 @@ describe('KTX demo next steps', () => {
agentIntegrationReady: true,
}).join('\n');
expect(rendered).toContain('KTX context is ready for agents.');
expect(rendered).toContain('ktx context is ready for agents.');
expect(rendered).toContain('ktx status --json');
expect(rendered).not.toContain('ktx agent');
expect(rendered).not.toContain('ktx serve --mcp stdio --user-id local');

View file

@ -21,7 +21,7 @@ describe('renderKtxCommandTree', () => {
expect(output).not.toContain('__complete');
expect(output).toContain('│ └── test [connectionId]');
expect(output).toContain('│ ├── status Show KTX MCP daemon status');
expect(output).toContain('│ ├── status Show ktx MCP daemon status');
expect(output).not.toContain('│ ├── add');
expect(output).not.toContain('│ ├── remove');
expect(output).not.toContain('│ ├── map');

View file

@ -7,8 +7,8 @@ describe('prompt navigation helpers', () => {
});
it('adds a blank separator between multiline menu copy and the option list', () => {
expect(withMenuOptionSpacing('Which embedding option should KTX use?\n\nKTX uses embeddings for search.')).toBe(
'Which embedding option should KTX use?\n\nKTX uses embeddings for search.\n',
expect(withMenuOptionSpacing('Which embedding option should ktx use?\n\nktx uses embeddings for search.')).toBe(
'Which embedding option should ktx use?\n\nktx uses embeddings for search.\n',
);
});
@ -25,10 +25,10 @@ describe('prompt navigation helpers', () => {
it('adds a blank separator between text input helper copy and the editable value', () => {
expect(
withTextInputNavigation(
'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.',
'Name this PostgreSQL connection\nktx will use this short name in commands and config. You can rename it now.',
),
).toBe(
'Name this PostgreSQL connection\n│\n│ KTX will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
'Name this PostgreSQL connection\n│\n│ ktx will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
);
});
@ -39,10 +39,10 @@ describe('prompt navigation helpers', () => {
it('normalizes already hinted text input prompts without duplicating the hint', () => {
expect(
withTextInputNavigation(
'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.',
'Name this PostgreSQL connection\nktx will use this short name in commands and config. You can rename it now.\nPress Escape to go back.',
),
).toBe(
'Name this PostgreSQL connection\n│\n│ KTX will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
'Name this PostgreSQL connection\n│\n│ ktx will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
);
});
@ -53,7 +53,7 @@ describe('prompt navigation helpers', () => {
it('is idempotent when text input navigation with body is applied twice', () => {
const once = withTextInputNavigation(
'Name this PostgreSQL connection\nKTX will use this short name in commands and config.',
'Name this PostgreSQL connection\nktx will use this short name in commands and config.',
);
expect(withTextInputNavigation(once)).toBe(once);
});

View file

@ -16,7 +16,7 @@ describe('public ingest copy sanitizers', () => {
it('maps database scan failure text into public database ingest wording', () => {
expect(
publicDatabaseIngestMessage(
'KTX scan enrichment failed after structural scan completed: embedding service timed out',
'ktx scan enrichment failed after structural scan completed: embedding service timed out',
),
).toBe('Database enrichment failed after schema context completed: embedding service timed out');
expect(publicDatabaseIngestMessage('structural scan wrote partial artifacts')).toBe(
@ -42,7 +42,7 @@ describe('public ingest copy sanitizers', () => {
it('sanitizes captured public output lines across database and query-history internals', () => {
expect(
publicIngestOutputLine(
'KTX scan enrichment failed after structural scan completed in raw-sources/warehouse/live-database/sync-1',
'ktx scan enrichment failed after structural scan completed in raw-sources/warehouse/live-database/sync-1',
),
).toBe('Database enrichment failed after schema context completed in raw-sources/warehouse/database schema/sync-1');
expect(publicIngestOutputLine('Historic SQL local ingest requires a configured reader')).toBe(

View file

@ -837,7 +837,7 @@ describe('runKtxPublicIngest', () => {
const io = makeIo();
const project = deepReadyProject({ warehouse: { driver: 'postgres' } });
const runScan = vi.fn(async (_args, scanIo) => {
scanIo.stdout.write('KTX scan completed\n');
scanIo.stdout.write('ktx scan completed\n');
scanIo.stdout.write('Mode: structural\n');
scanIo.stdout.write('Report: raw-sources/warehouse/live-database/sync-1/scan-report.json\n');
scanIo.stdout.write('Raw sources: raw-sources/warehouse/live-database/sync-1\n');
@ -861,7 +861,7 @@ describe('runKtxPublicIngest', () => {
expect(io.stdout()).toContain('Ingest finished\n');
expect(io.stdout()).toContain('warehouse');
expect(io.stdout()).not.toContain('KTX scan completed');
expect(io.stdout()).not.toContain('ktx scan completed');
expect(io.stdout()).not.toContain('Mode: structural');
expect(io.stdout()).not.toContain('Report: raw-sources');
expect(io.stdout()).not.toContain('live-database');
@ -871,7 +871,7 @@ describe('runKtxPublicIngest', () => {
const io = makeIo();
const project = deepReadyProject({ warehouse: { driver: 'postgres' } });
const runScan = vi.fn(async (_args, scanIo) => {
scanIo.stdout.write('KTX scan enrichment failed after structural scan completed: embedding service timed out\n');
scanIo.stdout.write('ktx scan enrichment failed after structural scan completed: embedding service timed out\n');
return 1;
});
@ -894,7 +894,7 @@ describe('runKtxPublicIngest', () => {
'warehouse failed: Database enrichment failed after schema context completed: embedding service timed out.',
);
expect(io.stdout()).toContain('Retry: ktx ingest warehouse --project-dir /tmp/project');
expect(io.stdout()).not.toContain('KTX scan enrichment failed');
expect(io.stdout()).not.toContain('ktx scan enrichment failed');
expect(io.stdout()).not.toContain('structural scan');
});
@ -988,7 +988,7 @@ describe('runKtxPublicIngest', () => {
docs: { driver: 'notion' },
});
const runScan = vi.fn<NonNullable<KtxPublicIngestDeps['runScan']>>(async (_args, scanIo, deps) => {
scanIo.stdout.write('KTX scan completed\n');
scanIo.stdout.write('ktx scan completed\n');
scanIo.stdout.write('Report: raw-sources/warehouse/live-database/sync-1/scan-report.json\n');
await deps?.progress?.update(0.12, 'Inspecting database schema');
const enrichmentProgress = deps?.progress?.startPhase(0.5);
@ -1030,7 +1030,7 @@ describe('runKtxPublicIngest', () => {
expect(io.stdout()).toContain('Ingest finished');
expect(io.stdout()).toContain('warehouse');
expect(io.stdout()).toContain('docs');
expect(io.stdout()).not.toContain('KTX scan completed');
expect(io.stdout()).not.toContain('ktx scan completed');
expect(io.stdout()).not.toContain('Report:');
expect(io.stdout()).not.toContain('Adapter:');
expect(io.stderr()).toContain('[1/2] warehouse · database schema\n');
@ -1080,7 +1080,7 @@ describe('runKtxPublicIngest', () => {
const project = deepReadyProject({ warehouse: { driver: 'postgres' } });
const runScan = vi.fn<NonNullable<KtxPublicIngestDeps['runScan']>>(async (_args, scanIo, deps) => {
await deps?.progress?.update(0.42, 'Enriching schema metadata');
scanIo.stdout.write('KTX scan enrichment failed after structural scan completed: embedding service timed out\n');
scanIo.stdout.write('ktx scan enrichment failed after structural scan completed: embedding service timed out\n');
return 1;
});
@ -1105,7 +1105,7 @@ describe('runKtxPublicIngest', () => {
expect(io.stdout()).toContain(
'warehouse failed: Database enrichment failed after schema context completed: embedding service timed out.',
);
expect(io.stdout()).not.toContain('KTX scan enrichment failed');
expect(io.stdout()).not.toContain('ktx scan enrichment failed');
expect(io.stdout()).not.toContain('structural scan');
});
@ -1477,7 +1477,7 @@ describe('runKtxPublicIngest', () => {
scanIo.stdout.write('Run: scan-run-1\n');
scanIo.stdout.write('Mode: enriched\n');
scanIo.stdout.write('Dry run: no\n');
scanIo.stdout.write('KTX scan completed\n');
scanIo.stdout.write('ktx scan completed\n');
return 0;
});
const runIngest = vi.fn(async (_args, ingestIo) => {
@ -1516,7 +1516,7 @@ describe('runKtxPublicIngest', () => {
const runIngest = vi.fn(async (_args, ingestIo) => {
ingestIo.stderr.write('Missing bundled Python runtime manifest: /repo/packages/cli/assets/python/manifest.json\n');
ingestIo.stderr.write('In a source checkout, build the local runtime assets with: pnpm run artifacts:build\n');
ingestIo.stderr.write('Then retry the runtime-backed KTX command.\n');
ingestIo.stderr.write('Then retry the runtime-backed ktx command.\n');
return 1;
});
@ -1542,7 +1542,7 @@ describe('runKtxPublicIngest', () => {
'In a source checkout, build the local runtime assets with: pnpm run artifacts:build',
);
expect(io.stdout()).toContain('Retry: ktx ingest warehouse --project-dir /tmp/project --query-history');
expect(io.stdout()).not.toContain('Then retry the runtime-backed KTX command');
expect(io.stdout()).not.toContain('Then retry the runtime-backed ktx command');
});
it('fails enrichment-readiness targets before work starts while continuing independent --all targets', async () => {

View file

@ -103,13 +103,13 @@ describe('runKtxRuntime', () => {
features: ['local-embeddings'],
force: true,
});
expect(io.stdout()).toContain('Installed KTX Python runtime');
expect(io.stdout()).toContain('Installed ktx Python runtime');
expect(io.stdout()).toContain('features: core, local-embeddings');
expect(io.stdout()).toContain('manifest: /runtime/0.2.0/manifest.json');
expect(io.stderr()).toBe('');
});
it('starts the KTX daemon and prints the base URL', async () => {
it('starts the ktx daemon and prints the base URL', async () => {
const io = makeIo();
const deps: KtxRuntimeDeps = {
startDaemon: vi.fn(async (): Promise<ManagedPythonDaemonStartResult> => ({
@ -160,14 +160,14 @@ describe('runKtxRuntime', () => {
features: ['local-embeddings'],
force: true,
});
expect(io.stdout()).toContain('Started KTX daemon');
expect(io.stdout()).toContain('Started ktx daemon');
expect(io.stdout()).toContain('url: http://127.0.0.1:61234');
expect(io.stdout()).toContain('pid: 4242');
expect(io.stdout()).toContain('features: core, local-embeddings');
expect(io.stdout()).toContain('stderr: /work/proj/.ktx/runtime/daemon.stderr.log');
});
it('stops the KTX daemon', async () => {
it('stops the ktx daemon', async () => {
const io = makeIo();
const deps: KtxRuntimeDeps = {
stopDaemon: vi.fn(async (): Promise<ManagedPythonDaemonStopResult> => ({
@ -208,11 +208,11 @@ describe('runKtxRuntime', () => {
).resolves.toBe(0);
expect(deps.stopDaemon).toHaveBeenCalledWith({ cliVersion: '0.2.0', projectDir: '/work/proj' });
expect(io.stdout()).toContain('Stopped KTX daemon');
expect(io.stdout()).toContain('Stopped ktx daemon');
expect(io.stdout()).toContain('pid: 4242');
});
it('stops all discovered KTX daemons and reports the summary', async () => {
it('stops all discovered ktx daemons and reports the summary', async () => {
const io = makeIo();
const deps: KtxRuntimeDeps = {
stopAllDaemons: vi.fn(async (): Promise<ManagedPythonDaemonStopAllResult> => ({
@ -231,7 +231,7 @@ describe('runKtxRuntime', () => {
).resolves.toBe(0);
expect(deps.stopAllDaemons).toHaveBeenCalledWith({ cliVersion: '0.2.0', projectDir: '/work/proj' });
expect(io.stdout()).toContain('Stopped 2 KTX daemons');
expect(io.stdout()).toContain('Stopped 2 ktx daemons');
expect(io.stdout()).toContain('pid: 4242 source: state url: http://127.0.0.1:61234');
expect(io.stdout()).toContain('pid: 5252 source: process url: http://127.0.0.1:8765');
});
@ -259,7 +259,7 @@ describe('runKtxRuntime', () => {
runKtxRuntime({ command: 'stop', cliVersion: '0.2.0', projectDir: '/work/proj', all: true }, io.io, deps),
).resolves.toBe(1);
expect(io.stderr()).toContain('Stopped 0 KTX daemons; failed 1');
expect(io.stderr()).toContain('Stopped 0 ktx daemons; failed 1');
expect(io.stderr()).toContain('pid: 4242 source: state url: http://127.0.0.1:61234');
expect(io.stderr()).toContain('process scan: ps failed');
});
@ -362,9 +362,9 @@ describe('runKtxRuntime', () => {
await expect(runKtxRuntime({ command: 'status', cliVersion: '0.2.0', json: false }, io.io, deps)).resolves.toBe(0);
expect(io.stdout()).toContain('KTX Python runtime');
expect(io.stdout()).toContain('ktx Python runtime');
expect(io.stdout()).toContain('status: ready');
expect(io.stdout()).toContain('KTX Python runtime checks');
expect(io.stdout()).toContain('ktx Python runtime checks');
expect(io.stdout()).toContain('PASS uv: uv 0.9.5');
expect(io.stdout()).toContain('PASS Managed Python runtime: Runtime ready at /runtime/0.2.0');
expect(io.stderr()).toBe('');

View file

@ -293,7 +293,7 @@ const reportWithAttention: KtxScanReport = {
warnings: [
{
code: 'connector_capability_missing',
message: 'KTX scan connector is missing optional capability: columnStats',
message: 'ktx scan connector is missing optional capability: columnStats',
recoverable: true,
metadata: { capability: 'columnStats' },
},
@ -372,7 +372,7 @@ describe('runKtxScan', () => {
connector: undefined,
}),
);
expect(io.stdout()).toContain('KTX scan completed\n');
expect(io.stdout()).toContain('ktx scan completed\n');
expect(io.stdout()).toContain('Run: scan-run-1');
expect(io.stdout()).toContain('Mode: structural');
expect(io.stdout()).toContain('What changed\n');
@ -494,7 +494,7 @@ describe('runKtxScan', () => {
);
});
it('passes KTX daemon options to local ingest adapters when no explicit daemon URL is set', async () => {
it('passes ktx daemon options to local ingest adapters when no explicit daemon URL is set', async () => {
await initKtxProject({ projectDir: tempDir });
const createLocalIngestAdapters = vi.fn(() => []);
const runLocalScan = vi.fn(
@ -649,7 +649,7 @@ describe('runKtxScan', () => {
warnings: [
{
code: 'connector_capability_missing',
message: 'KTX scan connector cannot run read-only SQL relationship validation',
message: 'ktx scan connector cannot run read-only SQL relationship validation',
recoverable: true,
metadata: { capability: 'readOnlySql' },
},
@ -690,7 +690,7 @@ describe('runKtxScan', () => {
expect(io.stdout()).toContain('Review: 12');
expect(io.stdout()).toContain('Rejected: 44');
expect(io.stdout()).toContain(
'connector_capability_missing: KTX scan connector cannot run read-only SQL relationship validation',
'connector_capability_missing: ktx scan connector cannot run read-only SQL relationship validation',
);
});
@ -972,7 +972,7 @@ describe('runKtxScan', () => {
}
expect(io.stdout()).toContain('✓');
expect(io.stdout()).toContain('KTX scan completed');
expect(io.stdout()).toContain('ktx scan completed');
expect(io.stdout()).toContain('\u001b[');
});
@ -1017,7 +1017,7 @@ describe('runKtxScan', () => {
}
}
expect(io.stdout()).toContain('KTX scan completed');
expect(io.stdout()).toContain('ktx scan completed');
expect(io.stdout()).not.toContain('\u001b[');
});

View file

@ -406,16 +406,16 @@ describe('setup agents', () => {
});
expect(prompts.select).toHaveBeenCalledWith({
message: 'What should agents be allowed to do with this KTX project?',
message: 'What should agents be allowed to do with this ktx project?',
options: [
{
value: 'mcp',
label: 'Ask data questions with KTX MCP',
label: 'Ask data questions with ktx MCP',
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
},
{
value: 'mcp-cli',
label: 'Ask data questions + manage KTX with CLI commands',
label: 'Ask data questions + manage ktx with CLI commands',
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
},
{
@ -459,16 +459,16 @@ describe('setup agents', () => {
).resolves.toMatchObject({ status: 'skipped', projectDir: tempDir });
expect(prompts.select).toHaveBeenCalledWith({
message: 'What should agents be allowed to do with this KTX project?',
message: 'What should agents be allowed to do with this ktx project?',
options: [
{
value: 'mcp',
label: 'Ask data questions with KTX MCP',
label: 'Ask data questions with ktx MCP',
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
},
{
value: 'mcp-cli',
label: 'Ask data questions + manage KTX with CLI commands',
label: 'Ask data questions + manage ktx with CLI commands',
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
},
{
@ -518,17 +518,17 @@ describe('setup agents', () => {
});
expect(prompts.select).toHaveBeenCalledWith({
message: `Where should KTX install supported agent config?\n\nKTX project: ${tempDir}`,
message: `Where should ktx install supported agent config?\n\nktx project: ${tempDir}`,
options: [
{
value: 'project',
label: 'Project scope (KTX project directory)',
hint: 'Only agents opened from this KTX project path load the project-scoped config.',
label: 'Project scope (ktx project directory)',
hint: 'Only agents opened from this ktx project path load the project-scoped config.',
},
{
value: 'global',
label: 'Global scope (user config)',
hint: 'Agents can load this KTX project from any working directory.',
hint: 'Agents can load this ktx project from any working directory.',
},
],
});
@ -584,7 +584,7 @@ describe('setup agents', () => {
args: [expect.stringContaining('bin.js'), '--project-dir', tempDir, 'mcp', 'stdio'],
});
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('ktx Analytics Workflow');
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
await expect(readZipText(analyticsSkillPath, '.claude-plugin/plugin.json')).rejects.toThrow('Missing zip entry');
await expect(readZipText(analyticsSkillPath, 'skills/ktx-analytics/SKILL.md')).rejects.toThrow(
@ -597,11 +597,11 @@ describe('setup agents', () => {
expect(io.stdout()).toContain('claude_desktop_config.json');
expect(io.stdout()).toContain('Required before using agents');
expect(io.stdout()).toContain('1. Restart Claude Desktop');
expect(io.stdout()).toContain('Claude Desktop loads KTX MCP after restart.');
expect(io.stdout()).toContain('Claude Desktop loads ktx MCP after restart.');
expect(io.stdout()).toContain('2. Upload Claude Desktop skills');
expect(io.stdout()).toContain('Customize > Skills > + > Create skill > Upload a skill');
expect(io.stdout()).toContain('Upload this file:');
expect(io.stdout()).toContain('Toggle the uploaded KTX skills on.');
expect(io.stdout()).toContain('Toggle the uploaded ktx skills on.');
expect(io.stdout()).not.toContain('Run `ktx mcp start`');
} finally {
process.env.HOME = previousHome;
@ -686,7 +686,7 @@ describe('setup agents', () => {
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('ktx Analytics Workflow');
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
const adminSkill = await readZipText(adminSkillPath, 'ktx/SKILL.md');
expect(adminSkill).toContain(`--project-dir ${tempDir}`);
@ -1026,7 +1026,7 @@ describe('setup agents', () => {
expect(io.stdout().match(/Space to select/g)).toHaveLength(1);
expect(prompts.multiselect).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Which agent targets should KTX install?',
message: 'Which agent targets should ktx install?',
}),
);
});
@ -1055,7 +1055,7 @@ describe('setup agents', () => {
expect(output).toContain('Analytics skill installed.');
expect(output).toContain('Admin CLI skill installed.');
expect(output).not.toContain('Agent integration complete');
expect(output).not.toContain(`KTX project\n ${tempDir}`);
expect(output).not.toContain(`ktx project\n ${tempDir}`);
expect(output).not.toContain('Installed agents');
expect(output).not.toContain('.claude/skills/ktx-analytics/SKILL.md');
expect(output).not.toContain('.claude/skills/ktx/SKILL.md');
@ -1167,11 +1167,11 @@ describe('setup agents', () => {
expect(output).toContain('Run this command before using Claude Code:');
expect(output).toContain(`ktx mcp start --project-dir ${tempDir}`);
expect(output).toContain(`ktx mcp stop --project-dir ${tempDir}\n\n2. Open Claude Code`);
expect(output).toContain('Open Claude Code from the KTX project directory');
expect(output).toContain('Open Claude Code from the ktx project directory');
expect(output).toContain('RUN:');
expect(output).toContain(`cd '${tempDir}'`);
expect(output).toContain('3. Restart Claude Desktop');
expect(output).toContain('Claude Desktop loads KTX MCP after restart.');
expect(output).toContain('Claude Desktop loads ktx MCP after restart.');
expect(output).toContain('4. Upload Claude Desktop skills');
expect(output).toContain('Customize > Skills > + > Create skill > Upload a skill');
expect(output).toContain(join(tempDir, '.ktx/agents/claude/ktx-analytics.zip'));
@ -1179,7 +1179,7 @@ describe('setup agents', () => {
expect(output).toContain('Upload this file:');
expect(output).toContain('All set.');
expect(output).not.toContain('Finish Claude Desktop setup');
expect(output).not.toContain('Run `ktx mcp start` to enable the configured KTX MCP server.');
expect(output).not.toContain('Run `ktx mcp start` to enable the configured ktx MCP server.');
} finally {
process.env.HOME = previousHome;
await rm(home, { recursive: true, force: true });
@ -1216,7 +1216,7 @@ describe('setup agents', () => {
expect(output).toContain('2. Open Claude Code');
expect(output).toContain('RUN:');
expect(output).toContain('claude');
expect(output).not.toContain('Open Claude Code from the KTX project directory');
expect(output).not.toContain('Open Claude Code from the ktx project directory');
expect(output).not.toContain(`cd '${tempDir}'`);
} finally {
process.env.HOME = previousHome;
@ -1263,7 +1263,7 @@ describe('setup agents', () => {
expect(output).toContain('3. Configure unsupported MCP clients');
expect(output).toContain('4. Start MCP');
expect(output).toContain('Run this command before using Codex, Cursor, OpenCode, and Universal .agents:');
expect(output).toContain('Open Cursor from the KTX project directory');
expect(output).toContain('Open Cursor from the ktx project directory');
expect(output).toContain('Open ~/.codex/config.toml, then paste this block:\n\n PASTE:\n [mcp_servers.ktx]');
expect(output).toContain('Open opencode.json, then paste this block:');
expect(output).toContain('Use this endpoint when setting up unsupported MCP clients:');
@ -1298,9 +1298,9 @@ describe('setup agents', () => {
expect(heading).toContain('Upload Claude Desktop skills');
expect(heading).not.toMatch(/^2\. /);
const sub = format(' Toggle the uploaded KTX skills on.');
const sub = format(' Toggle the uploaded ktx skills on.');
expect(sub).toMatch(/^ {3}/);
expect(sub).toContain('Toggle the uploaded KTX skills on.');
expect(sub).toContain('Toggle the uploaded ktx skills on.');
});
it('renders skill bundle .zip paths as bullets and shortens HOME to ~', () => {

View file

@ -288,7 +288,7 @@ describe('setup context build state', () => {
reportIds: ['report-docs-1'],
artifactPaths: ['raw-sources/warehouse/live-database/sync-1/scan-report.json'],
});
expect(io.stdout()).toContain('KTX context is ready for agents.');
expect(io.stdout()).toContain('ktx context is ready for agents.');
expect(io.stdout()).toContain('Databases:');
expect(io.stdout()).not.toContain(['Primary sources', ':'].join(''));
});
@ -398,7 +398,7 @@ describe('setup context build state', () => {
completedAt: '2026-05-09T10:00:00.000Z',
contextSourceConnectionIds: ['docs'],
});
expect(io.stdout()).toContain('KTX context is ready for agents.');
expect(io.stdout()).toContain('ktx context is ready for agents.');
expect(io.stdout()).not.toContain(['Primary sources', ':'].join(''));
});
@ -523,7 +523,7 @@ describe('setup context build state', () => {
),
).resolves.toEqual({ status: 'failed', projectDir: tempDir });
expect(io.stderr()).toContain('No databases or context sources are configured for a KTX context build.');
expect(io.stderr()).toContain('No databases or context sources are configured for a ktx context build.');
});
it('starts a fresh foreground build when stale state is found', async () => {

View file

@ -111,7 +111,7 @@ function makePromptAdapter(options: {
if (message.startsWith('Enable all tables in ') && message.includes(', or refine tables?')) {
return 'save';
}
if (message.includes('How much database context should KTX build?')) {
if (message.includes('How much database context should ktx build?')) {
const nextValue = selectValues[0];
return nextValue === 'fast' || nextValue === 'deep' || nextValue === 'back'
? (selectValues.shift() ?? 'fast')
@ -126,7 +126,7 @@ function makePromptAdapter(options: {
}
function connectionNamePrompt(label: string): string {
return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`;
return `Name this ${label} connection\nktx will use this short name in commands and config. You can rename it now.`;
}
function textInputPrompt(message: string): string {
@ -234,7 +234,7 @@ describe('setup databases step', () => {
expect(result.status).toBe('back');
expect(prompts.multiselect).toHaveBeenCalledWith({
message:
'Which databases should KTX connect to?\n' +
'Which databases should ktx connect to?\n' +
'Use Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.',
options: [
{ value: 'postgres', label: 'PostgreSQL' },
@ -272,7 +272,7 @@ describe('setup databases step', () => {
});
expect(prompts.multiselect).toHaveBeenCalledTimes(2);
expect(vi.mocked(prompts.multiselect).mock.calls[1]?.[0].message).toBe(
'Which databases should KTX connect to?\n' +
'Which databases should ktx connect to?\n' +
'Use Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.',
);
});
@ -926,7 +926,7 @@ describe('setup databases step', () => {
initialValues: ['postgres'],
required: true,
}));
expect(io.stdout()).not.toContain('KTX cannot work without at least one database');
expect(io.stdout()).not.toContain('ktx cannot work without at least one database');
expect(prompts.select).toHaveBeenNthCalledWith(3, {
message: 'Databases configured: postgres-warehouse\nWhat would you like to do?',
options: [
@ -971,7 +971,7 @@ describe('setup databases step', () => {
initialValues: ['postgres'],
required: true,
}));
expect(io.stdout()).not.toContain('KTX cannot work without at least one database');
expect(io.stdout()).not.toContain('ktx cannot work without at least one database');
expect(prompts.select).toHaveBeenNthCalledWith(2, {
message: 'Databases configured: warehouse\nWhat would you like to do?',
options: [
@ -1813,7 +1813,7 @@ describe('setup databases step', () => {
commandIo.stdout.write('[55%] Semantic layer comparison found 2 changes across 2 tables\n');
commandIo.stdout.write('[70%] Writing schema artifacts\n');
commandIo.stdout.write('[100%] Scan completed\n');
commandIo.stdout.write('✓ KTX scan completed\n');
commandIo.stdout.write('✓ ktx scan completed\n');
commandIo.stdout.write('Status: done\n');
commandIo.stdout.write('Run: local-moywh3ky\n');
commandIo.stdout.write('Connection: postgres-warehouse\n');
@ -3176,7 +3176,7 @@ describe('setup databases step', () => {
});
vi.mocked(prompts.select).mockImplementation(async ({ message, options }) => {
if (message.startsWith('Enable query-history ingest')) return 'yes';
if (message.includes('How much database context should KTX build?')) return 'fast';
if (message.includes('How much database context should ktx build?')) return 'fast';
if (message.startsWith('Connection setup failed for analytics')) {
failurePromptCount += 1;
failurePromptOptions.push(options);
@ -3300,89 +3300,6 @@ describe('setup databases step', () => {
expect(config.connections.warehouse.historicSql).toBeUndefined();
});
it('migrates legacy historicSql to context.queryHistory during database setup', async () => {
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'connections:',
' warehouse:',
' driver: postgres',
' readonly: true',
' schemas:',
" - 'public'",
' historicSql:',
' enabled: true',
' dialect: postgres',
' windowDays: 45',
' minExecutions: 9',
' concurrency: 3',
' staleArchiveAfterDays: 120',
' filters:',
' dropTrivialProbes: true',
' serviceAccounts:',
' mode: exclude',
' patterns:',
" - '^svc_'",
' orchestrators:',
' mode: exclude',
' patterns:',
' - airflow',
' dropFailedBelow: 2',
' redactionPatterns:',
" - '(?i)secret'",
'',
].join('\n'),
'utf-8',
);
const io = makeIo();
await expect(
runKtxSetupDatabasesStep(
{
projectDir: tempDir,
inputMode: 'disabled',
databaseConnectionIds: ['warehouse'],
databaseSchemas: [],
skipDatabases: false,
},
io.io,
{
testConnection: vi.fn(async () => 0),
scanConnection: vi.fn(async () => 0),
historicSqlReadinessProbe: vi.fn(async () => {
const runner = fakeHistoricSqlRunner('postgres', 'pg_stat_statements');
return {
ok: true as const,
dialect: 'postgres' as const,
runner,
result: { pgServerVersion: 'PostgreSQL 16.4', warnings: [], info: [] },
};
}),
},
),
).resolves.toMatchObject({ status: 'ready' });
const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8'));
expect(config.connections.warehouse.historicSql).toBeUndefined();
expect(config.connections.warehouse.context).toMatchObject({
queryHistory: {
enabled: true,
windowDays: 45,
minExecutions: 9,
concurrency: 3,
staleArchiveAfterDays: 120,
filters: {
dropTrivialProbes: true,
serviceAccounts: { mode: 'exclude', patterns: ['^svc_'] },
orchestrators: { mode: 'exclude', patterns: ['airflow'] },
dropFailedBelow: 2,
},
redactionPatterns: ['(?i)secret'],
},
});
});
it('prints a non-blocking Postgres query history probe failure after connection test succeeds', async () => {
const io = makeIo();
const runner = {
@ -3594,7 +3511,7 @@ describe('setup databases step', () => {
);
expect(result.status).toBe('skipped');
expect(io.stdout()).toContain('KTX cannot work until you add a database.');
expect(io.stdout()).toContain('ktx cannot work until you add a database.');
expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).not.toContain('completed_steps:');
});
});

View file

@ -99,7 +99,7 @@ describe('renderDemoCompletionSummary', () => {
it('includes star headline', () => {
const plain = stripAnsi(renderDemoCompletionSummary(projectDir, true));
expect(plain).toContain('★ KTX demo is ready');
expect(plain).toContain('★ ktx demo is ready');
});
it('shows manual instructions when agent not installed', () => {

View file

@ -9,9 +9,9 @@ import { ManagedPythonDaemonStartError } from '../src/managed-python-daemon.js';
import { type KtxSetupEmbeddingsPromptAdapter, runKtxSetupEmbeddingsStep } from '../src/setup-embeddings.js';
const EMBEDDING_OPTION_PROMPT_MESSAGE = [
'Which embedding option should KTX use?',
'Which embedding option should ktx use?',
'',
'KTX uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' +
'ktx uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' +
'and relationship evidence.',
].join('\n');
@ -128,7 +128,7 @@ describe('setup embeddings step', () => {
});
expect(vi.mocked(prompts.select).mock.calls.map((call) => call[0].message)).toEqual([
EMBEDDING_OPTION_PROMPT_MESSAGE,
'How should KTX find your OpenAI embedding API key?',
'How should ktx find your OpenAI embedding API key?',
EMBEDDING_OPTION_PROMPT_MESSAGE,
]);
});
@ -286,7 +286,7 @@ describe('setup embeddings step', () => {
const io = makeIo();
const ensureLocalEmbeddings = vi.fn(async () => {
throw new Error(
'KTX Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes',
'ktx Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes',
);
});
@ -304,7 +304,7 @@ describe('setup embeddings step', () => {
expect(result.status).toBe('failed');
expect(io.stderr()).toContain(
'KTX Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes',
'ktx Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes',
);
});
@ -361,7 +361,7 @@ describe('setup embeddings step', () => {
);
expect(result.status).toBe('failed');
expect(io.stderr()).toContain('Recent KTX daemon stderr:');
expect(io.stderr()).toContain('Recent ktx daemon stderr:');
expect(io.stderr()).toContain('daemon traceback line 6');
expect(io.stderr()).toContain('daemon traceback line 45');
expect(io.stderr()).not.toContain('daemon traceback line 5');
@ -395,7 +395,7 @@ describe('setup embeddings step', () => {
expect(result.status).toBe('failed');
expect(io.stderr()).toContain('Local embedding health check failed: fetch failed: connect ECONNREFUSED');
expect(io.stderr()).toContain('Recent KTX daemon stderr:');
expect(io.stderr()).toContain('Recent ktx daemon stderr:');
expect(io.stderr()).toContain('daemon startup traceback 6');
expect(io.stderr()).toContain('daemon startup traceback 45');
expect(io.stderr()).not.toContain('daemon startup traceback 5');
@ -425,7 +425,7 @@ describe('setup embeddings step', () => {
);
expect(result.status).toBe('failed');
expect(io.stderr()).not.toContain('Recent KTX daemon stderr:');
expect(io.stderr()).not.toContain('Recent ktx daemon stderr:');
});
it('uses fixed OpenAI defaults and only asks for credentials when OpenAI is selected', async () => {
@ -506,7 +506,7 @@ describe('setup embeddings step', () => {
});
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Local embeddings are not reachable. Start the local KTX daemon, then retry.',
message: 'Local embeddings are not reachable. Start the local ktx daemon, then retry.',
options: expect.arrayContaining([expect.objectContaining({ value: 'openai' })]),
}),
);

View file

@ -146,7 +146,7 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('back');
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which LLM provider should KTX use?'),
message: expect.stringContaining('Which LLM provider should ktx use?'),
options: [
{ value: 'claude-code', label: 'Claude subscription (Pro/Max)' },
{ value: 'codex', label: 'Codex subscription' },
@ -199,12 +199,12 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which LLM provider should KTX use?'),
message: expect.stringContaining('Which LLM provider should ktx use?'),
}),
);
expect(prompts.select).not.toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Claude Code model should KTX use?'),
message: expect.stringContaining('Which Claude Code model should ktx use?'),
}),
);
const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8'));
@ -299,7 +299,7 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(io.stderr()).toContain('claude-code ignores llm.promptCaching.systemTtl');
expect(io.stderr()).toContain('Claude Agent SDK does not expose KTX prompt-cache TTL, tool, or history markers');
expect(io.stderr()).toContain('Claude Agent SDK does not expose ktx prompt-cache TTL, tool, or history markers');
});
it('returns from Anthropic credential Back to provider selection', async () => {
@ -315,7 +315,7 @@ describe('setup Anthropic model step', () => {
expect(prompts.select).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
message: expect.stringContaining('Which LLM provider should KTX use?'),
message: expect.stringContaining('Which LLM provider should ktx use?'),
}),
);
});
@ -495,7 +495,7 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(prompts.select).not.toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('How should KTX authenticate with Google Vertex AI?'),
message: expect.stringContaining('How should ktx authenticate with Google Vertex AI?'),
}),
);
expect(readGcloudProject).toHaveBeenCalled();
@ -503,7 +503,7 @@ describe('setup Anthropic model step', () => {
expect(prompts.text).not.toHaveBeenCalled();
expect(prompts.autocomplete).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Google Cloud project should KTX use for Vertex AI?'),
message: expect.stringContaining('Which Google Cloud project should ktx use for Vertex AI?'),
options: [
{ value: 'local-gcp-project', label: 'local-gcp-project - Local project (current gcloud project)' },
{ value: 'other-gcp-project', label: 'other-gcp-project - Other project' },
@ -545,12 +545,12 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(prompts.select).not.toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('How should KTX authenticate with Google Vertex AI?'),
message: expect.stringContaining('How should ktx authenticate with Google Vertex AI?'),
}),
);
expect(prompts.autocomplete).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Google Cloud project should KTX use for Vertex AI?'),
message: expect.stringContaining('Which Google Cloud project should ktx use for Vertex AI?'),
}),
);
expect(healthCheck).toHaveBeenCalledWith(
@ -615,7 +615,7 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('ready');
expect(prompts.autocomplete).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Google Cloud project should KTX use for Vertex AI?'),
message: expect.stringContaining('Which Google Cloud project should ktx use for Vertex AI?'),
options: [
{ value: 'manual', label: 'Enter a project ID manually' },
{ value: 'back', label: 'Back' },
@ -710,7 +710,7 @@ describe('setup Anthropic model step', () => {
expect(prompts.select).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
message: expect.stringContaining('Which LLM provider should KTX use?'),
message: expect.stringContaining('Which LLM provider should ktx use?'),
}),
);
});
@ -899,20 +899,20 @@ describe('setup Anthropic model step', () => {
expect(result.status).toBe('back');
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('How should KTX find your Anthropic API key?'),
message: expect.stringContaining('How should ktx find your Anthropic API key?'),
options: expect.not.arrayContaining([expect.objectContaining({ value: 'skip' })]),
}),
);
});
it('explains why KTX asks for an Anthropic API key', async () => {
it('explains why ktx asks for an Anthropic API key', async () => {
const io = makeIo();
const prompts = makePromptAdapter({ credentialChoice: 'back' });
const expectedPromptMessage = [
'How should KTX find your Anthropic API key?',
'How should ktx find your Anthropic API key?',
'',
[
'KTX uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL,',
'ktx uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL,',
'BI metadata, and docs into semantic-layer sources and wiki context. ktx.yaml stores an env: or file:',
'reference, not the raw key.',
].join(' '),
@ -930,7 +930,7 @@ describe('setup Anthropic model step', () => {
message: expectedPromptMessage,
}),
);
expect(io.stdout()).not.toContain('KTX uses the key');
expect(io.stdout()).not.toContain('ktx uses the key');
});
it('does not persist llm completion when the health check fails', async () => {
@ -980,7 +980,7 @@ describe('setup Anthropic model step', () => {
expect(prompts.select).toHaveBeenCalledTimes(3);
expect(prompts.autocomplete).not.toHaveBeenCalledWith(
expect.objectContaining({
message: expect.stringContaining('Which Anthropic model should KTX use?'),
message: expect.stringContaining('Which Anthropic model should ktx use?'),
}),
);
expect(io.stderr()).toContain('Anthropic model health check failed: model not found');

View file

@ -131,7 +131,7 @@ describe('setup project step', () => {
expect(result.projectDir).toBe(projectDir);
expect(prompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Where should KTX create the project?',
message: 'Where should ktx create the project?',
options: [
expect.objectContaining({ value: 'current', label: `Current directory (${projectDir})` }),
expect.objectContaining({
@ -165,7 +165,7 @@ describe('setup project step', () => {
expect(prompts.select).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
message: 'Where should KTX create the project?',
message: 'Where should ktx create the project?',
options: expect.arrayContaining([
expect.objectContaining({
value: 'new-default',
@ -176,11 +176,11 @@ describe('setup project step', () => {
);
expect(prompts.select).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ message: `Create KTX project at ${projectDir}?` }),
expect.objectContaining({ message: `Create ktx project at ${projectDir}?` }),
);
expect(prompts.text).not.toHaveBeenCalled();
expect(result.status === 'ready' ? result.project.configPath : '').toBe(join(projectDir, 'ktx.yaml'));
expect(testIo.stdout()).toContain(`KTX will create:\n│ ${projectDir}`);
expect(testIo.stdout()).toContain(`ktx will create:\n│ ${projectDir}`);
await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined();
});
@ -243,7 +243,7 @@ describe('setup project step', () => {
expect(prompts.select).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
message: `Create KTX project at ${customProjectDir}?`,
message: `Create ktx project at ${customProjectDir}?`,
options: [
expect.objectContaining({ value: 'create', label: 'Create project' }),
expect.objectContaining({ value: 'choose-another', label: 'Choose another folder' }),
@ -253,7 +253,7 @@ describe('setup project step', () => {
);
expect(prompts.select).toHaveBeenNthCalledWith(
3,
expect.objectContaining({ message: 'Where should KTX create the project?' }),
expect.objectContaining({ message: 'Where should ktx create the project?' }),
);
await expect(stat(join(customProjectDir, 'ktx.yaml'))).rejects.toThrow();
});
@ -280,7 +280,7 @@ describe('setup project step', () => {
);
});
it('confirms before creating KTX files inside an existing non-empty folder', async () => {
it('confirms before creating ktx files inside an existing non-empty folder', async () => {
const startDir = join(tempDir, 'start');
const projectDir = join(startDir, 'analytics-ktx');
await mkdir(projectDir, { recursive: true });
@ -300,7 +300,7 @@ describe('setup project step', () => {
expect.objectContaining({
message: `That folder already exists and is not empty: ${projectDir}`,
options: expect.arrayContaining([
expect.objectContaining({ value: 'use-existing', label: 'Yes, create KTX files there' }),
expect.objectContaining({ value: 'use-existing', label: 'Yes, create ktx files there' }),
expect.objectContaining({ value: 'choose-another', label: 'Choose another folder' }),
]),
}),

View file

@ -73,14 +73,14 @@ describe('setup prompt adapter', () => {
await expect(
adapter.select({
message: 'Which embedding option should KTX use?\n\nKTX uses embeddings for search.',
message: 'Which embedding option should ktx use?\n\nktx uses embeddings for search.',
options,
}),
).resolves.toBe('openai');
expect(mocks.withSetupInterruptConfirmation).toHaveBeenCalledTimes(1);
expect(mocks.select).toHaveBeenCalledWith({
message: 'Which embedding option should KTX use?\n\nKTX uses embeddings for search.\n',
message: 'Which embedding option should ktx use?\n\nktx uses embeddings for search.\n',
options,
});
});
@ -229,10 +229,10 @@ describe('setup prompt adapter', () => {
};
const ui = createKtxSetupUiAdapter();
ui.intro('KTX setup', io);
ui.intro('ktx setup', io);
ui.note(' $ ktx status', 'What you can do next', io);
expect(chunks.join('')).toBe('KTX setup\n\nWhat you can do next:\n $ ktx status\n');
expect(chunks.join('')).toBe('ktx setup\n\nWhat you can do next:\n $ ktx status\n');
expect(mocks.intro).not.toHaveBeenCalled();
expect(mocks.note).not.toHaveBeenCalled();
});
@ -251,13 +251,13 @@ describe('setup prompt adapter', () => {
};
const ui = createKtxSetupUiAdapter();
ui.intro('KTX setup', io);
ui.intro('ktx setup', io);
ui.note(' $ ktx status', 'What you can do next', io);
const bannerWrite = output.write.mock.calls.map((call) => String(call[0])).join('');
expect(bannerWrite).toContain('██');
expect(bannerWrite).toContain('context layer for data agents');
expect(mocks.intro).toHaveBeenCalledWith('KTX setup', { output });
expect(mocks.intro).toHaveBeenCalledWith('ktx setup', { output });
expect(mocks.note).toHaveBeenCalledWith(' $ ktx status', 'What you can do next', { output });
});
});

View file

@ -74,7 +74,7 @@ describe('runKtxSetupReadyMenu', () => {
{ value: 'embeddings', label: 'Embeddings' },
{ value: 'databases', label: 'Databases' },
{ value: 'sources', label: 'Context sources' },
{ value: 'context', label: 'Rebuild KTX context' },
{ value: 'context', label: 'Rebuild ktx context' },
{ value: 'agents', label: 'Agent integration' },
{ value: 'exit', label: 'Exit' },
],
@ -95,7 +95,7 @@ describe('runKtxSetupReadyChangeMenu', () => {
{ value: 'embeddings', label: 'Embeddings' },
{ value: 'databases', label: 'Databases' },
{ value: 'sources', label: 'Context sources' },
{ value: 'context', label: 'Rebuild KTX context' },
{ value: 'context', label: 'Rebuild ktx context' },
{ value: 'agents', label: 'Agent integration' },
{ value: 'exit', label: 'Exit' },
],

View file

@ -71,7 +71,7 @@ describe('runKtxSetupRuntimeStep', () => {
it('fails fast when required runtime features cannot be installed in no-input mode', async () => {
const io = makeIo();
const ensureRuntime = vi.fn(async () => {
throw new Error('KTX Python runtime is required for this command. Run: ktx admin runtime install --yes');
throw new Error('ktx Python runtime is required for this command. Run: ktx admin runtime install --yes');
});
await expect(
@ -97,7 +97,7 @@ describe('runKtxSetupRuntimeStep', () => {
expect(io.stderr()).toContain('ktx admin runtime install --yes');
});
it('starts the KTX daemon for configured sentence-transformers embeddings', async () => {
it('starts the ktx daemon for configured sentence-transformers embeddings', async () => {
const io = makeIo();
const ensureLocalEmbeddings = vi.fn(async () => ({
baseUrl: 'http://127.0.0.1:61234',

View file

@ -57,7 +57,7 @@ function prompts(values: {
}
function connectionNamePrompt(label: string): string {
return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`;
return `Name this ${label} connection\nktx will use this short name in commands and config. You can rename it now.`;
}
function textInputPrompt(message: string): string {
@ -445,7 +445,7 @@ describe('setup sources step', () => {
expect(pickNotionRootPages).toHaveBeenCalledOnce();
expect(testPrompts.select).toHaveBeenCalledWith({
message: 'Which Notion pages should KTX ingest?',
message: 'Which Notion pages should ktx ingest?',
options: [
{ value: 'all_accessible', label: 'All pages the integration can access' },
{ value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
@ -673,7 +673,7 @@ describe('setup sources step', () => {
it('lets visible Metabase mapping surface refresh and validation failures', async () => {
await addPrimarySource();
const runMapping = vi.fn(async (_projectDir: string, _connectionId: string, io: KtxCliIo) => {
io.stderr.write('1: Metabase database does not match KTX connection database\n');
io.stderr.write('1: Metabase database does not match ktx connection database\n');
return 1;
});
const io = makeIo();
@ -704,7 +704,7 @@ describe('setup sources step', () => {
stderr: expect.objectContaining({ write: expect.any(Function) }),
}),
);
expect(io.stderr()).toContain('1: Metabase database does not match KTX connection database');
expect(io.stderr()).toContain('1: Metabase database does not match ktx connection database');
expect(io.stderr()).not.toContain('Metabase mapping validation failed');
expect(testPrompts.log).toHaveBeenCalledWith('Validating Metabase mapping...');
expect(testPrompts.select).toHaveBeenCalledWith(
@ -761,7 +761,7 @@ describe('setup sources step', () => {
expect(testPrompts.multiselect).toHaveBeenCalledWith(
expect.objectContaining({
message:
'Which context sources should KTX ingest?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.',
'Which context sources should ktx ingest?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.',
}),
);
const options = vi.mocked(testPrompts.multiselect).mock.calls[0]?.[0].options ?? [];
@ -1404,7 +1404,7 @@ describe('setup sources step', () => {
).resolves.toEqual({ status: 'ready', projectDir, connectionIds: ['notion-main'] });
expect(testPrompts.select).toHaveBeenCalledWith({
message: 'How should KTX find your Notion integration token?',
message: 'How should ktx find your Notion integration token?',
options: [
{ value: 'keep', label: 'Keep existing credential' },
{ value: 'paste', label: 'Paste a key and save it as a local secret file' },
@ -1473,7 +1473,7 @@ describe('setup sources step', () => {
initialValue: 'https://metabase-old.example.com',
});
expect(testPrompts.select).toHaveBeenCalledWith({
message: 'How should KTX find your Metabase API key?',
message: 'How should ktx find your Metabase API key?',
options: [
{ value: 'keep', label: 'Keep existing credential' },
{ value: 'paste', label: 'Paste a key and save it as a local secret file' },
@ -1817,7 +1817,7 @@ describe('setup sources step', () => {
select: ['env', 'back', 'env', 'all_accessible'],
text: ['notion-main'],
deps: { validateNotion: vi.fn(async () => ({ ok: true as const, detail: 'roots=0' })) },
repeatedSelectMessage: 'How should KTX find your Notion integration token?',
repeatedSelectMessage: 'How should ktx find your Notion integration token?',
},
];
@ -1961,7 +1961,7 @@ describe('setup sources step', () => {
expect(testPrompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Multiple dbt projects found — which one should KTX use?',
message: 'Multiple dbt projects found — which one should ktx use?',
}),
);
expect(testPrompts.text).toHaveBeenCalledTimes(2);

View file

@ -402,7 +402,7 @@ describe('setup status', () => {
expect(status.llm).toMatchObject({ backend: 'vertex', ready: true, model: 'claude-sonnet-4-6' });
expect(status.context).toMatchObject({ ready: true, status: 'completed' });
expect(rendered).toContain('LLM ready: yes (claude-sonnet-4-6)');
expect(rendered).toContain('KTX context built: yes');
expect(rendered).toContain('ktx context built: yes');
});
it('reports context ready after a partial ingest report saved memory', async () => {
@ -462,10 +462,10 @@ describe('setup status', () => {
const status = await readKtxSetupStatus(tempDir);
const rendered = formatKtxSetupStatus(status);
expect(rendered).toContain(`No KTX project found at ${tempDir}.`);
expect(rendered).toContain(`No ktx project found at ${tempDir}.`);
expect(rendered).toContain('Check another project: ktx --project-dir <folder> status');
expect(rendered).toContain('Or from that folder: ktx status');
expect(rendered).toContain('Create a new KTX project here: ktx setup');
expect(rendered).toContain('Create a new ktx project here: ktx setup');
expect(rendered).not.toContain('Project ready: no');
expect(JSON.parse(JSON.stringify(status))).toMatchObject({ project: { path: tempDir, ready: false } });
});
@ -475,13 +475,13 @@ describe('setup status', () => {
const rendered = formatKtxSetupStatus(await readKtxSetupStatus(tempDir));
expect(rendered).toContain(`KTX project: ${tempDir}`);
expect(rendered).toContain(`ktx project: ${tempDir}`);
expect(rendered).toContain('Project ready: yes');
expect(rendered).toContain('LLM ready: no');
expect(rendered).toContain('Databases configured: no');
expect(rendered).not.toContain(['Primary sources', 'configured'].join(' '));
expect(rendered).toContain('KTX context built: no');
expect(rendered).not.toContain('No KTX project found.');
expect(rendered).toContain('ktx context built: no');
expect(rendered).not.toContain('No ktx project found.');
});
it('formats a concise ready summary for completed agent setup', () => {
@ -511,7 +511,7 @@ describe('setup status', () => {
` ktx mcp stop --project-dir ${tempDir}`,
'',
'2. Open Claude Code',
' Open Claude Code from the KTX project directory:',
' Open Claude Code from the ktx project directory:',
'',
' RUN:',
` cd '${tempDir}'`,
@ -528,7 +528,7 @@ describe('setup status', () => {
expect(rendered).toContain(' RUN:');
expect(rendered).toContain(' If you need to stop MCP later:');
expect(rendered).toContain(`ktx mcp stop --project-dir ${tempDir}`);
expect(rendered).toContain('After that, try\n Ask your agent: "Use KTX to show me the available tables."');
expect(rendered).toContain('After that, try\n Ask your agent: "Use ktx to show me the available tables."');
expect(rendered).not.toContain('Verify');
expect(rendered).not.toContain('Project ready: yes');
expect(rendered).not.toContain('What you can do next');
@ -582,8 +582,8 @@ describe('setup status', () => {
expect(output).toContain('Requires MCP to be started.');
expect(output).toContain('Analytics skill installed.');
expect(output).not.toContain('Agent integration complete');
expect(output).toContain('Finish KTX agent setup');
expect(output).not.toContain('KTX project ready');
expect(output).toContain('Finish ktx agent setup');
expect(output).not.toContain('ktx project ready');
expect(output).toContain('REQUIRED BEFORE USING AGENTS');
expect(output).toContain('Run this command before using Claude Code:');
expect(output).toContain(`ktx mcp start --project-dir ${tempDir}`);
@ -651,7 +651,7 @@ describe('setup status', () => {
),
).resolves.toBe(0);
expect(testIo.stdout()).toContain('KTX setup');
expect(testIo.stdout()).toContain('ktx setup');
expect(testIo.stdout()).toContain(`Project: ${tempDir}`);
expect(testIo.stdout()).toContain('Project ready: yes');
expect(testIo.stdout()).toContain('What you can do next:');
@ -733,7 +733,7 @@ describe('setup status', () => {
await expect(stat(join(projectDir, '.ktx'))).resolves.toBeDefined();
});
it('preserves KTX scaffold files in an initially empty project directory when setup fails', async () => {
it('preserves ktx scaffold files in an initially empty project directory when setup fails', async () => {
const testIo = makeIo();
await expect(
@ -860,12 +860,12 @@ describe('setup status', () => {
const select = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => {
const labels = options.options.map((option) => option.label);
expect(labels).toEqual([
'Set up KTX for my data',
'Set up ktx for my data',
'Check setup status',
'Explore a pre-built KTX project',
'Explore a pre-built ktx project',
'Exit',
]);
expect(labels.indexOf('Explore a pre-built KTX project')).toBe(labels.length - 2);
expect(labels.indexOf('Explore a pre-built ktx project')).toBe(labels.length - 2);
return 'exit';
});
const cancel = vi.fn();
@ -901,17 +901,17 @@ describe('setup status', () => {
const missingIo = makeIo();
const existingIo = makeIo();
const missingSelect = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => {
expect(options.options.map((option) => option.label)).not.toContain('Connect a coding agent to KTX');
expect(options.options.map((option) => option.label)).not.toContain('Connect a coding agent to ktx');
return 'exit';
});
const existingSelect = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => {
const labels = options.options.map((option) => option.label);
expect(labels).toEqual([
'Resume or change an existing setup',
'Create a new KTX project',
'Connect a coding agent to KTX',
'Create a new ktx project',
'Connect a coding agent to ktx',
'Check setup status',
'Explore a pre-built KTX project',
'Explore a pre-built ktx project',
'Exit',
]);
return 'exit';
@ -1009,13 +1009,13 @@ describe('setup status', () => {
expect(projectPrompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Where should KTX create the project?',
message: 'Where should ktx create the project?',
options: expect.arrayContaining([expect.objectContaining({ value: 'back', label: 'Back' })]),
}),
);
expect(projectPrompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Where should KTX create the project?',
message: 'Where should ktx create the project?',
options: expect.not.arrayContaining([expect.objectContaining({ value: 'exit', label: 'Exit' })]),
}),
);
@ -1068,7 +1068,7 @@ describe('setup status', () => {
expect(projectPrompts.select).toHaveBeenCalledWith(
expect.objectContaining({
message: 'Where should KTX create the project?',
message: 'Where should ktx create the project?',
options: expect.arrayContaining([expect.objectContaining({ value: 'back', label: 'Back' })]),
}),
);
@ -1145,7 +1145,7 @@ describe('setup status', () => {
}),
);
expect(projectPrompts.select).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Where should KTX create the project?' }),
expect.objectContaining({ message: 'Where should ktx create the project?' }),
);
await expect(stat(join(newProjectDir, 'ktx.yaml'))).resolves.toBeDefined();
await expect(readFile(join(existingProjectDir, 'ktx.yaml'), 'utf-8')).resolves.toBe(existingConfig);
@ -1268,7 +1268,7 @@ describe('setup status', () => {
await expect(readFile(join(tempDir, '.ktx', 'setup', 'state.json'), 'utf-8')).resolves.toBe(
`${JSON.stringify({ completed_steps: ['project', 'sources'] }, null, 2)}\n`,
);
expect(testIo.stdout()).toContain('KTX setup');
expect(testIo.stdout()).toContain('ktx setup');
expect(testIo.stdout()).toContain(`Project: ${tempDir}`);
expect(testIo.stdout()).toContain('Project ready: yes');
expect(testIo.stderr()).toBe('');
@ -1969,7 +1969,7 @@ describe('setup status', () => {
).resolves.toBe(0);
expect(calls).toEqual(['model', 'embeddings', 'databases', 'sources']);
expect(io.stderr()).not.toContain('KTX cannot build agent-ready context yet.');
expect(io.stderr()).not.toContain('ktx cannot build agent-ready context yet.');
});
it('runs context after sources and before agents in full setup', async () => {
@ -2185,7 +2185,7 @@ describe('setup status', () => {
expect(runtime).not.toHaveBeenCalled();
expect(context).not.toHaveBeenCalled();
expect(agents).toHaveBeenCalledTimes(1);
expect(io.stderr()).not.toContain('KTX context is not ready for agents.');
expect(io.stderr()).not.toContain('ktx context is not ready for agents.');
});
it('runs non-TTY --agents with a target without requiring --no-input or --yes', async () => {

View file

@ -172,7 +172,7 @@ describe('standalone built ktx CLI smoke', () => {
it('runs status setup checks through the built binary', async () => {
const result = await runBuiltCli(['status', '--verbose', '--no-input'], { cwd: tempDir });
expect(result.stdout).toMatch(/KTX status/);
expect(result.stdout).toMatch(/ktx status/);
if (result.stdout.includes('No project here yet.')) {
expect(result.stdout).toContain('ktx setup');
} else {
@ -203,7 +203,7 @@ describe('standalone built ktx CLI smoke', () => {
const ingest = await runBuiltCli(['ingest', 'warehouse', '--project-dir', projectDir, '--no-input']);
expect(ingest.code).toBe(1);
expect(ingest.stdout).toContain('warehouse cannot be ingested: enrichment is not configured');
expect(ingest.stdout).not.toContain('KTX scan completed');
expect(ingest.stdout).not.toContain('ktx scan completed');
}, 30_000);
it('parses gateway LLM config and OpenAI enrichment embeddings used by standalone scans without network calls', async () => {

View file

@ -111,7 +111,7 @@ describe('runKtxTextIngest', () => {
expect.objectContaining({
userId: 'local-cli',
chatId: 'cli-text-ingest-1700000000000-1',
userMessage: 'Ingest external text artifact "Revenue means gross receipts." into KTX memory.',
userMessage: 'Ingest external text artifact "Revenue means gross receipts." into ktx memory.',
assistantMessage: 'Revenue means gross receipts.',
sourceType: 'external_ingest',
}),
@ -120,7 +120,7 @@ describe('runKtxTextIngest', () => {
2,
expect.objectContaining({
chatId: 'cli-text-ingest-1700000000000-2',
userMessage: 'Ingest external text artifact "Orders are completed purchases." into KTX memory.',
userMessage: 'Ingest external text artifact "Orders are completed purchases." into ktx memory.',
assistantMessage: 'Orders are completed purchases.',
}),
);
@ -176,7 +176,7 @@ describe('runKtxTextIngest', () => {
expect.objectContaining({
connectionId: 'warehouse',
userId: 'agent',
userMessage: 'Ingest external text artifact "revenue.md" into KTX memory.',
userMessage: 'Ingest external text artifact "revenue.md" into ktx memory.',
assistantMessage: 'file:/tmp/docs/revenue.md',
}),
);
@ -184,7 +184,7 @@ describe('runKtxTextIngest', () => {
2,
expect.objectContaining({
connectionId: 'warehouse',
userMessage: 'Ingest external text artifact "stdin" into KTX memory.',
userMessage: 'Ingest external text artifact "stdin" into ktx memory.',
assistantMessage: 'stdin content',
}),
);
@ -228,19 +228,19 @@ describe('runKtxTextIngest', () => {
expect(ingest.ingest).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
userMessage: 'Ingest external text artifact "remember to call me Andrey" into KTX memory.',
userMessage: 'Ingest external text artifact "remember to call me Andrey" into ktx memory.',
}),
);
expect(ingest.ingest).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
userMessage: 'Ingest external text artifact "first line second line" into KTX memory.',
userMessage: 'Ingest external text artifact "first line second line" into ktx memory.',
}),
);
expect(ingest.ingest).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
userMessage: 'Ingest external text artifact "This inline note is intentionally long xxxxxxxx..." into KTX memory.',
userMessage: 'Ingest external text artifact "This inline note is intentionally long xxxxxxxx..." into ktx memory.',
}),
);
});