feat(cli): add ktx admin reindex (#160)

* feat(cli): add admin reindex

* fix: keep lexical-only reindex incremental
This commit is contained in:
Andrey Avtomonov 2026-05-20 01:36:54 +02:00 committed by GitHub
parent 3db3e724cb
commit 6dbb0c8b3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1640 additions and 393 deletions

View file

@ -193,12 +193,12 @@ describe('standalone example docs', () => {
assert.match(rootReadme, publicPackagePattern('npm install -g {package}'));
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, /ktx admin runtime install --feature local-embeddings --yes/);
assert.match(quickstart, /ktx admin runtime start --feature local-embeddings/);
assert.match(packageArtifacts, /requires `uv` on `PATH`/);
assert.match(packageArtifacts, /ktx dev runtime status/);
assert.match(packageArtifacts, /ktx dev runtime status/);
assert.doesNotMatch(packageArtifacts, /ktx dev runtime prune/);
assert.match(packageArtifacts, /ktx admin runtime status/);
assert.match(packageArtifacts, /ktx admin runtime status/);
assert.doesNotMatch(packageArtifacts, /ktx admin runtime prune/);
assert.match(
packageArtifacts,
new RegExp(
@ -229,9 +229,9 @@ describe('standalone example docs', () => {
assert.doesNotMatch(readme, /standalone Python distributions/);
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 status/);
assert.doesNotMatch(readme, /ktx dev runtime prune/);
assert.match(readme, /ktx admin runtime status/);
assert.match(readme, /ktx admin runtime status/);
assert.doesNotMatch(readme, /ktx admin runtime prune/);
assert.doesNotMatch(readme, /@ktx\/context/);
assert.doesNotMatch(readme, /@ktx\/cli/);
assert.doesNotMatch(readme, /python -m ktx_daemon semantic-validate/);
@ -241,7 +241,7 @@ describe('standalone example docs', () => {
const rootReadme = await readText('README.md');
const cliMeta = await readText('docs-site/content/docs/cli-reference/meta.json');
const ingestReference = await readText('docs-site/content/docs/cli-reference/ktx-ingest.mdx');
const devReference = await readText('docs-site/content/docs/cli-reference/ktx-dev.mdx');
const adminReference = await readText('docs-site/content/docs/cli-reference/ktx-admin.mdx');
const setupReference = await readText('docs-site/content/docs/cli-reference/ktx-setup.mdx');
const buildingContext = await readText('docs-site/content/docs/guides/building-context.mdx');
const contextSources = await readText('docs-site/content/docs/integrations/context-sources.mdx');
@ -275,7 +275,7 @@ describe('standalone example docs', () => {
assert.doesNotMatch(ingestReference, /--adapter/);
assert.doesNotMatch(ingestReference, /ktx ingest watch/);
assert.doesNotMatch(ingestReference, /live-database/);
assert.doesNotMatch(devReference, /ktx scan/);
assert.doesNotMatch(adminReference, /ktx scan/);
assert.doesNotMatch(buildingContext, /ktx ingest watch/);
assert.doesNotMatch(buildingContext, /ktx ingest status/);
assert.doesNotMatch(buildingContext, /ktx ingest replay/);

View file

@ -237,17 +237,17 @@ function parseDaemonBaseUrl(stdout) {
}
async function startDaemon(cleanInstallDir) {
const result = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start'], {
const result = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 120_000,
});
requireSuccess('ktx dev runtime start', result);
requireSuccess('ktx admin runtime start', result);
return parseDaemonBaseUrl(result.stdout);
}
async function stopDaemon(cleanInstallDir) {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop'], {
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 30_000,
@ -271,7 +271,7 @@ async function prepareCleanInstall(layout, cleanInstallDir) {
await run('pnpm', ['install'], { cwd: cleanInstallDir, timeout: 120_000 }).then((result) =>
requireSuccess('pnpm install clean artifact project', result),
);
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'install', '--yes'], {
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'install', '--yes'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 120_000,

View file

@ -74,27 +74,27 @@ export function localEmbeddingsSmokeCommands(input) {
timeoutMs: 60_000,
},
{
label: 'ktx dev runtime status missing',
label: 'ktx admin runtime status missing',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'status', '--json'],
args: ['exec', 'ktx', 'admin', 'runtime', 'status', '--json'],
timeoutMs: 60_000,
},
{
label: 'ktx dev runtime install local embeddings',
label: 'ktx admin runtime install local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
args: ['exec', 'ktx', 'admin', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
timeoutMs: 1_200_000,
},
{
label: 'ktx dev runtime status local embeddings ready',
label: 'ktx admin runtime status local embeddings ready',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'status', '--json'],
args: ['exec', 'ktx', 'admin', 'runtime', 'status', '--json'],
timeoutMs: 60_000,
},
{
label: 'ktx dev runtime start local embeddings',
label: 'ktx admin runtime start local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'start', '--feature', 'local-embeddings'],
args: ['exec', 'ktx', 'admin', 'runtime', 'start', '--feature', 'local-embeddings'],
timeoutMs: 300_000,
},
{
@ -118,9 +118,9 @@ export function localEmbeddingsSmokeCommands(input) {
timeoutMs: 900_000,
},
{
label: 'ktx dev runtime stop local embeddings',
label: 'ktx admin runtime stop local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'stop'],
args: ['exec', 'ktx', 'admin', 'runtime', 'stop'],
timeoutMs: 60_000,
},
];
@ -374,7 +374,7 @@ export async function runLocalEmbeddingsRuntimeSmoke(options = {}) {
process.stdout.write('KTX local embeddings runtime smoke verified\n');
} finally {
if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop'], {
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop'], {
cwd: installDir,
env: smokeEnv,
timeoutMs: 60_000,

View file

@ -89,23 +89,23 @@ describe('localEmbeddingsSmokeCommands', () => {
assert.deepEqual(commands.map((command) => command.label), [
'ktx public package version',
'ktx dev runtime status missing',
'ktx dev runtime install local embeddings',
'ktx dev runtime status local embeddings ready',
'ktx dev runtime start local embeddings',
'ktx admin runtime status missing',
'ktx admin runtime install local embeddings',
'ktx admin runtime status local embeddings ready',
'ktx admin runtime start local embeddings',
'ktx setup local embeddings',
'ktx dev runtime stop local embeddings',
'ktx admin runtime stop local embeddings',
]);
assert.deepEqual(commands[2], {
label: 'ktx dev runtime install local embeddings',
label: 'ktx admin runtime install local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
args: ['exec', 'ktx', 'admin', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
timeoutMs: 1_200_000,
});
assert.deepEqual(commands[4], {
label: 'ktx dev runtime start local embeddings',
label: 'ktx admin runtime start local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'dev', 'runtime', 'start', '--feature', 'local-embeddings'],
args: ['exec', 'ktx', 'admin', 'runtime', 'start', '--feature', 'local-embeddings'],
timeoutMs: 300_000,
});
assert.deepEqual(commands[5].args, [

View file

@ -147,7 +147,7 @@ export async function findPythonArtifacts(pythonDir) {
files,
RUNTIME_WHEEL_DISTRIBUTION_NAME,
'.whl',
'kaelio-ktx dev runtime wheel',
'kaelio-ktx runtime wheel',
pythonDir,
RUNTIME_WHEEL_PACKAGE_VERSION,
),
@ -606,8 +606,8 @@ try {
requireOutput('ktx public package version', version, await installedPackageVersionPattern());
const runtimeStatusBefore = parseJsonResultWithExitCode(
'ktx dev runtime status missing',
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status', '--json']),
'ktx admin runtime status missing',
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status', '--json']),
1,
);
assert.equal(runtimeStatusBefore.kind, 'missing');
@ -768,8 +768,8 @@ try {
requireOutput('ktx sl query first managed runtime install', slQuery, /orders/);
const runtimeStatusAfter = parseJsonResult(
'ktx dev runtime status ready',
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status', '--json']),
'ktx admin runtime status ready',
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status', '--json']),
);
assert.equal(runtimeStatusAfter.kind, 'ready');
assert.deepEqual(runtimeStatusAfter.manifest.features, ['core']);
@ -797,29 +797,29 @@ 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', '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 runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'status']);
requireSuccess('ktx admin runtime status', runtimeDoctor);
requireOutput('ktx admin runtime status', runtimeDoctor, /KTX Python runtime/);
requireOutput('ktx admin runtime status', runtimeDoctor, /status: ready/);
process.stdout.write('ktx admin runtime status verified\\n');
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start']);
requireSuccess('ktx dev runtime start', runtimeStart);
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']);
requireSuccess('ktx admin runtime start', runtimeStart);
daemonStarted = true;
requireOutput('ktx dev runtime start', runtimeStart, /Started KTX Python daemon/);
requireOutput('ktx dev runtime start', runtimeStart, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
requireOutput('ktx dev runtime start', runtimeStart, /features: core/);
requireOutput('ktx admin runtime start', runtimeStart, /Started KTX Python daemon/);
requireOutput('ktx admin runtime start', runtimeStart, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
requireOutput('ktx admin runtime start', runtimeStart, /features: core/);
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start']);
requireSuccess('ktx dev runtime start reuse', runtimeStartReuse);
requireOutput('ktx dev runtime start reuse', runtimeStartReuse, /Using existing KTX Python daemon/);
requireOutput('ktx dev runtime start reuse', runtimeStartReuse, /features: core/);
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'start']);
requireSuccess('ktx admin runtime start reuse', runtimeStartReuse);
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /Using existing KTX Python daemon/);
requireOutput('ktx admin runtime start reuse', runtimeStartReuse, /features: core/);
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop']);
requireSuccess('ktx dev runtime stop', runtimeStop);
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop']);
requireSuccess('ktx admin runtime stop', runtimeStop);
daemonStarted = false;
requireOutput('ktx dev runtime stop', runtimeStop, /Stopped KTX Python daemon/);
process.stdout.write('ktx dev runtime daemon lifecycle verified\\n');
requireOutput('ktx admin runtime stop', runtimeStop, /Stopped KTX Python daemon/);
process.stdout.write('ktx admin runtime daemon lifecycle verified\\n');
const structuralScan = await run('pnpm', ['exec', 'ktx', 'ingest', 'warehouse',
'--project-dir',
@ -849,7 +849,7 @@ try {
process.stdout.write('ktx ingest state verified\\n');
} finally {
if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop']);
await run('pnpm', ['exec', 'ktx', 'admin', 'runtime', 'stop']);
}
if (previousRuntimeRoot === undefined) {
delete process.env.KTX_RUNTIME_ROOT;

View file

@ -167,7 +167,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: kaelio-ktx dev runtime wheel/);
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: kaelio-ktx runtime wheel/);
} finally {
await rm(root, { recursive: true, force: true });
}
@ -491,20 +491,20 @@ describe('verification snippets', () => {
assert.doesNotMatch(source, /run\('python'/);
assert.match(source, /KTX_RUNTIME_ROOT/);
assert.match(source, /managed-runtime/);
assert.match(source, /ktx dev runtime status missing/);
assert.match(source, /ktx admin 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 dev runtime status ready/);
assert.match(source, /ktx admin runtime status ready/);
assert.match(source, /runtimeStatusAfter\.kind, 'ready'/);
assert.match(source, /runtimeStatusAfter\.manifest\.features/);
assert.match(source, /ktx dev runtime status/);
assert.match(source, /ktx admin 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, /ktx admin runtime start/);
assert.match(source, /ktx admin runtime start reuse/);
assert.match(source, /Using existing KTX Python daemon/);
assert.match(source, /ktx dev runtime stop/);
assert.doesNotMatch(source, /ktx dev runtime prune/);
assert.match(source, /ktx admin runtime stop/);
assert.doesNotMatch(source, /ktx admin runtime prune/);
assert.doesNotMatch(source, /staleRuntimeDir/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'ingest',\s*'warehouse'/);
assert.match(source, /'--deep'/);