Merge remote-tracking branch 'origin/main' into ktx-agent-command-history

# Conflicts:
#	packages/cli/src/agent-search-readiness.test.ts
#	packages/cli/src/agent-search-readiness.ts
#	packages/cli/src/agent.test.ts
#	packages/cli/src/project-dir.test.ts
#	packages/cli/src/setup-context.test.ts
#	packages/cli/src/standalone-smoke.test.ts
#	scripts/package-artifacts.mjs
This commit is contained in:
Andrey Avtomonov 2026-05-13 12:05:09 +02:00
commit a8bf0470ec
56 changed files with 411 additions and 3910 deletions

View file

@ -186,10 +186,10 @@ 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 status/);
assert.match(packageArtifacts, /ktx dev runtime prune --dry-run/);
assert.match(packageArtifacts, /ktx dev runtime prune --yes/);
assert.match(
@ -223,7 +223,7 @@ 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 status/);
assert.match(readme, /ktx dev runtime prune --dry-run/);
assert.match(readme, /ktx dev runtime prune --yes/);
assert.doesNotMatch(readme, /@ktx\/context/);
@ -236,14 +236,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

@ -789,12 +789,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);
@ -835,7 +834,7 @@ try {
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,
]);
@ -844,34 +843,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',
@ -880,24 +855,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',
@ -907,14 +872,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

@ -484,8 +484,8 @@ 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/);
@ -497,20 +497,18 @@ describe('verification snippets', () => {
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.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';
@ -90,11 +90,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) {
@ -102,6 +98,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'];
@ -204,11 +205,9 @@ export function formatOrbitVerificationMarkdown(result) {
if (result.status === 'success') {
lines.push(
'## JSON Report Command',
'## Scan Report Artifact',
'',
'```bash',
result.reportCommand,
'```',
`- ${result.reportPath}`,
'',
...formatSuccess(result),
);
@ -250,6 +249,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 });
@ -285,33 +285,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, [