@kaelio/ktx 0.10.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.10.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 +7 -3
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +14 -8
- 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/community-cta.d.ts +11 -0
- package/dist/community-cta.js +19 -0
- 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-env.d.ts +12 -1
- package/dist/context/core/git-env.js +17 -2
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +86 -15
- 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 +17 -0
- package/dist/io/tty.js +21 -0
- package/dist/links.d.ts +1 -0
- package/dist/links.js +1 -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 +22 -35
- 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 -5
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +27 -7
- package/dist/setup.d.ts +25 -0
- package/dist/setup.js +90 -19
- 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/command-hook.d.ts +24 -0
- package/dist/telemetry/command-hook.js +37 -3
- package/dist/telemetry/events.d.ts +1 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.js +2 -2
- 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
|
@@ -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>;
|
|
@@ -53,7 +53,7 @@ const llmSchema = z
|
|
|
53
53
|
models: z
|
|
54
54
|
.partialRecord(z.enum(KTX_MODEL_ROLES), z.string().min(1))
|
|
55
55
|
.default({})
|
|
56
|
-
.describe('Per-role model overrides keyed by
|
|
56
|
+
.describe('Per-role model overrides keyed by ktx model role (e.g. "default", "triage"). Values are provider-specific model identifiers.'),
|
|
57
57
|
promptCaching: promptCachingSchema.optional().describe('Optional prompt-caching tunables.'),
|
|
58
58
|
})
|
|
59
59
|
.describe('LLM provider, per-role model overrides, and prompt-caching tunables.');
|
|
@@ -205,27 +205,26 @@ const setupSchema = z
|
|
|
205
205
|
.describe('Setup-wizard state captured during `ktx setup`.');
|
|
206
206
|
const storageGitSchema = z
|
|
207
207
|
.strictObject({
|
|
208
|
-
auto_commit: z.boolean().default(true).describe('When true, KTX automatically commits state changes to the local Git-backed store.'),
|
|
209
208
|
author: z
|
|
210
209
|
.string()
|
|
211
210
|
.min(1)
|
|
212
211
|
.default('ktx <ktx@example.com>')
|
|
213
|
-
.describe('Git author identity used for
|
|
212
|
+
.describe('Git author identity used for commits, in standard "Name <email>" form.'),
|
|
214
213
|
})
|
|
215
|
-
.describe('Git-backed storage
|
|
214
|
+
.describe('Git-backed storage author policy.');
|
|
216
215
|
const storageSchema = z
|
|
217
216
|
.strictObject({
|
|
218
217
|
state: z
|
|
219
218
|
.enum(KTX_STORAGE_STATES)
|
|
220
219
|
.default('sqlite')
|
|
221
|
-
.describe('Backend for
|
|
220
|
+
.describe('Backend for ktx state storage. "sqlite" uses .ktx/db.sqlite; "postgres" expects a configured Postgres connection.'),
|
|
222
221
|
search: z
|
|
223
222
|
.enum(KTX_SEARCH_BACKENDS)
|
|
224
223
|
.default('sqlite-fts5')
|
|
225
224
|
.describe('Backend for search indexes. "sqlite-fts5" uses SQLite FTS5; "postgres-hybrid" uses Postgres lexical + vector hybrid search.'),
|
|
226
225
|
git: storageGitSchema.prefault({}).describe('Git-backed storage commit policy.'),
|
|
227
226
|
})
|
|
228
|
-
.describe('Storage backends and commit policy for
|
|
227
|
+
.describe('Storage backends and commit policy for ktx state and search indexes.');
|
|
229
228
|
const connectionSchema = connectionConfigSchema;
|
|
230
229
|
const agentSchema = z
|
|
231
230
|
.strictObject({
|
|
@@ -247,11 +246,6 @@ const agentSchema = z
|
|
|
247
246
|
.describe('Research-agent configuration.'),
|
|
248
247
|
})
|
|
249
248
|
.describe('Agent feature configuration.');
|
|
250
|
-
const memorySchema = z
|
|
251
|
-
.strictObject({
|
|
252
|
-
auto_commit: z.boolean().default(true).describe('When true, KTX automatically commits memory updates to the Git-backed store.'),
|
|
253
|
-
})
|
|
254
|
-
.describe('Memory subsystem configuration.');
|
|
255
249
|
const ktxProjectConfigSchema = z
|
|
256
250
|
.strictObject({
|
|
257
251
|
setup: setupSchema.optional().describe('Setup-wizard state. Written by `ktx setup`; may be omitted.'),
|
|
@@ -259,14 +253,13 @@ const ktxProjectConfigSchema = z
|
|
|
259
253
|
.record(z.string(), connectionSchema)
|
|
260
254
|
.default({})
|
|
261
255
|
.describe('Map of connection ID to connector configuration. Keys are user-chosen names referenced elsewhere in the config.'),
|
|
262
|
-
storage: storageSchema.prefault({}).describe('Storage backends and commit policy for
|
|
256
|
+
storage: storageSchema.prefault({}).describe('Storage backends and commit policy for ktx state and search indexes.'),
|
|
263
257
|
llm: llmSchema.prefault({}).describe('LLM provider, per-role model overrides, and prompt-caching tunables.'),
|
|
264
258
|
ingest: ingestSchema.prefault({}).describe('Ingest pipeline configuration.'),
|
|
265
259
|
agent: agentSchema.prefault({}).describe('Agent feature configuration.'),
|
|
266
|
-
memory: memorySchema.prefault({}).describe('Memory subsystem configuration.'),
|
|
267
260
|
scan: scanSchema.prefault({}).describe('Schema-scan configuration: enrichment and relationship discovery.'),
|
|
268
261
|
})
|
|
269
|
-
.describe('Configuration schema for
|
|
262
|
+
.describe('Configuration schema for ktx project files (ktx.yaml).');
|
|
270
263
|
function isRecord(value) {
|
|
271
264
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
272
265
|
}
|
|
@@ -282,21 +275,53 @@ function valueAtPath(root, path) {
|
|
|
282
275
|
}
|
|
283
276
|
return cursor;
|
|
284
277
|
}
|
|
285
|
-
|
|
286
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Zod reports unknown keys in two shapes: strict objects emit
|
|
280
|
+
* `unrecognized_keys` (path → container, `keys` → offenders), enum-keyed
|
|
281
|
+
* records (`llm.models`) emit one `invalid_key` per offender (path ends with
|
|
282
|
+
* the key). Normalize both so the warning report and the strip always agree.
|
|
283
|
+
*/
|
|
284
|
+
function unknownKeyLocations(issue) {
|
|
287
285
|
if (issue.code === 'unrecognized_keys') {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
286
|
+
return issue.keys.map((key) => ({ containerPath: issue.path, key }));
|
|
287
|
+
}
|
|
288
|
+
if (issue.code === 'invalid_key' && issue.path.length > 0) {
|
|
289
|
+
return [
|
|
290
|
+
{
|
|
291
|
+
containerPath: issue.path.slice(0, -1),
|
|
292
|
+
key: String(issue.path[issue.path.length - 1]),
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
function formatIssue(issue, input) {
|
|
299
|
+
const unknownKeys = unknownKeyLocations(issue);
|
|
300
|
+
if (unknownKeys.length > 0) {
|
|
301
|
+
return unknownKeys.map(({ containerPath, key }) => {
|
|
302
|
+
const base = dottedPath(containerPath);
|
|
303
|
+
const fullPath = base.length > 0 ? `${base}.${key}` : key;
|
|
304
|
+
return {
|
|
305
|
+
path: fullPath,
|
|
306
|
+
message: `Unsupported ${fullPath}: unknown field (ignored)`,
|
|
307
|
+
fix: 'Unknown to this ktx version; it is ignored. Delete it from ktx.yaml when convenient.',
|
|
308
|
+
severity: 'warning',
|
|
309
|
+
};
|
|
292
310
|
});
|
|
293
311
|
}
|
|
312
|
+
const basePath = dottedPath(issue.path);
|
|
294
313
|
const lastSegment = issue.path[issue.path.length - 1];
|
|
295
314
|
if (lastSegment === 'backend' && (issue.code === 'invalid_value' || issue.code === 'invalid_type')) {
|
|
296
315
|
const value = valueAtPath(input, issue.path);
|
|
297
|
-
return [{ path: basePath, message: `Unsupported ${basePath}: ${String(value)}
|
|
316
|
+
return [{ path: basePath, message: `Unsupported ${basePath}: ${String(value)}`, severity: 'error' }];
|
|
298
317
|
}
|
|
299
|
-
return [
|
|
318
|
+
return [
|
|
319
|
+
{
|
|
320
|
+
path: basePath,
|
|
321
|
+
message: basePath.length > 0 ? `${basePath}: ${issue.message}` : issue.message,
|
|
322
|
+
severity: 'error',
|
|
323
|
+
},
|
|
324
|
+
];
|
|
300
325
|
}
|
|
301
326
|
function collectIssues(error, input) {
|
|
302
327
|
return error.issues.flatMap((issue) => formatIssue(issue, input));
|
|
@@ -309,16 +334,44 @@ function formatZodError(error, input) {
|
|
|
309
334
|
export function buildDefaultKtxProjectConfig() {
|
|
310
335
|
return ktxProjectConfigSchema.parse({});
|
|
311
336
|
}
|
|
337
|
+
function stripUnrecognizedKeys(input) {
|
|
338
|
+
const result = ktxProjectConfigSchema.safeParse(input);
|
|
339
|
+
if (result.success) {
|
|
340
|
+
return input;
|
|
341
|
+
}
|
|
342
|
+
const unknownKeys = result.error.issues.flatMap(unknownKeyLocations);
|
|
343
|
+
if (unknownKeys.length === 0) {
|
|
344
|
+
return input;
|
|
345
|
+
}
|
|
346
|
+
const value = structuredClone(input);
|
|
347
|
+
for (const { containerPath, key } of unknownKeys) {
|
|
348
|
+
const container = valueAtPath(value, containerPath);
|
|
349
|
+
if (container === null || typeof container !== 'object')
|
|
350
|
+
continue;
|
|
351
|
+
delete container[key];
|
|
352
|
+
}
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
function parseTolerant(input) {
|
|
356
|
+
const value = stripUnrecognizedKeys(input);
|
|
357
|
+
const result = ktxProjectConfigSchema.safeParse(value);
|
|
358
|
+
if (!result.success) {
|
|
359
|
+
throw new Error(formatZodError(result.error, value));
|
|
360
|
+
}
|
|
361
|
+
return result.data;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Parse and validate a ktx.yaml document. Keys this ktx version does not
|
|
365
|
+
* recognize are stripped from the returned config — never from the file, which
|
|
366
|
+
* a load must not rewrite — so a config written by a different ktx version
|
|
367
|
+
* still loads. Malformed values on recognized fields still throw.
|
|
368
|
+
*/
|
|
312
369
|
export function parseKtxProjectConfig(raw) {
|
|
313
370
|
const parsed = YAML.parse(raw);
|
|
314
371
|
if (!isRecord(parsed)) {
|
|
315
372
|
throw new Error('ktx.yaml must contain a YAML object');
|
|
316
373
|
}
|
|
317
|
-
|
|
318
|
-
if (!result.success) {
|
|
319
|
-
throw new Error(formatZodError(result.error, parsed));
|
|
320
|
-
}
|
|
321
|
-
return result.data;
|
|
374
|
+
return parseTolerant(parsed);
|
|
322
375
|
}
|
|
323
376
|
export function validateKtxProjectConfig(raw) {
|
|
324
377
|
let parsed;
|
|
@@ -327,16 +380,18 @@ export function validateKtxProjectConfig(raw) {
|
|
|
327
380
|
}
|
|
328
381
|
catch (error) {
|
|
329
382
|
const message = error instanceof Error ? error.message : String(error);
|
|
330
|
-
return { ok: false, issues: [{ path: '', message: `ktx.yaml parse error: ${message}
|
|
383
|
+
return { ok: false, issues: [{ path: '', message: `ktx.yaml parse error: ${message}`, severity: 'error' }] };
|
|
331
384
|
}
|
|
332
385
|
if (!isRecord(parsed)) {
|
|
333
|
-
return { ok: false, issues: [{ path: '', message: 'ktx.yaml must contain a YAML object' }] };
|
|
386
|
+
return { ok: false, issues: [{ path: '', message: 'ktx.yaml must contain a YAML object', severity: 'error' }] };
|
|
334
387
|
}
|
|
335
388
|
const result = ktxProjectConfigSchema.safeParse(parsed);
|
|
336
389
|
if (result.success) {
|
|
337
390
|
return { ok: true, issues: [] };
|
|
338
391
|
}
|
|
339
|
-
|
|
392
|
+
const issues = collectIssues(result.error, parsed);
|
|
393
|
+
const ok = !issues.some((issue) => issue.severity === 'error');
|
|
394
|
+
return { ok, issues };
|
|
340
395
|
}
|
|
341
396
|
export function generateKtxProjectConfigJsonSchema() {
|
|
342
397
|
const schema = z.toJSONSchema(ktxProjectConfigSchema, {
|
|
@@ -84,7 +84,7 @@ const lookerConnectionSchema = z
|
|
|
84
84
|
.min(1)
|
|
85
85
|
.optional()
|
|
86
86
|
.describe('Reference to Looker OAuth client secret (e.g. env:LOOKER_CLIENT_SECRET).'),
|
|
87
|
-
mappings: lookerMappingsSchema.optional().describe('Looker connection-name to
|
|
87
|
+
mappings: lookerMappingsSchema.optional().describe('Looker connection-name to ktx warehouse mappings.'),
|
|
88
88
|
})
|
|
89
89
|
.describe('Looker context-source connection.');
|
|
90
90
|
const lookmlConnectionSchema = z
|
|
@@ -12,7 +12,7 @@ export const metabaseMappingsSchema = z
|
|
|
12
12
|
databaseMappings: z
|
|
13
13
|
.record(z.string(), stringTargetSchema)
|
|
14
14
|
.default({})
|
|
15
|
-
.describe('Map of Metabase database ID (positive integer string) to
|
|
15
|
+
.describe('Map of Metabase database ID (positive integer string) to ktx connection ID. Use null to explicitly unmap.'),
|
|
16
16
|
syncEnabled: z
|
|
17
17
|
.record(z.string(), z.boolean())
|
|
18
18
|
.default({})
|
|
@@ -34,7 +34,7 @@ export const lookerMappingsSchema = z
|
|
|
34
34
|
connectionMappings: z
|
|
35
35
|
.record(z.string().min(1), stringTargetSchema)
|
|
36
36
|
.default({})
|
|
37
|
-
.describe('Map of Looker connection name to
|
|
37
|
+
.describe('Map of Looker connection name to ktx connection ID. Use null to explicitly unmap.'),
|
|
38
38
|
})
|
|
39
39
|
.describe('Looker connection-to-warehouse mapping configuration.');
|
|
40
40
|
export const lookmlMappingsSchema = z
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
-
import { GitService } from '../../context/core/git.service.js';
|
|
3
|
+
import { classifyKtxRepoOwnership, GitService, KtxForeignGitRepositoryError } from '../../context/core/git.service.js';
|
|
4
4
|
import { noopLogger } from '../../context/core/config.js';
|
|
5
5
|
import { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js';
|
|
6
6
|
import { LocalGitFileStore } from './local-git-file-store.js';
|
|
@@ -69,14 +69,22 @@ export async function initKtxProject(options) {
|
|
|
69
69
|
if (!options.force && (await fileExists(configPath))) {
|
|
70
70
|
throw new Error(`Project already contains ktx.yaml: ${configPath}`);
|
|
71
71
|
}
|
|
72
|
+
// Must run before ktx.yaml is written: once that file exists the directory
|
|
73
|
+
// classifies as ktx-managed, so a foreign repo would be silently adopted.
|
|
74
|
+
if ((await classifyKtxRepoOwnership(projectDir)) === 'foreign') {
|
|
75
|
+
throw new KtxForeignGitRepositoryError(projectDir);
|
|
76
|
+
}
|
|
72
77
|
const config = buildDefaultKtxProjectConfig();
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
// ktx.yaml (the ownership signal) is written before git init, so an
|
|
79
|
+
// interrupted init can never leave a bare `.git` without it — residue that
|
|
80
|
+
// would classify as a foreign repo and be unrecoverable.
|
|
75
81
|
await fs.mkdir(join(projectDir, '.ktx/cache'), { recursive: true });
|
|
76
82
|
for (const file of TRACKED_SCAFFOLD_FILES) {
|
|
77
83
|
await writeProjectFile(projectDir, file.path, file.content);
|
|
78
84
|
}
|
|
79
|
-
|
|
85
|
+
await writeProjectFile(projectDir, 'ktx.yaml', serializeKtxProjectConfig(config));
|
|
86
|
+
const runtime = await createRuntime(projectDir, config, authorName, authorEmail, logger);
|
|
87
|
+
const commit = await runtime.git.commitFiles(['ktx.yaml', ...TRACKED_SCAFFOLD_FILES.map((file) => file.path)], `Initialize ktx project: ${projectName}`, authorName, authorEmail);
|
|
80
88
|
return {
|
|
81
89
|
...runtime,
|
|
82
90
|
commitHash: commit.commitHash,
|
|
@@ -330,7 +330,7 @@ export class KtxDescriptionGenerator {
|
|
|
330
330
|
let fallbackReason = null;
|
|
331
331
|
if (!connector.sampleTable) {
|
|
332
332
|
fallbackReason = 'capability_missing';
|
|
333
|
-
this.logger?.warn('
|
|
333
|
+
this.logger?.warn('ktx scan connector does not support table sampling; falling back to metadata-only prompt', {
|
|
334
334
|
connectorId: input.connector.id,
|
|
335
335
|
table: input.table.name,
|
|
336
336
|
});
|
|
@@ -440,7 +440,7 @@ export class KtxDescriptionGenerator {
|
|
|
440
440
|
let fallbackReason = null;
|
|
441
441
|
if (!input.connector.sampleTable) {
|
|
442
442
|
fallbackReason = 'capability_missing';
|
|
443
|
-
this.logger?.warn('
|
|
443
|
+
this.logger?.warn('ktx scan connector does not support table sampling; falling back to metadata-only prompt', {
|
|
444
444
|
connectorId: input.connector.id,
|
|
445
445
|
table: input.table.name,
|
|
446
446
|
});
|
|
@@ -579,7 +579,7 @@ export class KtxDescriptionGenerator {
|
|
|
579
579
|
}
|
|
580
580
|
}
|
|
581
581
|
if (!input.connector.sampleTable) {
|
|
582
|
-
this.logger?.warn('
|
|
582
|
+
this.logger?.warn('ktx scan connector does not support table sampling for data-source description generation', {
|
|
583
583
|
connectorId: input.connector.id,
|
|
584
584
|
});
|
|
585
585
|
return 'No accessible tables found in database';
|
|
@@ -647,7 +647,7 @@ export class KtxDescriptionGenerator {
|
|
|
647
647
|
let columnValues = column.sampleValues;
|
|
648
648
|
if (!columnValues || columnValues.length === 0) {
|
|
649
649
|
if (!input.connector.sampleColumn) {
|
|
650
|
-
this.logger?.warn('
|
|
650
|
+
this.logger?.warn('ktx scan connector does not support column sampling; using available metadata only', {
|
|
651
651
|
connectorId: input.connector.id,
|
|
652
652
|
table: input.table.name,
|
|
653
653
|
column: column.name,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
import { buildLiveDatabaseManifestShards } from '../../context/ingest/adapters/live-database/manifest.js';
|
|
3
|
+
import { isSlYamlPath } from '../../context/sl/source-files.js';
|
|
3
4
|
import { buildKtxRelationshipArtifacts, buildKtxRelationshipDiagnostics, emptyKtxRelationshipProfileArtifact, } from './relationship-diagnostics.js';
|
|
4
5
|
const LIVE_DATABASE_ADAPTER = 'live-database';
|
|
5
6
|
const LOCAL_AUTHOR = 'ktx';
|
|
@@ -120,7 +121,7 @@ async function loadExistingManifestState(project, connectionId, snapshot) {
|
|
|
120
121
|
const columnsByTable = validColumns(snapshot);
|
|
121
122
|
let files;
|
|
122
123
|
try {
|
|
123
|
-
files = (await project.fileStore.listFiles(schemaDir(connectionId))).files.filter(
|
|
124
|
+
files = (await project.fileStore.listFiles(schemaDir(connectionId))).files.filter(isSlYamlPath);
|
|
124
125
|
}
|
|
125
126
|
catch {
|
|
126
127
|
return { descriptions, preservedJoins, usage };
|
|
@@ -63,7 +63,7 @@ function scanReportPath(connectionId, syncId) {
|
|
|
63
63
|
}
|
|
64
64
|
function assertSupportedMode(mode) {
|
|
65
65
|
if (mode !== 'structural' && mode !== 'relationships' && mode !== 'enriched') {
|
|
66
|
-
throw new Error(`Unsupported
|
|
66
|
+
throw new Error(`Unsupported ktx scan mode: ${mode}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
async function resolveScanConnector(options, mode) {
|
|
@@ -381,7 +381,7 @@ export async function runLocalScan(options) {
|
|
|
381
381
|
}
|
|
382
382
|
report.warnings.push({
|
|
383
383
|
code: 'enrichment_failed',
|
|
384
|
-
message: `
|
|
384
|
+
message: `ktx scan enrichment failed after structural scan completed: ${message}`,
|
|
385
385
|
recoverable: true,
|
|
386
386
|
metadata: { mode, detectRelationships: options.detectRelationships ?? false },
|
|
387
387
|
});
|