@kaelio/ktx 0.11.0 → 0.12.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.11.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +3 -3
- package/dist/cli-runtime.js +2 -2
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +6 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/connection.js +1 -1
- package/dist/connectors/clickhouse/connector.js +1 -1
- package/dist/connectors/mysql/connector.js +1 -1
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +3 -3
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +116 -2
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +71 -8
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +1 -1
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +7 -7
- package/dist/context/mcp/local-project-ports.js +23 -54
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +2 -1
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +3 -33
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +44 -69
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +46 -0
- package/dist/context/sl/source-files.js +131 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +8 -0
- package/dist/io/tty.js +16 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.js +21 -30
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.js +31 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -1
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +27 -7
- package/dist/setup.js +13 -13
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/events.d.ts +1 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +1 -1
|
@@ -1,13 +1,118 @@
|
|
|
1
|
+
import { findMissingJoinTargets, formatMissingJoinTarget } from '../../../context/sl/semantic-layer.service.js';
|
|
2
|
+
export function formatInvalidWuSources(invalid) {
|
|
3
|
+
return invalid.map((entry) => `${entry.source} (${entry.errors.join('; ')})`).join(', ');
|
|
4
|
+
}
|
|
5
|
+
function uniqueTouchedSources(sources) {
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const unique = [];
|
|
8
|
+
for (const source of sources) {
|
|
9
|
+
const key = `${source.connectionId}:${source.sourceName}`;
|
|
10
|
+
if (seen.has(key)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
seen.add(key);
|
|
14
|
+
unique.push(source);
|
|
15
|
+
}
|
|
16
|
+
return unique.sort((left, right) => {
|
|
17
|
+
const byConnection = left.connectionId.localeCompare(right.connectionId);
|
|
18
|
+
return byConnection === 0 ? left.sourceName.localeCompare(right.sourceName) : byConnection;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Expand the touched set with direct join neighbors that exist: targets the
|
|
23
|
+
* touched sources join to, and existing sources that join to a touched one.
|
|
24
|
+
* Missing targets are not added here — they are reported as join-target
|
|
25
|
+
* errors on the source that declares them, so the failure names the file
|
|
26
|
+
* that must change instead of the phantom neighbor.
|
|
27
|
+
*/
|
|
28
|
+
function expandWithExistingJoinNeighbors(touched, sourcesByConnection) {
|
|
29
|
+
const expanded = [...touched];
|
|
30
|
+
const touchedByConnection = new Map();
|
|
31
|
+
for (const source of touched) {
|
|
32
|
+
const bucket = touchedByConnection.get(source.connectionId) ?? new Set();
|
|
33
|
+
bucket.add(source.sourceName);
|
|
34
|
+
touchedByConnection.set(source.connectionId, bucket);
|
|
35
|
+
}
|
|
36
|
+
for (const [connectionId, sources] of sourcesByConnection) {
|
|
37
|
+
const touchedNames = touchedByConnection.get(connectionId);
|
|
38
|
+
if (!touchedNames || touchedNames.size === 0) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const existingNames = new Set(sources.map((source) => source.name));
|
|
42
|
+
for (const source of sources) {
|
|
43
|
+
if (touchedNames.has(source.name)) {
|
|
44
|
+
for (const join of source.joins ?? []) {
|
|
45
|
+
if (existingNames.has(join.to)) {
|
|
46
|
+
expanded.push({ connectionId, sourceName: join.to });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if ((source.joins ?? []).some((join) => touchedNames.has(join.to))) {
|
|
51
|
+
expanded.push({ connectionId, sourceName: source.name });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return uniqueTouchedSources(expanded);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Join-target errors attributable to this change set: every join declared by
|
|
59
|
+
* a touched source must resolve, and no source may be left joining to a name
|
|
60
|
+
* this change set removed. Pre-existing dangling joins on untouched sources
|
|
61
|
+
* are out of scope — they must not block unrelated work. Resolution is the
|
|
62
|
+
* Python engine's: exact source-name match within the connection.
|
|
63
|
+
*/
|
|
64
|
+
function findJoinTargetErrors(touched, sourcesByConnection) {
|
|
65
|
+
const errorsBySource = new Map();
|
|
66
|
+
const touchedByConnection = new Map();
|
|
67
|
+
for (const source of touched) {
|
|
68
|
+
const bucket = touchedByConnection.get(source.connectionId) ?? new Set();
|
|
69
|
+
bucket.add(source.sourceName);
|
|
70
|
+
touchedByConnection.set(source.connectionId, bucket);
|
|
71
|
+
}
|
|
72
|
+
for (const [connectionId, sources] of sourcesByConnection) {
|
|
73
|
+
const touchedNames = touchedByConnection.get(connectionId);
|
|
74
|
+
if (!touchedNames || touchedNames.size === 0) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const existingNames = sources.map((source) => source.name);
|
|
78
|
+
for (const source of sources) {
|
|
79
|
+
const sourceIsTouched = touchedNames.has(source.name);
|
|
80
|
+
const candidateJoins = sourceIsTouched
|
|
81
|
+
? source.joins
|
|
82
|
+
: (source.joins ?? []).filter((join) => touchedNames.has(join.to));
|
|
83
|
+
const missing = findMissingJoinTargets(candidateJoins, existingNames);
|
|
84
|
+
if (missing.length === 0) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const key = `${connectionId}:${source.name}`;
|
|
88
|
+
const messages = missing.map(formatMissingJoinTarget);
|
|
89
|
+
errorsBySource.set(key, [...(errorsBySource.get(key) ?? []), ...messages]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return errorsBySource;
|
|
93
|
+
}
|
|
1
94
|
export async function validateWuTouchedSources(deps, touched) {
|
|
95
|
+
if (touched.length === 0) {
|
|
96
|
+
return { validSources: [], invalidSources: [] };
|
|
97
|
+
}
|
|
98
|
+
const sourcesByConnection = new Map();
|
|
99
|
+
for (const connectionId of new Set(touched.map((source) => source.connectionId))) {
|
|
100
|
+
const { sources } = await deps.semanticLayerService.loadAllSources(connectionId);
|
|
101
|
+
sourcesByConnection.set(connectionId, sources);
|
|
102
|
+
}
|
|
103
|
+
const expanded = expandWithExistingJoinNeighbors(touched, sourcesByConnection);
|
|
104
|
+
const joinTargetErrors = findJoinTargetErrors(touched, sourcesByConnection);
|
|
2
105
|
const valid = [];
|
|
3
106
|
const invalid = [];
|
|
4
|
-
for (const source of
|
|
107
|
+
for (const source of expanded) {
|
|
108
|
+
const key = `${source.connectionId}:${source.sourceName}`;
|
|
5
109
|
const result = await deps.slValidator.validateSingleSource(deps, source.connectionId, source.sourceName);
|
|
6
|
-
|
|
7
|
-
|
|
110
|
+
const errors = [...result.errors, ...(joinTargetErrors.get(key) ?? [])];
|
|
111
|
+
if (errors.length === 0) {
|
|
112
|
+
valid.push(key);
|
|
8
113
|
}
|
|
9
114
|
else {
|
|
10
|
-
invalid.push(
|
|
115
|
+
invalid.push({ source: key, errors });
|
|
11
116
|
}
|
|
12
117
|
}
|
|
13
118
|
return { validSources: valid, invalidSources: invalid };
|
package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { KtxFileStorePort } from '../../../core/file-store.js';
|
|
2
2
|
import type { SlConnectionCatalogPort } from '../../../sl/ports.js';
|
|
3
|
+
import type { SqlAnalysisPort } from '../../../sql-analysis/ports.js';
|
|
3
4
|
import type { BaseTool } from '../../../tools/base-tool.js';
|
|
4
5
|
export declare function createWarehouseVerificationTools(deps: {
|
|
5
6
|
connections: SlConnectionCatalogPort;
|
|
7
|
+
sqlAnalysis?: SqlAnalysisPort;
|
|
6
8
|
fallbackFileStore: KtxFileStorePort;
|
|
7
9
|
wikiSearchTool: BaseTool;
|
|
8
10
|
slDiscoverTool: BaseTool;
|
package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js
CHANGED
|
@@ -8,7 +8,7 @@ export function createWarehouseVerificationTools(deps) {
|
|
|
8
8
|
});
|
|
9
9
|
return [
|
|
10
10
|
new EntityDetailsTool(catalogFactory),
|
|
11
|
-
new SqlExecutionTool(deps.connections),
|
|
11
|
+
new SqlExecutionTool(deps.connections, deps.sqlAnalysis),
|
|
12
12
|
new DiscoverDataTool({
|
|
13
13
|
wikiSearchTool: deps.wikiSearchTool,
|
|
14
14
|
slDiscoverTool: deps.slDiscoverTool,
|
|
@@ -47,7 +47,7 @@ export class DiscoverDataTool extends BaseTool {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
if (input.sourceName) {
|
|
50
|
-
const sl = await this.deps.slDiscoverTool.call({ sourceName: input.sourceName, connectionId: input.connectionId }, context);
|
|
50
|
+
const sl = (await this.deps.slDiscoverTool.call({ sourceName: input.sourceName, connectionId: input.connectionId }, context));
|
|
51
51
|
return { markdown: sl.markdown, structured: { wiki: null, sl: sl.structured, raw: null } };
|
|
52
52
|
}
|
|
53
53
|
const query = input.query?.trim() || '';
|
|
@@ -57,13 +57,13 @@ export class DiscoverDataTool extends BaseTool {
|
|
|
57
57
|
let sl = null;
|
|
58
58
|
let raw = null;
|
|
59
59
|
if (query) {
|
|
60
|
-
const wikiResult = await this.deps.wikiSearchTool.call({ query, limit }, context);
|
|
60
|
+
const wikiResult = (await this.deps.wikiSearchTool.call({ query, limit }, context));
|
|
61
61
|
if (totalFound(wikiResult.structured) > 0) {
|
|
62
62
|
parts.push('## Wiki Pages', '> use `wiki_read(blockKey)` for full content', wikiResult.markdown, '');
|
|
63
63
|
wiki = wikiResult.structured;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
const slResult = await this.deps.slDiscoverTool.call({ query: query || undefined, connectionId: input.connectionId }, context);
|
|
66
|
+
const slResult = (await this.deps.slDiscoverTool.call({ query: query || undefined, connectionId: input.connectionId }, context));
|
|
67
67
|
if (totalSources(slResult.structured) > 0) {
|
|
68
68
|
parts.push('## Semantic Layer Sources', '> use `sl_read_source(sourceName)` for the YAML, or `entity_details` for warehouse-shape details', slResult.markdown, '');
|
|
69
69
|
sl = slResult.structured;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { SlConnectionCatalogPort } from '../../../../context/sl/ports.js';
|
|
3
|
+
import type { SqlAnalysisPort } from '../../../../context/sql-analysis/ports.js';
|
|
3
4
|
import { BaseTool, type ToolContext, type ToolOutput } from '../../../../context/tools/base-tool.js';
|
|
4
5
|
declare const sqlExecutionInputSchema: z.ZodObject<{
|
|
5
6
|
connectionId: z.ZodString;
|
|
@@ -18,8 +19,9 @@ export interface SqlExecutionStructured {
|
|
|
18
19
|
}
|
|
19
20
|
export declare class SqlExecutionTool extends BaseTool<typeof sqlExecutionInputSchema> {
|
|
20
21
|
private readonly connections;
|
|
22
|
+
private readonly sqlAnalysis?;
|
|
21
23
|
readonly name = "sql_execution";
|
|
22
|
-
constructor(connections: SlConnectionCatalogPort);
|
|
24
|
+
constructor(connections: SlConnectionCatalogPort, sqlAnalysis?: SqlAnalysisPort | undefined);
|
|
23
25
|
get description(): string;
|
|
24
26
|
get inputSchema(): z.ZodObject<{
|
|
25
27
|
connectionId: z.ZodString;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { assertReadOnlySql, limitSqlForExecution } from '../../../../context/connections/read-only-sql.js';
|
|
3
|
+
import { sqlAnalysisDialectForDriver } from '../../../../context/sql-analysis/dialect.js';
|
|
3
4
|
import { BaseTool } from '../../../../context/tools/base-tool.js';
|
|
4
5
|
const sqlExecutionInputSchema = z.object({
|
|
5
6
|
connectionId: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/),
|
|
@@ -23,10 +24,12 @@ function markdownTable(headers, rows, totalRows) {
|
|
|
23
24
|
}
|
|
24
25
|
export class SqlExecutionTool extends BaseTool {
|
|
25
26
|
connections;
|
|
27
|
+
sqlAnalysis;
|
|
26
28
|
name = 'sql_execution';
|
|
27
|
-
constructor(connections) {
|
|
29
|
+
constructor(connections, sqlAnalysis) {
|
|
28
30
|
super();
|
|
29
31
|
this.connections = connections;
|
|
32
|
+
this.sqlAnalysis = sqlAnalysis;
|
|
30
33
|
}
|
|
31
34
|
get description() {
|
|
32
35
|
return 'Run a single read-only SELECT or WITH probe against an allowed warehouse connection and return a capped markdown table or the warehouse error.';
|
|
@@ -50,9 +53,20 @@ export class SqlExecutionTool extends BaseTool {
|
|
|
50
53
|
},
|
|
51
54
|
};
|
|
52
55
|
}
|
|
56
|
+
if (!this.sqlAnalysis) {
|
|
57
|
+
throw new Error('sql_execution requires parser-backed SQL validation.');
|
|
58
|
+
}
|
|
53
59
|
let sql;
|
|
54
60
|
let wrappedSql;
|
|
55
61
|
try {
|
|
62
|
+
const connection = await this.connections.getConnectionById(input.connectionId);
|
|
63
|
+
if (!connection) {
|
|
64
|
+
throw new Error(`Connection not found: ${input.connectionId}`);
|
|
65
|
+
}
|
|
66
|
+
const validation = await this.sqlAnalysis.validateReadOnly(input.sql, sqlAnalysisDialectForDriver(connection.connectionType));
|
|
67
|
+
if (!validation.ok) {
|
|
68
|
+
throw new Error(validation.error ?? 'SQL is not read-only.');
|
|
69
|
+
}
|
|
56
70
|
sql = assertReadOnlySql(input.sql);
|
|
57
71
|
wrappedSql = limitSqlForExecution(sql, input.rowLimit);
|
|
58
72
|
}
|
|
@@ -189,7 +189,7 @@ export class AiSdkKtxLlmRuntime {
|
|
|
189
189
|
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
190
190
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
191
191
|
if (typeof result.text !== 'string') {
|
|
192
|
-
throw new Error('
|
|
192
|
+
throw new Error('ktx LLM text generation returned no text');
|
|
193
193
|
}
|
|
194
194
|
return result.text;
|
|
195
195
|
}
|
|
@@ -223,7 +223,7 @@ export class AiSdkKtxLlmRuntime {
|
|
|
223
223
|
const result = await this.generateTextWithRateLimitRetry(modelProviderName(model), input.abortSignal, () => generateText(request));
|
|
224
224
|
input.onMetrics?.({ totalMs: Date.now() - startedAt, usage: toLlmTokenUsage(result.totalUsage ?? result.usage) });
|
|
225
225
|
if (result.output == null) {
|
|
226
|
-
throw new Error('
|
|
226
|
+
throw new Error('ktx LLM object generation returned no output');
|
|
227
227
|
}
|
|
228
228
|
return result.output;
|
|
229
229
|
}
|
|
@@ -176,7 +176,7 @@ function baseOptions(input) {
|
|
|
176
176
|
? { behavior: 'allow', toolUseID: options.toolUseID }
|
|
177
177
|
: {
|
|
178
178
|
behavior: 'deny',
|
|
179
|
-
message: `
|
|
179
|
+
message: `ktx claude-code runtime only permits current ktx MCP tools; denied ${toolName}.`,
|
|
180
180
|
toolUseID: options.toolUseID,
|
|
181
181
|
},
|
|
182
182
|
permissionMode: 'dontAsk',
|
|
@@ -143,7 +143,7 @@ export function resolveLocalKtxEmbeddingConfig(config, env) {
|
|
|
143
143
|
batchSize: config.batchSize,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
throw new Error(`Unsupported
|
|
146
|
+
throw new Error(`Unsupported ktx embedding backend: ${String(config.backend)}`);
|
|
147
147
|
}
|
|
148
148
|
/** @internal */
|
|
149
149
|
export function createLocalKtxEmbeddingProviderFromConfig(config, deps = {}) {
|
|
@@ -21,7 +21,7 @@ export function normalizeKtxRuntimeToolOutput(value) {
|
|
|
21
21
|
}
|
|
22
22
|
function assertObjectSchema(name, schema) {
|
|
23
23
|
if (!(schema instanceof z.ZodObject)) {
|
|
24
|
-
throw new Error(`
|
|
24
|
+
throw new Error(`ktx runtime tool "${name}" must use z.object input schema for claude-code`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
export function createAiSdkToolSet(tools = {}) {
|
|
@@ -57,7 +57,7 @@ export function createRuntimeToolDescriptorFromAiTool(name, aiSdkTool) {
|
|
|
57
57
|
inputSchema: aiSdkTool.inputSchema,
|
|
58
58
|
execute: async (input) => {
|
|
59
59
|
if (typeof aiSdkTool.execute !== 'function') {
|
|
60
|
-
throw new Error(`
|
|
60
|
+
throw new Error(`ktx runtime tool "${name}" has no execute function`);
|
|
61
61
|
}
|
|
62
62
|
return normalizeKtxRuntimeToolOutput(await aiSdkTool.execute(input, { toolCallId: `runtime-${name}` }));
|
|
63
63
|
},
|
|
@@ -24,16 +24,16 @@ const toolAnnotations = {
|
|
|
24
24
|
memory_ingest_status: { title: 'Memory Ingest Status', readOnlyHint: true, openWorldHint: false },
|
|
25
25
|
};
|
|
26
26
|
const toolDescriptions = {
|
|
27
|
-
connection_list: 'List configured read-only data connections available to this
|
|
28
|
-
discover_data: 'Search across
|
|
29
|
-
wiki_search: 'Search
|
|
30
|
-
wiki_read: 'Read a
|
|
27
|
+
connection_list: 'List configured read-only data connections available to this ktx project. Use this before connection-scoped tools when the project may have multiple warehouses.',
|
|
28
|
+
discover_data: 'Search across ktx wiki pages, semantic-layer sources, measures, dimensions, raw tables, and columns. Example: discover_data({ query: "monthly orders by customer", connectionId: "warehouse", kinds: ["sl_source", "table"] }).',
|
|
29
|
+
wiki_search: 'Search ktx wiki pages for reusable business context. Example: wiki_search({ query: "revenue recognition", limit: 5 }).',
|
|
30
|
+
wiki_read: 'Read a ktx wiki page by key returned from wiki_search. Example: wiki_read({ key: "global/revenue" }).',
|
|
31
31
|
entity_details: 'Read table and column metadata from the latest live-database scan snapshot. Example: entity_details({ connectionId: "warehouse", entities: [{ table: { catalog: null, db: "public", name: "orders" }, columns: ["id"] }] }).',
|
|
32
32
|
dictionary_search: 'Search profile-sampled warehouse values to locate likely source columns for business values. Example: dictionary_search({ values: ["Acme Corp"], connectionId: "warehouse" }).',
|
|
33
33
|
sl_read_source: 'Read a semantic-layer YAML source by connection id and source name. Example: sl_read_source({ connectionId: "warehouse", sourceName: "orders" }).',
|
|
34
34
|
sl_query: 'Execute a semantic-layer query and return headers, rows, and total row count, plus correctness notes (e.g. compile-only or fan-out) when relevant. The generated SQL and full query plan are omitted by default; request them with include: ["sql"] and/or include: ["plan"]. Example: sl_query({ connectionId: "warehouse", measures: ["orders.order_count"], dimensions: [{ field: "orders.created_at", granularity: "month" }], include: ["sql"] }).',
|
|
35
|
-
sql_execution: 'Execute one parser-validated read-only SQL query against a configured
|
|
36
|
-
memory_ingest: 'Ingest free-form markdown knowledge into durable
|
|
35
|
+
sql_execution: 'Execute one parser-validated read-only SQL query against a configured ktx connection. Example: sql_execution({ connectionId: "warehouse", sql: "select count(*) from public.orders", maxRows: 100 }).',
|
|
36
|
+
memory_ingest: 'Ingest free-form markdown knowledge into durable ktx memory. Use this for business rules, metric definitions, schema gotchas, recurring findings, or explicit user requests to remember something. Example: memory_ingest({ connectionId: "warehouse", content: "ARR is reported in cents in this warehouse." }).',
|
|
37
37
|
memory_ingest_status: 'Read the current or final status for a memory ingest run. Example: memory_ingest_status({ runId: "memory-run-1" }).',
|
|
38
38
|
};
|
|
39
39
|
const connectionListSchema = z.object({});
|
|
@@ -641,7 +641,7 @@ export function registerKtxContextTools(deps) {
|
|
|
641
641
|
const ingestInput = {
|
|
642
642
|
userId: userContext.userId,
|
|
643
643
|
chatId: `mcp-${randomUUID()}`,
|
|
644
|
-
userMessage: 'Ingest external knowledge into
|
|
644
|
+
userMessage: 'Ingest external knowledge into ktx memory.',
|
|
645
645
|
assistantMessage: input.content,
|
|
646
646
|
connectionId: input.connectionId,
|
|
647
647
|
sourceType: 'external_ingest',
|
|
@@ -1,58 +1,18 @@
|
|
|
1
|
+
import { KtxQueryError, isNativeProgrammingFault } from '../../errors.js';
|
|
1
2
|
import { localConnectionInfoFromConfig } from '../../context/connections/local-warehouse-descriptor.js';
|
|
2
3
|
import { createKtxEntityDetailsService } from '../../context/scan/entity-details.js';
|
|
3
4
|
import { createKtxDiscoverDataService } from '../../context/search/discover.js';
|
|
5
|
+
import { sqlAnalysisDialectForDriver } from '../../context/sql-analysis/dialect.js';
|
|
4
6
|
import { compileLocalSlQuery } from '../../context/sl/local-query.js';
|
|
5
7
|
import { createKtxDictionarySearchService } from '../../context/sl/dictionary-search.js';
|
|
8
|
+
import { readLocalSlSource } from '../../context/sl/local-sl.js';
|
|
9
|
+
import { assertSafeConnectionId } from '../../context/sl/source-files.js';
|
|
6
10
|
import { readLocalKnowledgePage, searchLocalKnowledgePages } from '../wiki/local-knowledge.js';
|
|
7
|
-
function dialectForDriver(driver) {
|
|
8
|
-
const normalized = (driver ?? 'postgres').toUpperCase();
|
|
9
|
-
const map = {
|
|
10
|
-
POSTGRES: 'postgres',
|
|
11
|
-
BIGQUERY: 'bigquery',
|
|
12
|
-
SNOWFLAKE: 'snowflake',
|
|
13
|
-
MYSQL: 'mysql',
|
|
14
|
-
SQLSERVER: 'tsql',
|
|
15
|
-
SQLITE: 'sqlite',
|
|
16
|
-
DUCKDB: 'duckdb',
|
|
17
|
-
CLICKHOUSE: 'clickhouse',
|
|
18
|
-
DATABRICKS: 'databricks',
|
|
19
|
-
};
|
|
20
|
-
return map[normalized] ?? 'postgres';
|
|
21
|
-
}
|
|
22
|
-
function sqlAnalysisDialectForDriver(driver) {
|
|
23
|
-
return dialectForDriver(driver);
|
|
24
|
-
}
|
|
25
|
-
function assertSafePathToken(kind, value) {
|
|
26
|
-
if (value.trim().length === 0 ||
|
|
27
|
-
value.includes('..') ||
|
|
28
|
-
value.includes('\\') ||
|
|
29
|
-
value.startsWith('/') ||
|
|
30
|
-
value.startsWith('.') ||
|
|
31
|
-
value.includes('//')) {
|
|
32
|
-
throw new Error(`Unsafe ${kind}: ${value}`);
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
function assertSafeConnectionId(connectionId) {
|
|
37
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
38
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
39
|
-
}
|
|
40
|
-
return assertSafePathToken('connection id', connectionId);
|
|
41
|
-
}
|
|
42
|
-
function assertSafeSourceName(sourceName) {
|
|
43
|
-
if (!/^[a-z0-9][a-z0-9_]*$/.test(sourceName)) {
|
|
44
|
-
throw new Error(`Unsafe semantic-layer source name: ${sourceName}`);
|
|
45
|
-
}
|
|
46
|
-
return assertSafePathToken('semantic-layer source name', sourceName);
|
|
47
|
-
}
|
|
48
11
|
async function cleanupConnector(connector) {
|
|
49
12
|
if (connector?.cleanup) {
|
|
50
13
|
await connector.cleanup();
|
|
51
14
|
}
|
|
52
15
|
}
|
|
53
|
-
function slPath(connectionId, sourceName) {
|
|
54
|
-
return `semantic-layer/${assertSafeConnectionId(connectionId)}/${assertSafeSourceName(sourceName)}.yaml`;
|
|
55
|
-
}
|
|
56
16
|
async function executeValidatedReadOnlySql(project, options, input, onProgress) {
|
|
57
17
|
await onProgress?.({ progress: 0, message: 'Validating SQL' });
|
|
58
18
|
const connectionId = assertSafeConnectionId(input.connectionId);
|
|
@@ -78,11 +38,23 @@ async function executeValidatedReadOnlySql(project, options, input, onProgress)
|
|
|
78
38
|
throw new Error(`Connection "${connectionId}" does not support read-only SQL execution.`);
|
|
79
39
|
}
|
|
80
40
|
await onProgress?.({ progress: 0.3, message: 'Executing' });
|
|
81
|
-
const result = await connector
|
|
41
|
+
const result = await connector
|
|
42
|
+
.executeReadOnly({
|
|
82
43
|
connectionId,
|
|
83
44
|
sql: input.sql,
|
|
84
45
|
maxRows: input.maxRows,
|
|
85
|
-
}, { runId: 'mcp-sql-execution' })
|
|
46
|
+
}, { runId: 'mcp-sql-execution' })
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
// A warehouse/driver rejection (e.g. the agent's SQL failed to compile)
|
|
49
|
+
// is a surfaced operational outcome, not a ktx fault: mark it expected
|
|
50
|
+
// while preserving the warehouse's own diagnostics. A native JS error
|
|
51
|
+
// (TypeError, etc.) signals a bug in connector code — let it propagate
|
|
52
|
+
// unchanged so Error Tracking still sees it.
|
|
53
|
+
if (isNativeProgrammingFault(error)) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
throw new KtxQueryError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
57
|
+
});
|
|
86
58
|
const response = {
|
|
87
59
|
headers: result.headers,
|
|
88
60
|
...(result.headerTypes ? { headerTypes: result.headerTypes } : {}),
|
|
@@ -148,14 +120,11 @@ export function createLocalProjectMcpContextPorts(project, options) {
|
|
|
148
120
|
},
|
|
149
121
|
semanticLayer: {
|
|
150
122
|
async readSource(input) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
123
|
+
const source = await readLocalSlSource(project, {
|
|
124
|
+
connectionId: input.connectionId,
|
|
125
|
+
sourceName: input.sourceName,
|
|
126
|
+
});
|
|
127
|
+
return source ? { sourceName: source.name, yaml: source.yaml } : null;
|
|
159
128
|
},
|
|
160
129
|
async query(input, executionOptions) {
|
|
161
130
|
if (!options.semanticLayerCompute) {
|
|
@@ -31,7 +31,7 @@ import { MemoryAgentService } from './memory-agent.service.js';
|
|
|
31
31
|
import { MemoryIngestService } from './memory-runs.js';
|
|
32
32
|
const promptsDir = fileURLToPath(new URL('../../prompts', import.meta.url));
|
|
33
33
|
const skillsDir = fileURLToPath(new URL('../../skills', import.meta.url));
|
|
34
|
-
const LOCAL_AUTHOR = { name: '
|
|
34
|
+
const LOCAL_AUTHOR = { name: 'ktx Local', email: 'local@ktx.local' };
|
|
35
35
|
const LOCAL_SHAPE_WARNING = 'Local memory ingest validates semantic-layer YAML shape only.';
|
|
36
36
|
export function createLocalProjectMemoryIngest(project, options = {}) {
|
|
37
37
|
const logger = options.logger ?? noopLogger;
|
|
@@ -307,6 +307,9 @@ class LocalShapeOnlySlValidator {
|
|
|
307
307
|
async validateSingleSource(deps, connectionId, sourceName) {
|
|
308
308
|
try {
|
|
309
309
|
const file = await deps.semanticLayerService.readSourceFile(connectionId, sourceName);
|
|
310
|
+
if (!file) {
|
|
311
|
+
return { errors: [`${sourceName}: no standalone or overlay file found`], warnings: [] };
|
|
312
|
+
}
|
|
310
313
|
const parsed = YAML.parse(file.content);
|
|
311
314
|
const isOverlay = parsed.table == null && parsed.sql == null;
|
|
312
315
|
const result = (isOverlay ? sourceOverlaySchema : sourceDefinitionSchema).safeParse(parsed);
|
|
@@ -391,7 +391,7 @@ export class MemoryAgentService {
|
|
|
391
391
|
if (session.connectionId) {
|
|
392
392
|
for (const { connectionId, sourceName } of listTouchedSlSources(session.touchedSlSources)) {
|
|
393
393
|
try {
|
|
394
|
-
const file = await this.deps.semanticLayerService.readSourceFile(connectionId, sourceName)
|
|
394
|
+
const file = await this.deps.semanticLayerService.readSourceFile(connectionId, sourceName);
|
|
395
395
|
if (file?.content) {
|
|
396
396
|
const parsed = this.parseYamlOrNull(file.content);
|
|
397
397
|
if (parsed) {
|
|
@@ -317,7 +317,6 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
317
317
|
"postgres-hybrid": "postgres-hybrid";
|
|
318
318
|
}>>;
|
|
319
319
|
git: z.ZodPrefault<z.ZodObject<{
|
|
320
|
-
auto_commit: z.ZodDefault<z.ZodBoolean>;
|
|
321
320
|
author: z.ZodDefault<z.ZodString>;
|
|
322
321
|
}, z.core.$strict>>;
|
|
323
322
|
}, z.core.$strict>>;
|
|
@@ -418,9 +417,6 @@ declare const ktxProjectConfigSchema: z.ZodObject<{
|
|
|
418
417
|
default_toolset: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
419
418
|
}, z.core.$strict>>;
|
|
420
419
|
}, z.core.$strict>>;
|
|
421
|
-
memory: z.ZodPrefault<z.ZodObject<{
|
|
422
|
-
auto_commit: z.ZodDefault<z.ZodBoolean>;
|
|
423
|
-
}, z.core.$strict>>;
|
|
424
420
|
scan: z.ZodPrefault<z.ZodObject<{
|
|
425
421
|
enrichment: z.ZodPrefault<z.ZodObject<{
|
|
426
422
|
mode: z.ZodDefault<z.ZodEnum<{
|
|
@@ -472,12 +468,23 @@ export interface KtxConfigIssue {
|
|
|
472
468
|
path: string;
|
|
473
469
|
message: string;
|
|
474
470
|
fix?: string;
|
|
471
|
+
/**
|
|
472
|
+
* 'error' blocks the project (bad value on a recognized field); 'warning' is
|
|
473
|
+
* a condition the loader recovers from on its own (an ignored unknown key).
|
|
474
|
+
*/
|
|
475
|
+
severity: 'error' | 'warning';
|
|
475
476
|
}
|
|
476
477
|
export interface KtxConfigValidation {
|
|
477
478
|
ok: boolean;
|
|
478
479
|
issues: KtxConfigIssue[];
|
|
479
480
|
}
|
|
480
481
|
export declare function buildDefaultKtxProjectConfig(): KtxProjectConfig;
|
|
482
|
+
/**
|
|
483
|
+
* Parse and validate a ktx.yaml document. Keys this ktx version does not
|
|
484
|
+
* recognize are stripped from the returned config — never from the file, which
|
|
485
|
+
* a load must not rewrite — so a config written by a different ktx version
|
|
486
|
+
* still loads. Malformed values on recognized fields still throw.
|
|
487
|
+
*/
|
|
481
488
|
export declare function parseKtxProjectConfig(raw: string): KtxProjectConfig;
|
|
482
489
|
export declare function validateKtxProjectConfig(raw: string): KtxConfigValidation;
|
|
483
490
|
export declare function generateKtxProjectConfigJsonSchema(): Record<string, unknown>;
|