Merge origin/main into dead-ts-code-tools

This commit is contained in:
Andrey Avtomonov 2026-05-13 13:10:43 +02:00
commit 0e2dcc9658
89 changed files with 1015 additions and 5955 deletions

View file

@ -152,14 +152,12 @@ describe('standalone example docs', () => {
const servingAgents = await readText('docs-site/content/docs/guides/serving-agents.mdx');
for (const command of [
'ktx agent tools --json',
'ktx agent context --json',
'ktx agent sl list --json',
'ktx agent sl read orders --json',
'ktx agent sl query --json',
'ktx agent wiki search "revenue recognition" --json',
'ktx agent wiki read order-status-definitions --json',
'ktx agent sql execute --json',
'ktx status --json',
'ktx sl list --json',
'ktx sl read orders --json',
'ktx sl query --json',
'ktx wiki search "revenue recognition" --json',
'ktx wiki read order-status-definitions --json',
]) {
assert.match(servingAgents, new RegExp(command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
}
@ -184,12 +182,11 @@ describe('standalone example docs', () => {
assert.match(quickstart, publicPackagePattern('npm install -g {package}'));
assert.match(quickstart, /ktx dev runtime install --feature local-embeddings --yes/);
assert.match(quickstart, /ktx dev runtime start --feature local-embeddings/);
assert.match(quickstart, /Install `uv`, run `ktx dev runtime doctor`/);
assert.match(quickstart, /Install `uv`, run `ktx dev runtime status`/);
assert.match(packageArtifacts, /requires `uv` on `PATH`/);
assert.match(packageArtifacts, /ktx dev runtime status/);
assert.match(packageArtifacts, /ktx dev runtime doctor/);
assert.match(packageArtifacts, /ktx dev runtime prune --dry-run/);
assert.match(packageArtifacts, /ktx dev runtime prune --yes/);
assert.match(packageArtifacts, /ktx dev runtime status/);
assert.doesNotMatch(packageArtifacts, /ktx dev runtime prune/);
assert.match(
packageArtifacts,
new RegExp(
@ -221,9 +218,8 @@ describe('standalone example docs', () => {
assert.doesNotMatch(readme, /installs the Python artifacts directly/);
assert.match(readme, /requires `uv` on `PATH`/);
assert.match(readme, /ktx dev runtime status/);
assert.match(readme, /ktx dev runtime doctor/);
assert.match(readme, /ktx dev runtime prune --dry-run/);
assert.match(readme, /ktx dev runtime prune --yes/);
assert.match(readme, /ktx dev runtime status/);
assert.doesNotMatch(readme, /ktx dev runtime prune/);
assert.doesNotMatch(readme, /@ktx\/context/);
assert.doesNotMatch(readme, /@ktx\/cli/);
assert.doesNotMatch(readme, /python -m ktx_daemon semantic-validate/);
@ -234,14 +230,15 @@ describe('standalone example docs', () => {
const buildingContext = await readText('docs-site/content/docs/guides/building-context.mdx');
const scanReference = await readText('docs-site/content/docs/cli-reference/ktx-scan.mdx');
assert.match(buildingContext, /ktx dev scan <connection-id>/);
assert.match(buildingContext, /ktx dev scan status <run-id>/);
assert.match(buildingContext, /ktx dev scan report <run-id>/);
assert.match(scanReference, /ktx dev scan <connectionId> \[options\]/);
assert.match(buildingContext, /ktx scan <connection-id>/);
assert.match(buildingContext, /ktx status/);
assert.doesNotMatch(buildingContext, /ktx scan status <run-id>/);
assert.doesNotMatch(buildingContext, /ktx scan report <run-id>/);
assert.match(scanReference, /ktx scan <connectionId> \[options\]/);
assert.match(rootReadme, /raw-sources\//);
assert.match(rootReadme, /live-database\//);
assert.doesNotMatch(rootReadme, /Run a local ingest smoke test/);
assert.doesNotMatch(rootReadme, /ktx dev ingest run --project-dir/);
assert.doesNotMatch(rootReadme, /ktx ingest run --project-dir/);
assert.doesNotMatch(rootReadme, /ktx ingest status --project-dir/);
});

View file

@ -107,7 +107,6 @@ export function buildLiveDatabaseIngestArgs(projectDir, databaseIntrospectionUrl
return [
'exec',
'ktx',
'dev',
'ingest',
'run',
'--project-dir',
@ -325,12 +324,12 @@ async function main() {
env: managedRuntimeEnv(cleanInstallDir),
timeout: 120_000,
});
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/);
requireSuccess('ktx ingest run live-database', ingestRun);
requireOutput('ktx ingest run live-database', ingestRun, /Status: done/);
requireOutput('ktx ingest run live-database', ingestRun, /Adapter: live-database/);
requireOutput('ktx ingest run live-database', ingestRun, /Diff: \+4\/~0\/-0\/=0/);
requireOutput('ktx ingest run live-database', ingestRun, /Raw files: 4/);
requireOutput('ktx ingest run live-database', ingestRun, /Work units: 2/);
const runId = getRunId(ingestRun.stdout);
const ingestStatus = await run('pnpm', buildLiveDatabaseStatusArgs(projectDir, runId), {

View file

@ -102,7 +102,6 @@ describe('installed live-database artifact smoke helpers', () => {
assert.deepEqual(buildLiveDatabaseIngestArgs('/tmp/project', 'http://127.0.0.1:8765'), [
'exec',
'ktx',
'dev',
'ingest',
'run',
'--project-dir',

View file

@ -205,6 +205,17 @@ function parseJsonStdout(label, result) {
}
}
function parseJsonStdoutWithExitCode(label, result, expectedCode) {
if (result.code !== expectedCode) {
throw new Error(`${label} failed with code ${result.code}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
}
try {
return JSON.parse(result.stdout);
} catch (error) {
throw new Error(`${label} did not write JSON stdout: ${error.message}\nstdout:\n${result.stdout}`);
}
}
function requireOutput(label, result, pattern) {
if (!pattern.test(result.stdout)) {
throw new Error(`${label} stdout did not match ${pattern}\nstdout:\n${result.stdout}`);
@ -283,13 +294,14 @@ export async function runLocalEmbeddingsRuntimeSmoke(options = {}) {
requireSuccess(commands[0].label, version);
requireOutput(commands[0].label, version, expectedPublicKtxVersionPattern());
const missingStatus = parseJsonStdout(
const missingStatus = parseJsonStdoutWithExitCode(
commands[1].label,
await run(commands[1].command, commands[1].args, {
cwd: installDir,
env: smokeEnv,
timeoutMs: commands[1].timeoutMs,
}),
1,
);
if (missingStatus.kind !== 'missing') {
throw new Error(`Expected missing runtime before install, got ${JSON.stringify(missingStatus)}`);

View file

@ -548,10 +548,13 @@ function parseJsonResult(label, result) {
return JSON.parse(result.stdout);
}
function parseJsonFailure(label, result) {
assert.equal(result.code, 1, label + ' should fail with exit code 1');
assert.equal(result.stdout, '', label + ' should not write stdout when failing');
return JSON.parse(result.stderr);
function parseJsonResultWithExitCode(label, result, expectedCode) {
assert.equal(
result.code,
expectedCode,
label + ' failed with code ' + result.code + '\\nstdout:\\n' + result.stdout + '\\nstderr:\\n' + result.stderr,
);
return JSON.parse(result.stdout);
}
function requireIncludes(values, expected, label) {
@ -594,38 +597,15 @@ try {
requireSuccess('ktx public package version', version);
requireOutput('ktx public package version', version, /@kaelio\\/ktx 0\\.1\\.0/);
const runtimeStatusBefore = parseJsonResult(
const runtimeStatusBefore = parseJsonResultWithExitCode(
'ktx dev runtime status missing',
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status', '--json']),
1,
);
assert.equal(runtimeStatusBefore.kind, 'missing');
assert.equal(runtimeStatusBefore.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT);
process.stdout.write('ktx managed runtime starts missing in isolated root\\n');
const missingProjectDir = join(root, 'missing-project');
await mkdir(missingProjectDir, { recursive: true });
const missingProjectSearch = await run('pnpm', [
'exec',
'ktx',
'agent',
'sl',
'list',
'--json',
'--query',
'revenue',
'--project-dir',
missingProjectDir,
]);
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, [
'ktx setup --project-dir ' + missingProjectDir,
'ktx status --project-dir ' + missingProjectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + missingProjectDir,
]);
process.stdout.write('ktx agent sl list missing project guidance verified\\n');
const init = await run('pnpm', [
'exec',
'ktx',
@ -661,28 +641,6 @@ try {
'--skip-agents',
]);
requireProjectStderr('ktx setup empty project', emptyInit, emptyProjectDir);
const emptySearch = await run('pnpm', [
'exec',
'ktx',
'agent',
'sl',
'list',
'--json',
'--query',
'revenue',
'--project-dir',
emptyProjectDir,
]);
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, [
'ktx setup --project-dir ' + emptyProjectDir,
'ktx status --project-dir ' + emptyProjectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + emptyProjectDir,
]);
process.stdout.write('ktx agent sl list no connections guidance verified\\n');
await writeFile(
join(projectDir, 'ktx.yaml'),
[
@ -727,10 +685,9 @@ try {
'utf-8',
);
const agentWikiSearch = await run('pnpm', [
const wikiSearch = await run('pnpm', [
'exec',
'ktx',
'agent',
'wiki',
'search',
'revenue',
@ -740,40 +697,17 @@ try {
'--project-dir',
projectDir,
]);
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('ktx agent wiki search hybrid metadata verified\\n');
const wikiSearchJson = parseJsonResult('ktx wiki search', wikiSearch);
assert.equal(wikiSearchJson.kind, 'list');
assert.equal(wikiSearchJson.data.items.length, 1);
assert.equal(wikiSearchJson.data.items[0].key, 'revenue');
assert.equal(wikiSearchJson.data.items[0].path, 'knowledge/global/revenue.md');
assert.equal(typeof wikiSearchJson.data.items[0].score, 'number');
requireIncludes(wikiSearchJson.data.items[0].matchReasons, 'lexical', 'wiki search match reasons');
process.stdout.write('ktx 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',
'ktx',
'agent',
'sl',
'list',
'--json',
'--connection-id',
'warehouse',
'--query',
'revenue',
'--project-dir',
projectDir,
]);
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, [
'ktx setup --project-dir ' + projectDir,
'ktx status --project-dir ' + projectDir,
'ktx ingest <connection>',
'ktx agent sl list --json --query "revenue" --project-dir ' + projectDir,
]);
process.stdout.write('ktx agent sl list no indexed sources guidance verified\\n');
const slYaml = [
'name: orders',
'table: orders',
@ -794,10 +728,9 @@ try {
await mkdir(join(projectDir, 'semantic-layer', 'warehouse'), { recursive: true });
await writeFile(join(projectDir, 'semantic-layer', 'warehouse', 'orders.yaml'), slYaml, 'utf-8');
const agentSlSearch = await run('pnpm', [
const slSearch = await run('pnpm', [
'exec',
'ktx',
'agent',
'sl',
'list',
'--json',
@ -808,13 +741,14 @@ try {
'--project-dir',
projectDir,
]);
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('ktx agent sl list hybrid metadata verified\\n');
const slSearchJson = parseJsonResult('ktx sl list', slSearch);
assert.equal(slSearchJson.kind, 'list');
assert.equal(slSearchJson.data.items.length, 1);
assert.equal(slSearchJson.data.items[0].connectionId, 'warehouse');
assert.equal(slSearchJson.data.items[0].name, 'orders');
assert.equal(typeof slSearchJson.data.items[0].score, 'number');
requireIncludes(slSearchJson.data.items[0].matchReasons, 'lexical', 'sl search match reasons');
process.stdout.write('ktx sl list hybrid metadata verified\\n');
const slQuery = await run('pnpm', ['exec', 'ktx', 'sl', 'query',
'--connection-id',
@ -865,12 +799,11 @@ try {
requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/);
process.stdout.write('ktx sl query sqlite execute verified\\n');
const runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'doctor']);
requireSuccess('ktx dev runtime doctor', runtimeDoctor);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS uv/);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS Bundled Python wheel/);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS Managed Python runtime/);
process.stdout.write('ktx dev runtime doctor verified\\n');
const runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status']);
requireSuccess('ktx dev runtime status', runtimeDoctor);
requireOutput('ktx dev runtime status', runtimeDoctor, /KTX Python runtime/);
requireOutput('ktx dev runtime status', runtimeDoctor, /status: ready/);
process.stdout.write('ktx dev runtime status verified\\n');
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start']);
requireSuccess('ktx dev runtime start', runtimeStart);
@ -890,28 +823,7 @@ try {
requireOutput('ktx dev runtime stop', runtimeStop, /Stopped KTX Python daemon/);
process.stdout.write('ktx dev runtime daemon lifecycle verified\\n');
const staleRuntimeDir = join(process.env.KTX_RUNTIME_ROOT, '0.0.0');
await mkdir(staleRuntimeDir, { recursive: true });
const runtimePruneDryRun = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune', '--dry-run']);
requireSuccess('ktx dev runtime prune dry run', runtimePruneDryRun);
requireOutput('ktx dev runtime prune dry run', runtimePruneDryRun, /Stale KTX Python runtimes/);
requireOutput('ktx dev runtime prune dry run', runtimePruneDryRun, /0\\.0\\.0/);
await access(staleRuntimeDir);
const runtimePruneNeedsConfirmation = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune']);
assert.equal(runtimePruneNeedsConfirmation.code, 1, 'ktx dev runtime prune needs confirmation');
assert.equal(runtimePruneNeedsConfirmation.stdout, '', 'ktx dev runtime prune needs confirmation wrote stdout');
assert.match(runtimePruneNeedsConfirmation.stderr, /Refusing to prune without --yes/);
const runtimePruneConfirmed = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune', '--yes']);
requireSuccess('ktx dev runtime prune confirmed', runtimePruneConfirmed);
requireOutput('ktx dev runtime prune confirmed', runtimePruneConfirmed, /Removed stale KTX Python runtimes/);
requireOutput('ktx dev runtime prune confirmed', runtimePruneConfirmed, /0\\.0\\.0/);
await assert.rejects(() => access(staleRuntimeDir));
process.stdout.write('ktx dev runtime prune verified\\n');
const structuralScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse',
const structuralScan = await run('pnpm', ['exec', 'ktx', 'scan', 'warehouse',
'--project-dir',
projectDir,
]);
@ -920,34 +832,10 @@ try {
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', 'ktx', 'dev', 'scan', 'status',
'--project-dir',
projectDir,
structuralScanRunId,
]);
requireProjectStderr('ktx scan status', scanStatus, projectDir);
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', 'ktx', 'dev', 'scan', 'report',
'--project-dir',
projectDir,
'--json',
structuralScanRunId,
]);
requireSuccess('ktx scan report', scanReport);
const scanReportJson = JSON.parse(scanReport.stdout);
assert.equal(scanReportJson.mode, 'structural');
assert.equal(scanReportJson.connectionId, 'warehouse');
assert.equal(scanReportJson.manifestShardsWritten, 1);
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('ktx scan structural verified: ' + structuralScanRunId + '\\n');
const enrichedScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse',
const enrichedScan = await run('pnpm', ['exec', 'ktx', 'scan', 'warehouse',
'--project-dir',
projectDir,
'--mode',
@ -956,24 +844,14 @@ try {
requireProjectStderr('ktx scan enriched', enrichedScan, projectDir);
requireOutput('ktx scan enriched', enrichedScan, /Status: done/);
requireOutput('ktx scan enriched', enrichedScan, /Mode: enriched/);
requireOutput('ktx scan enriched', enrichedScan, /Enrichment artifacts:/);
const enrichedScanRunId = getRunId(enrichedScan.stdout);
const enrichedScanReport = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'report',
'--project-dir',
projectDir,
'--json',
enrichedScanRunId,
]);
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('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', 'ktx', 'dev', 'ingest', 'run',
const ingestRun = await run('pnpm', ['exec', 'ktx', 'ingest', 'run',
'--project-dir',
projectDir,
'--connection-id',
@ -983,14 +861,14 @@ try {
'--source-dir',
sourceDir,
]);
assert.equal(ingestRun.code, 1, 'ktx dev ingest run without an LLM provider must fail');
assert.equal(ingestRun.code, 1, 'ktx ingest run without an LLM provider must fail');
assert.match(
ingestRun.stderr,
/ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/,
/ktx ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/,
);
await access(join(projectDir, '.ktx', 'db.sqlite'));
process.stdout.write('ktx dev ingest provider guard verified\\n');
process.stdout.write('ktx ingest provider guard verified\\n');
} finally {
if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop']);

View file

@ -459,9 +459,9 @@ describe('verification snippets', () => {
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, /run\('pnpm', \[\s*'exec',\s*'ktx',\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*'sl',\s*'list'/);
assert.match(source, /orders\.order_count/);
assert.match(source, /sqlite3/);
assert.match(source, /driver: sqlite/);
@ -482,33 +482,26 @@ describe('verification snippets', () => {
assert.match(source, /ktx dev runtime status ready/);
assert.match(source, /runtimeStatusAfter\.kind, 'ready'/);
assert.match(source, /runtimeStatusAfter\.manifest\.features/);
assert.match(source, /ktx dev runtime doctor/);
assert.match(source, /PASS Managed Python runtime/);
assert.match(source, /ktx dev runtime status/);
assert.match(source, /status: ready/);
assert.match(source, /ktx dev runtime start/);
assert.match(source, /ktx dev runtime start reuse/);
assert.match(source, /Using existing KTX Python daemon/);
assert.match(source, /ktx dev runtime stop/);
assert.match(source, /ktx dev runtime prune dry run/);
assert.match(source, /0\.0\.0/);
assert.match(source, /ktx dev runtime prune needs confirmation/);
assert.match(source, /Refusing to prune without --yes/);
assert.match(source, /ktx dev 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.doesNotMatch(source, /ktx dev runtime prune/);
assert.doesNotMatch(source, /staleRuntimeDir/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'scan',\s*'warehouse'/);
assert.match(source, /'--mode',\s*'enriched'/);
assert.doesNotMatch(source, /'--enrich'/);
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:/);
assert.match(source, /mode: deterministic/);
assert.match(source, /run\('pnpm', \['exec', 'ktx', 'dev', 'ingest', 'run'/);
assert.match(source, /run\('pnpm', \['exec', 'ktx', 'ingest', 'run'/);
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/);
assert.match(source, /ktx dev ingest provider guard verified/);
assert.match(source, /ktx ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/);
assert.match(source, /ktx ingest provider guard verified/);
});
describe('npmCliSmokeSource', () => {

View file

@ -1,6 +1,6 @@
#!/usr/bin/env node
import { mkdir as fsMkdir, writeFile as fsWriteFile } from 'node:fs/promises';
import { mkdir as fsMkdir, readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises';
import { execFile as childExecFile } from 'node:child_process';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@ -89,11 +89,7 @@ function parseArgs(argv) {
}
export function buildOrbitScanArgv(input) {
return ['dev', 'scan', input.connectionId, '--enrich', '--project-dir', input.projectDir];
}
export function buildOrbitReportArgv(input) {
return ['dev', 'scan', 'report', '--json', '--project-dir', input.projectDir, input.runId];
return ['scan', input.connectionId, '--mode', 'relationships', '--project-dir', input.projectDir];
}
export function extractRunId(stdout) {
@ -101,6 +97,11 @@ export function extractRunId(stdout) {
return match?.[1] ?? null;
}
export function extractReportPath(stdout) {
const match = stdout.match(/^\s*Report:\s*(\S+)/m);
return match?.[1] ?? null;
}
function listLines(values) {
if (!values || values.length === 0) {
return ['- none'];
@ -203,11 +204,9 @@ export function formatOrbitVerificationMarkdown(result) {
if (result.status === 'success') {
lines.push(
'## JSON Report Command',
'## Scan Report Artifact',
'',
'```bash',
result.reportCommand,
'```',
`- ${result.reportPath}`,
'',
...formatSuccess(result),
);
@ -249,6 +248,7 @@ export async function runOrbitVerification(options = {}) {
const now = options.now ?? (() => new Date());
const mkdir = options.mkdir ?? fsMkdir;
const writeFile = options.writeFile ?? fsWriteFile;
const readFile = options.readFile ?? fsReadFile;
const date = dateOnly(now());
const env = options.env ?? orbitVerificationEnv(projectDir);
const runWithEnv = (argv, runnerOptions) => runner(argv, { ...runnerOptions, env });
@ -284,33 +284,32 @@ export async function runOrbitVerification(options = {}) {
scanStderr: scan.stderr,
};
} else {
const reportArgv = buildOrbitReportArgv({ projectDir, runId });
const reportOutput = await runBufferedWorkspaceKtx(runWithEnv, reportArgv, rootDir, execFile);
if (reportOutput.exitCode !== 0) {
const scanReportPath = extractReportPath(scan.stdout);
if (!scanReportPath) {
result = {
status: 'blocked',
date,
connectionId,
projectDir,
scanCommand: shellCommand(scanArgv),
scanExitCode: reportOutput.exitCode,
blocker: firstNonEmptyLine(reportOutput.stderr, reportOutput.stdout),
scanStdout: `${scan.stdout}\n${reportOutput.stdout}`.trim(),
scanStderr: `${scan.stderr}\n${reportOutput.stderr}`.trim(),
scanExitCode: scan.exitCode,
blocker: 'KTX scan completed without printing a report artifact path',
scanStdout: scan.stdout,
scanStderr: scan.stderr,
};
} else {
const fullScanReportPath = resolve(projectDir, scanReportPath);
result = {
status: 'success',
date,
connectionId,
projectDir,
scanCommand: shellCommand(scanArgv),
reportCommand: shellCommand(reportArgv),
reportPath: fullScanReportPath,
scanExitCode: scan.exitCode,
reportExitCode: reportOutput.exitCode,
scanStdout: scan.stdout,
scanStderr: scan.stderr,
report: JSON.parse(reportOutput.stdout),
report: JSON.parse(await readFile(fullScanReportPath, 'utf8')),
};
}
}

View file

@ -3,9 +3,9 @@ import { readFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import { describe, it } from 'node:test';
import {
buildOrbitReportArgv,
buildOrbitScanArgv,
defaultOrbitVerificationProjectDir,
extractReportPath,
extractRunId,
formatOrbitVerificationMarkdown,
runOrbitVerification,
@ -59,24 +59,15 @@ describe('relationship Orbit verification helper', () => {
);
});
it('builds the current KTX launcher arguments for scan and JSON report commands', () => {
it('builds the current KTX launcher arguments for scan commands', () => {
assert.deepEqual(buildOrbitScanArgv({ connectionId: 'orbit', projectDir: '/tmp/orbit-project' }), [
'dev',
'scan',
'orbit',
'--enrich',
'--mode',
'relationships',
'--project-dir',
'/tmp/orbit-project',
]);
assert.deepEqual(buildOrbitReportArgv({ projectDir: '/tmp/orbit-project', runId: 'scan-orbit-1' }), [
'dev',
'scan',
'report',
'--json',
'--project-dir',
'/tmp/orbit-project',
'scan-orbit-1',
]);
});
it('uses the checked-in Orbit verification project by default', async () => {
@ -95,22 +86,17 @@ describe('relationship Orbit verification helper', () => {
runWorkspaceKtx: async (argv, options) => {
calls.push(argv);
envs.push(options.env);
if (argv[2] === 'report') {
options.stdout.write(successReportJson());
return 0;
}
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n Report: reports/scan-report.json\n');
return 0;
},
readFile: async () => successReportJson(),
});
assert.equal(result.status, 'success');
assert.deepEqual(calls, [
['dev', 'scan', 'orbit', '--enrich', '--project-dir', defaultProjectDir],
['dev', 'scan', 'report', '--json', '--project-dir', defaultProjectDir, 'scan-orbit-1'],
['scan', 'orbit', '--mode', 'relationships', '--project-dir', defaultProjectDir],
]);
assert.equal(envs[0].GIT_CEILING_DIRECTORIES, dirname(defaultProjectDir));
assert.equal(envs[1].GIT_CEILING_DIRECTORIES, dirname(defaultProjectDir));
assert.equal(writes.length, 1);
assert.match(writes[0].content, new RegExp(defaultProjectDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
});
@ -129,19 +115,15 @@ describe('relationship Orbit verification helper', () => {
writeFile: async () => {},
runWorkspaceKtx: async (argv, options) => {
calls.push(argv);
if (argv[2] === 'report') {
options.stdout.write(successReportJson());
return 0;
}
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n Report: reports/scan-report.json\n');
return 0;
},
readFile: async () => successReportJson(),
});
assert.equal(result.projectDir, '/tmp/orbit-project-from-env');
assert.deepEqual(calls, [
['dev', 'scan', 'orbit', '--enrich', '--project-dir', '/tmp/orbit-project-from-env'],
['dev', 'scan', 'report', '--json', '--project-dir', '/tmp/orbit-project-from-env', 'scan-orbit-1'],
['scan', 'orbit', '--mode', 'relationships', '--project-dir', '/tmp/orbit-project-from-env'],
]);
} finally {
if (previousProjectDir === undefined) {
@ -155,6 +137,7 @@ describe('relationship Orbit verification helper', () => {
it('extracts the run id from human scan output', () => {
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);
assert.equal(extractReportPath('Artifacts\n Report: reports/scan-report.json\n'), 'reports/scan-report.json');
});
it('formats successful Orbit verification evidence from the JSON report', () => {
@ -163,10 +146,9 @@ describe('relationship Orbit verification helper', () => {
date: '2026-05-07',
connectionId: 'orbit',
projectDir: '/tmp/orbit-project',
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',
scanCommand: 'pnpm run ktx -- scan orbit --mode relationships --project-dir /tmp/orbit-project',
reportPath: '/tmp/orbit-project/reports/scan-report.json',
scanExitCode: 0,
reportExitCode: 0,
scanStdout: 'KTX scan completed\nRun: scan-orbit-1\n',
scanStderr: '',
report: JSON.parse(successReportJson()),
@ -189,7 +171,7 @@ describe('relationship Orbit verification helper', () => {
date: '2026-05-07',
connectionId: 'orbit',
projectDir: '/tmp/orbit-project',
scanCommand: 'pnpm run ktx -- dev scan orbit --enrich --project-dir /tmp/orbit-project',
scanCommand: 'pnpm run ktx -- scan orbit --mode relationships --project-dir /tmp/orbit-project',
scanExitCode: 1,
blocker: 'Connection "orbit" was not found',
scanStdout: '',
@ -202,7 +184,7 @@ describe('relationship Orbit verification helper', () => {
assert.doesNotMatch(markdown, /scan\.enrichment\.mode is required/);
});
it('runs scan then JSON report and writes success Markdown', async () => {
it('runs scan then reads the report artifact and writes success Markdown', async () => {
const calls = [];
const writes = [];
const result = await runOrbitVerification({
@ -216,19 +198,15 @@ describe('relationship Orbit verification helper', () => {
},
runWorkspaceKtx: async (argv, options) => {
calls.push(argv);
if (argv[2] === 'report') {
options.stdout.write(successReportJson());
return 0;
}
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n');
options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n Report: reports/scan-report.json\n');
return 0;
},
readFile: async () => successReportJson(),
});
assert.equal(result.status, 'success');
assert.deepEqual(calls, [
['dev', 'scan', 'orbit', '--enrich', '--project-dir', '/tmp/orbit-project'],
['dev', 'scan', 'report', '--json', '--project-dir', '/tmp/orbit-project', 'scan-orbit-1'],
['scan', 'orbit', '--mode', 'relationships', '--project-dir', '/tmp/orbit-project'],
]);
assert.equal(writes.length, 1);
assert.equal(writes[0].path, '/tmp/orbit-report.md');

View file

@ -83,10 +83,6 @@ async function isBuildStale(rootDir, binPath, fs) {
return false;
}
function isShellCompletionRequest(argv) {
return argv[0] === '__complete' || (argv[0] === 'dev' && argv[1] === '__complete');
}
async function runBuffered(execFile, stdout, stderr, command, args, options) {
try {
const result = await execFile(command, args, { cwd: options.cwd, env: options.env, maxBuffer: 1024 * 1024 * 16 });
@ -150,8 +146,7 @@ export async function runWorkspaceKtx(argv, options = {}) {
const commandEnv = options.env;
const binExists = await fileExists(binPath, access);
const skipStaleBuildCheck = binExists && isShellCompletionRequest(cliArgv);
const needsBuild = !binExists || (!skipStaleBuildCheck && (await isBuildStale(rootDir, binPath, fs)));
const needsBuild = !binExists || (await isBuildStale(rootDir, binPath, fs));
if (needsBuild) {
stderr.write(
binExists

View file

@ -105,53 +105,6 @@ test('runWorkspaceKtx drops a leading npm argument separator', async () => {
]);
});
test('runWorkspaceKtx skips stale-build checks for shell completion when dist exists', async () => {
const calls = [];
let statCalls = 0;
const exitCode = await runWorkspaceKtx(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', ''], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: async (path) => {
statCalls += 1;
return {
mtimeMs: path.endsWith('/packages/cli/dist/bin.js') ? 2000 : 3000,
isDirectory: () => path.endsWith('/src') || path.endsWith('/packages'),
};
},
readdir: async () => {
throw new Error('completion should not scan source directories');
},
execFile: async (command, args, options) => {
calls.push({ command, args, cwd: options.cwd });
return { stdout: 'connect:Add, list, test, and map data sources\n', stderr: '' };
},
stdout: { write: () => undefined },
stderr: { write: () => undefined },
});
assert.equal(exitCode, 0);
assert.equal(statCalls, 0);
assert.deepEqual(calls, [
{
command: process.execPath,
args: [
'/workspace/ktx/packages/cli/dist/bin.js',
'dev',
'__complete',
'--shell',
'zsh',
'--position',
'2',
'--',
'ktx',
'',
],
cwd: '/workspace/ktx',
},
]);
});
test('runWorkspaceKtx builds the workspace CLI before running it when dist is missing', async () => {
const calls = [];
const logs = [];
@ -199,7 +152,7 @@ test('runWorkspaceKtx rebuilds before running when workspace sources are newer t
const logs = [];
let sourceMtimeMs = 3000;
const exitCode = await runWorkspaceKtx(['dev', 'scan', 'orbit', '--enrich'], {
const exitCode = await runWorkspaceKtx(['scan', 'orbit', '--mode', 'relationships'], {
rootDir: '/workspace/ktx',
access: async () => undefined,
stat: async (path) => ({
@ -232,7 +185,7 @@ test('runWorkspaceKtx rebuilds before running when workspace sources are newer t
calls.map((call) => [call.command, call.args]),
[
['pnpm', ['run', 'build']],
[process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'dev', 'scan', 'orbit', '--enrich']],
[process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'scan', 'orbit', '--mode', 'relationships']],
],
);
assert.deepEqual(logs, [