feat(cli): guide next action at end of ktx setup, not reruns (#256)

Re-running setup was the dominant action for installs that completed setup but never ingested. Classify completion (incomplete | needs-context | needs-agents | ready) and drive one obvious next action per state: route a config-complete project straight to the build, point unbuilt-context users at `ktx ingest` instead of re-running setup or dropping to a bare shell, and confirm readiness for fully-set-up projects rather than reopening the edit menu.
This commit is contained in:
Andrey Avtomonov 2026-06-03 01:00:21 +02:00 committed by GitHub
parent cb6a67c2d7
commit 45aa95d2cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 360 additions and 59 deletions

View file

@ -6,7 +6,7 @@ import { ktxLocalStateDbPath } from './context/project/local-state-db.js';
import { loadKtxProject, type KtxLocalProject } from './context/project/project.js';
import { readKtxSetupState } from './context/project/setup-config.js';
import { getKtxCliPackageInfo, type KtxCliIo } from './cli-runtime.js';
import { formatSetupNextStepLines } from './next-steps.js';
import { formatNextStepLines, formatSetupNextStepLines } from './next-steps.js';
import { runtimeInstallPolicyFromFlags } from './managed-python-command.js';
import { readManagedPythonRuntimeStatus } from './managed-python-runtime.js';
import { resolveProjectRuntimeRequirements } from './runtime-requirements.js';
@ -33,10 +33,10 @@ import {
} from './setup-models.js';
import { type KtxSetupProjectDeps, runKtxSetupProjectStep } from './setup-project.js';
import {
isKtxPreAgentSetupReady,
isKtxSetupReady,
classifyKtxSetupCompletion,
type KtxSetupReadyMenuDeps,
runKtxSetupReadyChangeMenu,
runKtxSetupReadyMenu,
setupHasContextTargets,
} from './setup-ready-menu.js';
import { type KtxSetupSourcesDeps, type KtxSetupSourceType, runKtxSetupSourcesStep } from './setup-sources.js';
import {
@ -529,10 +529,6 @@ function setupStatusReady(status: KtxSetupStatus): boolean {
);
}
function setupHasContextTargets(status: KtxSetupStatus): boolean {
return status.databases.length > 0 || status.sources.length > 0;
}
function setupContextReady(status: KtxSetupStatus): boolean {
return status.context.ready;
}
@ -630,12 +626,19 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
let readyAction: string | undefined;
if (args.inputMode !== 'disabled' && !agentsRequested) {
if (isKtxSetupReady(currentStatus)) {
readyAction = (await runKtxSetupReadyChangeMenu(currentStatus, deps.readyMenuDeps)).action;
if (readyAction === 'exit') return 0;
} else if (isKtxPreAgentSetupReady(currentStatus)) {
const completion = classifyKtxSetupCompletion(currentStatus);
if (completion === 'ready') {
setupUi.note(formatNextStepLines().join('\n'), 'ktx is ready', io);
const choice = (await runKtxSetupReadyMenu(currentStatus, deps.readyMenuDeps)).action;
if (choice === 'exit') return 0;
readyAction = choice;
} else if (completion === 'needs-context') {
// Config is done; skip the re-walk and land straight on the build prompt.
readyAction = 'context';
} else if (completion === 'needs-agents') {
readyAction = 'agents';
}
// 'incomplete' → readyAction stays undefined → run the full setup walk.
}
const runOnly = readyAction;
@ -872,7 +875,9 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
}
if (step === 'context' && stepResult.status !== 'ready') {
if (shouldRunAgents && args.skipAgents !== true) {
return 0;
// Context isn't built, so skip agent install — but still reach the
// completion screen, which states readiness and points at `ktx ingest`.
break setupLoop;
}
}