feat: npm-managed Python runtime for @kaelio/ktx (#7)

* docs: add npm managed python runtime design

* build: add bundled python runtime wheel builder

* build: make local embedding dependencies optional

* build: bundle python runtime wheel in cli artifacts

* build: track bundled python runtime release artifact

* test: verify bundled python runtime wheel

* docs: add plan for bundled python runtime wheel

* test: cover managed python runtime lifecycle

* feat: add managed python runtime installer

* feat: add runtime command runner

* feat: expose runtime management commands

* test: verify managed python runtime commands

* docs: add plan for managed python runtime installer

* feat: add managed python command helper

* feat: use managed runtime for sl query compute

* feat: route sl query managed runtime policy

* docs: add plan for managed runtime sl query integration

* feat: add managed runtime daemon metadata

* feat: manage python daemon lifecycle

* feat: add runtime daemon start stop commands

* fix: verify managed runtime daemon lifecycle

* docs: add plan for managed runtime daemon lifecycle

* feat: add managed local embeddings config marker

* feat: add managed local embeddings daemon helper

* feat: use managed runtime for local embedding setup

* feat: pass managed runtime policy through setup

* docs: add plan for managed local embeddings runtime

* feat: read CLI package metadata dynamically

* feat: assemble public kaelio ktx npm package

* feat: release one public kaelio ktx npm artifact

* test: cover public kaelio ktx package invocations

* chore: verify public kaelio ktx package artifacts

* docs: add plan for public kaelio ktx npm package

* test: verify managed runtime in public package smoke

* test: finalize managed runtime release smoke

* docs: add plan for managed runtime release smoke

* test: specify local embeddings release smoke

* feat: add local embeddings runtime smoke

* chore: register local embeddings smoke

* fix: verify local embeddings smoke

* fix: restore artifact smoke python env helper

* docs: add plan for managed local embeddings release smoke

* refactor: share managed runtime install policy parsing

* feat: use managed runtime for agent semantic queries

* feat: use managed runtime for MCP semantic compute

* docs: add plan for managed agent and MCP semantic runtime

* feat(cli): add managed daemon HTTP helpers

* feat(cli): route local adapters through managed daemon

* feat(cli): use managed daemon for ingest helpers

* feat(cli): pass managed daemon options to scan

* feat(context): pass MCP ingest pull config options

* feat(cli): pass managed daemon options to serve ingest

* test: verify managed local ingest daemon runtime

* docs: add plan for managed local ingest daemon runtime

* docs: align managed runtime examples

* docs: add plan for managed runtime docs cleanup

* test: cover published package runtime smoke commands

* test: validate published package smoke outputs

* docs: add plan for published package runtime smoke

* build: stamp public npm package version

* release: add npm public release policy

* release: add guarded npm publish script

* release: document public npm release handoff

* docs: add plan for public npm release handoff

* test: cover managed runtime prune in package smoke

* docs: document managed runtime prune

* docs: add plan for managed runtime prune smoke and docs

* chore: encode uv runtime prerequisite policy

* fix: clarify missing uv runtime error

* docs: document uv runtime prerequisite

* docs: add plan for uv runtime prerequisite contract

* refactor: limit release artifacts to public package runtime

* chore: align release policy with bundled runtime wheel

* docs: describe single public runtime artifact surface

* test: verify single public runtime artifact contract

* docs: add plan for single public runtime artifact cleanup

* fix: align local embeddings smoke with public version

* docs: add plan for local embeddings smoke public version

* release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag

Publish target moves to the pre-release version 0.1.0-rc.0 under the next
dist-tag so npm install @kaelio/ktx (which resolves to latest) does not
pick up the soft-launch build. Users opt in via @kaelio/ktx@next.

* Fix release script boundary checks

* Remove PostHog from public package bundle
This commit is contained in:
Andrey Avtomonov 2026-05-11 15:50:34 +02:00 committed by GitHub
parent 075764fe77
commit 9dad936ac7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
99 changed files with 25375 additions and 1538 deletions

View file

@ -6,19 +6,22 @@ import { join } from 'node:path';
import { describe, it } from 'node:test';
import {
CLI_PYTHON_ASSET_MANIFEST,
INTERNAL_NPM_WORKSPACE_PACKAGES,
RUNTIME_WHEEL_DISTRIBUTION_NAME,
RUNTIME_WHEEL_NORMALIZED_NAME,
RUNTIME_WHEEL_PACKAGE_VERSION,
artifactManifestPath,
buildArtifactCommands,
copyRuntimeWheelAssets,
findPythonArtifacts,
NPM_ARTIFACT_PACKAGES,
npmDemoSmokeSource,
npmRuntimeSmokeSource,
npmSmokePackageJson,
npmSmokePythonEnv,
npmVerifySource,
packageArtifactLayout,
packageReleaseMetadata,
pythonArtifactInstallArgs,
pythonVerifySource,
verifyArtifactManifest,
writeArtifactManifest,
} from './package-artifacts.mjs';
@ -29,47 +32,21 @@ async function writeJson(path, value) {
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
}
const CONNECTOR_PACKAGE_NAMES = [
'@ktx/connector-bigquery',
'@ktx/connector-clickhouse',
'@ktx/connector-mysql',
'@ktx/connector-postgres',
'@ktx/connector-snowflake',
'@ktx/connector-sqlite',
'@ktx/connector-sqlserver',
];
function packageRootForName(packageName) {
return `packages/${packageName.replace('@ktx/', '')}`;
}
function expectedNpmArtifactPath(packageName) {
return `npm/${packageName.replace('@ktx/', 'ktx-')}-0.0.0-private.tgz`;
}
const INTERNAL_BUILD_PACKAGE_NAMES = INTERNAL_NPM_WORKSPACE_PACKAGES.map((packageInfo) => packageInfo.name);
const CONNECTOR_PACKAGE_NAMES = INTERNAL_BUILD_PACKAGE_NAMES.filter((packageName) =>
packageName.startsWith('@ktx/connector-'),
);
const NPM_BUILD_PACKAGE_ORDER = ['@ktx/llm', '@ktx/context', ...CONNECTOR_PACKAGE_NAMES, '@ktx/cli'];
async function writeReleaseMetadataInputs(root) {
const npmPackages = ['@ktx/context', '@ktx/llm', ...CONNECTOR_PACKAGE_NAMES, '@ktx/cli'];
for (const packageName of npmPackages) {
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,
for (const packageInfo of INTERNAL_NPM_WORKSPACE_PACKAGES) {
await mkdir(join(root, packageInfo.packageRoot), { recursive: true });
await writeJson(join(root, packageInfo.packageRoot, 'package.json'), {
name: packageInfo.name,
version: '0.0.0-private',
private: true,
});
}
await mkdir(join(root, 'python', 'ktx-sl'), { recursive: true });
await mkdir(join(root, 'python', 'ktx-daemon'), { recursive: true });
await writeFile(
join(root, 'python', 'ktx-sl', 'pyproject.toml'),
['[project]', 'name = "ktx-sl"', 'version = "0.1.0"', ''].join('\n'),
);
await writeFile(
join(root, 'python', 'ktx-daemon', 'pyproject.toml'),
['[project]', 'name = "ktx-daemon"', 'version = "0.1.0"', ''].join('\n'),
);
}
async function writeUploadableArtifactFixtures(layout) {
@ -81,10 +58,10 @@ async function writeUploadableArtifactFixtures(layout) {
layout.npmTarballs[packageInfo.name],
`${packageInfo.name}-tarball`,
]),
[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'],
[
join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
'kaelio-ktx-runtime-wheel',
],
]);
for (const [path, contents] of fileContents) {
@ -99,47 +76,30 @@ describe('packageArtifactLayout', () => {
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['@ktx/connector-sqlite'],
'/repo/ktx/dist/artifacts/npm/ktx-connector-sqlite-0.0.0-private.tgz',
);
assert.equal(
layout.connectorTarballs['@ktx/connector-postgres'],
'/repo/ktx/dist/artifacts/npm/ktx-connector-postgres-0.0.0-private.tgz',
);
assert.deepEqual(
Object.keys(layout.npmTarballs),
NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name),
);
assert.equal(layout.cliTarball, '/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0-rc.0.tgz');
assert.deepEqual(Object.keys(layout.npmTarballs), ['@kaelio/ktx']);
});
});
describe('buildArtifactCommands', () => {
it('builds all TypeScript packages before packing npm artifacts and builds both Python packages', () => {
it('builds TypeScript packages and the runtime wheel before packing npm artifacts', () => {
const layout = packageArtifactLayout('/repo/ktx');
const commands = buildArtifactCommands(layout);
assert.deepEqual(
commands.slice(0, NPM_ARTIFACT_PACKAGES.length).map((command) => [command.command, command.args]),
NPM_ARTIFACT_PACKAGES.map((packageInfo) => ['pnpm', ['--filter', packageInfo.name, 'run', 'build']]),
commands.slice(0, NPM_BUILD_PACKAGE_ORDER.length).map((command) => [command.command, command.args]),
NPM_BUILD_PACKAGE_ORDER.map((packageName) => ['pnpm', ['--filter', packageName, 'run', 'build']]),
);
assert.deepEqual(
commands
.slice(NPM_ARTIFACT_PACKAGES.length, NPM_ARTIFACT_PACKAGES.length * 2)
.map((command) => [command.command, command.args]),
NPM_ARTIFACT_PACKAGES.map((packageInfo) => [
'pnpm',
['--filter', packageInfo.name, 'pack', '--out', layout.npmTarballs[packageInfo.name]],
commands.slice(NPM_BUILD_PACKAGE_ORDER.length, NPM_BUILD_PACKAGE_ORDER.length + 1).map((command) => [
command.command,
command.args,
]),
[[process.execPath, ['scripts/build-python-runtime-wheel.mjs']]],
);
assert.deepEqual(
commands.slice(NPM_ARTIFACT_PACKAGES.length * 2).map((command) => [command.command, command.args]),
[
['uv', ['build', '--package', 'ktx-sl', '--out-dir', '/repo/ktx/dist/artifacts/python']],
['uv', ['build', '--package', 'ktx-daemon', '--out-dir', '/repo/ktx/dist/artifacts/python']],
],
commands.slice(NPM_BUILD_PACKAGE_ORDER.length + 1).map((command) => [command.command, command.args]),
[[process.execPath, ['scripts/build-public-npm-package.mjs']]],
);
});
});
@ -151,26 +111,18 @@ describe('packageReleaseMetadata', () => {
await writeReleaseMetadataInputs(root);
assert.deepEqual(await packageReleaseMetadata(root), [
...NPM_ARTIFACT_PACKAGES.map((packageInfo) => ({
ecosystem: 'npm',
packageName: packageInfo.name,
packageRoot: packageInfo.packageRoot,
packageVersion: '0.0.0-private',
private: true,
releaseMode: 'ci-artifact-only',
})),
{
ecosystem: 'python',
packageName: 'ktx-sl',
packageRoot: 'python/ktx-sl',
packageVersion: '0.1.0',
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageRoot: 'packages/cli',
packageVersion: '0.1.0-rc.0',
private: false,
releaseMode: 'ci-artifact-only',
},
{
ecosystem: 'python',
packageName: 'ktx-daemon',
packageRoot: 'python/ktx-daemon',
packageName: 'kaelio-ktx',
packageRoot: 'python/runtime-wheel',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
@ -183,19 +135,13 @@ describe('packageReleaseMetadata', () => {
});
describe('findPythonArtifacts', () => {
it('finds one wheel and one source distribution for each Python package', async () => {
it('finds the bundled runtime wheel only', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
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'), '');
await writeFile(join(root, 'kaelio_ktx-0.1.0-py3-none-any.whl'), '');
assert.deepEqual(await findPythonArtifacts(root), {
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'),
runtimeWheel: join(root, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
});
} finally {
await rm(root, { recursive: true, force: true });
@ -205,7 +151,7 @@ describe('findPythonArtifacts', () => {
it('throws when a required Python artifact is missing', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: ktx-sl wheel/);
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: kaelio-ktx runtime wheel/);
} finally {
await rm(root, { recursive: true, force: true });
}
@ -230,30 +176,24 @@ describe('artifact manifest', () => {
assert.equal(manifest.sourceRevision, 'abc123');
assert.deepEqual(
manifest.packages.filter((entry) => entry.ecosystem === 'npm'),
NPM_ARTIFACT_PACKAGES.map((packageInfo) => ({
ecosystem: 'npm',
packageName: packageInfo.name,
packageRoot: packageInfo.packageRoot,
packageVersion: '0.0.0-private',
private: true,
releaseMode: 'ci-artifact-only',
})),
[
{
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageRoot: 'packages/cli',
packageVersion: '0.1.0-rc.0',
private: false,
releaseMode: 'ci-artifact-only',
},
],
);
assert.deepEqual(
manifest.packages.filter((entry) => entry.ecosystem === 'python'),
[
{
ecosystem: 'python',
packageName: 'ktx-sl',
packageRoot: 'python/ktx-sl',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
},
{
ecosystem: 'python',
packageName: 'ktx-daemon',
packageRoot: 'python/ktx-daemon',
packageName: 'kaelio-ktx',
packageRoot: 'python/runtime-wheel',
packageVersion: '0.1.0',
private: false,
releaseMode: 'ci-artifact-only',
@ -271,13 +211,15 @@ describe('artifact manifest', () => {
path: file.path,
}))
.sort((left, right) => left.packageName.localeCompare(right.packageName)),
NPM_ARTIFACT_PACKAGES.map((packageInfo) => ({
artifactKind: 'tarball',
ecosystem: 'npm',
packageName: packageInfo.name,
packageVersion: '0.0.0-private',
path: expectedNpmArtifactPath(packageInfo.name),
})).sort((left, right) => left.packageName.localeCompare(right.packageName)),
[
{
artifactKind: 'tarball',
ecosystem: 'npm',
packageName: '@kaelio/ktx',
packageVersion: '0.1.0-rc.0',
path: 'npm/kaelio-ktx-0.1.0-rc.0.tgz',
},
],
);
assert.deepEqual(
manifest.files
@ -293,38 +235,17 @@ describe('artifact manifest', () => {
{
artifactKind: 'wheel',
ecosystem: 'python',
packageName: 'ktx-daemon',
packageName: 'kaelio-ktx',
packageVersion: '0.1.0',
path: 'python/ktx_daemon-0.1.0-py3-none-any.whl',
},
{
artifactKind: 'sdist',
ecosystem: 'python',
packageName: 'ktx-daemon',
packageVersion: '0.1.0',
path: 'python/ktx_daemon-0.1.0.tar.gz',
},
{
artifactKind: 'wheel',
ecosystem: 'python',
packageName: 'ktx-sl',
packageVersion: '0.1.0',
path: 'python/ktx_sl-0.1.0-py3-none-any.whl',
},
{
artifactKind: 'sdist',
ecosystem: 'python',
packageName: 'ktx-sl',
packageVersion: '0.1.0',
path: 'python/ktx_sl-0.1.0.tar.gz',
path: 'python/kaelio_ktx-0.1.0-py3-none-any.whl',
},
],
);
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('@ktx/connector-sqlite-tarball'));
assert.equal(sqliteEntry.sha256, createHash('sha256').update('@ktx/connector-sqlite-tarball').digest('hex'));
const npmEntry = manifest.files.find((file) => file.path === 'npm/kaelio-ktx-0.1.0-rc.0.tgz');
assert.ok(npmEntry);
assert.equal(npmEntry.bytes, Buffer.byteLength('@kaelio/ktx-tarball'));
assert.equal(npmEntry.sha256, createHash('sha256').update('@kaelio/ktx-tarball').digest('hex'));
const writtenManifest = JSON.parse(await readFile(artifactManifestPath(layout), 'utf-8'));
assert.deepEqual(writtenManifest, manifest);
@ -351,7 +272,7 @@ describe('verifyArtifactManifest', () => {
assert.equal(manifest.schemaVersion, 2);
assert.equal(manifest.sourceRevision, 'abc123');
assert.equal(manifest.files.length, NPM_ARTIFACT_PACKAGES.length + 4);
assert.equal(manifest.files.length, NPM_ARTIFACT_PACKAGES.length + 1);
} finally {
await rm(root, { recursive: true, force: true });
}
@ -418,48 +339,89 @@ describe('verifyArtifactManifest', () => {
});
});
describe('pythonArtifactInstallArgs', () => {
it('installs the built Python wheels by artifact path', () => {
const args = pythonArtifactInstallArgs('/tmp/smoke/.venv/bin/python', {
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',
});
describe('copyRuntimeWheelAssets', () => {
it('copies the runtime wheel and checksum manifest into CLI assets', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-runtime-assets-test-'));
const layout = packageArtifactLayout(root);
try {
await mkdir(layout.pythonDir, { recursive: true });
await writeFile(
join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
'kaelio-ktx-runtime-wheel',
);
assert.deepEqual(args, [
'pip',
'install',
'--python',
'/tmp/smoke/.venv/bin/python',
'/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('ktx-daemon'), false);
assert.equal(args.includes('--find-links'), false);
const assets = await copyRuntimeWheelAssets(layout, {
runtimeWheel: join(layout.pythonDir, 'kaelio_ktx-0.1.0-py3-none-any.whl'),
});
assert.equal(
assets.wheelPath,
join(root, 'packages', 'cli', 'assets', 'python', 'kaelio_ktx-0.1.0-py3-none-any.whl'),
);
assert.equal(
assets.manifestPath,
join(root, 'packages', 'cli', 'assets', 'python', CLI_PYTHON_ASSET_MANIFEST),
);
const manifest = JSON.parse(await readFile(assets.manifestPath, 'utf8'));
assert.deepEqual(manifest, {
schemaVersion: 1,
distributionName: RUNTIME_WHEEL_DISTRIBUTION_NAME,
normalizedName: RUNTIME_WHEEL_NORMALIZED_NAME,
version: RUNTIME_WHEEL_PACKAGE_VERSION,
wheel: {
file: 'kaelio_ktx-0.1.0-py3-none-any.whl',
sha256: createHash('sha256')
.update('kaelio-ktx-runtime-wheel')
.digest('hex'),
bytes: Buffer.byteLength('kaelio-ktx-runtime-wheel'),
},
});
} finally {
await rm(root, { recursive: true, force: true });
}
});
});
describe('npmSmokePythonEnv', () => {
it('prepends the npm smoke virtualenv bin directory to PATH', () => {
const env = npmSmokePythonEnv('/tmp/ktx-npm-smoke', { PATH: '/usr/bin' });
describe('verifyNpmArtifacts', () => {
it('does not prepare an external Python environment for the npm smoke', async () => {
const source = await readFile(new URL('./package-artifacts.mjs', import.meta.url), 'utf8');
const start = source.indexOf('async function verifyNpmArtifacts');
const end = source.indexOf('async function verifyNpmDemoArtifacts');
assert.ok(start > 0, 'verifyNpmArtifacts function must exist');
assert.ok(end > start, 'verifyNpmDemoArtifacts must follow verifyNpmArtifacts');
assert.match(env.PATH, /^\/tmp\/ktx-npm-smoke\/\.venv\/(bin|Scripts)/);
assert.match(env.PATH, /\/usr\/bin$/);
const body = source.slice(start, end);
assert.doesNotMatch(body, /uv', \['venv', '\.venv'\]/);
assert.doesNotMatch(body, /pythonArtifactInstallArgs/);
assert.doesNotMatch(body, /npmSmokePythonEnv/);
});
});
describe('standalone Python artifact cleanup', () => {
it('does not build or verify standalone Python package artifacts', async () => {
const source = await readFile(new URL('./package-artifacts.mjs', import.meta.url), 'utf8');
assert.doesNotMatch(source, /uv', \['build', '--package', 'ktx-sl'/);
assert.doesNotMatch(source, /uv', \['build', '--package', 'ktx-daemon'/);
assert.doesNotMatch(source, /async function verifyPythonArtifacts/);
assert.doesNotMatch(source, /pythonArtifactInstallArgs/);
assert.doesNotMatch(source, /pythonVerifySource/);
assert.doesNotMatch(source, /ktx_sl-0\.1\.0/);
assert.doesNotMatch(source, /ktx_daemon-0\.1\.0/);
});
});
describe('verification snippets', () => {
it('pins smoke dependencies and connector packages to clean-install-safe artifacts', () => {
it('pins the smoke project to the public package artifact', () => {
const layout = packageArtifactLayout('/repo/ktx');
const packageJson = npmSmokePackageJson(layout);
for (const packageInfo of NPM_ARTIFACT_PACKAGES) {
assert.equal(packageJson.dependencies[packageInfo.name], `file:${layout.npmTarballs[packageInfo.name]}`);
assert.equal(packageJson.pnpm.overrides[packageInfo.name], `file:${layout.npmTarballs[packageInfo.name]}`);
}
assert.equal(packageJson.dependencies['@modelcontextprotocol/sdk'], '^1.27.1');
assert.deepEqual(packageJson.pnpm.onlyBuiltDependencies, ['better-sqlite3']);
const packageJson = npmSmokePackageJson(layout);
assert.deepEqual(packageJson.dependencies, {
'@kaelio/ktx': `file:${layout.cliTarball}`,
});
assert.deepEqual(packageJson.devDependencies, {
'better-sqlite3': '^12.6.2',
});
});
it('exposes manifest verification as a package artifact command', async () => {
@ -472,115 +434,64 @@ describe('verification snippets', () => {
assert.equal(packageJson.scripts['artifacts:verify-manifest'], 'node scripts/package-artifacts.mjs verify-manifest');
});
it('verifies installed dbt extraction exports from @ktx/context/ingest', () => {
const source = npmVerifySource();
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\);/);
for (const exportName of [
'parseMetricflowFiles',
'parseMetricflowPullConfig',
'importMetricflowSemanticModels',
'parseDbtSchemaFiles',
'toDescriptionUpdates',
'toRelationshipUpdates',
'mergeSemanticModelTables',
'loadProjectInfo',
'loadDbtSchemaFiles',
]) {
assert.match(source, new RegExp(`\\['${exportName}', ingest\\.${exportName}\\]`));
}
});
it('asserts the public npm and connector entry points that clean installs must expose', () => {
const source = npmVerifySource();
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, /KtxSqliteScanConnector/);
assert.match(source, /KtxPostgresScanConnector/);
assert.match(source, /KtxBigQueryScanConnector/);
assert.match(source, /KtxSnowflakeScanConnector/);
});
it('asserts installed hybrid search exports and CLI smoke coverage', () => {
it('asserts the public npm entry point that clean installs must expose', () => {
const verifySource = npmVerifySource();
const runtimeSource = npmRuntimeSmokeSource();
const demoSource = npmDemoSmokeSource();
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, /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, /ktx seeded demo agent wiki search verified/);
assert.match(demoSource, /ktx seeded demo agent sl search verified/);
assert.match(verifySource, /const cli = await import\('@kaelio\/ktx'\);/);
assert.match(verifySource, /getKtxCliPackageInfo/);
assert.match(verifySource, /runKtxCli/);
assert.doesNotMatch(verifySource, /@ktx\/context/);
assert.doesNotMatch(verifySource, /@ktx\/llm/);
assert.doesNotMatch(verifySource, /@ktx\/connector-/);
});
it('runs installed CLI commands and MCP through an installed daemon HTTP server', () => {
it('runs installed CLI commands through the public package runtime', () => {
const source = npmRuntimeSmokeSource();
assert.match(source, /@modelcontextprotocol\/sdk\/client\/index\.js/);
assert.match(source, /@modelcontextprotocol\/sdk\/client\/stdio\.js/);
assert.match(source, /spawn\(command, args/);
assert.match(source, /createServer/);
assert.match(source, /request as httpRequest/);
assert.match(source, /getAvailablePort/);
assert.match(source, /startSemanticDaemon/);
assert.match(source, /waitForHttpHealth/);
assert.match(source, /stopSemanticDaemon/);
assert.match(source, /'ktx-daemon'/);
assert.match(source, /'serve-http'/);
assert.match(source, /'--host'/);
assert.match(source, /'127\.0\.0\.1'/);
assert.match(source, /'--port'/);
assert.match(source, /\/health/);
assert.match(source, /--semantic-compute-url/);
assert.match(source, /createDaemonLookerTableIdentifierParser/);
assert.match(source, /LocalLookerRuntimeStore/);
assert.match(source, /Looker daemon table identifier parser verified/);
assert.match(source, /Looker local runtime store verified/);
assert.match(source, /semanticComputeUrl/);
assert.match(source, /ktx public package version/);
assert.match(source, /@kaelio\\\/ktx 0\\\.1\\\.0/);
assert.match(source, /'ktx', 'sl', 'query'/);
assert.doesNotMatch(source, /@ktx\/context/);
assert.doesNotMatch(source, /@modelcontextprotocol/);
assert.doesNotMatch(source, /startSemanticDaemon/);
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*'ktx',\s*'agent',\s*'wiki',\s*'search'/);
assert.match(source, /semantic-layer', 'warehouse', 'orders\.yaml'/);
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/);
assert.match(source, /path: warehouse\.db/);
assert.match(source, /live-database/);
assert.match(source, /'--execute'/);
assert.match(source, /'--execute-queries'/);
assert.match(source, /slValidateResult\.success, true/);
assert.match(source, /slQueryResult\.dialect, 'sqlite'/);
assert.match(source, /slQueryResult\.plan\.execution\.driver, 'sqlite'/);
assert.match(source, /"mode": "compile_only"/);
assert.match(source, /"mode": "executed"/);
assert.match(source, /ktx agent sl query sqlite execute/);
assert.match(source, /ktx sl query sqlite execute/);
assert.match(source, /import Database from 'better-sqlite3'/);
assert.doesNotMatch(source, /run\('python'/);
assert.match(source, /KTX_RUNTIME_ROOT/);
assert.match(source, /managed-runtime/);
assert.match(source, /ktx runtime status missing/);
assert.match(source, /runtimeStatusBefore\.kind, 'missing'/);
assert.ok(source.includes(String.raw`Installing KTX Python runtime \(core\) with uv`));
assert.match(source, /KTX Python runtime ready:/);
assert.match(source, /ktx runtime status ready/);
assert.match(source, /runtimeStatusAfter\.kind, 'ready'/);
assert.match(source, /runtimeStatusAfter\.manifest\.features/);
assert.match(source, /ktx runtime doctor/);
assert.match(source, /PASS Managed Python runtime/);
assert.match(source, /ktx runtime start/);
assert.match(source, /ktx runtime start reuse/);
assert.match(source, /Using existing KTX Python daemon/);
assert.match(source, /ktx runtime stop/);
assert.match(source, /ktx runtime prune dry run/);
assert.match(source, /0\.0\.0/);
assert.match(source, /ktx runtime prune needs confirmation/);
assert.match(source, /Refusing to prune without --yes/);
assert.match(source, /ktx runtime prune confirmed/);
assert.match(source, /Removed stale KTX Python runtimes/);
assert.match(source, /assert\.rejects\(\(\) => access\(staleRuntimeDir\)\)/);
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'/);
@ -590,28 +501,7 @@ describe('verification snippets', () => {
assert.match(source, /scanReportJson\.artifactPaths\.enrichmentArtifacts/);
assert.match(source, /enrichment:/);
assert.match(source, /mode: deterministic/);
assert.match(source, /backend: gateway/);
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', '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, /ktx serve stderr/);
assert.match(source, /sl_validate/);
assert.match(source, /sl_query/);
assert.match(source, /memory_capture/);
assert.match(source, /memory_capture_status/);
assert.match(source, /connection_test/);
assert.match(source, /scan_trigger/);
assert.match(source, /scan_status/);
assert.match(source, /scan_report/);
assert.match(source, /scan_list_artifacts/);
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, '\.ktx', 'db\.sqlite'\)\)/);
assert.match(source, /SQLite knowledge index/);
assert.match(source, /ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/);
@ -632,22 +522,8 @@ describe('verification snippets', () => {
assert.match(source, /'dev', 'doctor', 'setup', '--no-input'/);
assert.match(source, /'--plain'/);
assert.match(source, /ktx setup demo seeded wrote unexpected stderr/);
assert.match(source, /Object\.keys\(packageJson\.dependencies\)/);
assert.match(source, /'@kaelio\/ktx'/);
});
});
it('checks packaged ingest runtime assets in the installed npm smoke', () => {
const source = npmRuntimeSmokeSource();
assert.match(source, /notion_synthesize\/SKILL\.md/);
assert.match(source, /skills\/page_triage_classifier\.md/);
assert.match(source, /skills\/light_extraction\.md/);
});
it('asserts the Python modules that clean installs must expose', () => {
const source = pythonVerifySource();
assert.match(source, /semantic_layer/);
assert.match(source, /ktx_daemon/);
assert.match(source, /importlib.metadata/);
});
});