fix(setup): use schema context and query history wording

This commit is contained in:
Andrey Avtomonov 2026-05-13 18:53:19 +02:00
parent 9c7b4f9b84
commit 0b16578dd5
4 changed files with 129 additions and 136 deletions

View file

@ -118,12 +118,12 @@ function shouldShowSetupEntryMenu(
newDatabaseConnectionId?: string;
databaseUrl?: string;
databaseSchema?: string[];
enableHistoricSql?: boolean;
disableHistoricSql?: boolean;
historicSqlWindowDays?: number;
historicSqlMinExecutions?: number;
historicSqlServiceAccountPattern?: string[];
historicSqlRedactionPattern?: string[];
enableQueryHistory?: boolean;
disableQueryHistory?: boolean;
queryHistoryWindowDays?: number;
queryHistoryMinExecutions?: number;
queryHistoryServiceAccountPattern?: string[];
queryHistoryRedactionPattern?: string[];
skipDatabases?: boolean;
source?: KtxSetupSourceType;
sourceConnectionId?: string;
@ -157,10 +157,10 @@ function shouldShowSetupEntryMenu(
if (options.databaseSchema && options.databaseSchema.length > 0) {
return false;
}
if (options.historicSqlServiceAccountPattern && options.historicSqlServiceAccountPattern.length > 0) {
if (options.queryHistoryServiceAccountPattern && options.queryHistoryServiceAccountPattern.length > 0) {
return false;
}
if (options.historicSqlRedactionPattern && options.historicSqlRedactionPattern.length > 0) {
if (options.queryHistoryRedactionPattern && options.queryHistoryRedactionPattern.length > 0) {
return false;
}
if (options.notionRootPageId && options.notionRootPageId.length > 0) {
@ -190,10 +190,10 @@ function shouldShowSetupEntryMenu(
'skipEmbeddings',
'newDatabaseConnectionId',
'databaseUrl',
'enableHistoricSql',
'disableHistoricSql',
'historicSqlWindowDays',
'historicSqlMinExecutions',
'enableQueryHistory',
'disableQueryHistory',
'queryHistoryWindowDays',
'queryHistoryMinExecutions',
'skipDatabases',
'source',
'sourceConnectionId',
@ -282,19 +282,19 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
(value, previous: string[]) => [...previous, value],
[],
)
.option('--enable-historic-sql', 'Enable Historic SQL when the selected database supports it', false)
.option('--disable-historic-sql', 'Disable Historic SQL for the selected database', false)
.option('--historic-sql-window-days <number>', 'Historic SQL query-history window', positiveInteger)
.option('--historic-sql-min-executions <number>', 'Minimum Historic SQL executions for a template', positiveInteger)
.option('--enable-query-history', 'Enable query history when the selected database supports it', false)
.option('--disable-query-history', 'Disable query history for the selected database', false)
.option('--query-history-window-days <number>', 'Query-history lookback window', positiveInteger)
.option('--query-history-min-executions <number>', 'Minimum executions for a query-history template', positiveInteger)
.option(
'--historic-sql-service-account-pattern <pattern>',
'Historic SQL service-account regex; repeatable',
'--query-history-service-account-pattern <pattern>',
'Query-history service-account regex; repeatable',
(value, previous: string[]) => [...previous, value],
[],
)
.option(
'--historic-sql-redaction-pattern <pattern>',
'Historic SQL SQL-literal redaction regex; repeatable',
'--query-history-redaction-pattern <pattern>',
'Query-history SQL-literal redaction regex; repeatable',
(value, previous: string[]) => [...previous, value],
[],
)
@ -357,9 +357,9 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
context.setExitCode(1);
return;
}
if (options.enableHistoricSql && options.disableHistoricSql) {
if (options.enableQueryHistory && options.disableQueryHistory) {
context.io.stderr.write(
'Choose only one Historic SQL action: --enable-historic-sql or --disable-historic-sql.\n',
'Choose only one query-history action: --enable-query-history or --disable-query-history.\n',
);
context.setExitCode(1);
return;
@ -404,17 +404,17 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
...(options.newDatabaseConnectionId ? { databaseConnectionId: options.newDatabaseConnectionId } : {}),
...(options.databaseUrl ? { databaseUrl: options.databaseUrl } : {}),
databaseSchemas: options.databaseSchema,
...(options.enableHistoricSql ? { enableHistoricSql: true } : {}),
...(options.disableHistoricSql ? { disableHistoricSql: true } : {}),
...(options.historicSqlWindowDays !== undefined ? { historicSqlWindowDays: options.historicSqlWindowDays } : {}),
...(options.historicSqlMinExecutions !== undefined
? { historicSqlMinExecutions: options.historicSqlMinExecutions }
...(options.enableQueryHistory ? { enableQueryHistory: true } : {}),
...(options.disableQueryHistory ? { disableQueryHistory: true } : {}),
...(options.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: options.queryHistoryWindowDays } : {}),
...(options.queryHistoryMinExecutions !== undefined
? { queryHistoryMinExecutions: options.queryHistoryMinExecutions }
: {}),
...(options.historicSqlServiceAccountPattern.length > 0
? { historicSqlServiceAccountPatterns: options.historicSqlServiceAccountPattern }
...(options.queryHistoryServiceAccountPattern.length > 0
? { queryHistoryServiceAccountPatterns: options.queryHistoryServiceAccountPattern }
: {}),
...(options.historicSqlRedactionPattern.length > 0
? { historicSqlRedactionPatterns: options.historicSqlRedactionPattern }
...(options.queryHistoryRedactionPattern.length > 0
? { queryHistoryRedactionPatterns: options.queryHistoryRedactionPattern }
: {}),
skipDatabases: options.skipDatabases === true,
...(options.source ? { source: options.source } : {}),

View file

@ -443,6 +443,18 @@ describe('runKtxCli', () => {
expect(testIo.stdout()).not.toContain('--embedding-model');
expect(testIo.stdout()).not.toContain('--embedding-dimensions');
expect(testIo.stdout()).not.toContain('--embedding-base-url');
for (const expected of [
'--enable-query-history',
'--disable-query-history',
'--query-history-window-days',
'--query-history-min-executions',
'--query-history-service-account-pattern',
'--query-history-redaction-pattern',
]) {
expect(testIo.stdout()).toContain(expected);
}
expect(testIo.stdout()).not.toContain('--enable-historic-sql');
expect(testIo.stdout()).not.toContain('--historic-sql-window-days');
expect(testIo.stderr()).toBe('');
});
@ -1142,10 +1154,10 @@ describe('runKtxCli', () => {
'env:DATABASE_URL',
'--database-schema',
'public',
'--enable-historic-sql',
'--historic-sql-window-days',
'--enable-query-history',
'--query-history-window-days',
'30',
'--historic-sql-min-executions',
'--query-history-min-executions',
'12',
],
setupIo.io,
@ -1166,9 +1178,9 @@ describe('runKtxCli', () => {
databaseConnectionId: 'warehouse',
databaseUrl: 'env:DATABASE_URL',
databaseSchemas: ['public'],
enableHistoricSql: true,
historicSqlWindowDays: 30,
historicSqlMinExecutions: 12,
enableQueryHistory: true,
queryHistoryWindowDays: 30,
queryHistoryMinExecutions: 12,
skipDatabases: false,
}),
setupIo.io,
@ -1346,18 +1358,20 @@ describe('runKtxCli', () => {
expect(setupIo.stderr()).toContain('Choose only one embedding credential source');
});
it('rejects conflicting Historic SQL setup flags', async () => {
it('rejects conflicting query-history setup flags', async () => {
const setup = vi.fn(async () => 0);
const setupIo = makeIo();
await expect(
runKtxCli(['--project-dir', tempDir, 'setup', '--enable-historic-sql', '--disable-historic-sql'], setupIo.io, {
runKtxCli(['--project-dir', tempDir, 'setup', '--enable-query-history', '--disable-query-history'], setupIo.io, {
setup,
}),
).resolves.toBe(1);
expect(setup).not.toHaveBeenCalled();
expect(setupIo.stderr()).toContain('Choose only one Historic SQL action');
expect(setupIo.stderr()).toContain(
'Choose only one query-history action: --enable-query-history or --disable-query-history.',
);
});
it('rejects the removed hidden agent command', async () => {

View file

@ -954,23 +954,17 @@ describe('setup databases step', () => {
].join('\n'),
);
expect(io.stdout()).not.toContain('Tables: 2');
expect(io.stdout()).toContain(
[
'◇ Scanning postgres-warehouse',
'│ Running structural scan…',
'│',
].join('\n'),
);
expect(io.stdout()).toContain(
[
'◇ Scan complete for postgres-warehouse',
'│ Changes: 2 new tables',
'│ Report: raw-sources/postgres-warehouse/live-database/.../scan-report.json',
'│',
'◇ Primary source ready',
'│ postgres-warehouse · PostgreSQL · structural scan complete',
].join('\n'),
);
expect(io.stdout()).toContain('◇ Building schema context for postgres-warehouse');
expect(io.stdout()).toContain('│ Running fast database ingest…');
expect(io.stdout()).toContain('◇ Schema context complete for postgres-warehouse');
expect(io.stdout()).toContain('│ Changes: 2 new tables');
expect(io.stdout()).toContain('◇ Primary source ready');
expect(io.stdout()).toContain('│ postgres-warehouse · PostgreSQL · schema context complete');
expect(io.stdout()).not.toContain('Scanning postgres-warehouse');
expect(io.stdout()).not.toContain('Scan complete for postgres-warehouse');
expect(io.stdout()).not.toContain('structural scan complete');
expect(io.stdout()).not.toContain('Report: raw-sources');
expect(io.stdout()).not.toContain('live-database');
expect(io.stdout()).not.toContain('[5%] Preparing scan');
expect(io.stdout()).not.toContain('What changed');
expect(io.stdout()).not.toContain('Next:');
@ -1278,7 +1272,8 @@ describe('setup databases step', () => {
expect(io.stderr()).toContain('Native SQLite is built for a different Node.js ABI.');
expect(io.stderr()).toContain('│ Native SQLite is built for a different Node.js ABI.');
expect(io.stderr()).toContain('Fix: pnpm run native:rebuild');
expect(io.stderr()).toContain(`Retry: ktx scan --project-dir ${tempDir} warehouse`);
expect(io.stderr()).toContain(`Retry: ktx ingest warehouse --project-dir ${tempDir} --fast`);
expect(io.stderr()).not.toContain('ktx scan');
expect(io.stderr()).not.toContain('npm rebuild');
expect(io.stderr()).not.toMatch(/^Native SQLite is built for a different Node.js ABI\./m);
});
@ -1337,7 +1332,7 @@ describe('setup databases step', () => {
expect(io.stdout()).toContain('◇ Scan complete for warehouse');
});
it('writes Historic SQL config for supported Snowflake databases after validation succeeds', async () => {
it('writes query history config for supported Snowflake databases after validation succeeds', async () => {
const io = makeIo();
const result = await runKtxSetupDatabasesStep(
{
@ -1346,10 +1341,10 @@ describe('setup databases step', () => {
databaseDrivers: ['snowflake'],
databaseConnectionId: 'snowflake',
databaseSchemas: [],
enableHistoricSql: true,
historicSqlWindowDays: 30,
historicSqlServiceAccountPatterns: ['^svc_'],
historicSqlRedactionPatterns: ['(?i)secret'],
enableQueryHistory: true,
queryHistoryWindowDays: 30,
queryHistoryServiceAccountPatterns: ['^svc_'],
queryHistoryRedactionPatterns: ['(?i)secret'],
skipDatabases: false,
},
io.io,
@ -1391,7 +1386,7 @@ describe('setup databases step', () => {
expect(config.ingest.adapters).toEqual([]);
});
it('writes Postgres Historic SQL config with minExecutions and ignores window/redaction output', async () => {
it('writes Postgres query history config with minExecutions and ignores window/redaction output', async () => {
const io = makeIo();
const result = await runKtxSetupDatabasesStep(
{
@ -1401,11 +1396,11 @@ describe('setup databases step', () => {
databaseConnectionId: 'warehouse',
databaseUrl: 'env:DATABASE_URL',
databaseSchemas: ['public'],
enableHistoricSql: true,
historicSqlWindowDays: 30,
historicSqlMinExecutions: 12,
historicSqlServiceAccountPatterns: ['^svc_'],
historicSqlRedactionPatterns: ['(?i)secret'],
enableQueryHistory: true,
queryHistoryWindowDays: 30,
queryHistoryMinExecutions: 12,
queryHistoryServiceAccountPatterns: ['^svc_'],
queryHistoryRedactionPatterns: ['(?i)secret'],
skipDatabases: false,
},
io.io,
@ -1451,11 +1446,12 @@ describe('setup databases step', () => {
expect(configText).not.toMatch(/^\s+adapters:/m);
expect(config.ingest.adapters).toEqual([]);
expect(config.ingest.workUnits.maxConcurrency).toBe(6);
expect(io.stdout()).toContain('Historic SQL probe...');
expect(io.stdout()).toContain('Query history probe...');
expect(io.stdout()).not.toContain('Historic SQL probe...');
expect(io.stdout()).toContain('pg_stat_statements ready');
});
it('writes Historic SQL config for supported existing database connections', async () => {
it('writes query history config for supported existing database connections', async () => {
await writeFile(
join(tempDir, 'ktx.yaml'),
[
@ -1478,8 +1474,8 @@ describe('setup databases step', () => {
inputMode: 'disabled',
databaseConnectionIds: ['analytics'],
databaseSchemas: [],
enableHistoricSql: true,
historicSqlWindowDays: 45,
enableQueryHistory: true,
queryHistoryWindowDays: 45,
skipDatabases: false,
},
io.io,
@ -1511,7 +1507,7 @@ describe('setup databases step', () => {
expect(config.ingest.adapters).toEqual([]);
});
it('enables Historic SQL on an existing Postgres connection', async () => {
it('enables query history on an existing Postgres connection', async () => {
await writeFile(
join(tempDir, 'ktx.yaml'),
[
@ -1533,8 +1529,8 @@ describe('setup databases step', () => {
inputMode: 'disabled',
databaseConnectionIds: ['warehouse'],
databaseSchemas: [],
enableHistoricSql: true,
historicSqlMinExecutions: 8,
enableQueryHistory: true,
queryHistoryMinExecutions: 8,
skipDatabases: false,
},
io.io,
@ -1635,7 +1631,7 @@ describe('setup databases step', () => {
});
});
it('prints a non-blocking Postgres Historic SQL probe failure after connection test succeeds', async () => {
it('prints a non-blocking Postgres query history probe failure after connection test succeeds', async () => {
const io = makeIo();
const historicSqlProbe = vi.fn(async () => ({
ok: false,
@ -1654,7 +1650,7 @@ describe('setup databases step', () => {
databaseConnectionId: 'warehouse',
databaseUrl: 'env:DATABASE_URL',
databaseSchemas: [],
enableHistoricSql: true,
enableQueryHistory: true,
skipDatabases: false,
},
io.io,
@ -1673,12 +1669,13 @@ describe('setup databases step', () => {
dialect: 'postgres',
}),
);
expect(io.stdout()).toContain('Historic SQL probe...');
expect(io.stdout()).toContain('Query history probe...');
expect(io.stdout()).not.toContain('Historic SQL probe...');
expect(io.stdout()).toContain('pg_stat_statements extension is not installed');
expect(io.stdout()).toContain('Setup written; first ingest run will fail until fixed.');
});
it('does not run the Historic SQL probe when the regular connection test fails', async () => {
it('does not run the query history probe when the regular connection test fails', async () => {
const io = makeIo();
const historicSqlProbe = vi.fn(async () => ({ ok: true, lines: [] }));
@ -1690,7 +1687,7 @@ describe('setup databases step', () => {
databaseConnectionId: 'warehouse',
databaseUrl: 'env:DATABASE_URL',
databaseSchemas: [],
enableHistoricSql: true,
enableQueryHistory: true,
skipDatabases: false,
},
io.io,

View file

@ -43,12 +43,12 @@ export interface KtxSetupDatabasesArgs {
databaseConnectionId?: string;
databaseUrl?: string;
databaseSchemas: string[];
enableHistoricSql?: boolean;
disableHistoricSql?: boolean;
historicSqlWindowDays?: number;
historicSqlMinExecutions?: number;
historicSqlServiceAccountPatterns?: string[];
historicSqlRedactionPatterns?: string[];
enableQueryHistory?: boolean;
disableQueryHistory?: boolean;
queryHistoryWindowDays?: number;
queryHistoryMinExecutions?: number;
queryHistoryServiceAccountPatterns?: string[];
queryHistoryRedactionPatterns?: string[];
skipDatabases: boolean;
}
@ -301,7 +301,7 @@ function historicSqlProbeFailureLines(error: unknown): string[] {
if (error instanceof Error && error.name === 'HistoricSqlVersionUnsupportedError') {
return [` FAIL ${error.message}`];
}
return [` FAIL Historic SQL probe failed: ${error instanceof Error ? error.message : String(error)}`];
return [` FAIL Query history probe failed: ${error instanceof Error ? error.message : String(error)}`];
}
async function defaultHistoricSqlProbe(input: KtxSetupHistoricSqlProbeInput): Promise<KtxSetupHistoricSqlProbeResult> {
@ -831,23 +831,23 @@ async function maybeApplyHistoricSqlConfig(input: {
}): Promise<KtxProjectConnectionConfig | 'back'> {
const dialect = HISTORIC_SQL_DIALECT_BY_DRIVER[input.driver];
if (!dialect) {
if (input.args.enableHistoricSql === true) {
if (input.args.enableQueryHistory === true) {
throw new Error(
`Historic SQL setup is only supported for Snowflake, BigQuery, and Postgres, not ${driverLabel(input.driver)}.`,
`Query history setup is only supported for Snowflake, BigQuery, and Postgres, not ${driverLabel(input.driver)}.`,
);
}
return input.connection;
}
let enabled = input.args.enableHistoricSql === true;
if (input.args.disableHistoricSql === true) {
let enabled = input.args.enableQueryHistory === true;
if (input.args.disableQueryHistory === true) {
enabled = false;
} else if (input.args.inputMode !== 'disabled' && input.args.enableHistoricSql !== true && dialect !== 'postgres') {
} else if (input.args.inputMode !== 'disabled' && input.args.enableQueryHistory !== true && dialect !== 'postgres') {
const choice = await input.prompts.select({
message: `Enable Historic SQL query-history ingest for this ${driverLabel(input.driver)} connection?`,
message: `Enable query-history ingest for this ${driverLabel(input.driver)} connection?`,
options: [
{ value: 'yes', label: 'Enable Historic SQL' },
{ value: 'no', label: 'Do not enable Historic SQL' },
{ value: 'yes', label: 'Enable query history' },
{ value: 'no', label: 'Do not enable query history' },
{ value: 'back', label: 'Back' },
],
});
@ -855,7 +855,7 @@ async function maybeApplyHistoricSqlConfig(input: {
enabled = choice === 'yes';
}
if (dialect === 'postgres' && input.args.enableHistoricSql !== true && input.args.disableHistoricSql !== true) {
if (dialect === 'postgres' && input.args.enableQueryHistory !== true && input.args.disableQueryHistory !== true) {
return input.connection;
}
@ -869,20 +869,20 @@ async function maybeApplyHistoricSqlConfig(input: {
const common: Record<string, unknown> = {
...existing,
enabled: true,
filters: historicSqlFiltersForSetup(input.args.historicSqlServiceAccountPatterns),
filters: historicSqlFiltersForSetup(input.args.queryHistoryServiceAccountPatterns),
};
if (dialect === 'postgres') {
return withQueryHistoryConfig(input.connection, {
...common,
minExecutions: input.args.historicSqlMinExecutions ?? 5,
minExecutions: input.args.queryHistoryMinExecutions ?? 5,
});
}
return withQueryHistoryConfig(input.connection, {
...common,
windowDays: input.args.historicSqlWindowDays ?? 90,
redactionPatterns: input.args.historicSqlRedactionPatterns ?? [],
windowDays: input.args.queryHistoryWindowDays ?? 90,
redactionPatterns: input.args.queryHistoryRedactionPatterns ?? [],
});
}
@ -1097,20 +1097,6 @@ function summarizeScanChanges(output: string): string {
return 'no table changes';
}
function shortenScanReportPath(path: string): string {
const normalized = path.trim();
const liveDatabaseMarker = '/live-database/';
const markerIndex = normalized.indexOf(liveDatabaseMarker);
if (markerIndex === -1) {
return normalized;
}
const filename = normalized.split('/').at(-1);
if (!filename) {
return normalized;
}
return `${normalized.slice(0, markerIndex + liveDatabaseMarker.length)}.../${filename}`;
}
function writeSetupSection(io: KtxCliIo, title: string, lines: string[]): void {
io.stdout.write(`${title}\n`);
for (const line of lines) {
@ -1467,7 +1453,7 @@ async function maybeRunHistoricSqlSetupProbe(input: {
return;
}
input.io.stdout.write('│ Historic SQL probe...\n');
input.io.stdout.write('│ Query history probe...\n');
const probe = input.deps.historicSqlProbe ?? defaultHistoricSqlProbe;
const result = await probe({
projectDir: input.projectDir,
@ -1488,7 +1474,7 @@ async function applyHistoricSqlConfigToExistingConnection(input: {
args: KtxSetupDatabasesArgs;
prompts: KtxSetupDatabasesPromptAdapter;
}): Promise<'back' | void> {
if (input.args.enableHistoricSql !== true && input.args.disableHistoricSql !== true) {
if (input.args.enableQueryHistory !== true && input.args.disableQueryHistory !== true) {
return;
}
@ -1557,8 +1543,8 @@ async function validateAndScanConnection(input: {
io: input.io,
deps: input.deps,
});
writeSetupSection(input.io, `Scanning ${input.connectionId}`, [
'Running structural scan…',
writeSetupSection(input.io, `Building schema context for ${input.connectionId}`, [
'Running fast database ingest…',
]);
let scanIo = createBufferedCommandIo();
let scanCode = await scanConnection(input.projectDir, input.connectionId, scanIo);
@ -1567,11 +1553,11 @@ async function validateAndScanConnection(input: {
if (nativeSqliteDetail) {
writePrefixedLines(
(chunk) => input.io.stderr.write(chunk),
[
`Structural scan failed for ${input.connectionId}.`,
'Native SQLite is built for a different Node.js ABI.',
`Detail: ${nativeSqliteDetail}`,
'Rebuilding Native SQLite with pnpm run native:rebuild…',
[
`Fast database ingest failed for ${input.connectionId}.`,
'Native SQLite is built for a different Node.js ABI.',
`Detail: ${nativeSqliteDetail}`,
'Rebuilding Native SQLite with pnpm run native:rebuild…',
].join('\n'),
);
const rebuildNativeSqlite = input.deps.rebuildNativeSqlite ?? defaultRebuildNativeSqlite;
@ -1579,7 +1565,7 @@ async function validateAndScanConnection(input: {
if (rebuildCode === 0) {
writePrefixedLines(
(chunk) => input.io.stderr.write(chunk),
'Native SQLite rebuild complete. Retrying structural scan…',
'Native SQLite rebuild complete. Retrying fast database ingest…',
);
const retryScanIo = createBufferedCommandIo();
scanCode = await scanConnection(input.projectDir, input.connectionId, retryScanIo);
@ -1590,10 +1576,10 @@ async function validateAndScanConnection(input: {
(chunk) => input.io.stderr.write(chunk),
[
rebuildCode === 0
? `Structural scan still failed for ${input.connectionId} after rebuilding Native SQLite.`
? `Fast database ingest still failed for ${input.connectionId} after rebuilding Native SQLite.`
: `Native SQLite rebuild failed for ${input.connectionId}.`,
'Fix: pnpm run native:rebuild',
`Retry: ktx scan --project-dir ${input.projectDir} ${input.connectionId}`,
`Retry: ktx ingest ${input.connectionId} --project-dir ${input.projectDir} --fast`,
].join('\n'),
);
}
@ -1602,8 +1588,8 @@ async function validateAndScanConnection(input: {
writePrefixedLines(
(chunk) => input.io.stderr.write(chunk),
[
`Structural scan failed for ${input.connectionId}.`,
`Debug command: ktx scan --project-dir ${input.projectDir} ${input.connectionId}`,
`Fast database ingest failed for ${input.connectionId}.`,
`Debug command: ktx ingest ${input.connectionId} --project-dir ${input.projectDir} --fast --debug`,
].join('\n'),
);
}
@ -1612,17 +1598,13 @@ async function validateAndScanConnection(input: {
}
}
const scanOutput = scanIo.stdoutText();
const reportPath = readOutputValue(scanOutput, 'Report');
writeSetupSection(
input.io,
`Scan complete for ${input.connectionId}`,
[
`Changes: ${summarizeScanChanges(scanOutput)}`,
...(reportPath ? [`Report: ${shortenScanReportPath(reportPath)}`] : []),
],
`Schema context complete for ${input.connectionId}`,
[`Changes: ${summarizeScanChanges(scanOutput)}`],
);
writeSetupSection(input.io, 'Primary source ready', [
`${input.connectionId} · ${driverDisplay} · structural scan complete`,
`${input.connectionId} · ${driverDisplay} · schema context complete`,
]);
return true;
}