@kaelio/ktx 0.1.0-rc.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/commands/mcp-commands.js +11 -3
- package/dist/commands/mcp-commands.test.js +30 -1
- package/dist/ingest.test.js +2 -26
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.test.js +2 -0
- package/dist/runtime-requirements.d.ts +1 -2
- package/dist/runtime-requirements.js +0 -7
- package/dist/runtime-requirements.test.js +2 -2
- package/dist/setup-agents.d.ts +11 -3
- package/dist/setup-agents.js +397 -134
- package/dist/setup-agents.test.js +359 -61
- package/dist/setup-runtime.d.ts +0 -1
- package/dist/setup-runtime.js +0 -1
- package/dist/setup-runtime.test.js +7 -13
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +51 -25
- package/dist/setup.test.js +112 -16
- package/node_modules/@ktx/connector-clickhouse/dist/package-exports.test.js +1 -1
- package/node_modules/@ktx/context/dist/core/git.service.d.ts +0 -1
- package/node_modules/@ktx/context/dist/core/git.service.js +0 -12
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +1 -2
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +0 -18
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +6 -6
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +4 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +38 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +63 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +0 -5
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +0 -48
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +0 -83
- package/node_modules/@ktx/context/dist/ingest/index.d.ts +2 -1
- package/node_modules/@ktx/context/dist/ingest/index.js +1 -0
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +0 -2
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +0 -166
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +45 -235
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +38 -193
- package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +3 -22
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +4 -0
- package/node_modules/@ktx/context/dist/ingest/local-ingest.js +7 -0
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
- package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
- package/node_modules/@ktx/context/dist/ingest/ports.d.ts +20 -1
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -73
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +0 -27
- package/node_modules/@ktx/context/dist/ingest/reports.d.ts +5 -23
- package/node_modules/@ktx/context/dist/ingest/reports.js +24 -7
- package/node_modules/@ktx/context/dist/ingest/types.d.ts +0 -33
- package/node_modules/@ktx/context/dist/package-exports.test.js +1 -2
- package/package.json +4 -4
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +0 -22
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +0 -95
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +0 -114
- /package/node_modules/@ktx/context/dist/ingest/{finalization-scope.test.d.ts → adapters/historic-sql/post-processor.test.d.ts} +0 -0
|
@@ -36,27 +36,22 @@ describe('runKtxSetupRuntimeStep', () => {
|
|
|
36
36
|
afterEach(async () => {
|
|
37
37
|
await rm(tempDir, { recursive: true, force: true });
|
|
38
38
|
});
|
|
39
|
-
it('
|
|
39
|
+
it('skips runtime setup when the project has no direct runtime requirements', async () => {
|
|
40
40
|
const io = makeIo();
|
|
41
|
-
const ensureRuntime = vi.fn(
|
|
41
|
+
const ensureRuntime = vi.fn();
|
|
42
42
|
await expect(runKtxSetupRuntimeStep({
|
|
43
43
|
projectDir: tempDir,
|
|
44
44
|
inputMode: 'auto',
|
|
45
45
|
cliVersion: '0.2.0',
|
|
46
46
|
runtimeInstallPolicy: 'prompt',
|
|
47
|
-
agents: true,
|
|
48
47
|
}, io.io, {
|
|
49
48
|
loadProject: projectConfig(buildDefaultKtxProjectConfig()),
|
|
50
49
|
ensureRuntime,
|
|
51
50
|
env: {},
|
|
52
|
-
})).resolves.toMatchObject({ status: '
|
|
53
|
-
expect(ensureRuntime).
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
feature: 'core',
|
|
57
|
-
}));
|
|
58
|
-
expect((await readKtxSetupState(tempDir)).completed_steps).toContain('runtime');
|
|
59
|
-
expect(io.stdout()).toContain('Runtime ready: yes (core)');
|
|
51
|
+
})).resolves.toMatchObject({ status: 'skipped' });
|
|
52
|
+
expect(ensureRuntime).not.toHaveBeenCalled();
|
|
53
|
+
expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime');
|
|
54
|
+
expect(io.stdout()).toContain('Runtime setup skipped.');
|
|
60
55
|
});
|
|
61
56
|
it('fails fast when required runtime features cannot be installed in no-input mode', async () => {
|
|
62
57
|
const io = makeIo();
|
|
@@ -68,7 +63,7 @@ describe('runKtxSetupRuntimeStep', () => {
|
|
|
68
63
|
inputMode: 'disabled',
|
|
69
64
|
cliVersion: '0.2.0',
|
|
70
65
|
runtimeInstallPolicy: 'never',
|
|
71
|
-
|
|
66
|
+
databaseIntrospectionFallback: true,
|
|
72
67
|
}, io.io, {
|
|
73
68
|
loadProject: projectConfig(buildDefaultKtxProjectConfig()),
|
|
74
69
|
ensureRuntime,
|
|
@@ -101,7 +96,6 @@ describe('runKtxSetupRuntimeStep', () => {
|
|
|
101
96
|
inputMode: 'auto',
|
|
102
97
|
cliVersion: '0.2.0',
|
|
103
98
|
runtimeInstallPolicy: 'auto',
|
|
104
|
-
agents: false,
|
|
105
99
|
}, io.io, {
|
|
106
100
|
loadProject: projectConfig(config),
|
|
107
101
|
ensureLocalEmbeddings,
|
package/dist/setup.d.ts
CHANGED
|
@@ -143,4 +143,7 @@ export interface ReadKtxSetupStatusOptions {
|
|
|
143
143
|
}
|
|
144
144
|
export declare function readKtxSetupStatus(projectDir: string, options?: ReadKtxSetupStatusOptions): Promise<KtxSetupStatus>;
|
|
145
145
|
export declare function formatKtxSetupStatus(status: KtxSetupStatus): string;
|
|
146
|
+
export declare function formatKtxSetupCompletionSummary(status: KtxSetupStatus, options?: {
|
|
147
|
+
agentNextActions?: string;
|
|
148
|
+
}): string;
|
|
146
149
|
export declare function runKtxSetup(args: KtxSetupArgs, io: KtxCliIo, deps?: KtxSetupDeps): Promise<number>;
|
package/dist/setup.js
CHANGED
|
@@ -7,7 +7,7 @@ import { runtimeInstallPolicyFromFlags } from './managed-python-command.js';
|
|
|
7
7
|
import { readManagedPythonRuntimeStatus } from './managed-python-runtime.js';
|
|
8
8
|
import { resolveProjectRuntimeRequirements } from './runtime-requirements.js';
|
|
9
9
|
import { isKtxSetupExitError } from './setup-interrupt.js';
|
|
10
|
-
import { readKtxAgentInstallManifest, runKtxSetupAgentsStep, } from './setup-agents.js';
|
|
10
|
+
import { readKtxAgentInstallManifest, runKtxSetupAgentsStep, targetDisplayName, } from './setup-agents.js';
|
|
11
11
|
import { runKtxSetupDatabasesStep, } from './setup-databases.js';
|
|
12
12
|
import { runKtxSetupEmbeddingsStep } from './setup-embeddings.js';
|
|
13
13
|
import { isKtxSetupLlmConfigReady, runKtxSetupAnthropicModelStep, } from './setup-models.js';
|
|
@@ -136,7 +136,6 @@ export async function readKtxSetupStatus(projectDir, options = {}) {
|
|
|
136
136
|
}
|
|
137
137
|
const agents = [...agentMap.values()];
|
|
138
138
|
const runtimeRequirements = resolveProjectRuntimeRequirements(project.config, {
|
|
139
|
-
agents: agents.length > 0,
|
|
140
139
|
env: options.env ?? process.env,
|
|
141
140
|
});
|
|
142
141
|
let runtimeReady = runtimeRequirements.features.length === 0 || completedSteps.includes('runtime');
|
|
@@ -219,6 +218,26 @@ export function formatKtxSetupStatus(status) {
|
|
|
219
218
|
}
|
|
220
219
|
return `${lines.join('\n')}\n`;
|
|
221
220
|
}
|
|
221
|
+
export function formatKtxSetupCompletionSummary(status, options = {}) {
|
|
222
|
+
const readyAgents = status.agents.filter((agent) => agent.ready).map((agent) => targetDisplayName(agent.target));
|
|
223
|
+
const lines = [
|
|
224
|
+
'Project',
|
|
225
|
+
` ${status.project.path}`,
|
|
226
|
+
'',
|
|
227
|
+
'Context',
|
|
228
|
+
` ${status.context.ready ? 'built' : formatContextBuilt(status.context)}`,
|
|
229
|
+
'',
|
|
230
|
+
'Agents configured',
|
|
231
|
+
` ${readyAgents.length > 0 ? readyAgents.join(', ') : 'not installed'}`,
|
|
232
|
+
];
|
|
233
|
+
const agentNextActions = options.agentNextActions?.trim();
|
|
234
|
+
if (agentNextActions) {
|
|
235
|
+
lines.push('', 'REQUIRED BEFORE USING AGENTS', '', ...agentNextActions.split('\n').map((line) => (line ? ` ${line}` : '')));
|
|
236
|
+
}
|
|
237
|
+
lines.push('', agentNextActions ? 'After that, try' : 'Try it');
|
|
238
|
+
lines.push(' Ask your agent: "Use KTX to show me the available tables."');
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
}
|
|
222
241
|
function setupStatusReady(status) {
|
|
223
242
|
if (!status.project.ready) {
|
|
224
243
|
return false;
|
|
@@ -238,10 +257,8 @@ function setupHasContextTargets(status) {
|
|
|
238
257
|
function setupContextReady(status) {
|
|
239
258
|
return status.context.ready;
|
|
240
259
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
io.stderr.write(`Build context first:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`);
|
|
244
|
-
io.stderr.write(`Then install agent integration:\n ktx setup --agents --project-dir ${resolve(projectDir)}\n`);
|
|
260
|
+
function shouldPrintConciseReadySummary(status) {
|
|
261
|
+
return setupStatusReady(status) && setupContextReady(status) && status.agents.some((agent) => agent.ready);
|
|
245
262
|
}
|
|
246
263
|
function setupRuntimeInstallPolicy(args) {
|
|
247
264
|
if (args.yes) {
|
|
@@ -269,6 +286,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
269
286
|
setupUi.intro('KTX setup', io);
|
|
270
287
|
let entryAction;
|
|
271
288
|
let projectResult;
|
|
289
|
+
let agentNextActions;
|
|
272
290
|
const canShowEntryMenu = args.showEntryMenu === true &&
|
|
273
291
|
args.inputMode !== 'disabled' &&
|
|
274
292
|
!args.agents &&
|
|
@@ -318,16 +336,17 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
338
|
const runOnly = readyAction;
|
|
339
|
+
const agentOnlySetup = agentsRequested || runOnly === 'agents';
|
|
321
340
|
const shouldRunModels = !runOnly || runOnly === 'models';
|
|
322
341
|
const shouldRunEmbeddings = !runOnly || runOnly === 'embeddings';
|
|
323
342
|
const shouldRunDatabases = !runOnly || runOnly === 'databases';
|
|
324
343
|
const shouldRunSources = !runOnly || runOnly === 'sources';
|
|
325
|
-
const shouldRunRuntime =
|
|
326
|
-
const shouldRunContext =
|
|
344
|
+
const shouldRunRuntime = !agentOnlySetup && (!runOnly || runOnly === 'runtime' || runOnly === 'context');
|
|
345
|
+
const shouldRunContext = !agentOnlySetup && (!runOnly || runOnly === 'context');
|
|
327
346
|
const shouldRunAgents = agentsRequested || !runOnly || runOnly === 'agents';
|
|
328
347
|
const showPromptInstructions = projectResult.confirmedCreation !== true;
|
|
329
|
-
const setupSteps =
|
|
330
|
-
? [
|
|
348
|
+
const setupSteps = agentOnlySetup
|
|
349
|
+
? []
|
|
331
350
|
: ['models', 'embeddings', 'databases', 'sources', 'runtime', 'context'];
|
|
332
351
|
if (shouldRunAgents && args.skipAgents !== true) {
|
|
333
352
|
setupSteps.push('agents');
|
|
@@ -456,7 +475,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
456
475
|
inputMode: args.inputMode,
|
|
457
476
|
cliVersion: args.cliVersion,
|
|
458
477
|
runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
|
|
459
|
-
agents: shouldRunAgents && args.skipAgents !== true,
|
|
460
478
|
}, io);
|
|
461
479
|
}
|
|
462
480
|
else if (step === 'context') {
|
|
@@ -473,7 +491,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
473
491
|
}
|
|
474
492
|
else {
|
|
475
493
|
const agentsRunner = deps.agents ?? ((agentArgs, agentIo) => runKtxSetupAgentsStep(agentArgs, agentIo, deps.agentsDeps));
|
|
476
|
-
|
|
494
|
+
const agentResult = await agentsRunner({
|
|
477
495
|
projectDir: projectResult.projectDir,
|
|
478
496
|
inputMode: args.inputMode,
|
|
479
497
|
yes: args.yes,
|
|
@@ -482,7 +500,12 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
482
500
|
scope: args.agentScope ?? 'project',
|
|
483
501
|
mode: 'mcp',
|
|
484
502
|
skipAgents: false,
|
|
503
|
+
showNextActions: agentsRequested,
|
|
485
504
|
}, io);
|
|
505
|
+
stepResult = agentResult;
|
|
506
|
+
if (agentResult.status === 'ready') {
|
|
507
|
+
agentNextActions = agentResult.nextActions;
|
|
508
|
+
}
|
|
486
509
|
}
|
|
487
510
|
if (stepResult.status === 'failed' || stepResult.status === 'missing-input') {
|
|
488
511
|
return 1;
|
|
@@ -504,10 +527,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
504
527
|
}
|
|
505
528
|
if (step === 'context' && stepResult.status !== 'ready') {
|
|
506
529
|
if (shouldRunAgents && args.skipAgents !== true) {
|
|
507
|
-
if (agentsRequested) {
|
|
508
|
-
writeContextNotReadyForAgents(projectResult.projectDir, io);
|
|
509
|
-
return args.inputMode === 'disabled' ? 1 : 0;
|
|
510
|
-
}
|
|
511
530
|
return 0;
|
|
512
531
|
}
|
|
513
532
|
}
|
|
@@ -520,15 +539,22 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
520
539
|
const status = await readKtxSetupStatus(projectResult.projectDir, { cliVersion: args.cliVersion });
|
|
521
540
|
const focusedOnAgents = args.agents || entryAction === 'agents';
|
|
522
541
|
if (!focusedOnAgents) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
542
|
+
if (shouldPrintConciseReadySummary(status)) {
|
|
543
|
+
setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish KTX agent setup' : 'KTX project ready', io, {
|
|
544
|
+
format: (line) => line,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
setupUi.note(formatKtxSetupStatus(status).trimEnd(), 'Project status', io, {
|
|
549
|
+
format: (line) => line,
|
|
550
|
+
});
|
|
551
|
+
setupUi.note(formatSetupNextStepLines({
|
|
552
|
+
setupReady: setupStatusReady(status),
|
|
553
|
+
hasContextTargets: setupHasContextTargets(status),
|
|
554
|
+
contextReady: setupContextReady(status),
|
|
555
|
+
agentIntegrationReady: status.agents.some((agent) => agent.ready),
|
|
556
|
+
}).join('\n'), 'What you can do next', io);
|
|
557
|
+
}
|
|
532
558
|
}
|
|
533
559
|
return 0;
|
|
534
560
|
}
|
package/dist/setup.test.js
CHANGED
|
@@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
8
8
|
import { localFakeBundleReport, persistLocalBundleReport } from './ingest.test-utils.js';
|
|
9
9
|
import { contextBuildCommands, writeKtxSetupContextState } from './setup-context.js';
|
|
10
10
|
import { runDemoTour } from './setup-demo-tour.js';
|
|
11
|
-
import { formatKtxSetupStatus, readKtxSetupStatus, runKtxSetup } from './setup.js';
|
|
11
|
+
import { formatKtxSetupCompletionSummary, formatKtxSetupStatus, readKtxSetupStatus, runKtxSetup } from './setup.js';
|
|
12
12
|
vi.mock('./setup-demo-tour.js', () => ({
|
|
13
13
|
runDemoTour: vi.fn(async () => 0),
|
|
14
14
|
}));
|
|
@@ -337,6 +337,98 @@ describe('setup status', () => {
|
|
|
337
337
|
expect(rendered).toContain('KTX context built: no');
|
|
338
338
|
expect(rendered).not.toContain('No KTX project found.');
|
|
339
339
|
});
|
|
340
|
+
it('formats a concise ready summary for completed agent setup', () => {
|
|
341
|
+
const rendered = formatKtxSetupCompletionSummary({
|
|
342
|
+
project: { path: tempDir, ready: true },
|
|
343
|
+
llm: { ready: true, model: 'sonnet' },
|
|
344
|
+
embeddings: { ready: true, model: 'text-embedding-3-small' },
|
|
345
|
+
databases: [{ connectionId: 'postgres-warehouse', ready: true }],
|
|
346
|
+
sources: [{ connectionId: 'dbt-main', type: 'dbt', ready: true }],
|
|
347
|
+
runtime: { required: true, ready: true, features: ['core'] },
|
|
348
|
+
context: { ready: true, status: 'completed' },
|
|
349
|
+
agents: [
|
|
350
|
+
{ target: 'claude-code', scope: 'project', ready: true },
|
|
351
|
+
{ target: 'claude-desktop', scope: 'global', ready: true },
|
|
352
|
+
],
|
|
353
|
+
}, {
|
|
354
|
+
agentNextActions: [
|
|
355
|
+
'1. Start MCP',
|
|
356
|
+
' Run this command before using Claude Code:',
|
|
357
|
+
'',
|
|
358
|
+
' RUN:',
|
|
359
|
+
` ktx mcp start --project-dir ${tempDir}`,
|
|
360
|
+
'',
|
|
361
|
+
' If you need to stop MCP later:',
|
|
362
|
+
` ktx mcp stop --project-dir ${tempDir}`,
|
|
363
|
+
'',
|
|
364
|
+
'2. Open Claude Code',
|
|
365
|
+
' Open Claude Code from the KTX project directory:',
|
|
366
|
+
'',
|
|
367
|
+
' RUN:',
|
|
368
|
+
` cd '${tempDir}'`,
|
|
369
|
+
' claude',
|
|
370
|
+
].join('\n'),
|
|
371
|
+
});
|
|
372
|
+
expect(rendered).toContain(`Project\n ${tempDir}`);
|
|
373
|
+
expect(rendered).toContain('Context\n built');
|
|
374
|
+
expect(rendered).toContain('Agents configured\n Claude Code, Claude Desktop');
|
|
375
|
+
expect(rendered).toContain('REQUIRED BEFORE USING AGENTS\n\n 1. Start MCP');
|
|
376
|
+
expect(rendered).toContain(' Run this command before using Claude Code:');
|
|
377
|
+
expect(rendered).toContain(' RUN:');
|
|
378
|
+
expect(rendered).toContain(' If you need to stop MCP later:');
|
|
379
|
+
expect(rendered).toContain(`ktx mcp stop --project-dir ${tempDir}`);
|
|
380
|
+
expect(rendered).toContain('After that, try\n Ask your agent: "Use KTX to show me the available tables."');
|
|
381
|
+
expect(rendered).not.toContain('Verify');
|
|
382
|
+
expect(rendered).not.toContain('Project ready: yes');
|
|
383
|
+
expect(rendered).not.toContain('What you can do next');
|
|
384
|
+
});
|
|
385
|
+
it('prints agent next actions inside the final ready summary during full setup', async () => {
|
|
386
|
+
const testIo = makeIo();
|
|
387
|
+
await expect(runKtxSetup({
|
|
388
|
+
command: 'run',
|
|
389
|
+
projectDir: tempDir,
|
|
390
|
+
mode: 'new',
|
|
391
|
+
agents: false,
|
|
392
|
+
target: 'claude-code',
|
|
393
|
+
skipAgents: false,
|
|
394
|
+
inputMode: 'disabled',
|
|
395
|
+
yes: true,
|
|
396
|
+
cliVersion: '0.2.0',
|
|
397
|
+
skipLlm: true,
|
|
398
|
+
skipEmbeddings: true,
|
|
399
|
+
skipDatabases: true,
|
|
400
|
+
skipSources: true,
|
|
401
|
+
databaseSchemas: [],
|
|
402
|
+
}, testIo.io, {
|
|
403
|
+
runtime: async () => runtimeReady(tempDir),
|
|
404
|
+
context: async () => {
|
|
405
|
+
await writeKtxSetupContextState(tempDir, {
|
|
406
|
+
runId: 'setup-context-local-test',
|
|
407
|
+
status: 'completed',
|
|
408
|
+
primarySourceConnectionIds: [],
|
|
409
|
+
contextSourceConnectionIds: [],
|
|
410
|
+
reportIds: [],
|
|
411
|
+
artifactPaths: [],
|
|
412
|
+
retryableFailedTargets: [],
|
|
413
|
+
commands: contextBuildCommands(tempDir, 'setup-context-local-test'),
|
|
414
|
+
});
|
|
415
|
+
await writeKtxSetupState(tempDir, { completed_steps: ['project', 'context'] });
|
|
416
|
+
return { status: 'ready', projectDir: tempDir, runId: 'setup-context-local-test' };
|
|
417
|
+
},
|
|
418
|
+
})).resolves.toBe(0);
|
|
419
|
+
const output = testIo.stdout();
|
|
420
|
+
expect(output).toContain('Claude Code · Project scope');
|
|
421
|
+
expect(output).toContain(join(tempDir, '.mcp.json'));
|
|
422
|
+
expect(output).toContain('Requires MCP to be started.');
|
|
423
|
+
expect(output).toContain('Analytics skill installed.');
|
|
424
|
+
expect(output).not.toContain('Agent integration complete');
|
|
425
|
+
expect(output).toContain('Finish KTX agent setup');
|
|
426
|
+
expect(output).not.toContain('KTX project ready');
|
|
427
|
+
expect(output).toContain('REQUIRED BEFORE USING AGENTS');
|
|
428
|
+
expect(output).toContain('Run this command before using Claude Code:');
|
|
429
|
+
expect(output).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
430
|
+
expect(output).not.toContain('Finish agent setup');
|
|
431
|
+
});
|
|
340
432
|
it('prints the setup shell intro for auto-created run mode', async () => {
|
|
341
433
|
const testIo = makeIo();
|
|
342
434
|
await expect(runKtxSetup({
|
|
@@ -1257,7 +1349,7 @@ describe('setup status', () => {
|
|
|
1257
1349
|
const committedConfig = await execFileAsync('git', ['-C', tempDir, 'show', 'HEAD:ktx.yaml']);
|
|
1258
1350
|
expect(committedConfig.stdout).toContain('warehouse:');
|
|
1259
1351
|
});
|
|
1260
|
-
it('runs agent setup
|
|
1352
|
+
it('runs agent setup without runtime or context in --agents mode', async () => {
|
|
1261
1353
|
const calls = [];
|
|
1262
1354
|
const io = makeIo();
|
|
1263
1355
|
await writeFile(join(tempDir, 'ktx.yaml'), ['connections: {}', ''].join('\n'), 'utf-8');
|
|
@@ -1284,11 +1376,11 @@ describe('setup status', () => {
|
|
|
1284
1376
|
sources: async () => ({ status: 'skipped', projectDir: tempDir }),
|
|
1285
1377
|
runtime: async () => {
|
|
1286
1378
|
calls.push('runtime');
|
|
1287
|
-
|
|
1379
|
+
throw new Error('runtime should not run');
|
|
1288
1380
|
},
|
|
1289
1381
|
context: async () => {
|
|
1290
1382
|
calls.push('context');
|
|
1291
|
-
|
|
1383
|
+
throw new Error('context should not run');
|
|
1292
1384
|
},
|
|
1293
1385
|
agents: async () => {
|
|
1294
1386
|
calls.push('agents');
|
|
@@ -1299,10 +1391,12 @@ describe('setup status', () => {
|
|
|
1299
1391
|
};
|
|
1300
1392
|
},
|
|
1301
1393
|
})).resolves.toBe(0);
|
|
1302
|
-
expect(calls).toEqual(['
|
|
1394
|
+
expect(calls).toEqual(['agents']);
|
|
1303
1395
|
});
|
|
1304
|
-
it('
|
|
1396
|
+
it('installs agents when non-interactive --agents finds context incomplete', async () => {
|
|
1305
1397
|
const io = makeIo();
|
|
1398
|
+
const runtime = vi.fn(async () => runtimeReady(tempDir));
|
|
1399
|
+
const context = vi.fn(async () => ({ status: 'skipped', projectDir: tempDir }));
|
|
1306
1400
|
const agents = vi.fn(async () => ({
|
|
1307
1401
|
status: 'ready',
|
|
1308
1402
|
projectDir: tempDir,
|
|
@@ -1326,12 +1420,14 @@ describe('setup status', () => {
|
|
|
1326
1420
|
skipAgents: false,
|
|
1327
1421
|
databaseSchemas: [],
|
|
1328
1422
|
}, io.io, {
|
|
1329
|
-
runtime
|
|
1330
|
-
context
|
|
1423
|
+
runtime,
|
|
1424
|
+
context,
|
|
1331
1425
|
agents,
|
|
1332
|
-
})).resolves.toBe(
|
|
1333
|
-
expect(
|
|
1334
|
-
expect(
|
|
1426
|
+
})).resolves.toBe(0);
|
|
1427
|
+
expect(runtime).not.toHaveBeenCalled();
|
|
1428
|
+
expect(context).not.toHaveBeenCalled();
|
|
1429
|
+
expect(agents).toHaveBeenCalledTimes(1);
|
|
1430
|
+
expect(io.stderr()).not.toContain('KTX context is not ready for agents.');
|
|
1335
1431
|
});
|
|
1336
1432
|
it('routes a ready project menu selection to agent setup', async () => {
|
|
1337
1433
|
const calls = [];
|
|
@@ -1433,7 +1529,7 @@ describe('setup status', () => {
|
|
|
1433
1529
|
process.env.KTX_RUNTIME_ROOT = previousRuntimeRoot;
|
|
1434
1530
|
}
|
|
1435
1531
|
}
|
|
1436
|
-
expect(calls).toEqual(['
|
|
1532
|
+
expect(calls).toEqual(['agents']);
|
|
1437
1533
|
});
|
|
1438
1534
|
it('skips to agent setup when context is ready but agents are not configured', async () => {
|
|
1439
1535
|
const calls = [];
|
|
@@ -1517,9 +1613,9 @@ describe('setup status', () => {
|
|
|
1517
1613
|
},
|
|
1518
1614
|
})).resolves.toBe(0);
|
|
1519
1615
|
expect(readyMenuSelect).not.toHaveBeenCalled();
|
|
1520
|
-
expect(calls).toEqual(['
|
|
1616
|
+
expect(calls).toEqual(['agents']);
|
|
1521
1617
|
});
|
|
1522
|
-
it('runs only project resolution
|
|
1618
|
+
it('runs only project resolution and agent setup in --agents mode', async () => {
|
|
1523
1619
|
const io = makeIo();
|
|
1524
1620
|
const runtime = vi.fn(async () => runtimeReady(tempDir));
|
|
1525
1621
|
const context = vi.fn(async () => ({ status: 'ready', projectDir: tempDir, runId: 'setup-context-local-test' }));
|
|
@@ -1552,8 +1648,8 @@ describe('setup status', () => {
|
|
|
1552
1648
|
context,
|
|
1553
1649
|
agents,
|
|
1554
1650
|
})).resolves.toBe(0);
|
|
1555
|
-
expect(runtime).
|
|
1556
|
-
expect(context).
|
|
1651
|
+
expect(runtime).not.toHaveBeenCalled();
|
|
1652
|
+
expect(context).not.toHaveBeenCalled();
|
|
1557
1653
|
expect(agents).toHaveBeenCalledTimes(1);
|
|
1558
1654
|
});
|
|
1559
1655
|
it('does not run embedding setup when the model step fails', async () => {
|
|
@@ -6,5 +6,5 @@ describe('@ktx/connector-clickhouse package exports', () => {
|
|
|
6
6
|
expect(connector.KtxClickHouseScanConnector).toBeTypeOf('function');
|
|
7
7
|
expect(connector.clickHouseClientConfigFromConfig).toBeTypeOf('function');
|
|
8
8
|
expect(connector.createClickHouseLiveDatabaseIntrospection).toBeTypeOf('function');
|
|
9
|
-
});
|
|
9
|
+
}, 20_000);
|
|
10
10
|
});
|
|
@@ -101,7 +101,6 @@ export declare class GitService {
|
|
|
101
101
|
status: 'A' | 'M' | 'D';
|
|
102
102
|
path: string;
|
|
103
103
|
}>>;
|
|
104
|
-
changedPaths(): Promise<string[]>;
|
|
105
104
|
/**
|
|
106
105
|
* List all paths under the working tree that match `pathSpec`, scoped to HEAD.
|
|
107
106
|
* Used for the reconciler's first-ever run when there's no watermark to diff from.
|
|
@@ -424,18 +424,6 @@ export class GitService {
|
|
|
424
424
|
}
|
|
425
425
|
return out;
|
|
426
426
|
}
|
|
427
|
-
async changedPaths() {
|
|
428
|
-
const raw = await this.git.raw(['status', '--porcelain=v1', '-z']);
|
|
429
|
-
const fields = raw.split('\0').filter(Boolean);
|
|
430
|
-
const paths = [];
|
|
431
|
-
for (const field of fields) {
|
|
432
|
-
const path = field.slice(3);
|
|
433
|
-
if (path.length > 0) {
|
|
434
|
-
paths.push(path);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
return [...new Set(paths)].sort();
|
|
438
|
-
}
|
|
439
427
|
/**
|
|
440
428
|
* List all paths under the working tree that match `pathSpec`, scoped to HEAD.
|
|
441
429
|
* Used for the reconciler's first-ever run when there's no watermark to diff from.
|
package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChunkResult,
|
|
1
|
+
import type { ChunkResult, DiffSet, FetchContext, ScopeDescriptor, SourceAdapter } from '../../types.js';
|
|
2
2
|
import { type HistoricSqlSourceAdapterDeps } from './types.js';
|
|
3
3
|
export declare class HistoricSqlSourceAdapter implements SourceAdapter {
|
|
4
4
|
private readonly deps;
|
|
@@ -11,5 +11,4 @@ export declare class HistoricSqlSourceAdapter implements SourceAdapter {
|
|
|
11
11
|
fetch(pullConfig: unknown, stagedDir: string, ctx: FetchContext): Promise<void>;
|
|
12
12
|
chunk(stagedDir: string, diffSet?: DiffSet): Promise<ChunkResult>;
|
|
13
13
|
describeScope(stagedDir: string): Promise<ScopeDescriptor>;
|
|
14
|
-
finalize(ctx: DeterministicFinalizationContext): Promise<FinalizationResult>;
|
|
15
14
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { chunkHistoricSqlUnifiedStagedDir, describeHistoricSqlUnifiedScope } from './chunk-unified.js';
|
|
2
2
|
import { detectHistoricSqlStagedDir } from './detect.js';
|
|
3
|
-
import { projectHistoricSqlEvidence } from './projection.js';
|
|
4
3
|
import { stageHistoricSqlAggregatedSnapshot } from './stage-unified.js';
|
|
5
4
|
export class HistoricSqlSourceAdapter {
|
|
6
5
|
deps;
|
|
@@ -31,21 +30,4 @@ export class HistoricSqlSourceAdapter {
|
|
|
31
30
|
describeScope(stagedDir) {
|
|
32
31
|
return describeHistoricSqlUnifiedScope(stagedDir);
|
|
33
32
|
}
|
|
34
|
-
async finalize(ctx) {
|
|
35
|
-
const projection = await projectHistoricSqlEvidence({
|
|
36
|
-
workdir: ctx.workdir,
|
|
37
|
-
connectionId: ctx.connectionId,
|
|
38
|
-
syncId: ctx.syncId,
|
|
39
|
-
runId: ctx.runId,
|
|
40
|
-
overrideReplay: ctx.overrideReplay,
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
result: projection,
|
|
44
|
-
warnings: projection.warnings,
|
|
45
|
-
errors: [],
|
|
46
|
-
touchedSources: projection.touchedSources,
|
|
47
|
-
changedWikiPageKeys: projection.changedWikiPageKeys,
|
|
48
|
-
actions: projection.actions,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
33
|
}
|
package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js
CHANGED
|
@@ -194,12 +194,12 @@ describe('historic-SQL local ingest retrieval acceptance', () => {
|
|
|
194
194
|
expect(result.result.failedWorkUnits).toEqual([]);
|
|
195
195
|
expect(result.result.workUnitCount).toBe(3);
|
|
196
196
|
expect(agentRunner.runLoop).toHaveBeenCalledTimes(3);
|
|
197
|
-
const
|
|
198
|
-
expect(
|
|
199
|
-
if (!
|
|
200
|
-
throw new Error('Expected historic-SQL
|
|
197
|
+
const postProcessor = result.report.body.postProcessor;
|
|
198
|
+
expect(postProcessor).toBeDefined();
|
|
199
|
+
if (!postProcessor) {
|
|
200
|
+
throw new Error('Expected historic-SQL post-processor result');
|
|
201
201
|
}
|
|
202
|
-
expect(
|
|
202
|
+
expect(postProcessor).toMatchObject({
|
|
203
203
|
sourceKey: 'historic-sql',
|
|
204
204
|
status: 'success',
|
|
205
205
|
result: {
|
|
@@ -207,7 +207,7 @@ describe('historic-SQL local ingest retrieval acceptance', () => {
|
|
|
207
207
|
patternPagesWritten: 1,
|
|
208
208
|
},
|
|
209
209
|
});
|
|
210
|
-
expect(
|
|
210
|
+
expect(postProcessor.touchedSources).toEqual(expect.arrayContaining([
|
|
211
211
|
{ connectionId: 'warehouse', sourceName: 'customers' },
|
|
212
212
|
{ connectionId: 'warehouse', sourceName: 'orders' },
|
|
213
213
|
]));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IngestBundlePostProcessorInput, IngestBundlePostProcessorPort, IngestBundlePostProcessorResult } from '../../ports.js';
|
|
2
|
+
export declare class HistoricSqlProjectionPostProcessor implements IngestBundlePostProcessorPort {
|
|
3
|
+
run(input: IngestBundlePostProcessorInput): Promise<IngestBundlePostProcessorResult>;
|
|
4
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createSimpleGit } from '../../../core/git-env.js';
|
|
2
|
+
import { projectHistoricSqlEvidence } from './projection.js';
|
|
3
|
+
async function commitProjectionChanges(workdir) {
|
|
4
|
+
const git = createSimpleGit(workdir);
|
|
5
|
+
if (!(await git.checkIsRepo().catch(() => false))) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const status = await git.status();
|
|
9
|
+
const paths = status.files
|
|
10
|
+
.map((file) => file.path)
|
|
11
|
+
.filter((path) => path.startsWith('semantic-layer/') || path.startsWith('wiki/global/historic-sql'));
|
|
12
|
+
if (paths.length === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
await git.add(paths);
|
|
16
|
+
const staged = await git.diff(['--cached', '--name-only']);
|
|
17
|
+
if (!staged.trim()) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await git.commit('Project historic SQL evidence', { '--author': 'System User <system@example.com>' });
|
|
21
|
+
}
|
|
22
|
+
export class HistoricSqlProjectionPostProcessor {
|
|
23
|
+
async run(input) {
|
|
24
|
+
const projection = await projectHistoricSqlEvidence({
|
|
25
|
+
workdir: input.workdir,
|
|
26
|
+
connectionId: input.connectionId,
|
|
27
|
+
syncId: input.syncId,
|
|
28
|
+
runId: input.runId,
|
|
29
|
+
});
|
|
30
|
+
await commitProjectionChanges(input.workdir);
|
|
31
|
+
return {
|
|
32
|
+
result: projection,
|
|
33
|
+
warnings: projection.warnings,
|
|
34
|
+
errors: [],
|
|
35
|
+
touchedSources: projection.touchedSources,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { HistoricSqlProjectionPostProcessor } from './post-processor.js';
|
|
7
|
+
async function tempWorkdir() {
|
|
8
|
+
return mkdtemp(join(tmpdir(), 'historic-sql-post-processor-'));
|
|
9
|
+
}
|
|
10
|
+
async function writeJson(root, relPath, value) {
|
|
11
|
+
const target = join(root, relPath);
|
|
12
|
+
await mkdir(join(target, '..'), { recursive: true });
|
|
13
|
+
await writeFile(target, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
describe('HistoricSqlProjectionPostProcessor', () => {
|
|
16
|
+
it('projects current run evidence before the ingest squash commit', async () => {
|
|
17
|
+
const workdir = await tempWorkdir();
|
|
18
|
+
await mkdir(join(workdir, 'semantic-layer/warehouse/_schema'), { recursive: true });
|
|
19
|
+
await writeFile(join(workdir, 'semantic-layer/warehouse/_schema/public.yaml'), YAML.stringify({ tables: { orders: { table: 'public.orders', columns: [{ name: 'id', type: 'string' }] } } }), 'utf-8');
|
|
20
|
+
await writeJson(workdir, 'raw-sources/warehouse/historic-sql/sync-1/manifest.json', {
|
|
21
|
+
source: 'historic-sql',
|
|
22
|
+
connectionId: 'warehouse',
|
|
23
|
+
dialect: 'postgres',
|
|
24
|
+
fetchedAt: '2026-05-11T00:00:00.000Z',
|
|
25
|
+
windowStart: '2026-02-10T00:00:00.000Z',
|
|
26
|
+
windowEnd: '2026-05-11T00:00:00.000Z',
|
|
27
|
+
snapshotRowCount: 1,
|
|
28
|
+
touchedTableCount: 1,
|
|
29
|
+
parseFailures: 0,
|
|
30
|
+
warnings: [],
|
|
31
|
+
probeWarnings: [],
|
|
32
|
+
staleArchiveAfterDays: 90,
|
|
33
|
+
});
|
|
34
|
+
await writeJson(workdir, 'raw-sources/warehouse/historic-sql/sync-1/tables/public.orders.json', { table: 'public.orders' });
|
|
35
|
+
await writeJson(workdir, '.ktx/ingest-evidence/historic-sql/run-1/orders.json', {
|
|
36
|
+
kind: 'table_usage',
|
|
37
|
+
connectionId: 'warehouse',
|
|
38
|
+
table: 'public.orders',
|
|
39
|
+
rawPath: 'tables/public.orders.json',
|
|
40
|
+
usage: {
|
|
41
|
+
narrative: 'Orders are repeatedly queried by lifecycle status.',
|
|
42
|
+
frequencyTier: 'high',
|
|
43
|
+
commonFilters: ['status'],
|
|
44
|
+
commonJoins: [],
|
|
45
|
+
staleSince: null,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const result = await new HistoricSqlProjectionPostProcessor().run({
|
|
49
|
+
connectionId: 'warehouse',
|
|
50
|
+
sourceKey: 'historic-sql',
|
|
51
|
+
syncId: 'sync-1',
|
|
52
|
+
jobId: 'job-1',
|
|
53
|
+
runId: 'run-1',
|
|
54
|
+
workdir,
|
|
55
|
+
parseArtifacts: null,
|
|
56
|
+
});
|
|
57
|
+
expect(result.errors).toEqual([]);
|
|
58
|
+
expect(result.warnings).toEqual([]);
|
|
59
|
+
expect(result.touchedSources).toEqual([{ connectionId: 'warehouse', sourceName: 'orders' }]);
|
|
60
|
+
expect(result.result).toMatchObject({ tableUsageMerged: 1 });
|
|
61
|
+
await expect(readFile(join(workdir, 'semantic-layer/warehouse/_schema/public.yaml'), 'utf-8')).resolves.toContain('Orders are repeatedly queried by lifecycle status.');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import type { MemoryAction } from '../../../memory/index.js';
|
|
2
|
-
import type { FinalizationOverrideReplay } from '../../types.js';
|
|
3
1
|
export interface HistoricSqlProjectionInput {
|
|
4
2
|
workdir: string;
|
|
5
3
|
connectionId: string;
|
|
6
4
|
syncId: string;
|
|
7
5
|
runId: string;
|
|
8
|
-
overrideReplay?: FinalizationOverrideReplay;
|
|
9
6
|
}
|
|
10
7
|
export interface HistoricSqlProjectionResult {
|
|
11
8
|
tableUsageMerged: number;
|
|
@@ -17,8 +14,6 @@ export interface HistoricSqlProjectionResult {
|
|
|
17
14
|
connectionId: string;
|
|
18
15
|
sourceName: string;
|
|
19
16
|
}>;
|
|
20
|
-
changedWikiPageKeys: string[];
|
|
21
|
-
actions: MemoryAction[];
|
|
22
17
|
warnings: string[];
|
|
23
18
|
}
|
|
24
19
|
export declare function projectHistoricSqlEvidence(input: HistoricSqlProjectionInput): Promise<HistoricSqlProjectionResult>;
|