diff --git a/packages/cli/src/setup-context.test.ts b/packages/cli/src/setup-context.test.ts index 61a9019a..8cd81dff 100644 --- a/packages/cli/src/setup-context.test.ts +++ b/packages/cli/src/setup-context.test.ts @@ -198,7 +198,7 @@ describe('setup context build state', () => { await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-abc123', - status: 'running', + status: 'stale', startedAt: '2026-05-09T10:00:00.000Z', updatedAt: '2026-05-09T10:00:00.000Z', primarySourceConnectionIds: ['warehouse'], @@ -207,6 +207,7 @@ describe('setup context build state', () => { artifactPaths: [], retryableFailedTargets: [], commands: contextBuildCommands(tempDir, 'setup-context-local-abc123'), + failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.', sourceProgress: [ { connectionId: 'warehouse', @@ -623,34 +624,13 @@ describe('setup context build state', () => { expect(io.stderr()).toContain('No databases or context sources are configured for a KTX context build.'); }); - it('normalizes legacy detached and paused setup context states to stale', async () => { - await writeReadyProject(tempDir); - await writeKtxSetupContextState(tempDir, { - runId: 'setup-context-local-old', - status: 'detached' as never, - startedAt: '2026-05-09T09:00:00.000Z', - updatedAt: '2026-05-09T09:00:00.000Z', - primarySourceConnectionIds: ['warehouse'], - contextSourceConnectionIds: [], - reportIds: [], - artifactPaths: [], - retryableFailedTargets: [], - commands: contextBuildCommands(tempDir, 'setup-context-local-old'), - }); - - await expect(readKtxSetupContextState(tempDir)).resolves.toMatchObject({ - status: 'stale', - failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.', - }); - }); - - it('starts a fresh foreground build when a stale running state is found', async () => { + it('starts a fresh foreground build when stale state is found', async () => { await writeReadyProject(tempDir, { connections: { warehouse: { driver: 'postgres', readonly: true, context: { depth: 'fast' } } }, }); await writeKtxSetupContextState(tempDir, { - runId: 'setup-context-local-running', - status: 'running', + runId: 'setup-context-local-stale', + status: 'stale', startedAt: '2026-05-09T09:00:00.000Z', updatedAt: '2026-05-09T09:00:00.000Z', primarySourceConnectionIds: ['warehouse'], @@ -658,7 +638,8 @@ describe('setup context build state', () => { reportIds: [], artifactPaths: [], retryableFailedTargets: [], - commands: contextBuildCommands(tempDir, 'setup-context-local-running'), + commands: contextBuildCommands(tempDir, 'setup-context-local-stale'), + failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.', }); const io = makeIo(); const runContextBuildMock = vi.fn(async () => ({ exitCode: 0 })); diff --git a/packages/cli/src/setup-context.ts b/packages/cli/src/setup-context.ts index 413230b1..de670224 100644 --- a/packages/cli/src/setup-context.ts +++ b/packages/cli/src/setup-context.ts @@ -27,10 +27,8 @@ import { export type KtxSetupContextBuildStatus = | 'not_started' - | 'running' | 'completed' | 'failed' - | 'interrupted' | 'stale'; export interface KtxSetupContextCommands { @@ -84,7 +82,6 @@ export interface KtxSetupContextStepArgs { forcePrompt?: boolean; allowEmpty?: boolean; prompt?: boolean; - autoWatch?: boolean; cliVersion?: string; runtimeInstallPolicy?: KtxManagedPythonInstallPolicy; } @@ -154,14 +151,8 @@ function normalizeState(projectDir: string, value: unknown): KtxSetupContextStat } const record = value as Record; const rawStatus = typeof record.status === 'string' ? record.status : 'not_started'; - const legacyActive = rawStatus === 'detached' || rawStatus === 'paused' || rawStatus === 'running'; - const status: KtxSetupContextBuildStatus = legacyActive - ? 'stale' - : rawStatus === 'completed' || - rawStatus === 'failed' || - rawStatus === 'interrupted' || - rawStatus === 'not_started' || - rawStatus === 'stale' + const status: KtxSetupContextBuildStatus = + rawStatus === 'completed' || rawStatus === 'failed' || rawStatus === 'not_started' || rawStatus === 'stale' ? rawStatus : 'not_started'; const runId = typeof record.runId === 'string' && record.runId.length > 0 ? record.runId : undefined; @@ -187,11 +178,7 @@ function normalizeState(projectDir: string, value: unknown): KtxSetupContextStat ? record.retryableFailedTargets.filter((item): item is string => typeof item === 'string') : [], commands: contextBuildCommands(projectDir, runId), - ...(typeof record.failureReason === 'string' - ? { failureReason: record.failureReason } - : legacyActive - ? { failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.' } - : {}), + ...(typeof record.failureReason === 'string' ? { failureReason: record.failureReason } : {}), ...(normalizeSourceProgress(record.sourceProgress) ? { sourceProgress: normalizeSourceProgress(record.sourceProgress) } : {}), }; } @@ -552,9 +539,9 @@ async function runBuild( const now = deps.now ?? (() => new Date()); const runId = deps.runIdFactory?.() ?? runIdFactory(); const startedAt = now().toISOString(); - const runningState: KtxSetupContextState = { + const incompleteState: KtxSetupContextState = { runId, - status: 'running', + status: 'stale', startedAt, updatedAt: startedAt, primarySourceConnectionIds: targets.primarySourceConnectionIds, @@ -563,8 +550,9 @@ async function runBuild( artifactPaths: [], retryableFailedTargets: [], commands: contextBuildCommands(args.projectDir, runId), + failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.', }; - await writeKtxSetupContextState(args.projectDir, runningState); + await writeKtxSetupContextState(args.projectDir, incompleteState); let lastSourceProgress: ContextBuildSourceProgressUpdate[] | undefined; const contextBuild = deps.runContextBuild ?? runContextBuild; @@ -584,7 +572,7 @@ async function runBuild( const resolvedDir = resolve(args.projectDir); mkdirSync(join(resolvedDir, '.ktx', 'setup'), { recursive: true }); const progressState = normalizeState(resolvedDir, { - ...runningState, + ...incompleteState, sourceProgress: sources, updatedAt: new Date().toISOString(), }); @@ -600,7 +588,7 @@ async function runBuild( if (buildResult.exitCode !== 0) { const updatedAt = now().toISOString(); await writeKtxSetupContextState(args.projectDir, { - ...runningState, + ...incompleteState, status: 'failed', updatedAt, reportIds: completedReportIds, @@ -616,7 +604,7 @@ async function runBuild( if (!readiness.ready) { const updatedAt = now().toISOString(); await writeKtxSetupContextState(args.projectDir, { - ...runningState, + ...incompleteState, status: 'failed', updatedAt, reportIds: completedReportIds, @@ -635,13 +623,14 @@ async function runBuild( await markContextComplete(project.projectDir); const completedAt = now().toISOString(); await writeKtxSetupContextState(args.projectDir, { - ...runningState, + ...incompleteState, status: 'completed', updatedAt: completedAt, completedAt, reportIds: completedReportIds, artifactPaths: completedArtifactPaths, retryableFailedTargets: [], + failureReason: undefined, ...(lastSourceProgress ? { sourceProgress: lastSourceProgress } : {}), }); writeSuccess(project, readiness, targets, io); diff --git a/packages/cli/src/setup.test.ts b/packages/cli/src/setup.test.ts index ff1261c3..650570c7 100644 --- a/packages/cli/src/setup.test.ts +++ b/packages/cli/src/setup.test.ts @@ -7,7 +7,7 @@ import { writeKtxSetupState } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { localFakeBundleReport, persistLocalBundleReport } from './ingest.test-utils.js'; -import { contextBuildCommands, readKtxSetupContextState, writeKtxSetupContextState } from './setup-context.js'; +import { contextBuildCommands, writeKtxSetupContextState } from './setup-context.js'; import { runDemoTour } from './setup-demo-tour.js'; import { formatKtxSetupStatus, readKtxSetupStatus, runKtxSetup } from './setup.js'; @@ -276,7 +276,7 @@ describe('setup status', () => { }); await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-abc123', - status: 'running', + status: 'stale', startedAt: '2026-05-09T10:00:00.000Z', updatedAt: '2026-05-09T10:01:00.000Z', primarySourceConnectionIds: ['warehouse'], @@ -285,6 +285,7 @@ describe('setup status', () => { artifactPaths: [], retryableFailedTargets: [], commands: contextBuildCommands(tempDir, 'setup-context-local-abc123'), + failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.', }); await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ @@ -1619,40 +1620,6 @@ describe('setup status', () => { expect(io.stderr()).toContain('KTX context is not ready for agents.'); }); - it('does not offer background watch choices from setup status', async () => { - await writeFile( - join(tempDir, 'ktx.yaml'), - [ - 'setup:', - ' database_connection_ids:', - ' - warehouse', - 'connections:', - ' warehouse:', - ' driver: postgres', - ' url: env:DATABASE_URL', - '', - ].join('\n'), - 'utf-8', - ); - await writeKtxSetupContextState(tempDir, { - runId: 'setup-context-local-stale', - status: 'running', - startedAt: '2026-05-09T09:00:00.000Z', - updatedAt: '2026-05-09T09:00:00.000Z', - primarySourceConnectionIds: ['warehouse'], - contextSourceConnectionIds: [], - reportIds: [], - artifactPaths: [], - retryableFailedTargets: [], - commands: contextBuildCommands(tempDir, 'setup-context-local-stale'), - }); - - const status = await readKtxSetupStatus(tempDir); - expect(status.context.status).toBe('stale'); - const state = await readKtxSetupContextState(tempDir); - expect(state.status).toBe('stale'); - }); - it('routes a ready project menu selection to agent setup', async () => { const calls: string[] = []; const io = makeIo(); diff --git a/packages/cli/src/setup.ts b/packages/cli/src/setup.ts index cf458f1d..eb554bde 100644 --- a/packages/cli/src/setup.ts +++ b/packages/cli/src/setup.ts @@ -163,10 +163,7 @@ type KtxSetupFlowStatus = | 'skipped' | 'back' | 'missing-input' - | 'failed' - | 'detached' - | 'paused' - | 'interrupted'; + | 'failed'; export interface KtxSetupEntryMenuPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; @@ -408,10 +405,6 @@ function setupContextReady(status: KtxSetupStatus): boolean { return status.context.ready; } -function setupContextActive(status: KtxSetupStatus): boolean { - return status.context.status === 'running'; -} - function writeContextNotReadyForAgents(projectDir: string, io: KtxCliIo): void { io.stderr.write('KTX context is not ready for agents.\n\n'); io.stderr.write(`Build context first:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`); @@ -451,27 +444,22 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup args.inputMode !== 'disabled' && !args.agents && (io.stdout.isTTY === true || deps.entryMenuDeps?.prompts !== undefined); - let autoWatchActiveBuild = false; setupLoop: while (true) { entryAction = undefined; if (canShowEntryMenu) { const status = await readKtxSetupStatus(args.projectDir); - if (setupContextActive(status)) { - autoWatchActiveBuild = true; - } else { - entryAction = (await runKtxSetupEntryMenu(status, deps.entryMenuDeps)).action; - if (entryAction === 'exit') { - (deps.entryMenuDeps?.prompts ?? createEntryMenuPromptAdapter()).cancel('Setup cancelled.'); - return 0; - } - if (entryAction === 'status') { - io.stdout.write(formatKtxSetupStatus(status)); - return 0; - } - if (entryAction === 'demo') { - return await runKtxSetupDemoFromEntryMenu(args, io, deps); - } + entryAction = (await runKtxSetupEntryMenu(status, deps.entryMenuDeps)).action; + if (entryAction === 'exit') { + (deps.entryMenuDeps?.prompts ?? createEntryMenuPromptAdapter()).cancel('Setup cancelled.'); + return 0; + } + if (entryAction === 'status') { + io.stdout.write(formatKtxSetupStatus(status)); + return 0; + } + if (entryAction === 'demo') { + return await runKtxSetupDemoFromEntryMenu(args, io, deps); } } @@ -500,30 +488,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup const currentStatus = await readKtxSetupStatus(projectResult.projectDir); let readyAction: string | undefined; - if (args.inputMode !== 'disabled' && !agentsRequested && setupContextActive(currentStatus)) { - const contextRunner = - deps.context ?? ((contextArgs, contextIo) => runKtxSetupContextStep(contextArgs, contextIo, deps.contextDeps)); - const contextResult = await contextRunner( - { - projectDir: projectResult.projectDir, - inputMode: args.inputMode, - allowEmpty: true, - ...(autoWatchActiveBuild ? { autoWatch: true } : {}), - }, - io, - ); - autoWatchActiveBuild = false; - if (contextResult.status === 'back') { - continue; - } - if (contextResult.status === 'failed' || contextResult.status === 'missing-input') { - return 1; - } - if (contextResult.status !== 'ready') { - return 0; - } - } - if (args.inputMode !== 'disabled' && !agentsRequested) { if (isKtxSetupReady(currentStatus)) { readyAction = (await runKtxSetupReadyChangeMenu(currentStatus, deps.readyMenuDeps)).action;