rename klo to ktx

This commit is contained in:
Andrey Avtomonov 2026-05-10 23:51:24 +02:00
parent 1a42152e6f
commit 3ce510b55b
704 changed files with 10205 additions and 10255 deletions

View file

@ -6,7 +6,7 @@ import { describe, it } from 'node:test';
import { acquirePublicBenchmarkFixtures } from './acquire-public-benchmark-fixtures.mjs';
function tempRoot() {
return mkdtempSync(path.join(tmpdir(), 'klo-acquire-'));
return mkdtempSync(path.join(tmpdir(), 'ktx-acquire-'));
}
function writeManifest(dir, fixtures) {

View file

@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import { describe, it } from 'node:test';
const KLO_ROOT = new URL('../', import.meta.url);
const KTX_ROOT = new URL('../', import.meta.url);
const RELATIONSHIP_RUNTIME_SOURCES = Object.freeze([
'packages/context/src/scan/relationship-benchmarks.ts',
@ -19,7 +19,7 @@ const RELATIONSHIP_RUNTIME_SOURCES = Object.freeze([
]);
async function checkedInFixtureIds() {
const fixtureRoot = new URL('packages/context/test/fixtures/relationship-benchmarks/', KLO_ROOT);
const fixtureRoot = new URL('packages/context/test/fixtures/relationship-benchmarks/', KTX_ROOT);
const entries = await readdir(fixtureRoot, { withFileTypes: true });
return entries
.filter((entry) => entry.isDirectory())
@ -31,7 +31,7 @@ async function readRuntimeSources() {
return Promise.all(
RELATIONSHIP_RUNTIME_SOURCES.map(async (relativePath) => ({
relativePath,
source: await readFile(new URL(relativePath, KLO_ROOT), 'utf8'),
source: await readFile(new URL(relativePath, KTX_ROOT), 'utf8'),
})),
);
}

View file

@ -216,16 +216,16 @@ export async function buildAdventureWorksOltpFixture(input) {
}
async function main() {
const url = process.env.KLO_ADVENTUREWORKS_SQLSERVER_URL;
const url = process.env.KTX_ADVENTUREWORKS_SQLSERVER_URL;
if (!url) {
throw new Error(
'Set KLO_ADVENTUREWORKS_SQLSERVER_URL to a read-only SQL Server URL for a full AdventureWorks OLTP database before running this script.',
'Set KTX_ADVENTUREWORKS_SQLSERVER_URL to a read-only SQL Server URL for a full AdventureWorks OLTP database before running this script.',
);
}
const source = JSON.parse(readFileSync(path.join(scriptDir, 'adventureworks-oltp-source.json'), 'utf8'));
const { KloSqlServerScanConnector } = await import('../packages/connector-sqlserver/dist/index.js');
const connector = new KloSqlServerScanConnector({
const { KtxSqlServerScanConnector } = await import('../packages/connector-sqlserver/dist/index.js');
const connector = new KtxSqlServerScanConnector({
connectionId: fixtureId,
connection: {
driver: 'sqlserver',

View file

@ -7,7 +7,7 @@ const require = createRequire(new URL('../packages/context/package.json', import
const Database = require('better-sqlite3');
describe('buildBenchmarkSnapshot', () => {
it('emits a KloSchemaSnapshot-shaped object plus expected-links from declared FKs', () => {
it('emits a KtxSchemaSnapshot-shaped object plus expected-links from declared FKs', () => {
const db = new Database(':memory:');
db.exec(`
PRAGMA foreign_keys = ON;

View file

@ -7,8 +7,8 @@ import { gzipSync } from 'node:zlib';
import { buildBenchmarkSnapshot, writeFixtureFiles } from './build-benchmark-snapshot.mjs';
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const kloRoot = path.resolve(scriptDir, '..');
const fixtureRoot = path.join(kloRoot, 'packages', 'context', 'test', 'fixtures', 'relationship-benchmarks');
const ktxRoot = path.resolve(scriptDir, '..');
const fixtureRoot = path.join(ktxRoot, 'packages', 'context', 'test', 'fixtures', 'relationship-benchmarks');
const require = createRequire(new URL('../packages/context/package.json', import.meta.url));
const Database = require('better-sqlite3');
const { stringify: yamlStringify } = require('yaml');

View file

@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
const codeExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py']);
const runtimeAssetPatterns = [/^packages\/[^/]+\/prompts\/.+\.md$/, /^packages\/[^/]+\/skills\/.+\.md$/];
const identifierSkipPrefixes = ['docs/', 'examples/', 'python/klo-sl/plans/', 'python/klo-sl/openspec/'];
const identifierSkipPrefixes = ['docs/', 'examples/', 'python/ktx-sl/plans/', 'python/ktx-sl/openspec/'];
const forbiddenIdentifierTerms = ['kae' + 'lio', 'Kae' + 'lio', 'KAE' + 'LIO_'];
const appImportPatterns = [
@ -47,7 +47,7 @@ const llmBoundaryPatterns = [
},
{
label: 'legacy scan LLM provider port',
pattern: /\bKloScanLlmPort\b/,
pattern: /\bKtxScanLlmPort\b/,
},
{
label: 'legacy gateway LLM provider helper',
@ -120,7 +120,7 @@ export function scanFileContent(relativePath, content) {
violations.push({
file: normalizedPath,
kind: 'llm-boundary',
message: `Forbidden ${llmBoundaryPattern.label}; use @klo/llm`,
message: `Forbidden ${llmBoundaryPattern.label}; use @ktx/llm`,
});
}
}
@ -132,7 +132,7 @@ export function scanFileContent(relativePath, content) {
violations.push({
file: normalizedPath,
kind: 'llm-boundary',
message: `Forbidden ${llmBoundaryPattern.label}; use getModel(role) inside @klo/context`,
message: `Forbidden ${llmBoundaryPattern.label}; use getModel(role) inside @ktx/context`,
});
}
}
@ -197,7 +197,7 @@ async function main() {
const violations = await collectViolations(rootDir);
if (violations.length === 0) {
process.stdout.write('klo boundary check passed\n');
process.stdout.write('ktx boundary check passed\n');
return;
}

View file

@ -61,22 +61,22 @@ describe('scanFileContent', () => {
assert.equal(scanFileContent('docs/transition.md', name).length, 0);
assert.equal(scanFileContent('examples/transition.md', name).length, 0);
assert.equal(scanFileContent('python/klo-sl/plans/brainstorm.md', name).length, 0);
assert.equal(scanFileContent('python/klo-sl/openspec/specs/semantic-layer/spec.md', name).length, 0);
assert.equal(scanFileContent('python/ktx-sl/plans/brainstorm.md', name).length, 0);
assert.equal(scanFileContent('python/ktx-sl/openspec/specs/semantic-layer/spec.md', name).length, 0);
});
it('allows clean source files and clean runtime prompt assets', () => {
assert.deepEqual(
scanFileContent('packages/context/src/index.ts', "export const packageName = '@klo/context';"),
scanFileContent('packages/context/src/index.ts', "export const packageName = '@ktx/context';"),
[],
);
assert.deepEqual(
scanFileContent('packages/context/prompts/memory_agent_bundle_ingest_work_unit.md', 'Write output for KLO.'),
scanFileContent('packages/context/prompts/memory_agent_bundle_ingest_work_unit.md', 'Write output for KTX.'),
[],
);
});
it('rejects context-owned LLM provider construction after @klo/llm migration', () => {
it('rejects context-owned LLM provider construction after @ktx/llm migration', () => {
const violations = [
...scanFileContent(
'packages/context/src/agent/local-llm-provider.ts',
@ -92,10 +92,10 @@ describe('scanFileContent', () => {
);
});
it('rejects old KLO LLM port declarations in context', () => {
it('rejects old KTX LLM port declarations in context', () => {
const violations = [
...scanFileContent('packages/context/src/agent/agent-runner.service.ts', 'export interface LlmProviderPort {}'),
...scanFileContent('packages/context/src/scan/types.ts', 'export interface KloScanLlmPort {}'),
...scanFileContent('packages/context/src/scan/types.ts', 'export interface KtxScanLlmPort {}'),
...scanFileContent('packages/context/src/agent/gateway-llm-provider.ts', 'export function createGatewayLlmProvider() {}'),
];
@ -115,7 +115,7 @@ describe('scanFileContent', () => {
assert.equal(violations[0]?.kind, 'llm-boundary');
assert.equal(
violations[0]?.message,
'Forbidden context getModelByName call; use getModel(role) inside @klo/context',
'Forbidden context getModelByName call; use getModel(role) inside @ktx/context',
);
});

View file

@ -12,7 +12,7 @@ async function readCiWorkflowOrSkip(testContext) {
await access(ciWorkflowPath);
} catch (error) {
if (error && error.code === 'ENOENT') {
testContext.skip('root CI workflow is absent from sparse klo checkout');
testContext.skip('root CI workflow is absent from sparse ktx checkout');
return null;
}
throw error;
@ -20,8 +20,8 @@ async function readCiWorkflowOrSkip(testContext) {
return readFile(ciWorkflowPath, 'utf-8');
}
describe('KLO CI artifact upload contract', () => {
it('uploads verified KLO package artifacts from check-klo-subtree', async (testContext) => {
describe('KTX CI artifact upload contract', () => {
it('uploads verified KTX package artifacts from check-ktx-subtree', async (testContext) => {
const workflow = await readCiWorkflowOrSkip(testContext);
if (workflow === null) {
return;
@ -29,14 +29,14 @@ describe('KLO CI artifact upload contract', () => {
assert.match(
workflow,
/name: Build klo package artifacts and verify public smoke\s+run: cd klo && pnpm run artifacts:build && pnpm run artifacts:verify-manifest && pnpm run artifacts:verify-demo\s+- name: Upload klo package artifacts/s,
/name: Build ktx package artifacts and verify public smoke\s+run: cd ktx && pnpm run artifacts:build && pnpm run artifacts:verify-manifest && pnpm run artifacts:verify-demo\s+- name: Upload ktx package artifacts/s,
);
assert.match(workflow, /uses: actions\/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f/);
assert.match(workflow, /name: klo-package-artifacts-\$\{\{ github\.sha \}\}/);
assert.match(workflow, /klo\/dist\/artifacts\/manifest\.json/);
assert.match(workflow, /klo\/dist\/artifacts\/npm\/\*\.tgz/);
assert.match(workflow, /klo\/dist\/artifacts\/python\/\*\.whl/);
assert.match(workflow, /klo\/dist\/artifacts\/python\/\*\.tar\.gz/);
assert.match(workflow, /name: ktx-package-artifacts-\$\{\{ github\.sha \}\}/);
assert.match(workflow, /ktx\/dist\/artifacts\/manifest\.json/);
assert.match(workflow, /ktx\/dist\/artifacts\/npm\/\*\.tgz/);
assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.whl/);
assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.tar\.gz/);
assert.match(workflow, /if-no-files-found: error/);
assert.match(workflow, /retention-days: 7/);
});
@ -47,11 +47,11 @@ describe('KLO CI artifact upload contract', () => {
return;
}
assert.match(workflow, /check-klo-packed-demo:/);
assert.match(workflow, /check-ktx-packed-demo:/);
assert.match(workflow, /matrix:\s+os: \[ubuntu-latest, macos-latest\]/s);
assert.match(workflow, /name: Download klo package artifacts/);
assert.match(workflow, /path: klo\/dist\/artifacts/);
assert.match(workflow, /run: cd klo && pnpm run artifacts:verify-demo/);
assert.match(workflow, /name: Download ktx package artifacts/);
assert.match(workflow, /path: ktx\/dist\/artifacts/);
assert.match(workflow, /run: cd ktx && pnpm run artifacts:verify-demo/);
});
it('includes packed demo artifact smoke in ci-success', async (testContext) => {
@ -62,9 +62,9 @@ describe('KLO CI artifact upload contract', () => {
assert.match(
workflow,
/needs: \[check-klo-subtree, check-klo-packed-demo, build-python-service, test-server, build-frontend, run-pre-commit, build-docker-images\]/,
/needs: \[check-ktx-subtree, check-ktx-packed-demo, build-python-service, test-server, build-frontend, run-pre-commit, build-docker-images\]/,
);
assert.match(workflow, /needs\.check-klo-packed-demo\.result.*== "failure"/);
assert.match(workflow, /needs\.check-klo-packed-demo\.result.*== "cancelled"/);
assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "failure"/);
assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "cancelled"/);
});
});

View file

@ -18,7 +18,7 @@ describe('standalone example docs', () => {
it('documents the Orbit relationship verification example project', async () => {
const examples = await readText('examples/README.md');
const readme = await readText('examples/orbit-relationship-verification/README.md');
const config = await readText('examples/orbit-relationship-verification/klo.yaml');
const config = await readText('examples/orbit-relationship-verification/ktx.yaml');
assert.match(examples, /orbit-relationship-verification/);
assert.match(examples, /relationships:verify-orbit/);
@ -51,14 +51,14 @@ describe('standalone example docs', () => {
assert.match(examples, /pg_stat_statements/);
assert.match(readme, /--enable-historic-sql/);
assert.match(readme, /--historic-sql-min-calls 2/);
assert.match(readme, /klo dev doctor --project-dir/);
assert.match(readme, /ktx dev doctor --project-dir/);
assert.match(readme, /Postgres Historic SQL/);
assert.match(readme, /dev ingest run/);
assert.match(compose, /postgres:14/);
assert.match(compose, /shared_preload_libraries=pg_stat_statements/);
assert.match(compose, /pg_stat_statements.track=top/);
assert.match(initSql, /CREATE EXTENSION IF NOT EXISTS pg_stat_statements/);
assert.match(initSql, /GRANT pg_read_all_stats TO klo_reader/);
assert.match(initSql, /GRANT pg_read_all_stats TO ktx_reader/);
assert.match(workload, /JOIN customers/);
assert.match(workload, /app_user/);
assert.match(workload, /etl_user/);
@ -81,8 +81,8 @@ describe('standalone example docs', () => {
assert.match(rootReadme, /`packages\/connector-snowflake`/);
assert.match(rootReadme, /`packages\/connector-sqlite`/);
assert.match(rootReadme, /`packages\/connector-sqlserver`/);
assert.match(rootReadme, /`python\/klo-sl`/);
assert.match(rootReadme, /`python\/klo-daemon`/);
assert.match(rootReadme, /`python\/ktx-sl`/);
assert.match(rootReadme, /`python\/ktx-daemon`/);
});
it('documents every standalone MCP tool that the CLI server exposes', async () => {
@ -103,7 +103,7 @@ describe('standalone example docs', () => {
assert.match(rootReadme, /`ingest_replay`/);
});
it('walks through klo connection list and klo connection test in the README quickstart', async () => {
it('walks through ktx connection list and ktx connection test in the README quickstart', async () => {
const rootReadme = await readText('README.md');
assert.match(rootReadme, /connection list --project-dir/);
@ -112,7 +112,7 @@ describe('standalone example docs', () => {
assert.match(rootReadme, /Tables: 1/);
});
it('replaces the fake-ingest smoke with a klo scan walkthrough in the README', async () => {
it('replaces the fake-ingest smoke with a ktx scan walkthrough in the README', async () => {
const rootReadme = await readText('README.md');
assert.match(rootReadme, /### Scan the demo warehouse/);
@ -121,15 +121,15 @@ describe('standalone example docs', () => {
assert.match(rootReadme, /scan report --project-dir/);
assert.match(rootReadme, /raw-sources\/warehouse\/live-database/);
assert.doesNotMatch(rootReadme, /Run a local ingest smoke test/);
assert.doesNotMatch(rootReadme, /klo dev ingest run --project-dir/);
assert.doesNotMatch(rootReadme, /klo ingest status --project-dir/);
assert.doesNotMatch(rootReadme, /ktx dev ingest run --project-dir/);
assert.doesNotMatch(rootReadme, /ktx ingest status --project-dir/);
});
it('documents pnpm setup as a prerequisite when optional dev linking fails', async () => {
const rootReadme = await readText('README.md');
assert.match(rootReadme, /pnpm run link:dev/);
assert.match(rootReadme, /klo-dev --help/);
assert.match(rootReadme, /ktx-dev --help/);
assert.doesNotMatch(
rootReadme,
/If the setup command reports that pnpm's global bin directory is not on your\n`PATH`, add the printed directory to your shell profile/,
@ -144,7 +144,7 @@ describe('standalone example docs', () => {
});
it('documents daemon HTTP database, source generation, LookML, embedding, and code execution support', async () => {
const readme = await readText('python/klo-daemon/README.md');
const readme = await readText('python/ktx-daemon/README.md');
assert.match(readme, /semantic-generate-sources/);
assert.match(readme, /database-introspect/);

View file

@ -16,13 +16,13 @@ import {
pythonArtifactInstallArgs,
} from './package-artifacts.mjs';
const POSTGRES_IMAGE = process.env.KLO_ARTIFACT_POSTGRES_IMAGE ?? 'postgres:16-alpine';
const POSTGRES_USER = 'klo';
const POSTGRES_IMAGE = process.env.KTX_ARTIFACT_POSTGRES_IMAGE ?? 'postgres:16-alpine';
const POSTGRES_USER = 'ktx';
const POSTGRES_PASSWORD = 'postgres'; // pragma: allowlist secret
const POSTGRES_DB = 'warehouse';
export function smokeContainerName(pid = process.pid, now = Date.now()) {
return `klo-live-db-smoke-${pid}-${now}`;
return `ktx-live-db-smoke-${pid}-${now}`;
}
export function buildPostgresUrl(hostPort) {
@ -88,7 +88,7 @@ export function buildSeedSql() {
].join('\n');
}
export function buildKloYaml(postgresUrl) {
export function buildKtxYaml(postgresUrl) {
return [
'project: artifact-live-database',
'connections:',
@ -109,7 +109,7 @@ export function buildKloYaml(postgresUrl) {
export function buildLiveDatabaseIngestArgs(projectDir, databaseIntrospectionUrl) {
return [
'exec',
'klo',
'ktx',
'dev',
'ingest',
'run',
@ -125,7 +125,7 @@ export function buildLiveDatabaseIngestArgs(projectDir, databaseIntrospectionUrl
}
export function buildLiveDatabaseStatusArgs(projectDir, runId) {
return ['exec', 'klo', 'ingest', 'status', '--project-dir', projectDir, runId];
return ['exec', 'ktx', 'ingest', 'status', '--project-dir', projectDir, runId];
}
async function run(command, args, options = {}) {
@ -283,12 +283,12 @@ async function waitForHttpHealth(url, daemon) {
if (daemon.error()) {
const output = daemon.output();
throw new Error(
`Failed to start klo-daemon: ${daemon.error().message}\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`,
`Failed to start ktx-daemon: ${daemon.error().message}\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`,
);
}
if (daemon.child.exitCode !== null || daemon.child.signalCode !== null) {
const output = daemon.output();
throw new Error(`klo-daemon exited before health check passed\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`);
throw new Error(`ktx-daemon exited before health check passed\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`);
}
try {
if (await httpGetOk(url)) {
@ -306,7 +306,7 @@ async function waitForHttpHealth(url, daemon) {
async function startDaemon(port, cleanInstallDir) {
const daemon = spawnLogged(
'klo-daemon',
'ktx-daemon',
['serve-http', '--host', '127.0.0.1', '--port', String(port), '--log-level', 'warning'],
{ cwd: cleanInstallDir, env: npmSmokePythonEnv(cleanInstallDir) },
);
@ -337,8 +337,8 @@ async function assertPathExists(path, label) {
async function prepareCleanInstall(layout, cleanInstallDir) {
const pythonArtifacts = await findPythonArtifacts(layout.pythonDir);
await assertPathExists(layout.contextTarball, '@klo/context tarball');
await assertPathExists(layout.cliTarball, '@klo/cli tarball');
await assertPathExists(layout.contextTarball, '@ktx/context tarball');
await assertPathExists(layout.cliTarball, '@ktx/cli tarball');
await mkdir(cleanInstallDir, { recursive: true });
await writeFile(join(cleanInstallDir, 'package.json'), `${JSON.stringify(npmSmokePackageJson(layout), null, 2)}\n`);
await run('pnpm', ['install'], { cwd: cleanInstallDir, timeout: 120_000 }).then((result) =>
@ -362,7 +362,7 @@ async function prepareCleanInstall(layout, cleanInstallDir) {
async function main() {
const layout = packageArtifactLayout();
const root = await mkdtemp(join(tmpdir(), 'klo-live-db-artifact-smoke-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-live-db-artifact-smoke-'));
const containerName = smokeContainerName();
let daemon;
try {
@ -379,12 +379,12 @@ async function main() {
await prepareCleanInstall(layout, cleanInstallDir);
await mkdir(projectDir, { recursive: true });
const init = await run('pnpm', ['exec', 'klo', 'init', projectDir, '--name', 'artifact-live-database'], {
const init = await run('pnpm', ['exec', 'ktx', 'init', projectDir, '--name', 'artifact-live-database'], {
cwd: cleanInstallDir,
timeout: 30_000,
});
requireSuccess('klo init', init);
await writeFile(join(projectDir, 'klo.yaml'), buildKloYaml(postgresUrl), 'utf8');
requireSuccess('ktx init', init);
await writeFile(join(projectDir, 'ktx.yaml'), buildKtxYaml(postgresUrl), 'utf8');
daemon = await startDaemon(daemonPort, cleanInstallDir);
@ -393,12 +393,12 @@ async function main() {
env: npmSmokePythonEnv(cleanInstallDir),
timeout: 120_000,
});
requireSuccess('klo dev ingest run live-database', ingestRun);
requireOutput('klo dev ingest run live-database', ingestRun, /Status: done/);
requireOutput('klo dev ingest run live-database', ingestRun, /Adapter: live-database/);
requireOutput('klo dev ingest run live-database', ingestRun, /Diff: \+4\/~0\/-0\/=0/);
requireOutput('klo dev ingest run live-database', ingestRun, /Raw files: 4/);
requireOutput('klo dev ingest run live-database', ingestRun, /Work units: 2/);
requireSuccess('ktx dev ingest run live-database', ingestRun);
requireOutput('ktx dev ingest run live-database', ingestRun, /Status: done/);
requireOutput('ktx dev ingest run live-database', ingestRun, /Adapter: live-database/);
requireOutput('ktx dev ingest run live-database', ingestRun, /Diff: \+4\/~0\/-0\/=0/);
requireOutput('ktx dev ingest run live-database', ingestRun, /Raw files: 4/);
requireOutput('ktx dev ingest run live-database', ingestRun, /Work units: 2/);
const runId = getRunId(ingestRun.stdout);
const ingestStatus = await run('pnpm', buildLiveDatabaseStatusArgs(projectDir, runId), {
@ -406,12 +406,12 @@ async function main() {
env: npmSmokePythonEnv(cleanInstallDir),
timeout: 30_000,
});
requireSuccess('klo ingest status live-database', ingestStatus);
requireOutput('klo ingest status live-database', ingestStatus, new RegExp(`Run: ${runId}`));
requireOutput('klo ingest status live-database', ingestStatus, /Status: done/);
requireOutput('klo ingest status live-database', ingestStatus, /Raw files: 4/);
requireOutput('klo ingest status live-database', ingestStatus, /Work units: 2/);
await assertPathExists(join(projectDir, '.klo', 'db.sqlite'), 'SQLite local ingest state');
requireSuccess('ktx ingest status live-database', ingestStatus);
requireOutput('ktx ingest status live-database', ingestStatus, new RegExp(`Run: ${runId}`));
requireOutput('ktx ingest status live-database', ingestStatus, /Status: done/);
requireOutput('ktx ingest status live-database', ingestStatus, /Raw files: 4/);
requireOutput('ktx ingest status live-database', ingestStatus, /Work units: 2/);
await assertPathExists(join(projectDir, '.ktx', 'db.sqlite'), 'SQLite local ingest state');
process.stdout.write(`Installed live-database artifact smoke passed: ${runId}\n`);
} finally {
if (daemon) {

View file

@ -3,7 +3,7 @@ import { describe, it } from 'node:test';
import {
buildDockerRunArgs,
buildKloYaml,
buildKtxYaml,
buildLiveDatabaseIngestArgs,
buildLiveDatabaseStatusArgs,
buildPostgresUrl,
@ -16,7 +16,7 @@ describe('installed live-database artifact smoke helpers', () => {
it('builds a deterministic disposable Postgres container command', () => {
assert.deepEqual(
buildDockerRunArgs({
containerName: 'klo-live-db-smoke-test',
containerName: 'ktx-live-db-smoke-test',
hostPort: 15432,
image: 'postgres:16-alpine',
}),
@ -25,11 +25,11 @@ describe('installed live-database artifact smoke helpers', () => {
'--rm',
'-d',
'--name',
'klo-live-db-smoke-test',
'ktx-live-db-smoke-test',
'-e',
'POSTGRES_PASSWORD=postgres', // pragma: allowlist secret
'-e',
'POSTGRES_USER=klo',
'POSTGRES_USER=ktx',
'-e',
'POSTGRES_DB=warehouse',
'-p',
@ -40,25 +40,25 @@ describe('installed live-database artifact smoke helpers', () => {
});
it('uses a collision-resistant Docker container name prefix', () => {
assert.match(smokeContainerName(1234, 5678), /^klo-live-db-smoke-1234-5678$/);
assert.match(smokeContainerName(1234, 5678), /^ktx-live-db-smoke-1234-5678$/);
});
it('builds the Postgres URL used by klo.yaml and daemon introspection', () => {
it('builds the Postgres URL used by ktx.yaml and daemon introspection', () => {
assert.equal(
buildPostgresUrl(15432),
'postgresql://klo:postgres@127.0.0.1:15432/warehouse', // pragma: allowlist secret
'postgresql://ktx:postgres@127.0.0.1:15432/warehouse', // pragma: allowlist secret
);
});
it('writes a live-database-only KLO project config with SQLite local state', () => {
it('writes a live-database-only KTX project config with SQLite local state', () => {
assert.equal(
buildKloYaml('postgresql://klo:postgres@127.0.0.1:15432/warehouse'), // pragma: allowlist secret
buildKtxYaml('postgresql://ktx:postgres@127.0.0.1:15432/warehouse'), // pragma: allowlist secret
[
'project: artifact-live-database',
'connections:',
' warehouse:',
' driver: postgres',
' url: "postgresql://klo:postgres@127.0.0.1:15432/warehouse"', // pragma: allowlist secret
' url: "postgresql://ktx:postgres@127.0.0.1:15432/warehouse"', // pragma: allowlist secret
' readonly: true',
'storage:',
' state: sqlite',
@ -83,12 +83,12 @@ describe('installed live-database artifact smoke helpers', () => {
});
it('waits for a real SQL connection to the target Postgres database', () => {
assert.deepEqual(buildPostgresReadyArgs('klo-live-db-smoke-test'), [
assert.deepEqual(buildPostgresReadyArgs('ktx-live-db-smoke-test'), [
'exec',
'klo-live-db-smoke-test',
'ktx-live-db-smoke-test',
'psql',
'-U',
'klo',
'ktx',
'-d',
'warehouse',
'-v',
@ -101,7 +101,7 @@ describe('installed live-database artifact smoke helpers', () => {
it('builds installed CLI live-database ingest and status commands', () => {
assert.deepEqual(buildLiveDatabaseIngestArgs('/tmp/project', 'http://127.0.0.1:8765'), [
'exec',
'klo',
'ktx',
'dev',
'ingest',
'run',
@ -117,7 +117,7 @@ describe('installed live-database artifact smoke helpers', () => {
assert.deepEqual(buildLiveDatabaseStatusArgs('/tmp/project', 'local-run-1'), [
'exec',
'klo',
'ktx',
'ingest',
'status',
'--project-dir',

View file

@ -6,7 +6,7 @@ import { access as fsAccess, chmod as fsChmod, writeFile as fsWriteFile } from '
import { delimiter, join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { promisify } from 'node:util';
import { ensureCliBinExecutable, kloRootDir } from './prepare-cli-bin.mjs';
import { ensureCliBinExecutable, ktxRootDir } from './prepare-cli-bin.mjs';
const execFileAsync = promisify(execFile);
@ -73,7 +73,7 @@ async function writePinnedPosixLauncher(globalBin, binPath, binaryName, writeFil
const launcherPath = join(globalBin, binaryName);
const script = [
'#!/bin/sh',
'# Generated by `pnpm run link:dev` in the KLO workspace.',
'# Generated by `pnpm run link:dev` in the KTX workspace.',
'# Keep this launcher pinned to the Node binary that built native dependencies.',
`exec ${shellDoubleQuote(process.execPath)} ${shellDoubleQuote(binPath)} "$@"`,
'',
@ -88,7 +88,7 @@ async function writePinnedWindowsLauncher(globalBin, binPath, binaryName, writeF
const launcherPath = join(globalBin, `${binaryName}.cmd`);
const script = [
'@echo off',
'REM Generated by `pnpm run link:dev` in the KLO workspace.',
'REM Generated by `pnpm run link:dev` in the KTX workspace.',
`"${process.execPath}" "${binPath}" %*`,
'',
].join('\r\n');
@ -134,8 +134,8 @@ async function assertBuiltCli(rootDir, access, binPathOverride) {
}
export async function linkDevCli(options = {}) {
const rootDir = options.rootDir ?? kloRootDir();
const binaryName = options.binaryName ?? 'klo-dev';
const rootDir = options.rootDir ?? ktxRootDir();
const binaryName = options.binaryName ?? 'ktx-dev';
const access = options.access ?? fsAccess;
const chmod = options.chmod ?? fsChmod;
const writeFile = options.writeFile ?? fsWriteFile;
@ -177,9 +177,9 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
const result = await linkDevCli({
checkOnly: hasFlag('--check-only'),
binaryName: optionValue('--name', 'klo-dev'),
binaryName: optionValue('--name', 'ktx-dev'),
});
process.stdout.write(`KLO CLI bin: ${result.binPath}\n`);
process.stdout.write(`KTX CLI bin: ${result.binPath}\n`);
if (result.linked) {
process.stdout.write(`Linked binary: ${result.binaryName}\n`);
process.stdout.write(`Verified: ${result.verification.output}\n`);

View file

@ -2,44 +2,44 @@ import assert from 'node:assert/strict';
import { test } from 'node:test';
import { linkDevCli } from './link-dev-cli.mjs';
test('linkDevCli writes a klo-dev launcher by default', async () => {
test('linkDevCli writes a ktx-dev launcher by default', async () => {
const writes = [];
const chmods = [];
const result = await linkDevCli({
rootDir: '/workspace/klo',
rootDir: '/workspace/ktx',
globalBin: '/pnpm/bin',
binPath: '/workspace/klo/packages/cli/dist/bin.js',
binPath: '/workspace/ktx/packages/cli/dist/bin.js',
execText: async (command, args) => {
assert.equal(command, 'klo-dev');
assert.equal(command, 'ktx-dev');
assert.deepEqual(args, ['--version']);
return '@klo/cli 0.0.0-private';
return '@ktx/cli 0.0.0-private';
},
writeFile: async (path, content) => writes.push({ path, content }),
chmod: async (path, mode) => chmods.push({ path, mode }),
access: async () => undefined,
});
assert.equal(result.binaryName, 'klo-dev');
assert.equal(writes[0].path, '/pnpm/bin/klo-dev');
assert.equal(result.binaryName, 'ktx-dev');
assert.equal(writes[0].path, '/pnpm/bin/ktx-dev');
assert.match(writes[0].content, /packages\/cli\/dist\/bin.js/);
assert.deepEqual(chmods, [{ path: '/pnpm/bin/klo-dev', mode: 0o755 }]);
assert.deepEqual(chmods, [{ path: '/pnpm/bin/ktx-dev', mode: 0o755 }]);
});
test('linkDevCli can explicitly write klo when requested', async () => {
test('linkDevCli can explicitly write ktx when requested', async () => {
const writes = [];
const result = await linkDevCli({
rootDir: '/workspace/klo',
binaryName: 'klo',
rootDir: '/workspace/ktx',
binaryName: 'ktx',
globalBin: '/pnpm/bin',
binPath: '/workspace/klo/packages/cli/dist/bin.js',
execText: async () => '@klo/cli 0.0.0-private',
binPath: '/workspace/ktx/packages/cli/dist/bin.js',
execText: async () => '@ktx/cli 0.0.0-private',
writeFile: async (path, content) => writes.push({ path, content }),
chmod: async () => undefined,
access: async () => undefined,
});
assert.equal(result.binaryName, 'klo');
assert.equal(writes[0].path, '/pnpm/bin/klo');
assert.equal(result.binaryName, 'ktx');
assert.equal(writes[0].path, '/pnpm/bin/ktx');
});

View file

@ -11,22 +11,22 @@ const PACKAGE_VERSION = '0.0.0-private';
const PYTHON_PACKAGE_VERSION = '0.1.0';
export const NPM_ARTIFACT_PACKAGES = [
{ name: '@klo/context', packageRoot: 'packages/context' },
{ name: '@klo/llm', packageRoot: 'packages/llm' },
{ name: '@klo/connector-bigquery', packageRoot: 'packages/connector-bigquery' },
{ name: '@klo/connector-clickhouse', packageRoot: 'packages/connector-clickhouse' },
{ name: '@klo/connector-mysql', packageRoot: 'packages/connector-mysql' },
{ name: '@klo/connector-postgres', packageRoot: 'packages/connector-postgres' },
{ name: '@klo/connector-posthog', packageRoot: 'packages/connector-posthog' },
{ name: '@klo/connector-snowflake', packageRoot: 'packages/connector-snowflake' },
{ name: '@klo/connector-sqlite', packageRoot: 'packages/connector-sqlite' },
{ name: '@klo/connector-sqlserver', packageRoot: 'packages/connector-sqlserver' },
{ name: '@klo/cli', packageRoot: 'packages/cli' },
{ name: '@ktx/context', packageRoot: 'packages/context' },
{ name: '@ktx/llm', packageRoot: 'packages/llm' },
{ name: '@ktx/connector-bigquery', packageRoot: 'packages/connector-bigquery' },
{ name: '@ktx/connector-clickhouse', packageRoot: 'packages/connector-clickhouse' },
{ name: '@ktx/connector-mysql', packageRoot: 'packages/connector-mysql' },
{ name: '@ktx/connector-postgres', packageRoot: 'packages/connector-postgres' },
{ name: '@ktx/connector-posthog', packageRoot: 'packages/connector-posthog' },
{ name: '@ktx/connector-snowflake', packageRoot: 'packages/connector-snowflake' },
{ name: '@ktx/connector-sqlite', packageRoot: 'packages/connector-sqlite' },
{ name: '@ktx/connector-sqlserver', packageRoot: 'packages/connector-sqlserver' },
{ name: '@ktx/cli', packageRoot: 'packages/cli' },
];
const CONNECTOR_PACKAGE_NAMES = NPM_ARTIFACT_PACKAGES
.map((packageInfo) => packageInfo.name)
.filter((packageName) => packageName.startsWith('@klo/connector-'));
.filter((packageName) => packageName.startsWith('@ktx/connector-'));
const ordersSource = {
name: 'orders',
@ -46,7 +46,7 @@ function scriptRootDir() {
}
function npmPackageTarballName(packageName) {
return `${packageName.replace('@klo/', 'klo-')}-${PACKAGE_VERSION}.tgz`;
return `${packageName.replace('@ktx/', 'ktx-')}-${PACKAGE_VERSION}.tgz`;
}
function npmPackageTarballs(npmDir) {
@ -67,8 +67,8 @@ export function packageArtifactLayout(rootDir = scriptRootDir()) {
npmDir,
pythonDir,
npmTarballs,
contextTarball: npmTarballs['@klo/context'],
cliTarball: npmTarballs['@klo/cli'],
contextTarball: npmTarballs['@ktx/context'],
cliTarball: npmTarballs['@ktx/cli'],
connectorTarballs: Object.fromEntries(
CONNECTOR_PACKAGE_NAMES.map((packageName) => [packageName, npmTarballs[packageName]]),
),
@ -93,12 +93,12 @@ export function buildArtifactCommands(layout) {
...npmPackCommands,
{
command: 'uv',
args: ['build', '--package', 'klo-sl', '--out-dir', layout.pythonDir],
args: ['build', '--package', 'ktx-sl', '--out-dir', layout.pythonDir],
cwd: layout.rootDir,
},
{
command: 'uv',
args: ['build', '--package', 'klo-daemon', '--out-dir', layout.pythonDir],
args: ['build', '--package', 'ktx-daemon', '--out-dir', layout.pythonDir],
cwd: layout.rootDir,
},
];
@ -136,10 +136,10 @@ export async function findPythonArtifacts(pythonDir) {
const files = await readdir(pythonDir);
return {
kloSlWheel: findOne(files, 'klo-sl', '.whl', 'klo-sl wheel', pythonDir),
kloSlSdist: findOne(files, 'klo-sl', '.tar.gz', 'klo-sl source distribution', pythonDir),
kloDaemonWheel: findOne(files, 'klo-daemon', '.whl', 'klo-daemon wheel', pythonDir),
kloDaemonSdist: findOne(files, 'klo-daemon', '.tar.gz', 'klo-daemon source distribution', pythonDir),
ktxSlWheel: findOne(files, 'ktx-sl', '.whl', 'ktx-sl wheel', pythonDir),
ktxSlSdist: findOne(files, 'ktx-sl', '.tar.gz', 'ktx-sl source distribution', pythonDir),
ktxDaemonWheel: findOne(files, 'ktx-daemon', '.whl', 'ktx-daemon wheel', pythonDir),
ktxDaemonSdist: findOne(files, 'ktx-daemon', '.tar.gz', 'ktx-daemon source distribution', pythonDir),
};
}
@ -223,23 +223,23 @@ export async function packageReleaseMetadata(rootDir = scriptRootDir()) {
const npmPackages = await Promise.all(
NPM_ARTIFACT_PACKAGES.map((packageInfo) => readNpmPackageMetadata(rootDir, packageInfo)),
);
const kloSlPackage = await readPyprojectMetadata(join(rootDir, 'python', 'klo-sl', 'pyproject.toml'));
const kloDaemonPackage = await readPyprojectMetadata(join(rootDir, 'python', 'klo-daemon', 'pyproject.toml'));
const ktxSlPackage = await readPyprojectMetadata(join(rootDir, 'python', 'ktx-sl', 'pyproject.toml'));
const ktxDaemonPackage = await readPyprojectMetadata(join(rootDir, 'python', 'ktx-daemon', 'pyproject.toml'));
return [
...npmPackages,
releaseMetadataEntry({
ecosystem: 'python',
packageName: kloSlPackage.name,
packageRoot: 'python/klo-sl',
packageVersion: kloSlPackage.version,
packageName: ktxSlPackage.name,
packageRoot: 'python/ktx-sl',
packageVersion: ktxSlPackage.version,
privatePackage: false,
}),
releaseMetadataEntry({
ecosystem: 'python',
packageName: kloDaemonPackage.name,
packageRoot: 'python/klo-daemon',
packageVersion: kloDaemonPackage.version,
packageName: ktxDaemonPackage.name,
packageRoot: 'python/ktx-daemon',
packageVersion: ktxDaemonPackage.version,
privatePackage: false,
}),
];
@ -269,23 +269,23 @@ function artifactPackageRecords(layout, pythonArtifacts, packages) {
...npmRecords,
{
artifactKind: 'wheel',
artifactPath: pythonArtifacts.kloSlWheel,
metadata: requirePackageMetadata(packagesByName, 'klo-sl'),
artifactPath: pythonArtifacts.ktxSlWheel,
metadata: requirePackageMetadata(packagesByName, 'ktx-sl'),
},
{
artifactKind: 'sdist',
artifactPath: pythonArtifacts.kloSlSdist,
metadata: requirePackageMetadata(packagesByName, 'klo-sl'),
artifactPath: pythonArtifacts.ktxSlSdist,
metadata: requirePackageMetadata(packagesByName, 'ktx-sl'),
},
{
artifactKind: 'wheel',
artifactPath: pythonArtifacts.kloDaemonWheel,
metadata: requirePackageMetadata(packagesByName, 'klo-daemon'),
artifactPath: pythonArtifacts.ktxDaemonWheel,
metadata: requirePackageMetadata(packagesByName, 'ktx-daemon'),
},
{
artifactKind: 'sdist',
artifactPath: pythonArtifacts.kloDaemonSdist,
metadata: requirePackageMetadata(packagesByName, 'klo-daemon'),
artifactPath: pythonArtifacts.ktxDaemonSdist,
metadata: requirePackageMetadata(packagesByName, 'ktx-daemon'),
},
];
}
@ -399,7 +399,7 @@ export async function verifyArtifactManifest(layout, options = {}) {
const manifest = await readJson(artifactManifestPath(layout));
assertManifestShape(manifest);
const expectedSourceRevision = options.expectedSourceRevision ?? process.env.KLO_EXPECTED_SOURCE_REVISION;
const expectedSourceRevision = options.expectedSourceRevision ?? process.env.KTX_EXPECTED_SOURCE_REVISION;
if (expectedSourceRevision !== undefined && manifest.sourceRevision !== expectedSourceRevision) {
throw new Error(
`Artifact manifest sourceRevision mismatch: expected ${expectedSourceRevision}, got ${manifest.sourceRevision}`,
@ -435,8 +435,8 @@ export function pythonArtifactInstallArgs(python, pythonArtifacts) {
'install',
'--python',
python,
pythonArtifacts.kloSlWheel,
pythonArtifacts.kloDaemonWheel,
pythonArtifacts.ktxSlWheel,
pythonArtifacts.ktxDaemonWheel,
];
}
@ -486,7 +486,7 @@ function npmTarballDependencyEntries(layout) {
export function npmSmokePackageJson(layout) {
const npmTarballDependencies = npmTarballDependencyEntries(layout);
return {
name: 'klo-artifact-npm-smoke',
name: 'ktx-artifact-npm-smoke',
version: '0.0.0',
private: true,
type: 'module',
@ -503,53 +503,53 @@ export function npmSmokePackageJson(layout) {
export function npmVerifySource() {
return `
const context = await import('@klo/context');
const project = await import('@klo/context/project');
const mcp = await import('@klo/context/mcp');
const memory = await import('@klo/context/memory');
const daemon = await import('@klo/context/daemon');
const ingest = await import('@klo/context/ingest');
const search = await import('@klo/context/search');
const llm = await import('@klo/llm');
const cli = await import('@klo/cli');
const bigqueryConnector = await import('@klo/connector-bigquery');
const clickhouseConnector = await import('@klo/connector-clickhouse');
const mysqlConnector = await import('@klo/connector-mysql');
const postgresConnector = await import('@klo/connector-postgres');
const posthogConnector = await import('@klo/connector-posthog');
const snowflakeConnector = await import('@klo/connector-snowflake');
const sqliteConnector = await import('@klo/connector-sqlite');
const sqlserverConnector = await import('@klo/connector-sqlserver');
const context = await import('@ktx/context');
const project = await import('@ktx/context/project');
const mcp = await import('@ktx/context/mcp');
const memory = await import('@ktx/context/memory');
const daemon = await import('@ktx/context/daemon');
const ingest = await import('@ktx/context/ingest');
const search = await import('@ktx/context/search');
const llm = await import('@ktx/llm');
const cli = await import('@ktx/cli');
const bigqueryConnector = await import('@ktx/connector-bigquery');
const clickhouseConnector = await import('@ktx/connector-clickhouse');
const mysqlConnector = await import('@ktx/connector-mysql');
const postgresConnector = await import('@ktx/connector-postgres');
const posthogConnector = await import('@ktx/connector-posthog');
const snowflakeConnector = await import('@ktx/connector-snowflake');
const sqliteConnector = await import('@ktx/connector-sqlite');
const sqlserverConnector = await import('@ktx/connector-sqlserver');
if (context.kloContextPackageInfo.name !== '@klo/context') {
throw new Error('Unexpected @klo/context package info');
if (context.ktxContextPackageInfo.name !== '@ktx/context') {
throw new Error('Unexpected @ktx/context package info');
}
if (typeof llm.createKloLlmProvider !== 'function') {
throw new Error('Missing createKloLlmProvider export');
if (typeof llm.createKtxLlmProvider !== 'function') {
throw new Error('Missing createKtxLlmProvider export');
}
if (typeof llm.KloMessageBuilder !== 'function') {
throw new Error('Missing KloMessageBuilder export');
if (typeof llm.KtxMessageBuilder !== 'function') {
throw new Error('Missing KtxMessageBuilder export');
}
if (typeof llm.createKloEmbeddingProvider !== 'function') {
throw new Error('Missing createKloEmbeddingProvider export');
if (typeof llm.createKtxEmbeddingProvider !== 'function') {
throw new Error('Missing createKtxEmbeddingProvider export');
}
if (typeof project.initKloProject !== 'function') {
throw new Error('Missing initKloProject export');
if (typeof project.initKtxProject !== 'function') {
throw new Error('Missing initKtxProject export');
}
if (typeof mcp.createDefaultKloMcpServer !== 'function') {
throw new Error('Missing createDefaultKloMcpServer export');
if (typeof mcp.createDefaultKtxMcpServer !== 'function') {
throw new Error('Missing createDefaultKtxMcpServer export');
}
if (typeof memory.createLocalProjectMemoryCapture !== 'function') {
throw new Error('Missing createLocalProjectMemoryCapture export');
}
if (typeof search.HybridSearchCore !== 'function') {
throw new Error('Missing HybridSearchCore export from @klo/context/search');
throw new Error('Missing HybridSearchCore export from @ktx/context/search');
}
if (typeof search.assertSearchBackendConformanceCase !== 'function') {
throw new Error('Missing assertSearchBackendConformanceCase export from @klo/context/search');
throw new Error('Missing assertSearchBackendConformanceCase export from @ktx/context/search');
}
if (typeof search.assertSearchBackendCapabilities !== 'function') {
throw new Error('Missing assertSearchBackendCapabilities export from @klo/context/search');
throw new Error('Missing assertSearchBackendCapabilities export from @ktx/context/search');
}
if (typeof daemon.createPythonSemanticLayerComputePort !== 'function') {
throw new Error('Missing createPythonSemanticLayerComputePort export');
@ -576,21 +576,21 @@ const metricflowConfig = ingest.parseMetricflowPullConfig({
repoUrl: 'https://example.com/acme/analytics.git',
});
if (metricflowConfig.branch !== 'main' || metricflowConfig.path !== null) {
throw new Error('Unexpected MetricFlow pull-config defaults from installed @klo/context/ingest');
throw new Error('Unexpected MetricFlow pull-config defaults from installed @ktx/context/ingest');
}
if (cli.getKloCliPackageInfo().name !== '@klo/cli') {
throw new Error('Unexpected @klo/cli package info');
if (cli.getKtxCliPackageInfo().name !== '@ktx/cli') {
throw new Error('Unexpected @ktx/cli package info');
}
const connectorExports = [
['@klo/connector-bigquery', bigqueryConnector.KloBigQueryScanConnector, bigqueryConnector.KloBigQueryDialect],
['@klo/connector-clickhouse', clickhouseConnector.KloClickHouseScanConnector, clickhouseConnector.KloClickHouseDialect],
['@klo/connector-mysql', mysqlConnector.KloMysqlScanConnector, mysqlConnector.KloMysqlDialect],
['@klo/connector-postgres', postgresConnector.KloPostgresScanConnector, postgresConnector.KloPostgresDialect],
['@klo/connector-posthog', posthogConnector.KloPostHogScanConnector, posthogConnector.KloPostHogDialect],
['@klo/connector-snowflake', snowflakeConnector.KloSnowflakeScanConnector, snowflakeConnector.KloSnowflakeDialect],
['@klo/connector-sqlite', sqliteConnector.KloSqliteScanConnector, sqliteConnector.KloSqliteDialect],
['@klo/connector-sqlserver', sqlserverConnector.KloSqlServerScanConnector, sqlserverConnector.KloSqlServerDialect],
['@ktx/connector-bigquery', bigqueryConnector.KtxBigQueryScanConnector, bigqueryConnector.KtxBigQueryDialect],
['@ktx/connector-clickhouse', clickhouseConnector.KtxClickHouseScanConnector, clickhouseConnector.KtxClickHouseDialect],
['@ktx/connector-mysql', mysqlConnector.KtxMysqlScanConnector, mysqlConnector.KtxMysqlDialect],
['@ktx/connector-postgres', postgresConnector.KtxPostgresScanConnector, postgresConnector.KtxPostgresDialect],
['@ktx/connector-posthog', posthogConnector.KtxPostHogScanConnector, posthogConnector.KtxPostHogDialect],
['@ktx/connector-snowflake', snowflakeConnector.KtxSnowflakeScanConnector, snowflakeConnector.KtxSnowflakeDialect],
['@ktx/connector-sqlite', sqliteConnector.KtxSqliteScanConnector, sqliteConnector.KtxSqliteDialect],
['@ktx/connector-sqlserver', sqlserverConnector.KtxSqlServerScanConnector, sqlserverConnector.KtxSqlServerDialect],
];
for (const [packageName, ScanConnector, Dialect] of connectorExports) {
@ -621,11 +621,11 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import {
createDaemonLookerTableIdentifierParser,
LocalLookerRuntimeStore,
} from '@klo/context/ingest';
} from '@ktx/context/ingest';
const execFileAsync = promisify(execFile);
const require = createRequire(import.meta.url);
const contextPackageRoot = dirname(require.resolve('@klo/context/package.json'));
const contextPackageRoot = dirname(require.resolve('@ktx/context/package.json'));
async function requireContextRuntimeAsset(relativePath) {
await access(join(contextPackageRoot, relativePath));
@ -760,7 +760,7 @@ async function waitForHttpHealth(url, daemon) {
if (daemon.error()) {
const output = daemon.output();
throw new Error(
'Failed to start klo-daemon serve-http: ' +
'Failed to start ktx-daemon serve-http: ' +
daemon.error().message +
'\\nstdout:\\n' +
output.stdout +
@ -771,7 +771,7 @@ async function waitForHttpHealth(url, daemon) {
if (daemon.child.exitCode !== null || daemon.child.signalCode !== null) {
const output = daemon.output();
throw new Error(
'klo-daemon serve-http exited before health check passed\\nstdout:\\n' +
'ktx-daemon serve-http exited before health check passed\\nstdout:\\n' +
output.stdout +
'\\nstderr:\\n' +
output.stderr,
@ -792,7 +792,7 @@ async function waitForHttpHealth(url, daemon) {
}
async function startSemanticDaemon(port) {
const daemon = spawnLogged('klo-daemon', [
const daemon = spawnLogged('ktx-daemon', [
'serve-http',
'--host',
'127.0.0.1',
@ -847,7 +847,7 @@ await requireContextRuntimeAsset('prompts/skills/page_triage_classifier.md');
await requireContextRuntimeAsset('prompts/skills/light_extraction.md');
process.stdout.write('packaged ingest runtime assets verified\\n');
const root = await mkdtemp(join(tmpdir(), 'klo-installed-cli-smoke-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-installed-cli-smoke-'));
try {
const projectDir = join(root, 'project');
const sourceDir = join(root, 'source');
@ -856,7 +856,7 @@ try {
await mkdir(missingProjectDir, { recursive: true });
const missingProjectSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'sl',
'list',
@ -866,19 +866,19 @@ try {
'--project-dir',
missingProjectDir,
]);
const missingProjectError = parseJsonFailure('klo agent sl list missing project', missingProjectSearch);
const missingProjectError = parseJsonFailure('ktx agent sl list missing project', missingProjectSearch);
assert.equal(missingProjectError.error.code, 'agent_sl_search_missing_project');
assert.deepEqual(missingProjectError.error.nextSteps, [
'klo demo',
'klo setup --project-dir ' + missingProjectDir,
'klo ingest <connection>',
'klo agent sl list --json --query "revenue" --project-dir ' + missingProjectDir,
'ktx demo',
'ktx setup --project-dir ' + missingProjectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + missingProjectDir,
]);
process.stdout.write('klo agent sl list missing project guidance verified\\n');
process.stdout.write('ktx agent sl list missing project guidance verified\\n');
const init = await run('pnpm', [
'exec',
'klo',
'ktx',
'setup',
'--project-dir',
projectDir,
@ -891,13 +891,13 @@ try {
'--skip-sources',
'--skip-agents',
]);
requireSuccess('klo setup', init);
requireOutput('klo setup', init, /Project: /);
requireSuccess('ktx setup', init);
requireOutput('ktx setup', init, /Project: /);
const emptyProjectDir = join(root, 'empty-project');
const emptyInit = await run('pnpm', [
'exec',
'klo',
'ktx',
'setup',
'--project-dir',
emptyProjectDir,
@ -910,10 +910,10 @@ try {
'--skip-sources',
'--skip-agents',
]);
requireSuccess('klo setup empty project', emptyInit);
requireSuccess('ktx setup empty project', emptyInit);
const emptySearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'sl',
'list',
@ -923,18 +923,18 @@ try {
'--project-dir',
emptyProjectDir,
]);
const emptySearchError = parseJsonFailure('klo agent sl list no connections', emptySearch);
const emptySearchError = parseJsonFailure('ktx agent sl list no connections', emptySearch);
assert.equal(emptySearchError.error.code, 'agent_sl_search_no_connections');
assert.deepEqual(emptySearchError.error.nextSteps, [
'klo demo',
'klo setup --project-dir ' + emptyProjectDir,
'klo ingest <connection>',
'klo agent sl list --json --query "revenue" --project-dir ' + emptyProjectDir,
'ktx demo',
'ktx setup --project-dir ' + emptyProjectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + emptyProjectDir,
]);
process.stdout.write('klo agent sl list no connections guidance verified\\n');
process.stdout.write('ktx agent sl list no connections guidance verified\\n');
await writeFile(
join(projectDir, 'klo.yaml'),
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
@ -958,7 +958,7 @@ try {
);
await writeSqliteWarehouse(projectDir);
const lookerStore = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.klo', 'db.sqlite') });
const lookerStore = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.ktx', 'db.sqlite') });
await lookerStore.setCursors('prod-looker', {
dashboardsLastSyncedAt: null,
looksLastSyncedAt: null,
@ -966,12 +966,12 @@ try {
await lookerStore.upsertConnectionMapping({
lookerConnectionId: 'prod-looker',
lookerConnectionName: 'analytics',
kloConnectionId: 'warehouse',
ktxConnectionId: 'warehouse',
source: 'cli',
});
const lookerMappings = await lookerStore.readMappings('prod-looker');
assert.equal(lookerMappings.length, 1);
assert.equal(lookerMappings[0].kloConnectionId, 'warehouse');
assert.equal(lookerMappings[0].ktxConnectionId, 'warehouse');
process.stdout.write('Looker local runtime store verified\\n');
await mkdir(join(projectDir, 'knowledge', 'global'), { recursive: true });
@ -995,7 +995,7 @@ try {
const agentWikiSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'wiki',
'search',
@ -1006,19 +1006,19 @@ try {
'--project-dir',
projectDir,
]);
const agentWikiSearchJson = parseJsonResult('klo agent wiki search', agentWikiSearch);
const agentWikiSearchJson = parseJsonResult('ktx agent wiki search', agentWikiSearch);
assert.equal(agentWikiSearchJson.totalFound, 1);
assert.equal(agentWikiSearchJson.results[0].key, 'revenue');
assert.equal(agentWikiSearchJson.results[0].path, 'knowledge/global/revenue.md');
assert.equal(typeof agentWikiSearchJson.results[0].score, 'number');
requireIncludes(agentWikiSearchJson.results[0].matchReasons, 'lexical', 'agent wiki search match reasons');
process.stdout.write('klo agent wiki search hybrid metadata verified\\n');
await access(join(projectDir, '.klo', 'db.sqlite'));
process.stdout.write('SQLite knowledge index: ' + join(projectDir, '.klo', 'db.sqlite') + '\\n');
process.stdout.write('ktx agent wiki search hybrid metadata verified\\n');
await access(join(projectDir, '.ktx', 'db.sqlite'));
process.stdout.write('SQLite knowledge index: ' + join(projectDir, '.ktx', 'db.sqlite') + '\\n');
const noSourceSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'sl',
'list',
@ -1030,15 +1030,15 @@ try {
'--project-dir',
projectDir,
]);
const noSourceSearchError = parseJsonFailure('klo agent sl list no indexed sources', noSourceSearch);
const noSourceSearchError = parseJsonFailure('ktx agent sl list no indexed sources', noSourceSearch);
assert.equal(noSourceSearchError.error.code, 'agent_sl_search_no_indexed_sources');
assert.deepEqual(noSourceSearchError.error.nextSteps, [
'klo demo',
'klo setup --project-dir ' + projectDir,
'klo ingest <connection>',
'klo agent sl list --json --query "revenue" --project-dir ' + projectDir,
'ktx demo',
'ktx setup --project-dir ' + projectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + projectDir,
]);
process.stdout.write('klo agent sl list no indexed sources guidance verified\\n');
process.stdout.write('ktx agent sl list no indexed sources guidance verified\\n');
const slYaml = [
'name: orders',
@ -1062,7 +1062,7 @@ try {
const agentSlSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'sl',
'list',
@ -1074,18 +1074,18 @@ try {
'--project-dir',
projectDir,
]);
const agentSlSearchJson = parseJsonResult('klo agent sl list', agentSlSearch);
const agentSlSearchJson = parseJsonResult('ktx agent sl list', agentSlSearch);
assert.equal(agentSlSearchJson.totalSources, 1);
assert.equal(agentSlSearchJson.sources[0].connectionId, 'warehouse');
assert.equal(agentSlSearchJson.sources[0].name, 'orders');
assert.equal(typeof agentSlSearchJson.sources[0].score, 'number');
requireIncludes(agentSlSearchJson.sources[0].matchReasons, 'lexical', 'agent sl search match reasons');
process.stdout.write('klo agent sl list hybrid metadata verified\\n');
process.stdout.write('ktx agent sl list hybrid metadata verified\\n');
const slQueryFile = join(projectDir, 'sl-query.json');
await writeFile(slQueryFile, '{"measures":["orders.order_count"],"dimensions":[]}\\n', 'utf-8');
const slQuery = await run('pnpm', ['exec', 'klo', 'agent', 'sl', 'query',
const slQuery = await run('pnpm', ['exec', 'ktx', 'agent', 'sl', 'query',
'--json',
'--connection-id',
'warehouse',
@ -1094,11 +1094,11 @@ try {
'--project-dir',
projectDir,
]);
requireSuccess('klo agent sl query', slQuery);
requireOutput('klo agent sl query', slQuery, /"mode": "compile_only"/);
requireOutput('klo agent sl query', slQuery, /orders/);
requireSuccess('ktx agent sl query', slQuery);
requireOutput('ktx agent sl query', slQuery, /"mode": "compile_only"/);
requireOutput('ktx agent sl query', slQuery, /orders/);
const sqliteSlQuery = await run('pnpm', ['exec', 'klo', 'agent', 'sl', 'query',
const sqliteSlQuery = await run('pnpm', ['exec', 'ktx', 'agent', 'sl', 'query',
'--json',
'--connection-id',
'warehouse',
@ -1110,40 +1110,40 @@ try {
'--project-dir',
projectDir,
]);
requireSuccess('klo agent sl query sqlite execute', sqliteSlQuery);
requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/);
requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/);
requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"driver": "sqlite"/);
requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/);
process.stdout.write('klo agent sl query sqlite execute verified\\n');
requireSuccess('ktx agent sl query sqlite execute', sqliteSlQuery);
requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/);
requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/);
requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"driver": "sqlite"/);
requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/);
process.stdout.write('ktx agent sl query sqlite execute verified\\n');
const structuralScan = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'warehouse',
const structuralScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse',
'--project-dir',
projectDir,
]);
requireSuccess('klo scan structural', structuralScan);
requireOutput('klo scan structural', structuralScan, /Status: done/);
requireOutput('klo scan structural', structuralScan, /Mode: structural/);
requireOutput('klo scan structural', structuralScan, /Needs attention\\s+None/);
requireSuccess('ktx scan structural', structuralScan);
requireOutput('ktx scan structural', structuralScan, /Status: done/);
requireOutput('ktx scan structural', structuralScan, /Mode: structural/);
requireOutput('ktx scan structural', structuralScan, /Needs attention\\s+None/);
const structuralScanRunId = getRunId(structuralScan.stdout);
const scanStatus = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'status',
const scanStatus = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'status',
'--project-dir',
projectDir,
structuralScanRunId,
]);
requireSuccess('klo scan status', scanStatus);
requireOutput('klo scan status', scanStatus, new RegExp('Run: ' + structuralScanRunId));
requireOutput('klo scan status', scanStatus, /Status: done/);
requireOutput('klo scan status', scanStatus, /Mode: structural/);
requireSuccess('ktx scan status', scanStatus);
requireOutput('ktx scan status', scanStatus, new RegExp('Run: ' + structuralScanRunId));
requireOutput('ktx scan status', scanStatus, /Status: done/);
requireOutput('ktx scan status', scanStatus, /Mode: structural/);
const scanReport = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'report',
const scanReport = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'report',
'--project-dir',
projectDir,
'--json',
structuralScanRunId,
]);
requireSuccess('klo scan report', scanReport);
requireSuccess('ktx scan report', scanReport);
const scanReportJson = JSON.parse(scanReport.stdout);
assert.equal(scanReportJson.mode, 'structural');
assert.equal(scanReportJson.connectionId, 'warehouse');
@ -1151,35 +1151,35 @@ try {
assert.deepEqual(scanReportJson.artifactPaths.enrichmentArtifacts, []);
assert.deepEqual(scanReportJson.artifactPaths.manifestShards, ['semantic-layer/warehouse/_schema/public.yaml']);
await access(join(projectDir, 'semantic-layer', 'warehouse', '_schema', 'public.yaml'));
process.stdout.write('klo scan structural verified: ' + structuralScanRunId + '\\n');
process.stdout.write('ktx scan structural verified: ' + structuralScanRunId + '\\n');
const enrichedScan = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'warehouse',
const enrichedScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse',
'--project-dir',
projectDir,
'--mode',
'enriched',
]);
requireSuccess('klo scan enriched', enrichedScan);
requireOutput('klo scan enriched', enrichedScan, /Status: done/);
requireOutput('klo scan enriched', enrichedScan, /Mode: enriched/);
requireSuccess('ktx scan enriched', enrichedScan);
requireOutput('ktx scan enriched', enrichedScan, /Status: done/);
requireOutput('ktx scan enriched', enrichedScan, /Mode: enriched/);
const enrichedScanRunId = getRunId(enrichedScan.stdout);
const enrichedScanReport = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'report',
const enrichedScanReport = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'report',
'--project-dir',
projectDir,
'--json',
enrichedScanRunId,
]);
requireSuccess('klo scan enriched report', enrichedScanReport);
requireSuccess('ktx scan enriched report', enrichedScanReport);
const enrichedScanReportJson = JSON.parse(enrichedScanReport.stdout);
assert.equal(enrichedScanReportJson.mode, 'enriched');
assert.ok(enrichedScanReportJson.artifactPaths.enrichmentArtifacts.length > 0);
assert.deepEqual(enrichedScanReportJson.artifactPaths.manifestShards, ['semantic-layer/warehouse/_schema/public.yaml']);
process.stdout.write('klo scan enriched verified: ' + enrichedScanRunId + '\\n');
process.stdout.write('ktx scan enriched verified: ' + enrichedScanRunId + '\\n');
await mkdir(join(sourceDir, 'orders'), { recursive: true });
await writeFile(join(sourceDir, 'orders', 'orders.json'), '{"name":"orders"}\\n', 'utf-8');
const ingestRun = await run('pnpm', ['exec', 'klo', 'dev', 'ingest', 'run',
const ingestRun = await run('pnpm', ['exec', 'ktx', 'dev', 'ingest', 'run',
'--project-dir',
projectDir,
'--connection-id',
@ -1189,17 +1189,17 @@ try {
'--source-dir',
sourceDir,
]);
assert.equal(ingestRun.code, 1, 'klo dev ingest run without an LLM provider must fail');
assert.equal(ingestRun.code, 1, 'ktx dev ingest run without an LLM provider must fail');
assert.match(
ingestRun.stderr,
/klo dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/,
/ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/,
);
await access(join(projectDir, '.klo', 'db.sqlite'));
process.stdout.write('klo dev ingest provider guard verified\\n');
await access(join(projectDir, '.ktx', 'db.sqlite'));
process.stdout.write('ktx dev ingest provider guard verified\\n');
await writeFile(
join(projectDir, 'klo.yaml'),
join(projectDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
@ -1231,7 +1231,7 @@ try {
const daemonPort = await getAvailablePort();
const semanticComputeUrl = 'http://127.0.0.1:' + daemonPort;
process.stdout.write('klo-daemon serve-http --host 127.0.0.1 --port ' + daemonPort + '\\n');
process.stdout.write('ktx-daemon serve-http --host 127.0.0.1 --port ' + daemonPort + '\\n');
const daemon = await startSemanticDaemon(daemonPort);
const lookerParser = createDaemonLookerTableIdentifierParser({ baseUrl: semanticComputeUrl });
const parsedLookerTables = await lookerParser.parse([
@ -1241,13 +1241,13 @@ try {
assert.equal(parsedLookerTables.orders.name, 'orders');
assert.equal(parsedLookerTables.orders.canonical_table, 'orders');
process.stdout.write('Looker daemon table identifier parser verified\\n');
const client = new Client({ name: 'klo-artifact-smoke-client', version: '0.0.0' });
process.stdout.write('klo serve --mcp stdio --semantic-compute-url ' + semanticComputeUrl + ' --execute-queries\\n');
const client = new Client({ name: 'ktx-artifact-smoke-client', version: '0.0.0' });
process.stdout.write('ktx serve --mcp stdio --semantic-compute-url ' + semanticComputeUrl + ' --execute-queries\\n');
const transport = new StdioClientTransport({
command: 'pnpm',
args: [
'exec',
'klo',
'ktx',
'serve', '--mcp', 'stdio',
'--project-dir',
projectDir,
@ -1379,7 +1379,7 @@ try {
} catch (error) {
const stderr = Buffer.concat(mcpServerStderr).toString('utf8');
if (stderr) {
error.message += '\\nklo serve stderr:\\n' + stderr;
error.message += '\\nktx serve stderr:\\n' + stderr;
}
throw error;
} finally {
@ -1434,31 +1434,31 @@ function requireStdout(label, result, pattern) {
assert.match(result.stdout, pattern, label + ' stdout did not match ' + pattern);
}
const root = await mkdtemp(join(tmpdir(), 'klo-packed-demo-smoke-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-packed-demo-smoke-'));
try {
const projectDir = join(root, 'demo-project');
const help = await run('pnpm', ['exec', 'klo', '--help']);
requireSuccess('klo --help', help);
requireStdout('klo --help', help, /Usage: klo/);
requireStdout('klo --help', help, /setup/);
const help = await run('pnpm', ['exec', 'ktx', '--help']);
requireSuccess('ktx --help', help);
requireStdout('ktx --help', help, /Usage: ktx/);
requireStdout('ktx --help', help, /setup/);
const seeded = await run(
'pnpm',
['exec', 'klo', 'setup', 'demo', '--project-dir', projectDir, '--no-input', '--plain'],
['exec', 'ktx', 'setup', 'demo', '--project-dir', projectDir, '--no-input', '--plain'],
);
requireSuccess('klo setup demo seeded', seeded);
requireStdout('klo setup demo seeded', seeded, /Mode: seeded/);
requireStdout('klo setup demo seeded', seeded, /Source: packaged demo project/);
requireStdout('klo setup demo seeded', seeded, /LLM calls: none/);
requireStdout('klo setup demo seeded', seeded, /klo serve --mcp stdio/);
requireSuccess('ktx setup demo seeded', seeded);
requireStdout('ktx setup demo seeded', seeded, /Mode: seeded/);
requireStdout('ktx setup demo seeded', seeded, /Source: packaged demo project/);
requireStdout('ktx setup demo seeded', seeded, /LLM calls: none/);
requireStdout('ktx setup demo seeded', seeded, /ktx serve --mcp stdio/);
assert.doesNotMatch(seeded.stdout, new RegExp(['--mode', 'deterministic'].join(' ')));
assert.doesNotMatch(seeded.stdout, /KLO memory flow/);
assert.equal(seeded.stderr, '', 'klo setup demo seeded wrote unexpected stderr');
assert.doesNotMatch(seeded.stdout, /KTX memory flow/);
assert.equal(seeded.stderr, '', 'ktx setup demo seeded wrote unexpected stderr');
const demoWikiSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'wiki',
'search',
@ -1469,18 +1469,18 @@ try {
'--project-dir',
projectDir,
]);
requireSuccess('klo seeded demo agent wiki search', demoWikiSearch);
requireSuccess('ktx seeded demo agent wiki search', demoWikiSearch);
const demoWikiSearchJson = JSON.parse(demoWikiSearch.stdout);
assert.ok(demoWikiSearchJson.totalFound > 0, 'seeded demo wiki search should find results');
assert.ok(
demoWikiSearchJson.results.some((result) => Array.isArray(result.matchReasons) && result.matchReasons.length > 0),
'seeded demo wiki search should expose match reasons',
);
process.stdout.write('klo seeded demo agent wiki search verified\\n');
process.stdout.write('ktx seeded demo agent wiki search verified\\n');
const demoSlSearch = await run('pnpm', [
'exec',
'klo',
'ktx',
'agent',
'sl',
'list',
@ -1490,20 +1490,20 @@ try {
'--project-dir',
projectDir,
]);
requireSuccess('klo seeded demo agent sl search', demoSlSearch);
requireSuccess('ktx seeded demo agent sl search', demoSlSearch);
const demoSlSearchJson = JSON.parse(demoSlSearch.stdout);
assert.ok(demoSlSearchJson.totalSources > 0, 'seeded demo semantic-layer search should find sources');
assert.ok(
demoSlSearchJson.sources.some((source) => Array.isArray(source.matchReasons) && source.matchReasons.length > 0),
'seeded demo semantic-layer search should expose match reasons',
);
process.stdout.write('klo seeded demo agent sl search verified\\n');
process.stdout.write('ktx seeded demo agent sl search verified\\n');
const doctor = await run('pnpm', ['exec', 'klo', 'dev', 'doctor', 'setup', '--no-input']);
assert.ok([0, 1].includes(doctor.code), 'klo dev doctor setup exit code must be 0 or 1');
requireStdout('klo dev doctor setup', doctor, /KLO setup doctor/);
requireStdout('klo dev doctor setup', doctor, /Node 22\\+/);
assert.equal(doctor.stderr, '', 'klo dev doctor setup wrote unexpected stderr');
const doctor = await run('pnpm', ['exec', 'ktx', 'dev', 'doctor', 'setup', '--no-input']);
assert.ok([0, 1].includes(doctor.code), 'ktx dev doctor setup exit code must be 0 or 1');
requireStdout('ktx dev doctor setup', doctor, /KTX setup doctor/);
requireStdout('ktx dev doctor setup', doctor, /Node 22\\+/);
assert.equal(doctor.stderr, '', 'ktx dev doctor setup wrote unexpected stderr');
} finally {
await rm(root, { recursive: true, force: true });
}
@ -1513,13 +1513,13 @@ try {
export function pythonVerifySource() {
return `
import importlib.metadata
import klo_daemon
import ktx_daemon
import semantic_layer
assert importlib.metadata.version("klo-sl") == "0.1.0"
assert importlib.metadata.version("klo-daemon") == "0.1.0"
assert importlib.metadata.version("ktx-sl") == "0.1.0"
assert importlib.metadata.version("ktx-daemon") == "0.1.0"
assert semantic_layer is not None
assert klo_daemon.PACKAGE_NAME == "klo-daemon"
assert ktx_daemon.PACKAGE_NAME == "ktx-daemon"
`;
}
@ -1580,7 +1580,7 @@ async function verifyNpmArtifacts(layout, tmpRoot) {
cwd: projectDir,
});
await runCommand('node', ['verify-npm.mjs'], { cwd: projectDir });
await runCommand('pnpm', ['exec', 'klo', '--version'], { cwd: projectDir });
await runCommand('pnpm', ['exec', 'ktx', '--version'], { cwd: projectDir });
await runCommand('node', ['verify-installed-cli.mjs'], {
cwd: projectDir,
env: npmSmokePythonEnv(projectDir),
@ -1618,7 +1618,7 @@ async function verifyPythonArtifacts(layout, tmpRoot) {
cwd: projectDir,
});
await runCommand(python, ['verify_python.py'], { cwd: projectDir });
await runCommand(python, ['-m', 'klo_daemon', 'semantic-validate'], {
await runCommand(python, ['-m', 'ktx_daemon', 'semantic-validate'], {
cwd: projectDir,
input: `${JSON.stringify({ sources: [ordersSource], dialect: 'postgres' })}\n`,
});
@ -1627,7 +1627,7 @@ async function verifyPythonArtifacts(layout, tmpRoot) {
async function verifyArtifacts(layout) {
await verifyArtifactManifest(layout);
const tmpRoot = await mkdtemp(join(tmpdir(), 'klo-artifacts-'));
const tmpRoot = await mkdtemp(join(tmpdir(), 'ktx-artifacts-'));
try {
await verifyNpmArtifacts(layout, tmpRoot);
await verifyPythonArtifacts(layout, tmpRoot);
@ -1639,7 +1639,7 @@ async function verifyArtifacts(layout) {
async function verifyDemoArtifacts(layout) {
await verifyArtifactManifest(layout);
const tmpRoot = await mkdtemp(join(tmpdir(), 'klo-demo-artifacts-'));
const tmpRoot = await mkdtemp(join(tmpdir(), 'ktx-demo-artifacts-'));
try {
await verifyNpmDemoArtifacts(layout, tmpRoot);
} finally {

View file

@ -30,29 +30,29 @@ async function writeJson(path, value) {
}
const CONNECTOR_PACKAGE_NAMES = [
'@klo/connector-bigquery',
'@klo/connector-clickhouse',
'@klo/connector-mysql',
'@klo/connector-postgres',
'@klo/connector-posthog',
'@klo/connector-snowflake',
'@klo/connector-sqlite',
'@klo/connector-sqlserver',
'@ktx/connector-bigquery',
'@ktx/connector-clickhouse',
'@ktx/connector-mysql',
'@ktx/connector-postgres',
'@ktx/connector-posthog',
'@ktx/connector-snowflake',
'@ktx/connector-sqlite',
'@ktx/connector-sqlserver',
];
function packageRootForName(packageName) {
return `packages/${packageName.replace('@klo/', '')}`;
return `packages/${packageName.replace('@ktx/', '')}`;
}
function expectedNpmArtifactPath(packageName) {
return `npm/${packageName.replace('@klo/', 'klo-')}-0.0.0-private.tgz`;
return `npm/${packageName.replace('@ktx/', 'ktx-')}-0.0.0-private.tgz`;
}
async function writeReleaseMetadataInputs(root) {
const npmPackages = ['@klo/context', '@klo/llm', ...CONNECTOR_PACKAGE_NAMES, '@klo/cli'];
const npmPackages = ['@ktx/context', '@ktx/llm', ...CONNECTOR_PACKAGE_NAMES, '@ktx/cli'];
for (const packageName of npmPackages) {
const packageRoot = packageName === '@klo/context' ? 'packages/context' : packageRootForName(packageName);
const packageRoot = packageName === '@ktx/context' ? 'packages/context' : packageRootForName(packageName);
await mkdir(join(root, packageRoot), { recursive: true });
await writeJson(join(root, packageRoot, 'package.json'), {
name: packageName,
@ -61,15 +61,15 @@ async function writeReleaseMetadataInputs(root) {
});
}
await mkdir(join(root, 'python', 'klo-sl'), { recursive: true });
await mkdir(join(root, 'python', 'klo-daemon'), { recursive: true });
await mkdir(join(root, 'python', 'ktx-sl'), { recursive: true });
await mkdir(join(root, 'python', 'ktx-daemon'), { recursive: true });
await writeFile(
join(root, 'python', 'klo-sl', 'pyproject.toml'),
['[project]', 'name = "klo-sl"', 'version = "0.1.0"', ''].join('\n'),
join(root, 'python', 'ktx-sl', 'pyproject.toml'),
['[project]', 'name = "ktx-sl"', 'version = "0.1.0"', ''].join('\n'),
);
await writeFile(
join(root, 'python', 'klo-daemon', 'pyproject.toml'),
['[project]', 'name = "klo-daemon"', 'version = "0.1.0"', ''].join('\n'),
join(root, 'python', 'ktx-daemon', 'pyproject.toml'),
['[project]', 'name = "ktx-daemon"', 'version = "0.1.0"', ''].join('\n'),
);
}
@ -82,10 +82,10 @@ async function writeUploadableArtifactFixtures(layout) {
layout.npmTarballs[packageInfo.name],
`${packageInfo.name}-tarball`,
]),
[join(layout.pythonDir, 'klo_sl-0.1.0-py3-none-any.whl'), 'klo-sl-wheel'],
[join(layout.pythonDir, 'klo_sl-0.1.0.tar.gz'), 'klo-sl-sdist'],
[join(layout.pythonDir, 'klo_daemon-0.1.0-py3-none-any.whl'), 'klo-daemon-wheel'],
[join(layout.pythonDir, 'klo_daemon-0.1.0.tar.gz'), 'klo-daemon-sdist'],
[join(layout.pythonDir, 'ktx_sl-0.1.0-py3-none-any.whl'), 'ktx-sl-wheel'],
[join(layout.pythonDir, 'ktx_sl-0.1.0.tar.gz'), 'ktx-sl-sdist'],
[join(layout.pythonDir, 'ktx_daemon-0.1.0-py3-none-any.whl'), 'ktx-daemon-wheel'],
[join(layout.pythonDir, 'ktx_daemon-0.1.0.tar.gz'), 'ktx-daemon-sdist'],
]);
for (const [path, contents] of fileContents) {
@ -94,21 +94,21 @@ async function writeUploadableArtifactFixtures(layout) {
}
describe('packageArtifactLayout', () => {
it('uses stable artifact paths under klo/dist/artifacts', () => {
const layout = packageArtifactLayout('/repo/klo');
it('uses stable artifact paths under ktx/dist/artifacts', () => {
const layout = packageArtifactLayout('/repo/ktx');
assert.equal(layout.artifactDir, '/repo/klo/dist/artifacts');
assert.equal(layout.npmDir, '/repo/klo/dist/artifacts/npm');
assert.equal(layout.pythonDir, '/repo/klo/dist/artifacts/python');
assert.equal(layout.contextTarball, '/repo/klo/dist/artifacts/npm/klo-context-0.0.0-private.tgz');
assert.equal(layout.cliTarball, '/repo/klo/dist/artifacts/npm/klo-cli-0.0.0-private.tgz');
assert.equal(layout.artifactDir, '/repo/ktx/dist/artifacts');
assert.equal(layout.npmDir, '/repo/ktx/dist/artifacts/npm');
assert.equal(layout.pythonDir, '/repo/ktx/dist/artifacts/python');
assert.equal(layout.contextTarball, '/repo/ktx/dist/artifacts/npm/ktx-context-0.0.0-private.tgz');
assert.equal(layout.cliTarball, '/repo/ktx/dist/artifacts/npm/ktx-cli-0.0.0-private.tgz');
assert.equal(
layout.connectorTarballs['@klo/connector-sqlite'],
'/repo/klo/dist/artifacts/npm/klo-connector-sqlite-0.0.0-private.tgz',
layout.connectorTarballs['@ktx/connector-sqlite'],
'/repo/ktx/dist/artifacts/npm/ktx-connector-sqlite-0.0.0-private.tgz',
);
assert.equal(
layout.connectorTarballs['@klo/connector-postgres'],
'/repo/klo/dist/artifacts/npm/klo-connector-postgres-0.0.0-private.tgz',
layout.connectorTarballs['@ktx/connector-postgres'],
'/repo/ktx/dist/artifacts/npm/ktx-connector-postgres-0.0.0-private.tgz',
);
assert.deepEqual(
Object.keys(layout.npmTarballs),
@ -119,7 +119,7 @@ describe('packageArtifactLayout', () => {
describe('buildArtifactCommands', () => {
it('builds all TypeScript packages before packing npm artifacts and builds both Python packages', () => {
const layout = packageArtifactLayout('/repo/klo');
const layout = packageArtifactLayout('/repo/ktx');
const commands = buildArtifactCommands(layout);
assert.deepEqual(
@ -138,8 +138,8 @@ describe('buildArtifactCommands', () => {
assert.deepEqual(
commands.slice(NPM_ARTIFACT_PACKAGES.length * 2).map((command) => [command.command, command.args]),
[
['uv', ['build', '--package', 'klo-sl', '--out-dir', '/repo/klo/dist/artifacts/python']],
['uv', ['build', '--package', 'klo-daemon', '--out-dir', '/repo/klo/dist/artifacts/python']],
['uv', ['build', '--package', 'ktx-sl', '--out-dir', '/repo/ktx/dist/artifacts/python']],
['uv', ['build', '--package', 'ktx-daemon', '--out-dir', '/repo/ktx/dist/artifacts/python']],
],
);
});
@ -147,7 +147,7 @@ describe('buildArtifactCommands', () => {
describe('packageReleaseMetadata', () => {
it('reads package identities and versions from package manifests', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-metadata-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-metadata-test-'));
try {
await writeReleaseMetadataInputs(root);
@ -162,16 +162,16 @@ describe('packageReleaseMetadata', () => {
})),
{
ecosystem: 'python',
packageName: 'klo-sl',
packageRoot: 'python/klo-sl',
packageName: 'ktx-sl',
packageRoot: 'python/ktx-sl',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
},
{
ecosystem: 'python',
packageName: 'klo-daemon',
packageRoot: 'python/klo-daemon',
packageName: 'ktx-daemon',
packageRoot: 'python/ktx-daemon',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
@ -185,18 +185,18 @@ describe('packageReleaseMetadata', () => {
describe('findPythonArtifacts', () => {
it('finds one wheel and one source distribution for each Python package', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
await writeFile(join(root, 'klo_sl-0.1.0-py3-none-any.whl'), '');
await writeFile(join(root, 'klo_sl-0.1.0.tar.gz'), '');
await writeFile(join(root, 'klo_daemon-0.1.0-py3-none-any.whl'), '');
await writeFile(join(root, 'klo_daemon-0.1.0.tar.gz'), '');
await writeFile(join(root, 'ktx_sl-0.1.0-py3-none-any.whl'), '');
await writeFile(join(root, 'ktx_sl-0.1.0.tar.gz'), '');
await writeFile(join(root, 'ktx_daemon-0.1.0-py3-none-any.whl'), '');
await writeFile(join(root, 'ktx_daemon-0.1.0.tar.gz'), '');
assert.deepEqual(await findPythonArtifacts(root), {
kloSlWheel: join(root, 'klo_sl-0.1.0-py3-none-any.whl'),
kloSlSdist: join(root, 'klo_sl-0.1.0.tar.gz'),
kloDaemonWheel: join(root, 'klo_daemon-0.1.0-py3-none-any.whl'),
kloDaemonSdist: join(root, 'klo_daemon-0.1.0.tar.gz'),
ktxSlWheel: join(root, 'ktx_sl-0.1.0-py3-none-any.whl'),
ktxSlSdist: join(root, 'ktx_sl-0.1.0.tar.gz'),
ktxDaemonWheel: join(root, 'ktx_daemon-0.1.0-py3-none-any.whl'),
ktxDaemonSdist: join(root, 'ktx_daemon-0.1.0.tar.gz'),
});
} finally {
await rm(root, { recursive: true, force: true });
@ -204,9 +204,9 @@ describe('findPythonArtifacts', () => {
});
it('throws when a required Python artifact is missing', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: klo-sl wheel/);
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: ktx-sl wheel/);
} finally {
await rm(root, { recursive: true, force: true });
}
@ -215,7 +215,7 @@ describe('findPythonArtifacts', () => {
describe('artifact manifest', () => {
it('writes release metadata, source revision, checksums, and byte counts for every uploadable artifact', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-manifest-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-manifest-test-'));
const layout = packageArtifactLayout(root);
try {
await writeReleaseMetadataInputs(root);
@ -245,16 +245,16 @@ describe('artifact manifest', () => {
[
{
ecosystem: 'python',
packageName: 'klo-sl',
packageRoot: 'python/klo-sl',
packageName: 'ktx-sl',
packageRoot: 'python/ktx-sl',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
},
{
ecosystem: 'python',
packageName: 'klo-daemon',
packageRoot: 'python/klo-daemon',
packageName: 'ktx-daemon',
packageRoot: 'python/ktx-daemon',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
@ -294,38 +294,38 @@ describe('artifact manifest', () => {
{
artifactKind: 'wheel',
ecosystem: 'python',
packageName: 'klo-daemon',
packageName: 'ktx-daemon',
packageVersion: '0.1.0',
path: 'python/klo_daemon-0.1.0-py3-none-any.whl',
path: 'python/ktx_daemon-0.1.0-py3-none-any.whl',
},
{
artifactKind: 'sdist',
ecosystem: 'python',
packageName: 'klo-daemon',
packageName: 'ktx-daemon',
packageVersion: '0.1.0',
path: 'python/klo_daemon-0.1.0.tar.gz',
path: 'python/ktx_daemon-0.1.0.tar.gz',
},
{
artifactKind: 'wheel',
ecosystem: 'python',
packageName: 'klo-sl',
packageName: 'ktx-sl',
packageVersion: '0.1.0',
path: 'python/klo_sl-0.1.0-py3-none-any.whl',
path: 'python/ktx_sl-0.1.0-py3-none-any.whl',
},
{
artifactKind: 'sdist',
ecosystem: 'python',
packageName: 'klo-sl',
packageName: 'ktx-sl',
packageVersion: '0.1.0',
path: 'python/klo_sl-0.1.0.tar.gz',
path: 'python/ktx_sl-0.1.0.tar.gz',
},
],
);
const sqliteEntry = manifest.files.find((file) => file.path === 'npm/klo-connector-sqlite-0.0.0-private.tgz');
const sqliteEntry = manifest.files.find((file) => file.path === 'npm/ktx-connector-sqlite-0.0.0-private.tgz');
assert.ok(sqliteEntry);
assert.equal(sqliteEntry.bytes, Buffer.byteLength('@klo/connector-sqlite-tarball'));
assert.equal(sqliteEntry.sha256, createHash('sha256').update('@klo/connector-sqlite-tarball').digest('hex'));
assert.equal(sqliteEntry.bytes, Buffer.byteLength('@ktx/connector-sqlite-tarball'));
assert.equal(sqliteEntry.sha256, createHash('sha256').update('@ktx/connector-sqlite-tarball').digest('hex'));
const writtenManifest = JSON.parse(await readFile(artifactManifestPath(layout), 'utf-8'));
assert.deepEqual(writtenManifest, manifest);
@ -337,7 +337,7 @@ describe('artifact manifest', () => {
describe('verifyArtifactManifest', () => {
it('accepts a schema version 2 manifest that matches the artifact directory', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-verify-manifest-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-verify-manifest-test-'));
const layout = packageArtifactLayout(root);
try {
await writeReleaseMetadataInputs(root);
@ -359,7 +359,7 @@ describe('verifyArtifactManifest', () => {
});
it('rejects a manifest when a file checksum has drifted', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-checksum-drift-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-checksum-drift-test-'));
const layout = packageArtifactLayout(root);
try {
await writeReleaseMetadataInputs(root);
@ -379,7 +379,7 @@ describe('verifyArtifactManifest', () => {
});
it('rejects a manifest with an unsafe artifact path', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-path-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-path-test-'));
const layout = packageArtifactLayout(root);
try {
await writeReleaseMetadataInputs(root);
@ -397,7 +397,7 @@ describe('verifyArtifactManifest', () => {
});
it('rejects a manifest from the wrong source revision when one is required', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-revision-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-revision-test-'));
const layout = packageArtifactLayout(root);
try {
await writeReleaseMetadataInputs(root);
@ -422,10 +422,10 @@ describe('verifyArtifactManifest', () => {
describe('pythonArtifactInstallArgs', () => {
it('installs the built Python wheels by artifact path', () => {
const args = pythonArtifactInstallArgs('/tmp/smoke/.venv/bin/python', {
kloSlWheel: '/repo/klo/dist/artifacts/python/klo_sl-0.1.0-py3-none-any.whl',
kloSlSdist: '/repo/klo/dist/artifacts/python/klo_sl-0.1.0.tar.gz',
kloDaemonWheel: '/repo/klo/dist/artifacts/python/klo_daemon-0.1.0-py3-none-any.whl',
kloDaemonSdist: '/repo/klo/dist/artifacts/python/klo_daemon-0.1.0.tar.gz',
ktxSlWheel: '/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0-py3-none-any.whl',
ktxSlSdist: '/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0.tar.gz',
ktxDaemonWheel: '/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0-py3-none-any.whl',
ktxDaemonSdist: '/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0.tar.gz',
});
assert.deepEqual(args, [
@ -433,26 +433,26 @@ describe('pythonArtifactInstallArgs', () => {
'install',
'--python',
'/tmp/smoke/.venv/bin/python',
'/repo/klo/dist/artifacts/python/klo_sl-0.1.0-py3-none-any.whl',
'/repo/klo/dist/artifacts/python/klo_daemon-0.1.0-py3-none-any.whl',
'/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0-py3-none-any.whl',
'/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0-py3-none-any.whl',
]);
assert.equal(args.includes('klo-daemon'), false);
assert.equal(args.includes('ktx-daemon'), false);
assert.equal(args.includes('--find-links'), false);
});
});
describe('npmSmokePythonEnv', () => {
it('prepends the npm smoke virtualenv bin directory to PATH', () => {
const env = npmSmokePythonEnv('/tmp/klo-npm-smoke', { PATH: '/usr/bin' });
const env = npmSmokePythonEnv('/tmp/ktx-npm-smoke', { PATH: '/usr/bin' });
assert.match(env.PATH, /^\/tmp\/klo-npm-smoke\/\.venv\/(bin|Scripts)/);
assert.match(env.PATH, /^\/tmp\/ktx-npm-smoke\/\.venv\/(bin|Scripts)/);
assert.match(env.PATH, /\/usr\/bin$/);
});
});
describe('verification snippets', () => {
it('pins smoke dependencies and connector packages to clean-install-safe artifacts', () => {
const layout = packageArtifactLayout('/repo/klo');
const layout = packageArtifactLayout('/repo/ktx');
const packageJson = npmSmokePackageJson(layout);
for (const packageInfo of NPM_ARTIFACT_PACKAGES) {
@ -473,10 +473,10 @@ describe('verification snippets', () => {
assert.equal(packageJson.scripts['artifacts:verify-manifest'], 'node scripts/package-artifacts.mjs verify-manifest');
});
it('verifies installed dbt extraction exports from @klo/context/ingest', () => {
it('verifies installed dbt extraction exports from @ktx/context/ingest', () => {
const source = npmVerifySource();
assert.match(source, /const ingest = await import\('@klo\/context\/ingest'\);/);
assert.match(source, /const ingest = await import\('@ktx\/context\/ingest'\);/);
assert.match(source, /const dbtExtractionExports = \[/);
assert.match(source, /throw new Error\('Missing dbt extraction export: ' \+ exportName\);/);
@ -498,26 +498,26 @@ describe('verification snippets', () => {
it('asserts the public npm and connector entry points that clean installs must expose', () => {
const source = npmVerifySource();
assert.match(source, /@klo\/context/);
assert.match(source, /@klo\/context\/project/);
assert.match(source, /@klo\/context\/mcp/);
assert.match(source, /@klo\/context\/memory/);
assert.match(source, /@klo\/context\/daemon/);
assert.match(source, /@klo\/cli/);
assert.match(source, /@klo\/llm/);
assert.match(source, /createKloLlmProvider/);
assert.match(source, /KloMessageBuilder/);
assert.match(source, /createKloEmbeddingProvider/);
assert.match(source, /@ktx\/context/);
assert.match(source, /@ktx\/context\/project/);
assert.match(source, /@ktx\/context\/mcp/);
assert.match(source, /@ktx\/context\/memory/);
assert.match(source, /@ktx\/context\/daemon/);
assert.match(source, /@ktx\/cli/);
assert.match(source, /@ktx\/llm/);
assert.match(source, /createKtxLlmProvider/);
assert.match(source, /KtxMessageBuilder/);
assert.match(source, /createKtxEmbeddingProvider/);
assert.doesNotMatch(source, /createGatewayLlmProvider/);
assert.match(source, /createLocalProjectMemoryCapture/);
for (const packageName of CONNECTOR_PACKAGE_NAMES) {
assert.match(source, new RegExp(packageName.replace('/', '\\/')));
}
assert.match(source, /KloSqliteScanConnector/);
assert.match(source, /KloPostgresScanConnector/);
assert.match(source, /KloBigQueryScanConnector/);
assert.match(source, /KloSnowflakeScanConnector/);
assert.match(source, /KloPostHogScanConnector/);
assert.match(source, /KtxSqliteScanConnector/);
assert.match(source, /KtxPostgresScanConnector/);
assert.match(source, /KtxBigQueryScanConnector/);
assert.match(source, /KtxSnowflakeScanConnector/);
assert.match(source, /KtxPostHogScanConnector/);
});
it('asserts installed hybrid search exports and CLI smoke coverage', () => {
@ -525,19 +525,19 @@ describe('verification snippets', () => {
const runtimeSource = npmRuntimeSmokeSource();
const demoSource = npmDemoSmokeSource();
assert.match(verifySource, /const search = await import\('@klo\/context\/search'\);/);
assert.match(verifySource, /const search = await import\('@ktx\/context\/search'\);/);
assert.match(verifySource, /HybridSearchCore/);
assert.match(verifySource, /assertSearchBackendConformanceCase/);
assert.match(verifySource, /assertSearchBackendCapabilities/);
assert.match(runtimeSource, /klo agent wiki search hybrid metadata verified/);
assert.match(runtimeSource, /klo agent sl list hybrid metadata verified/);
assert.match(runtimeSource, /ktx agent wiki search hybrid metadata verified/);
assert.match(runtimeSource, /ktx agent sl list hybrid metadata verified/);
assert.match(runtimeSource, /agent_sl_search_missing_project/);
assert.match(runtimeSource, /agent_sl_search_no_connections/);
assert.match(runtimeSource, /agent_sl_search_no_indexed_sources/);
assert.match(demoSource, /klo seeded demo agent wiki search verified/);
assert.match(demoSource, /klo seeded demo agent sl search verified/);
assert.match(demoSource, /ktx seeded demo agent wiki search verified/);
assert.match(demoSource, /ktx seeded demo agent sl search verified/);
});
it('runs installed CLI commands and MCP through an installed daemon HTTP server', () => {
@ -552,7 +552,7 @@ describe('verification snippets', () => {
assert.match(source, /startSemanticDaemon/);
assert.match(source, /waitForHttpHealth/);
assert.match(source, /stopSemanticDaemon/);
assert.match(source, /'klo-daemon'/);
assert.match(source, /'ktx-daemon'/);
assert.match(source, /'serve-http'/);
assert.match(source, /'--host'/);
assert.match(source, /'127\.0\.0\.1'/);
@ -564,12 +564,12 @@ describe('verification snippets', () => {
assert.match(source, /Looker daemon table identifier parser verified/);
assert.match(source, /Looker local runtime store verified/);
assert.match(source, /semanticComputeUrl/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'setup'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'setup'/);
assert.match(source, /knowledge', 'global', 'revenue\.md'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'wiki',\s*'search'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'wiki',\s*'search'/);
assert.match(source, /semantic-layer', 'warehouse', 'orders\.yaml'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'sl',\s*'list'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'sl',\s*'query'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'sl',\s*'list'/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'sl',\s*'query'/);
assert.match(source, /orders\.order_count/);
assert.match(source, /sqlite3/);
assert.match(source, /driver: sqlite/);
@ -582,12 +582,12 @@ describe('verification snippets', () => {
assert.match(source, /slQueryResult\.plan\.execution\.driver, 'sqlite'/);
assert.match(source, /"mode": "compile_only"/);
assert.match(source, /"mode": "executed"/);
assert.match(source, /klo agent sl query sqlite execute/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'dev',\s*'scan',\s*'warehouse'/);
assert.match(source, /ktx agent sl query sqlite execute/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'dev',\s*'scan',\s*'warehouse'/);
assert.match(source, /'--mode',\s*'enriched'/);
assert.doesNotMatch(source, /'--enrich'/);
assert.match(source, /klo scan structural verified/);
assert.match(source, /klo scan enriched verified/);
assert.match(source, /ktx scan structural verified/);
assert.match(source, /ktx scan enriched verified/);
assert.match(source, /scanReportJson\.artifactPaths\.manifestShards/);
assert.match(source, /scanReportJson\.artifactPaths\.enrichmentArtifacts/);
assert.match(source, /enrichment:/);
@ -596,12 +596,12 @@ describe('verification snippets', () => {
assert.match(source, /models:/);
assert.match(source, /default: smoke\/provider/);
assert.match(source, /api_key: env:AI_GATEWAY_API_KEY/);
assert.match(source, /run\('pnpm', \['exec', 'klo', 'dev', 'ingest', 'run'/);
assert.match(source, /run\('pnpm', \['exec', 'ktx', 'dev', 'ingest', 'run'/);
assert.match(source, /'serve', '--mcp', 'stdio'/);
assert.doesNotMatch(source, /'--semantic-compute',\n\s*'--execute-queries'/);
assert.match(source, /'--memory-capture', '--memory-model', 'smoke\/provider'/);
assert.match(source, /mcpServerStderr/);
assert.match(source, /klo serve stderr/);
assert.match(source, /ktx serve stderr/);
assert.match(source, /sl_validate/);
assert.match(source, /sl_query/);
assert.match(source, /memory_capture/);
@ -614,26 +614,26 @@ describe('verification snippets', () => {
assert.match(source, /scan_read_artifact/);
assert.match(source, /mcpScanArtifacts\.artifacts\.find/);
assert.match(source, /AI_GATEWAY_API_KEY/);
assert.match(source, /access\(join\(projectDir, '\.klo', 'db\.sqlite'\)\)/);
assert.match(source, /access\(join\(projectDir, '\.ktx', 'db\.sqlite'\)\)/);
assert.match(source, /SQLite knowledge index/);
assert.match(source, /klo dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/);
assert.match(source, /klo dev ingest provider guard verified/);
assert.match(source, /ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/);
assert.match(source, /ktx dev ingest provider guard verified/);
});
describe('npmDemoSmokeSource', () => {
it('exercises the public packed-demo first-run contract', () => {
const source = npmDemoSmokeSource();
assert.match(source, /pnpm', \['exec', 'klo', '--help'\]/);
assert.match(source, /pnpm', \['exec', 'ktx', '--help'\]/);
assert.match(source, /'demo', '--project-dir', projectDir, '--no-input', '--plain'/);
assert.match(source, /Mode: seeded/);
assert.match(source, /Source: packaged demo project/);
assert.match(source, /LLM calls: none/);
assert.match(source, /klo serve --mcp stdio/);
assert.match(source, /ktx serve --mcp stdio/);
assert.doesNotMatch(source, new RegExp(["'demo'", "'--mode'", "'deterministic'"].join(', ')));
assert.match(source, /'dev', 'doctor', 'setup', '--no-input'/);
assert.match(source, /'--plain'/);
assert.match(source, /klo setup demo seeded wrote unexpected stderr/);
assert.match(source, /ktx setup demo seeded wrote unexpected stderr/);
});
});
@ -649,7 +649,7 @@ describe('verification snippets', () => {
const source = pythonVerifySource();
assert.match(source, /semantic_layer/);
assert.match(source, /klo_daemon/);
assert.match(source, /ktx_daemon/);
assert.match(source, /importlib.metadata/);
});
});

View file

@ -5,8 +5,8 @@ import { dirname, join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
const scriptPath = fileURLToPath(import.meta.url);
const kloRoot = dirname(dirname(scriptPath));
const repoRoot = dirname(kloRoot);
const ktxRoot = dirname(dirname(scriptPath));
const repoRoot = dirname(ktxRoot);
const packageNameByDir = new Map(
[
@ -22,7 +22,7 @@ const packageNameByDir = new Map(
'context',
'llm',
].map((packageDir) => {
const manifestPath = join(kloRoot, 'packages', packageDir, 'package.json');
const manifestPath = join(ktxRoot, 'packages', packageDir, 'package.json');
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
return [packageDir, manifest.name];
}),
@ -31,8 +31,8 @@ const packageNameByDir = new Map(
const packageCodePattern = /\.(?:ts|tsx|js|jsx|json)$/;
const scriptPattern = /\.(?:mjs|js|json)$/;
const pythonPackageTests = new Map([
['klo-sl', 'python/klo-sl/tests'],
['klo-daemon', 'python/klo-daemon/tests'],
['ktx-sl', 'python/ktx-sl/tests'],
['ktx-daemon', 'python/ktx-daemon/tests'],
]);
function normalizeFilePath(filePath) {
@ -57,7 +57,7 @@ function maybeScriptTest(scriptFile) {
}
const testFile = scriptFile.replace(/\.mjs$/, '.test.mjs');
return existsSync(join(kloRoot, testFile)) ? testFile : null;
return existsSync(join(ktxRoot, testFile)) ? testFile : null;
}
export function planChecks(files) {
@ -71,14 +71,14 @@ export function planChecks(files) {
for (const rawFile of files) {
const file = normalizeFilePath(rawFile);
if (!file.startsWith('klo/')) {
if (!file.startsWith('ktx/')) {
continue;
}
const kloFile = file.slice('klo/'.length);
const ktxFile = file.slice('ktx/'.length);
if (kloFile.startsWith('packages/')) {
const [, packageDir, ...rest] = kloFile.split('/');
if (ktxFile.startsWith('packages/')) {
const [, packageDir, ...rest] = ktxFile.split('/');
const packageName = packageNameByDir.get(packageDir);
const packageFile = rest.join('/');
@ -90,8 +90,8 @@ export function planChecks(files) {
continue;
}
if (kloFile.startsWith('scripts/') && scriptPattern.test(kloFile)) {
const testFile = maybeScriptTest(kloFile);
if (ktxFile.startsWith('scripts/') && scriptPattern.test(ktxFile)) {
const testFile = maybeScriptTest(ktxFile);
if (testFile) {
stablePush(commands, `script-test:${testFile}`, 'node', ['--test', testFile]);
@ -100,8 +100,8 @@ export function planChecks(files) {
continue;
}
if (kloFile.startsWith('python/')) {
const [, packageDir] = kloFile.split('/');
if (ktxFile.startsWith('python/')) {
const [, packageDir] = ktxFile.split('/');
if (pythonPackageTests.has(packageDir)) {
pythonPackages.add(packageDir);
@ -112,7 +112,7 @@ export function planChecks(files) {
if (
['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'release-policy.json', 'tsconfig.base.json'].includes(
kloFile,
ktxFile,
)
) {
runBoundaryCheck = true;
@ -120,7 +120,7 @@ export function planChecks(files) {
continue;
}
if (['pyproject.toml', 'uv.lock', 'uv.toml'].includes(kloFile)) {
if (['pyproject.toml', 'uv.lock', 'uv.toml'].includes(ktxFile)) {
runAllPythonTests = true;
}
}
@ -164,7 +164,7 @@ export function runChecks(files) {
const commands = planChecks(files);
if (commands.length === 0) {
console.log('No KLO package checks needed for these files.');
console.log('No KTX package checks needed for these files.');
return 0;
}
@ -172,7 +172,7 @@ export function runChecks(files) {
printCommand(command);
const result = spawnSync(command.cmd, command.args, {
cwd: kloRoot,
cwd: ktxRoot,
stdio: 'inherit',
env: process.env,
});
@ -190,6 +190,6 @@ export function runChecks(files) {
return 0;
}
if (process.argv[1] && relative(repoRoot, process.argv[1]).split(sep).join('/') === 'klo/scripts/precommit-check.mjs') {
if (process.argv[1] && relative(repoRoot, process.argv[1]).split(sep).join('/') === 'ktx/scripts/precommit-check.mjs') {
process.exitCode = runChecks(process.argv.slice(2));
}

View file

@ -8,26 +8,26 @@ function commandKeys(files) {
}
describe('precommit-check', () => {
it('skips files outside klo', () => {
it('skips files outside ktx', () => {
assert.deepEqual(commandKeys(['server/src/app.ts']), []);
});
it('runs only the touched package checks for package code', () => {
assert.deepEqual(commandKeys(['klo/packages/cli/src/index.ts']), [
assert.deepEqual(commandKeys(['ktx/packages/cli/src/index.ts']), [
'boundary-check',
'type-check:@klo/cli',
'build:@klo/cli',
'test:@klo/cli',
'type-check:@ktx/cli',
'build:@ktx/cli',
'test:@ktx/cli',
]);
});
it('runs the matching script test when a script changes', () => {
assert.deepEqual(commandKeys(['klo/scripts/check-boundaries.mjs']), [
assert.deepEqual(commandKeys(['ktx/scripts/check-boundaries.mjs']), [
'script-test:scripts/check-boundaries.test.mjs',
]);
});
it('runs the touched python package tests', () => {
assert.deepEqual(commandKeys(['klo/python/klo-sl/semantic_layer/parser.py']), ['pytest:klo-sl']);
assert.deepEqual(commandKeys(['ktx/python/ktx-sl/semantic_layer/parser.py']), ['pytest:ktx-sl']);
});
});

View file

@ -5,11 +5,11 @@ import { access, chmod } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
export function kloRootDir() {
export function ktxRootDir() {
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
}
export function cliBinPath(rootDir = kloRootDir()) {
export function cliBinPath(rootDir = ktxRootDir()) {
return resolve(rootDir, 'packages', 'cli', 'dist', 'bin.js');
}
@ -22,7 +22,7 @@ async function canExecute(path) {
}
}
export async function ensureCliBinExecutable(rootDir = kloRootDir()) {
export async function ensureCliBinExecutable(rootDir = ktxRootDir()) {
const binPath = cliBinPath(rootDir);
await access(binPath, constants.R_OK);
@ -36,7 +36,7 @@ export async function ensureCliBinExecutable(rootDir = kloRootDir()) {
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
try {
const binPath = await ensureCliBinExecutable();
process.stdout.write(`Prepared KLO CLI bin: ${binPath}\n`);
process.stdout.write(`Prepared KTX CLI bin: ${binPath}\n`);
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;

View file

@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
export const DEFAULT_VERSION_TAG = 'latest';
export const NO_PACKAGE_REASON =
'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.';
'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.';
function optionalTrimmedString(value) {
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
@ -53,7 +53,7 @@ export function readPublishedPackageSmokeConfig(env = process.env, args = proces
const requireConfig = args.includes('--require-config');
const policy = normalizePolicyConfig(policyConfig);
const envPackageName = optionalTrimmedString(env.KLO_PUBLISHED_KLO_PACKAGE);
const envPackageName = optionalTrimmedString(env.KTX_PUBLISHED_KTX_PACKAGE);
const packageName = envPackageName ?? policy.packageName;
if (!packageName) {
@ -68,24 +68,24 @@ export function readPublishedPackageSmokeConfig(env = process.env, args = proces
assertSafePackageName(
packageName,
configSource === 'environment'
? 'KLO_PUBLISHED_KLO_PACKAGE'
? 'KTX_PUBLISHED_KTX_PACKAGE'
: 'release-policy.json publishedPackageSmoke.packageName',
);
const packageVersion = optionalTrimmedString(env.KLO_PUBLISHED_KLO_VERSION) ?? policy.version;
const packageVersion = optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION) ?? policy.version;
assertSafeVersionTag(
packageVersion,
optionalTrimmedString(env.KLO_PUBLISHED_KLO_VERSION)
? 'KLO_PUBLISHED_KLO_VERSION'
optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION)
? 'KTX_PUBLISHED_KTX_VERSION'
: 'release-policy.json publishedPackageSmoke.version',
);
const registry = optionalTrimmedString(env.KLO_PUBLISHED_KLO_REGISTRY) ?? policy.registry;
const registry = optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY) ?? policy.registry;
if (registry) {
assertHttpRegistry(
registry,
optionalTrimmedString(env.KLO_PUBLISHED_KLO_REGISTRY)
? 'KLO_PUBLISHED_KLO_REGISTRY'
optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY)
? 'KTX_PUBLISHED_KTX_REGISTRY'
: 'release-policy.json publishedPackageSmoke.registry',
);
}

View file

@ -94,19 +94,19 @@ function assertMissingProjectReadiness(result, emptyProjectDir) {
ok: false,
error: {
code: 'agent_sl_search_missing_project',
message: `Semantic-layer search needs an initialized KLO project at ${emptyProjectDir}.`,
message: `Semantic-layer search needs an initialized KTX project at ${emptyProjectDir}.`,
nextSteps: [
'klo demo',
`klo setup --project-dir ${emptyProjectDir}`,
'klo ingest <connection>',
`klo agent sl list --json --query "revenue" --project-dir ${emptyProjectDir}`,
'ktx demo',
`ktx setup --project-dir ${emptyProjectDir}`,
'ktx ingest <connection>',
`ktx agent sl list --json --query "revenue" --project-dir ${emptyProjectDir}`,
],
},
});
}
export async function runPublishedPackageSmoke(config) {
const root = await mkdtemp(join(tmpdir(), 'klo-published-package-smoke-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-published-package-smoke-'));
try {
const projectDir = join(root, 'demo-project');
const emptyProjectDir = join(root, 'empty-project');
@ -147,7 +147,7 @@ async function main() {
if (config.requireConfig) {
throw new Error(config.reason);
}
process.stdout.write(`Published KLO package smoke skipped: ${config.reason}\n`);
process.stdout.write(`Published KTX package smoke skipped: ${config.reason}\n`);
return;
}

View file

@ -15,7 +15,7 @@ describe('published package smoke config', () => {
enabled: false,
requireConfig: false,
reason:
'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.',
'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.',
});
});
@ -24,7 +24,7 @@ describe('published package smoke config', () => {
enabled: false,
requireConfig: true,
reason:
'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.',
'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.',
});
});
@ -32,9 +32,9 @@ describe('published package smoke config', () => {
assert.deepEqual(
readPublishedPackageSmokeConfig(
{
KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public',
KLO_PUBLISHED_KLO_VERSION: 'latest',
KLO_PUBLISHED_KLO_REGISTRY: 'https://registry.npmjs.org/',
KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public',
KTX_PUBLISHED_KTX_VERSION: 'latest',
KTX_PUBLISHED_KTX_REGISTRY: 'https://registry.npmjs.org/',
},
[],
),
@ -42,7 +42,7 @@ describe('published package smoke config', () => {
enabled: true,
requireConfig: false,
configSource: 'environment',
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
packageVersion: 'latest',
registry: 'https://registry.npmjs.org/',
},
@ -55,7 +55,7 @@ describe('published package smoke config', () => {
{},
[],
{
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -64,7 +64,7 @@ describe('published package smoke config', () => {
enabled: true,
requireConfig: false,
configSource: 'release-policy',
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
packageVersion: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -75,12 +75,12 @@ describe('published package smoke config', () => {
assert.deepEqual(
readPublishedPackageSmokeConfig(
{
KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-from-env',
KLO_PUBLISHED_KLO_VERSION: 'latest',
KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-from-env',
KTX_PUBLISHED_KTX_VERSION: 'latest',
},
[],
{
packageName: '@klo/cli-from-policy',
packageName: '@ktx/cli-from-policy',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -89,7 +89,7 @@ describe('published package smoke config', () => {
enabled: true,
requireConfig: false,
configSource: 'environment',
packageName: '@klo/cli-from-env',
packageName: '@ktx/cli-from-env',
packageVersion: 'latest',
registry: 'https://registry.npmjs.org/',
},
@ -98,12 +98,12 @@ describe('published package smoke config', () => {
it('rejects package names that would be unsafe as npx package specs', () => {
assert.throws(
() => readPublishedPackageSmokeConfig({ KLO_PUBLISHED_KLO_PACKAGE: '--package=@evil/pkg' }, []),
/Invalid KLO_PUBLISHED_KLO_PACKAGE/,
() => readPublishedPackageSmokeConfig({ KTX_PUBLISHED_KTX_PACKAGE: '--package=@evil/pkg' }, []),
/Invalid KTX_PUBLISHED_KTX_PACKAGE/,
);
assert.throws(
() => readPublishedPackageSmokeConfig({ KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli public' }, []),
/Invalid KLO_PUBLISHED_KLO_PACKAGE/,
() => readPublishedPackageSmokeConfig({ KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli public' }, []),
/Invalid KTX_PUBLISHED_KTX_PACKAGE/,
);
assert.throws(
() =>
@ -111,7 +111,7 @@ describe('published package smoke config', () => {
{},
[],
{
packageName: '@klo/cli public',
packageName: '@ktx/cli public',
version: 'latest',
registry: null,
},
@ -125,23 +125,23 @@ describe('published package smoke config', () => {
() =>
readPublishedPackageSmokeConfig(
{
KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public',
KLO_PUBLISHED_KLO_VERSION: '--tag latest',
KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public',
KTX_PUBLISHED_KTX_VERSION: '--tag latest',
},
[],
),
/Invalid KLO_PUBLISHED_KLO_VERSION/,
/Invalid KTX_PUBLISHED_KTX_VERSION/,
);
assert.throws(
() =>
readPublishedPackageSmokeConfig(
{
KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public',
KLO_PUBLISHED_KLO_REGISTRY: 'file:///tmp/npm',
KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public',
KTX_PUBLISHED_KTX_REGISTRY: 'file:///tmp/npm',
},
[],
),
/KLO_PUBLISHED_KLO_REGISTRY must be an http\(s\) URL/,
/KTX_PUBLISHED_KTX_REGISTRY must be an http\(s\) URL/,
);
});
});
@ -150,30 +150,30 @@ describe('published package smoke command construction', () => {
const config = {
enabled: true,
requireConfig: false,
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
packageVersion: 'latest',
registry: 'https://registry.npmjs.org/',
};
it('builds the npx package spec from package name and version tag', () => {
assert.equal(publishedPackageSpec(config), '@klo/cli-public@latest');
assert.equal(publishedPackageSpec(config), '@ktx/cli-public@latest');
});
it('builds npx commands with a registry env patch instead of shell interpolation', () => {
assert.deepEqual(buildPublishedPackageNpxCommand(config, ['--version']), {
label: 'published package command',
command: 'npx',
args: ['--yes', '@klo/cli-public@latest', '--version'],
args: ['--yes', '@ktx/cli-public@latest', '--version'],
env: { npm_config_registry: 'https://registry.npmjs.org/' },
});
});
it('builds the full hybrid-search smoke command list', () => {
assert.deepEqual(buildPublishedPackageSmokeCommands(config, '/tmp/klo-smoke/demo', '/tmp/klo-smoke/empty'), [
assert.deepEqual(buildPublishedPackageSmokeCommands(config, '/tmp/ktx-smoke/demo', '/tmp/ktx-smoke/empty'), [
{
label: 'published package version',
command: 'npx',
args: ['--yes', '@klo/cli-public@latest', '--version'],
args: ['--yes', '@ktx/cli-public@latest', '--version'],
env: { npm_config_registry: 'https://registry.npmjs.org/' },
},
{
@ -181,10 +181,10 @@ describe('published package smoke command construction', () => {
command: 'npx',
args: [
'--yes',
'@klo/cli-public@latest',
'@ktx/cli-public@latest',
'demo',
'--project-dir',
'/tmp/klo-smoke/demo',
'/tmp/ktx-smoke/demo',
'--no-input',
'--plain',
],
@ -195,7 +195,7 @@ describe('published package smoke command construction', () => {
command: 'npx',
args: [
'--yes',
'@klo/cli-public@latest',
'@ktx/cli-public@latest',
'agent',
'wiki',
'search',
@ -204,7 +204,7 @@ describe('published package smoke command construction', () => {
'--limit',
'5',
'--project-dir',
'/tmp/klo-smoke/demo',
'/tmp/ktx-smoke/demo',
],
env: { npm_config_registry: 'https://registry.npmjs.org/' },
},
@ -213,7 +213,7 @@ describe('published package smoke command construction', () => {
command: 'npx',
args: [
'--yes',
'@klo/cli-public@latest',
'@ktx/cli-public@latest',
'agent',
'sl',
'list',
@ -221,7 +221,7 @@ describe('published package smoke command construction', () => {
'--query',
'ARR',
'--project-dir',
'/tmp/klo-smoke/demo',
'/tmp/ktx-smoke/demo',
],
env: { npm_config_registry: 'https://registry.npmjs.org/' },
},
@ -230,7 +230,7 @@ describe('published package smoke command construction', () => {
command: 'npx',
args: [
'--yes',
'@klo/cli-public@latest',
'@ktx/cli-public@latest',
'agent',
'sl',
'list',
@ -238,7 +238,7 @@ describe('published package smoke command construction', () => {
'--query',
'revenue',
'--project-dir',
'/tmp/klo-smoke/empty',
'/tmp/ktx-smoke/empty',
],
env: { npm_config_registry: 'https://registry.npmjs.org/' },
},

View file

@ -5,14 +5,14 @@ import { execFile as childExecFile } from 'node:child_process';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { promisify } from 'node:util';
import { runWorkspaceKlo } from './run-klo.mjs';
import { runWorkspaceKtx } from './run-ktx.mjs';
const scriptDir = dirname(fileURLToPath(import.meta.url));
const kloRootDir = resolve(scriptDir, '..');
const repoRootDir = resolve(kloRootDir, '..');
const defaultProjectDir = resolve(kloRootDir, 'examples/orbit-relationship-verification');
const ktxRootDir = resolve(scriptDir, '..');
const repoRootDir = resolve(ktxRootDir, '..');
const defaultProjectDir = resolve(ktxRootDir, 'examples/orbit-relationship-verification');
const defaultReportPath = resolve(
kloRootDir,
ktxRootDir,
'examples/orbit-relationship-verification/reports/orbit-verification.md',
);
const defaultExecFile = promisify(childExecFile);
@ -43,7 +43,7 @@ export function defaultOrbitVerificationProjectDir() {
}
function shellCommand(argv) {
return ['pnpm', 'run', 'klo', '--', ...argv].join(' ');
return ['pnpm', 'run', 'ktx', '--', ...argv].join(' ');
}
function firstNonEmptyLine(...values) {
@ -61,8 +61,8 @@ function firstNonEmptyLine(...values) {
function parseArgs(argv) {
const options = {
connectionId: process.env.KLO_ORBIT_CONNECTION_ID ?? 'orbit',
projectDir: process.env.KLO_ORBIT_PROJECT_DIR ?? defaultProjectDir,
connectionId: process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit',
projectDir: process.env.KTX_ORBIT_PROJECT_DIR ?? defaultProjectDir,
reportPath: defaultReportPath,
};
@ -190,7 +190,7 @@ function formatBlocked(result) {
export function formatOrbitVerificationMarkdown(result) {
const lines = [
'# KLO Relationship Discovery Orbit Verification',
'# KTX Relationship Discovery Orbit Verification',
'',
`Date: ${result.date}`,
'',
@ -219,7 +219,7 @@ export function formatOrbitVerificationMarkdown(result) {
return `${lines.join('\n')}\n`;
}
async function runBufferedWorkspaceKlo(runner, argv, rootDir, execFile) {
async function runBufferedWorkspaceKtx(runner, argv, rootDir, execFile) {
const stdout = new BufferWriter();
const stderr = new BufferWriter();
const exitCode = await runner(argv, { rootDir, execFile, stdout, stderr });
@ -241,11 +241,11 @@ function orbitVerificationEnv(projectDir) {
}
export async function runOrbitVerification(options = {}) {
const connectionId = options.connectionId ?? process.env.KLO_ORBIT_CONNECTION_ID ?? 'orbit';
const projectDir = options.projectDir ?? process.env.KLO_ORBIT_PROJECT_DIR ?? defaultProjectDir;
const connectionId = options.connectionId ?? process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit';
const projectDir = options.projectDir ?? process.env.KTX_ORBIT_PROJECT_DIR ?? defaultProjectDir;
const reportPath = options.reportPath ?? defaultReportPath;
const rootDir = options.rootDir ?? kloRootDir;
const runner = options.runWorkspaceKlo ?? runWorkspaceKlo;
const rootDir = options.rootDir ?? ktxRootDir;
const runner = options.runWorkspaceKtx ?? runWorkspaceKtx;
const execFile = options.execFile ?? defaultExecFile;
const now = options.now ?? (() => new Date());
const mkdir = options.mkdir ?? fsMkdir;
@ -255,7 +255,7 @@ export async function runOrbitVerification(options = {}) {
const runWithEnv = (argv, runnerOptions) => runner(argv, { ...runnerOptions, env });
const scanArgv = buildOrbitScanArgv({ connectionId, projectDir });
const scan = await runBufferedWorkspaceKlo(runWithEnv, scanArgv, rootDir, execFile);
const scan = await runBufferedWorkspaceKtx(runWithEnv, scanArgv, rootDir, execFile);
let result;
if (scan.exitCode !== 0) {
@ -280,13 +280,13 @@ export async function runOrbitVerification(options = {}) {
projectDir,
scanCommand: shellCommand(scanArgv),
scanExitCode: scan.exitCode,
blocker: 'KLO scan completed without printing a Run id',
blocker: 'KTX scan completed without printing a Run id',
scanStdout: scan.stdout,
scanStderr: scan.stderr,
};
} else {
const reportArgv = buildOrbitReportArgv({ projectDir, runId });
const reportOutput = await runBufferedWorkspaceKlo(runWithEnv, reportArgv, rootDir, execFile);
const reportOutput = await runBufferedWorkspaceKtx(runWithEnv, reportArgv, rootDir, execFile);
if (reportOutput.exitCode !== 0) {
result = {
status: 'blocked',

View file

@ -50,7 +50,7 @@ function successReportJson() {
}
describe('relationship Orbit verification helper', () => {
it('exposes the Orbit verification command from the KLO workspace package', async () => {
it('exposes the Orbit verification command from the KTX workspace package', async () => {
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
assert.equal(
@ -59,7 +59,7 @@ describe('relationship Orbit verification helper', () => {
);
});
it('builds the current KLO launcher arguments for scan and JSON report commands', () => {
it('builds the current KTX launcher arguments for scan and JSON report commands', () => {
assert.deepEqual(buildOrbitScanArgv({ connectionId: 'orbit', projectDir: '/tmp/orbit-project' }), [
'dev',
'scan',
@ -92,14 +92,14 @@ describe('relationship Orbit verification helper', () => {
writeFile: async (path, content) => {
writes.push({ path, content });
},
runWorkspaceKlo: async (argv, options) => {
runWorkspaceKtx: async (argv, options) => {
calls.push(argv);
envs.push(options.env);
if (argv[2] === 'report') {
options.stdout.write(successReportJson());
return 0;
}
options.stdout.write('KLO scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
return 0;
},
});
@ -116,8 +116,8 @@ describe('relationship Orbit verification helper', () => {
});
it('extracts the run id from human scan output', () => {
assert.equal(extractRunId(`KLO scan completed\nStatus: done\nRun: scan-orbit-1\nConnection: orbit\n`), 'scan-orbit-1');
assert.equal(extractRunId('KLO scan completed without a run line\n'), null);
assert.equal(extractRunId(`KTX scan completed\nStatus: done\nRun: scan-orbit-1\nConnection: orbit\n`), 'scan-orbit-1');
assert.equal(extractRunId('KTX scan completed without a run line\n'), null);
});
it('formats successful Orbit verification evidence from the JSON report', () => {
@ -126,16 +126,16 @@ describe('relationship Orbit verification helper', () => {
date: '2026-05-07',
connectionId: 'orbit',
projectDir: '/tmp/orbit-project',
scanCommand: 'pnpm run klo -- dev scan orbit --enrich --project-dir /tmp/orbit-project',
reportCommand: 'pnpm run klo -- dev scan report --json --project-dir /tmp/orbit-project scan-orbit-1',
scanCommand: 'pnpm run ktx -- dev scan orbit --enrich --project-dir /tmp/orbit-project',
reportCommand: 'pnpm run ktx -- dev scan report --json --project-dir /tmp/orbit-project scan-orbit-1',
scanExitCode: 0,
reportExitCode: 0,
scanStdout: 'KLO scan completed\nRun: scan-orbit-1\n',
scanStdout: 'KTX scan completed\nRun: scan-orbit-1\n',
scanStderr: '',
report: JSON.parse(successReportJson()),
});
assert.match(markdown, /# KLO Relationship Discovery Orbit Verification/);
assert.match(markdown, /# KTX Relationship Discovery Orbit Verification/);
assert.match(markdown, /Outcome/);
assert.match(markdown, /Exit code: 0/);
assert.match(markdown, /Accepted: 14/);
@ -152,7 +152,7 @@ describe('relationship Orbit verification helper', () => {
date: '2026-05-07',
connectionId: 'orbit',
projectDir: '/tmp/orbit-project',
scanCommand: 'pnpm run klo -- dev scan orbit --enrich --project-dir /tmp/orbit-project',
scanCommand: 'pnpm run ktx -- dev scan orbit --enrich --project-dir /tmp/orbit-project',
scanExitCode: 1,
blocker: 'Connection "orbit" was not found',
scanStdout: '',
@ -177,13 +177,13 @@ describe('relationship Orbit verification helper', () => {
writeFile: async (path, content) => {
writes.push({ path, content });
},
runWorkspaceKlo: async (argv, options) => {
runWorkspaceKtx: async (argv, options) => {
calls.push(argv);
if (argv[2] === 'report') {
options.stdout.write(successReportJson());
return 0;
}
options.stdout.write('KLO scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
return 0;
},
});
@ -209,7 +209,7 @@ describe('relationship Orbit verification helper', () => {
writeFile: async (path, content) => {
writes.push({ path, content });
},
runWorkspaceKlo: async (_argv, options) => {
runWorkspaceKtx: async (_argv, options) => {
options.stderr.write('Connection "orbit" was not found\n');
return 1;
},
@ -231,14 +231,14 @@ describe('relationship Orbit verification helper', () => {
mkdir: async () => {},
writeFile: async () => {},
execFile: async () => ({ stdout: '', stderr: '' }),
runWorkspaceKlo: async (_argv, options) => {
runWorkspaceKtx: async (_argv, options) => {
sawExecFile = typeof options.execFile === 'function';
options.stderr.write('ENOENT: no such file or directory, open \'/tmp/orbit-project/klo.yaml\'\n');
options.stderr.write('ENOENT: no such file or directory, open \'/tmp/orbit-project/ktx.yaml\'\n');
return 1;
},
});
assert.equal(sawExecFile, true);
assert.equal(result.blocker, "ENOENT: no such file or directory, open '/tmp/orbit-project/klo.yaml'");
assert.equal(result.blocker, "ENOENT: no such file or directory, open '/tmp/orbit-project/ktx.yaml'");
});
});

View file

@ -218,9 +218,9 @@ async function main() {
return;
}
process.stdout.write(`KLO release mode: ${report.releaseMode}\n`);
process.stdout.write(`KLO source revision: ${report.sourceRevision ?? 'local'}\n`);
process.stdout.write(`KLO packages: ${report.packageNames.join(', ')}\n`);
process.stdout.write(`KTX release mode: ${report.releaseMode}\n`);
process.stdout.write(`KTX source revision: ${report.sourceRevision ?? 'local'}\n`);
process.stdout.write(`KTX packages: ${report.packageNames.join(', ')}\n`);
process.stdout.write(`Published package smoke: ${report.publishedPackageSmokeGate.status}\n`);
process.stdout.write(`Published package smoke script: ${report.publishedPackageSmokeGate.script}\n`);
process.stdout.write(`Published package smoke reason: ${report.publishedPackageSmokeGate.reason}\n`);

View file

@ -18,24 +18,24 @@ async function writeReleaseMetadataInputs(root, options = {}) {
name: packageInfo.name,
version: '0.0.0-private',
private:
packageInfo.name === '@klo/context'
packageInfo.name === '@ktx/context'
? (options.contextPrivate ?? true)
: packageInfo.name === '@klo/cli'
: packageInfo.name === '@ktx/cli'
? (options.cliPrivate ?? true)
: true,
});
}
await mkdir(join(root, 'python', 'klo-sl'), { recursive: true });
await mkdir(join(root, 'python', 'klo-daemon'), { recursive: true });
await mkdir(join(root, 'python', 'ktx-sl'), { recursive: true });
await mkdir(join(root, 'python', 'ktx-daemon'), { recursive: true });
await writeFile(
join(root, 'python', 'klo-sl', 'pyproject.toml'),
['[project]', 'name = "klo-sl"', 'version = "0.1.0"', ''].join('\n'),
join(root, 'python', 'ktx-sl', 'pyproject.toml'),
['[project]', 'name = "ktx-sl"', 'version = "0.1.0"', ''].join('\n'),
);
await writeFile(
join(root, 'python', 'klo-daemon', 'pyproject.toml'),
['[project]', 'name = "klo-daemon"', 'version = "0.1.0"', ''].join('\n'),
join(root, 'python', 'ktx-daemon', 'pyproject.toml'),
['[project]', 'name = "ktx-daemon"', 'version = "0.1.0"', ''].join('\n'),
);
}
@ -48,10 +48,10 @@ async function writeUploadableArtifactFixtures(layout) {
layout.npmTarballs[packageInfo.name],
`${packageInfo.name}-tarball`,
]),
[join(layout.pythonDir, 'klo_sl-0.1.0-py3-none-any.whl'), 'klo-sl-wheel'],
[join(layout.pythonDir, 'klo_sl-0.1.0.tar.gz'), 'klo-sl-sdist'],
[join(layout.pythonDir, 'klo_daemon-0.1.0-py3-none-any.whl'), 'klo-daemon-wheel'],
[join(layout.pythonDir, 'klo_daemon-0.1.0.tar.gz'), 'klo-daemon-sdist'],
[join(layout.pythonDir, 'ktx_sl-0.1.0-py3-none-any.whl'), 'ktx-sl-wheel'],
[join(layout.pythonDir, 'ktx_sl-0.1.0.tar.gz'), 'ktx-sl-sdist'],
[join(layout.pythonDir, 'ktx_daemon-0.1.0-py3-none-any.whl'), 'ktx-daemon-wheel'],
[join(layout.pythonDir, 'ktx_daemon-0.1.0.tar.gz'), 'ktx-daemon-sdist'],
]);
for (const [path, contents] of fileContents) {
@ -74,7 +74,7 @@ function releasePolicy(overrides = {}) {
python: {
publish: false,
repository: null,
packages: ['klo-sl', 'klo-daemon'],
packages: ['ktx-sl', 'ktx-daemon'],
...pythonOverrides,
},
publishedPackageSmoke: {
@ -109,8 +109,8 @@ async function writeReadyFixture(root, options = {}) {
}
describe('release readiness policy', () => {
it('reads the checked release policy path from the KLO root', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-policy-test-'));
it('reads the checked release policy path from the KTX root', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-release-policy-test-'));
try {
const policy = releasePolicy();
await writePolicy(root, policy);
@ -123,7 +123,7 @@ describe('release readiness policy', () => {
});
it('accepts the current ci-artifact-only policy, package metadata, and artifact manifest', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-ready-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-ready-test-'));
try {
await writeReadyFixture(root);
@ -135,7 +135,7 @@ describe('release readiness policy', () => {
sourceRevision: 'abc123',
npmPublishEnabled: false,
pythonPublishEnabled: false,
packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'klo-sl', 'klo-daemon'],
packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'ktx-sl', 'ktx-daemon'],
publishedPackageSmokeGate: {
status: 'not_required',
script: 'pnpm run release:published-smoke',
@ -159,12 +159,12 @@ describe('release readiness policy', () => {
});
it('reports policy-controlled published package smoke config when present', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-config-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-config-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
publishedPackageSmoke: {
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -178,7 +178,7 @@ describe('release readiness policy', () => {
script: 'pnpm run release:published-smoke',
reason: 'Published package smoke remains pending until release-policy.json enables npm registry publishing.',
configSource: 'release-policy',
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
});
@ -188,13 +188,13 @@ describe('release readiness policy', () => {
});
it('reports required published package smoke when release mode requires it', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
releaseMode: 'published-package-smoke-required',
publishedPackageSmoke: {
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -210,13 +210,13 @@ describe('release readiness policy', () => {
sourceRevision: 'abc123',
npmPublishEnabled: false,
pythonPublishEnabled: false,
packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'klo-sl', 'klo-daemon'],
packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'ktx-sl', 'ktx-daemon'],
publishedPackageSmokeGate: {
status: 'required',
script: 'pnpm run release:published-smoke',
reason: 'Run the published package smoke before accepting the hybrid-search release.',
configSource: 'release-policy',
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: '2026.5.8',
registry: 'https://registry.npmjs.org/',
},
@ -228,7 +228,7 @@ describe('release readiness policy', () => {
});
it('rejects required published smoke mode without a package name', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-missing-config-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-missing-config-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
@ -247,13 +247,13 @@ describe('release readiness policy', () => {
});
it('rejects required published smoke mode while publishing decisions remain', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-blocked-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-blocked-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
releaseMode: 'published-package-smoke-required',
publishedPackageSmoke: {
packageName: '@klo/cli-public',
packageName: '@ktx/cli-public',
version: 'latest',
registry: null,
},
@ -270,7 +270,7 @@ describe('release readiness policy', () => {
});
it('rejects unsupported release modes', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-unsupported-mode-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-unsupported-mode-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
@ -288,7 +288,7 @@ describe('release readiness policy', () => {
});
it('rejects publish-enabled npm policy while releaseMode is ci-artifact-only', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-npm-publish-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-npm-publish-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
@ -306,7 +306,7 @@ describe('release readiness policy', () => {
});
it('rejects publish-enabled Python policy while releaseMode is ci-artifact-only', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-python-publish-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-python-publish-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
@ -324,12 +324,12 @@ describe('release readiness policy', () => {
});
it('rejects unsafe release-policy published package smoke config', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-invalid-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-invalid-test-'));
try {
await writeReadyFixture(root, {
policy: releasePolicy({
publishedPackageSmoke: {
packageName: '@klo/cli public',
packageName: '@ktx/cli public',
version: 'latest',
registry: null,
},
@ -346,13 +346,13 @@ describe('release readiness policy', () => {
});
it('rejects a public npm package while releaseMode is ci-artifact-only', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-public-npm-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-public-npm-test-'));
try {
await writeReadyFixture(root, { contextPrivate: false });
await assert.rejects(
() => releaseReadinessReport(root),
/ci-artifact-only policy npm package @klo\/context must remain private/,
/ci-artifact-only policy npm package @ktx\/context must remain private/,
);
} finally {
await rm(root, { recursive: true, force: true });
@ -360,7 +360,7 @@ describe('release readiness policy', () => {
});
it('rejects stale artifacts before reporting release readiness', async () => {
const root = await mkdtemp(join(tmpdir(), 'klo-release-stale-artifact-test-'));
const root = await mkdtemp(join(tmpdir(), 'ktx-release-stale-artifact-test-'));
try {
const layout = await writeReadyFixture(root);
await writeFile(layout.cliTarball, 'changed-cli-tarball');

View file

@ -6,7 +6,7 @@ import { access as fsAccess, readdir as fsReaddir, stat as fsStat } from 'node:f
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
function kloRootDir() {
function ktxRootDir() {
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
}
@ -131,9 +131,9 @@ function runInherited(command, args, options) {
});
}
export async function runWorkspaceKlo(argv, options = {}) {
export async function runWorkspaceKtx(argv, options = {}) {
const cliArgv = argv[0] === '--' ? argv.slice(1) : argv;
const rootDir = options.rootDir ?? kloRootDir();
const rootDir = options.rootDir ?? ktxRootDir();
const stdout = options.stdout ?? process.stdout;
const stderr = options.stderr ?? process.stderr;
const access = options.access ?? fsAccess;
@ -155,13 +155,13 @@ export async function runWorkspaceKlo(argv, options = {}) {
if (needsBuild) {
stderr.write(
binExists
? 'KLO CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'
: 'KLO CLI build output is missing. Building it now with `pnpm run build`...\n',
? 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'
: 'KTX CLI build output is missing. Building it now with `pnpm run build`...\n',
);
const buildExitCode = await runCommand('pnpm', ['run', 'build'], { cwd: rootDir, env: commandEnv });
if (buildExitCode !== 0) {
stderr.write(
'\nKLO CLI build failed. Run `pnpm run setup:dev` from the KLO directory, then retry this command.\n',
'\nKTX CLI build failed. Run `pnpm run setup:dev` from the KTX directory, then retry this command.\n',
);
return buildExitCode;
}
@ -171,5 +171,5 @@ export async function runWorkspaceKlo(argv, options = {}) {
}
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exitCode = await runWorkspaceKlo(process.argv.slice(2));
process.exitCode = await runWorkspaceKtx(process.argv.slice(2));
}

View file

@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { runWorkspaceKlo } from './run-klo.mjs';
import { runWorkspaceKtx } from './run-ktx.mjs';
function freshBuildFs() {
return {
@ -20,19 +20,19 @@ function freshBuildFs() {
};
}
test('runWorkspaceKlo runs the built CLI when it already exists', async () => {
test('runWorkspaceKtx runs the built CLI when it already exists', async () => {
const calls = [];
const logs = [];
const fs = freshBuildFs();
const exitCode = await runWorkspaceKlo(['--version'], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['--version'], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: fs.stat,
readdir: fs.readdir,
execFile: async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd });
return { stdout: '@klo/cli 0.0.0-private\n', stderr: '' };
return { stdout: '@ktx/cli 0.0.0-private\n', stderr: '' };
},
stdout: { write: (chunk) => logs.push(['stdout', chunk]) },
stderr: { write: (chunk) => logs.push(['stderr', chunk]) },
@ -42,26 +42,26 @@ test('runWorkspaceKlo runs the built CLI when it already exists', async () => {
assert.deepEqual(calls, [
{
command: process.execPath,
args: ['/workspace/klo/packages/cli/dist/bin.js', '--version'],
cwd: '/workspace/klo',
args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'],
cwd: '/workspace/ktx',
},
]);
assert.deepEqual(logs, [['stdout', '@klo/cli 0.0.0-private\n']]);
assert.deepEqual(logs, [['stdout', '@ktx/cli 0.0.0-private\n']]);
});
test('runWorkspaceKlo forwards a caller-provided environment to buffered commands', async () => {
test('runWorkspaceKtx forwards a caller-provided environment to buffered commands', async () => {
const calls = [];
const fs = freshBuildFs();
const exitCode = await runWorkspaceKlo(['--version'], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['--version'], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: fs.stat,
readdir: fs.readdir,
env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/klo/examples' },
env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' },
execFile: async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd, env: options.env });
return { stdout: '@klo/cli 0.0.0-private\n', stderr: '' };
return { stdout: '@ktx/cli 0.0.0-private\n', stderr: '' };
},
stdout: { write: () => undefined },
stderr: { write: () => undefined },
@ -71,25 +71,25 @@ test('runWorkspaceKlo forwards a caller-provided environment to buffered command
assert.deepEqual(calls, [
{
command: process.execPath,
args: ['/workspace/klo/packages/cli/dist/bin.js', '--version'],
cwd: '/workspace/klo',
env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/klo/examples' },
args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'],
cwd: '/workspace/ktx',
env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' },
},
]);
});
test('runWorkspaceKlo drops a leading npm argument separator', async () => {
test('runWorkspaceKtx drops a leading npm argument separator', async () => {
const calls = [];
const fs = freshBuildFs();
const exitCode = await runWorkspaceKlo(['--', 'connection', 'test', 'warehouse', '--help'], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['--', 'connection', 'test', 'warehouse', '--help'], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: fs.stat,
readdir: fs.readdir,
execFile: async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd });
return { stdout: 'Usage: klo connection test\n', stderr: '' };
return { stdout: 'Usage: ktx connection test\n', stderr: '' };
},
stdout: { write: () => undefined },
stderr: { write: () => undefined },
@ -99,18 +99,18 @@ test('runWorkspaceKlo drops a leading npm argument separator', async () => {
assert.deepEqual(calls, [
{
command: process.execPath,
args: ['/workspace/klo/packages/cli/dist/bin.js', 'connection', 'test', 'warehouse', '--help'],
cwd: '/workspace/klo',
args: ['/workspace/ktx/packages/cli/dist/bin.js', 'connection', 'test', 'warehouse', '--help'],
cwd: '/workspace/ktx',
},
]);
});
test('runWorkspaceKlo skips stale-build checks for shell completion when dist exists', async () => {
test('runWorkspaceKtx skips stale-build checks for shell completion when dist exists', async () => {
const calls = [];
let statCalls = 0;
const exitCode = await runWorkspaceKlo(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'klo', ''], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', ''], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: async (path) => {
statCalls += 1;
@ -136,7 +136,7 @@ test('runWorkspaceKlo skips stale-build checks for shell completion when dist ex
{
command: process.execPath,
args: [
'/workspace/klo/packages/cli/dist/bin.js',
'/workspace/ktx/packages/cli/dist/bin.js',
'dev',
'__complete',
'--shell',
@ -144,21 +144,21 @@ test('runWorkspaceKlo skips stale-build checks for shell completion when dist ex
'--position',
'2',
'--',
'klo',
'ktx',
'',
],
cwd: '/workspace/klo',
cwd: '/workspace/ktx',
},
]);
});
test('runWorkspaceKlo builds the workspace CLI before running it when dist is missing', async () => {
test('runWorkspaceKtx builds the workspace CLI before running it when dist is missing', async () => {
const calls = [];
const logs = [];
let binExists = false;
const exitCode = await runWorkspaceKlo(['setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], {
rootDir: '/workspace/ktx',
access: async () => {
if (!binExists) {
throw Object.assign(new Error('missing'), { code: 'ENOENT' });
@ -183,24 +183,24 @@ test('runWorkspaceKlo builds the workspace CLI before running it when dist is mi
['pnpm', ['run', 'build']],
[
process.execPath,
['/workspace/klo/packages/cli/dist/bin.js', 'setup', 'demo', '--mode', 'replay', '--no-input', '--viz'],
['/workspace/ktx/packages/cli/dist/bin.js', 'setup', 'demo', '--mode', 'replay', '--no-input', '--viz'],
],
],
);
assert.deepEqual(logs, [
['stderr', 'KLO CLI build output is missing. Building it now with `pnpm run build`...\n'],
['stderr', 'KTX CLI build output is missing. Building it now with `pnpm run build`...\n'],
['stdout', 'build ok\n'],
['stdout', 'Replay complete\n'],
]);
});
test('runWorkspaceKlo rebuilds before running when workspace sources are newer than dist', async () => {
test('runWorkspaceKtx rebuilds before running when workspace sources are newer than dist', async () => {
const calls = [];
const logs = [];
let sourceMtimeMs = 3000;
const exitCode = await runWorkspaceKlo(['dev', 'scan', 'orbit', '--enrich'], {
rootDir: '/workspace/klo',
const exitCode = await runWorkspaceKtx(['dev', 'scan', 'orbit', '--enrich'], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: async (path) => ({
mtimeMs: path.endsWith('/packages/cli/dist/bin.js') ? 2000 : sourceMtimeMs,
@ -232,11 +232,11 @@ test('runWorkspaceKlo rebuilds before running when workspace sources are newer t
calls.map((call) => [call.command, call.args]),
[
['pnpm', ['run', 'build']],
[process.execPath, ['/workspace/klo/packages/cli/dist/bin.js', 'dev', 'scan', 'orbit', '--enrich']],
[process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'dev', 'scan', 'orbit', '--enrich']],
],
);
assert.deepEqual(logs, [
['stderr', 'KLO CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'],
['stderr', 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'],
['stdout', 'build ok\n'],
['stdout', 'scan ok\n'],
]);

View file

@ -7,7 +7,7 @@ import { promisify } from 'node:util';
const execFileAsync = promisify(execFileCallback);
function kloRootDir() {
function ktxRootDir() {
return resolve(dirname(fileURLToPath(import.meta.url)), '..');
}
@ -19,7 +19,7 @@ function failureText(error) {
}
export async function runSetupDev(options = {}) {
const rootDir = options.rootDir ?? kloRootDir();
const rootDir = options.rootDir ?? ktxRootDir();
const execFile = options.execFile ?? execFileAsync;
const log = options.log ?? ((line) => process.stdout.write(`${line}\n`));
const phases = [
@ -45,7 +45,7 @@ export async function runSetupDev(options = {}) {
name: 'doctor setup',
command: process.execPath,
args: ['packages/cli/dist/bin.js', 'dev', 'doctor', 'setup', '--no-input'],
retry: 'pnpm run klo -- dev doctor setup --no-input',
retry: 'pnpm run ktx -- dev doctor setup --no-input',
},
];
@ -61,7 +61,7 @@ export async function runSetupDev(options = {}) {
}
}
log('Workspace CLI: pnpm run klo -- --help');
log('Workspace CLI: pnpm run ktx -- --help');
log('Optional global dev link: pnpm run link:dev');
return { ok: true };
}

View file

@ -7,7 +7,7 @@ test('runSetupDev runs phased setup without global linking', async () => {
const logs = [];
const result = await runSetupDev({
rootDir: '/workspace/klo',
rootDir: '/workspace/ktx',
execFile: async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd });
return { stdout: `${command} ${args.join(' ')}`, stderr: '' };
@ -34,7 +34,7 @@ test('runSetupDev stops at the failed phase and prints a retry command', async (
const logs = [];
const result = await runSetupDev({
rootDir: '/workspace/klo',
rootDir: '/workspace/ktx',
execFile: async (command, args) => {
calls.push({ command, args });
if (args.includes('native:rebuild')) {

View file

@ -12,11 +12,11 @@ function assertIncludesAll(text, values) {
}
}
describe('standalone KLO CI workflow', () => {
describe('standalone KTX CI workflow', () => {
it('runs the package checks from a filtered repository root', async () => {
const workflow = await readText('.github/workflows/ci.yml');
assert.match(workflow, /^name: KLO CI/m);
assert.match(workflow, /^name: KTX CI/m);
assertIncludesAll(workflow, [
'permissions:',
'contents: read',
@ -37,9 +37,9 @@ describe('standalone KLO CI workflow', () => {
]);
assert.doesNotMatch(workflow, /sparse-checkout/);
assert.doesNotMatch(workflow, /cd klo/);
assert.doesNotMatch(workflow, /klo\/pnpm-lock\.yaml/);
assert.doesNotMatch(workflow, /klo\/uv\.lock/);
assert.doesNotMatch(workflow, /cd ktx/);
assert.doesNotMatch(workflow, /ktx\/pnpm-lock\.yaml/);
assert.doesNotMatch(workflow, /ktx\/uv\.lock/);
});
it('uploads verified artifacts from root-relative paths', async () => {
@ -47,7 +47,7 @@ describe('standalone KLO CI workflow', () => {
assertIncludesAll(workflow, [
'actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f',
'name: klo-package-artifacts-${{ github.sha }}',
'name: ktx-package-artifacts-${{ github.sha }}',
'dist/artifacts/manifest.json',
'dist/artifacts/npm/*.tgz',
'dist/artifacts/python/*.whl',
@ -56,7 +56,7 @@ describe('standalone KLO CI workflow', () => {
'retention-days: 7',
]);
assert.doesNotMatch(workflow, /klo\/dist\/artifacts/);
assert.doesNotMatch(workflow, /ktx\/dist\/artifacts/);
});
it('syncs injected workspace packages after package builds', async () => {

View file

@ -5,7 +5,7 @@ import { readFileSync } from 'node:fs';
const [backend, filePath] = process.argv.slice(2);
function usage() {
process.stderr.write('Usage: node klo/scripts/validate-llm-debug-jsonl.mjs anthropic|vertex /path/to/debug.jsonl\n');
process.stderr.write('Usage: node ktx/scripts/validate-llm-debug-jsonl.mjs anthropic|vertex /path/to/debug.jsonl\n');
}
function fail(message) {

View file

@ -14,7 +14,7 @@ function runValidator(args) {
}
function writeDebugJsonl(records) {
const dir = mkdtempSync(join(tmpdir(), 'klo-llm-debug-validator-'));
const dir = mkdtempSync(join(tmpdir(), 'ktx-llm-debug-validator-'));
const filePath = join(dir, 'debug.jsonl');
writeFileSync(filePath, `${records.map((record) => JSON.stringify(record)).join('\n')}\n`, 'utf8');
return filePath;
@ -52,7 +52,7 @@ test('prints usage and exits 2 when required arguments are missing', () => {
const result = runValidator([]);
assert.equal(result.status, 2);
assert.match(result.stderr, /Usage: node klo\/scripts\/validate-llm-debug-jsonl\.mjs anthropic\|vertex/);
assert.match(result.stderr, /Usage: node ktx\/scripts\/validate-llm-debug-jsonl\.mjs anthropic\|vertex/);
});
test('accepts sanitized debug JSONL with message, message-part, and tool cache markers', () => {