@kaelio/ktx 0.11.0 → 0.13.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.13.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 +19 -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 +15 -3
- package/dist/connectors/bigquery/connector.js +1 -14
- package/dist/connectors/clickhouse/connector.js +2 -16
- package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
- package/dist/connectors/duckdb/federated-attach.js +86 -0
- package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
- package/dist/connectors/duckdb/federated-executor.js +59 -0
- package/dist/connectors/mysql/connector.js +2 -16
- package/dist/connectors/postgres/connector.js +1 -14
- package/dist/connectors/shared/string-reference.d.ts +6 -0
- package/dist/connectors/shared/string-reference.js +19 -0
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/snowflake/connector.js +1 -14
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +4 -17
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/federation.d.ts +33 -0
- package/dist/context/connections/federation.js +51 -0
- package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
- package/dist/context/connections/project-sql-executor.d.ts +18 -0
- package/dist/context/connections/project-sql-executor.js +39 -0
- package/dist/context/connections/query-executor.d.ts +2 -2
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +119 -4
- package/dist/context/connections/resolve-connection.d.ts +12 -0
- package/dist/context/connections/resolve-connection.js +37 -0
- package/dist/context/core/git-env.d.ts +4 -0
- package/dist/context/core/git-env.js +5 -1
- 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/live-database/manifest.d.ts +3 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
- 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 +19 -3
- 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 +33 -8
- package/dist/context/mcp/local-project-ports.js +63 -89
- package/dist/context/mcp/types.d.ts +2 -0
- 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 +33 -4
- 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 +31 -44
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +71 -70
- 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 +48 -0
- package/dist/context/sl/source-files.js +138 -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-query-executor.d.ts +2 -0
- package/dist/ingest-query-executor.js +8 -22
- 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.d.ts +21 -15
- package/dist/setup-agents.js +143 -66
- 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.d.ts +3 -0
- package/dist/setup-databases.js +47 -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 +28 -12
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +14 -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/sql.d.ts +2 -0
- package/dist/sql.js +35 -53
- 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 +3 -2
- package/dist/telemetry/events.js +11 -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 +2 -1
- package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lookml_ingest
|
|
3
|
-
description: Map a LookML view/model/explore into
|
|
3
|
+
description: Map a LookML view/model/explore into ktx semantic layer sources. Covers the LookML to ktx primitive table, provenance tagging, and three worked examples (overlay, standalone from derived_table, standalone with sql_always_where). Load when the turn contains `.lkml` content.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# LookML to
|
|
7
|
+
# LookML to ktx Semantic Layer
|
|
8
8
|
|
|
9
9
|
LookML views map to SL sources, `measure:` to measures, `explore: { join: }` to the join graph. This skill lays out the mapping and the three capture shapes.
|
|
10
10
|
|
|
11
11
|
## Mapping table
|
|
12
12
|
|
|
13
|
-
| LookML |
|
|
13
|
+
| LookML | ktx form | Notes |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `view: X { sql_table_name: …; measure:/dimension:/join: }` | **Overlay**
|
|
15
|
+
| `view: X { sql_table_name: …; measure:/dimension:/join: }` | **Overlay** named `X` with `measures`, computed-only `columns`, `column_overrides`, `joins`, `segments` | Manifest-backed; inherit grain/columns |
|
|
16
16
|
| `view: X { derived_table: { sql: … } }` | **Standalone** with top-level `sql:`, explicit `grain:` + `columns:` | No manifest entry exists |
|
|
17
17
|
| `view: X { sql_always_where: <p> }` | **Standalone** with `sql: SELECT * FROM <base> WHERE <p>` | Enforcement, not opt-in |
|
|
18
18
|
| `explore: { join: Y { sql_on: …; relationship: … } }` | `joins:` entry `{ to: Y, on: "<local> = Y.<col>", relationship: … }` | On the overlay or standalone |
|
|
@@ -23,7 +23,7 @@ Type map: `date`/`datetime`/`timestamp` → `time`; `yesno` → `boolean`; `numb
|
|
|
23
23
|
|
|
24
24
|
## Decision rules
|
|
25
25
|
|
|
26
|
-
LookML writes target the run connection directly. Unlike Looker runtime ingestion, the LookML adapter is configured on the warehouse
|
|
26
|
+
LookML writes target the run connection directly. Unlike Looker runtime ingestion, the LookML adapter is configured on the warehouse ktx connection, so do not look for `targetWarehouseConnectionId` and do not route through a mapping array.
|
|
27
27
|
|
|
28
28
|
Before any SL write, inspect the WorkUnit notes.
|
|
29
29
|
|
|
@@ -94,7 +94,7 @@ SL source, `tables:` frontmatter, `sl_refs`, or `emit_unmapped_fallback`:
|
|
|
94
94
|
schema or dataset, and table from the WorkUnit evidence.
|
|
95
95
|
3. Use only those names in `sql:`, `columns:`, and `grain:`. Map each `dimension_group` to ONE `{ name: <physical_col>, type: time, role: time }` entry - never one per timeframe.
|
|
96
96
|
|
|
97
|
-
| LookML input |
|
|
97
|
+
| LookML input | ktx `columns:` entry |
|
|
98
98
|
|---|---|
|
|
99
99
|
| `dimension_group: month { type: time; timeframes: [month]; sql: ${TABLE}.month_date ;; }` | `{ name: month_date, type: time, role: time }` |
|
|
100
100
|
| `dimension_group: date { type: time; timeframes: [raw, date, week, month]; sql: ${TABLE}.date ;; }` | `{ name: date, type: time, role: time }` - single entry, NOT `date_raw`/`date_date`/`date_week` |
|
|
@@ -132,7 +132,7 @@ explore: fct_labs {
|
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
ktx overlay at `<connId>/fct_labs.yaml`:
|
|
136
136
|
|
|
137
137
|
```yaml
|
|
138
138
|
name: fct_labs
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: metabase_ingest
|
|
3
|
-
description: Convert Metabase questions, models, and metrics into
|
|
3
|
+
description: Convert Metabase questions, models, and metrics into ktx Semantic Layer source definitions. Covers result-metadata to KSL column type mapping, FK/PK detection, near-duplicate deduplication, pre-aggregation decomposition, join-graph connectivity, and how to react to priorProvenance from earlier ingest syncs. Load when the WorkUnit contains `cards/<id>.json` files under a Metabase bundle.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Metabase to
|
|
7
|
+
# Metabase to ktx Semantic Layer
|
|
8
8
|
|
|
9
|
-
Each WorkUnit represents one Metabase collection's cards for one Metabase database (mapped to exactly one
|
|
9
|
+
Each WorkUnit represents one Metabase collection's cards for one Metabase database (mapped to exactly one ktx connection). Every `cards/<id>.json` file carries the resolved SQL, result_metadata, card type, collection path, and referenced-card ids. The WU's `sync-config.json` tells you which sync mode is active and which selections apply. `databases/<id>.json` tells you the target ktx connection.
|
|
10
10
|
|
|
11
11
|
## Context format
|
|
12
12
|
|
|
@@ -100,7 +100,7 @@ measures:
|
|
|
100
100
|
|
|
101
101
|
Overlay shape: `name:` plus any of `measures:`, `segments:`, `descriptions:`, `joins:`, `disable_joins:`, `exclude_columns:`, `column_overrides:`, or computed-only `columns:` entries with `expr` + `type`. Never include `sql:`, `table:`, `grain:`, or base-table `columns:` on a manifest-backed name — those would shadow the manifest's schema and drop its joins. Use `column_overrides:` for inherited column descriptions. Overlay `joins:` are merged additively with the manifest's joins (deduped by `to` + `on`); use `disable_joins: ["<on-clause>"]` to suppress a specific manifest join. After the overlay exists, use `sl_edit_source` for further tweaks. See `sl_capture` skill for the canonical overlay rule.
|
|
102
102
|
|
|
103
|
-
**Join discovery:** When your card's SQL references warehouse tables (e.g. in `FROM` or `JOIN` clauses), call `sl_discover({ query: '<table>' })` before writing. The matching manifest entry's `name` is the value you use in `joins: [- to: <name>]` only when the card output exposes a local key that matches the target source grain (for example `account_id = mart_account_segments.account_id`). Do not declare a
|
|
103
|
+
**Join discovery:** When your card's SQL references warehouse tables (e.g. in `FROM` or `JOIN` clauses), call `sl_discover({ query: '<table>' })` before writing. The matching manifest entry's `name` is the value you use in `joins: [- to: <name>]` only when the card output exposes a local key that matches the target source grain (for example `account_id = mart_account_segments.account_id`). Do not declare a ktx join just because the card SQL joins that table internally. If the output only exposes display fields such as `account_name`, keep the SQL source self-contained or project the key before adding the join. Use `many_to_one` for FK-to-dimension joins, `one_to_many` for the reverse.
|
|
104
104
|
|
|
105
105
|
**Hard rule on join columns (prevents broken joins):** For every join you declare, the local column on the left of `on:` MUST be (a) present in your source's projected output and (b) a key/ID column, never a display value. If the natural FK isn't in your SELECT, add it to SELECT before declaring the join. Joining `account_name = mart_account_segments.account_id` is always wrong - names are not identifiers and the equality produces zero matches. The validator rejects this with a "display value to identifier" error; the tool will refuse to save it. Add `account_id` to your SELECT and join on `account_id = mart_account_segments.account_id`, or omit the join entirely.
|
|
106
106
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: metricflow_ingest
|
|
3
|
-
description: Map a MetricFlow semantic_model or metric into
|
|
3
|
+
description: Map a MetricFlow semantic_model or metric into ktx semantic layer sources. Covers the MetricFlow to ktx primitive table, `extends:` inheritance flattening, metric-type handling (simple / derived / ratio / cumulative / conversion), `model: ref('x')` resolution, and four worked examples. Load when the turn contains `.yml`/`.yaml` files with top-level `semantic_models:` or `metrics:`.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# MetricFlow to
|
|
7
|
+
# MetricFlow to ktx Semantic Layer
|
|
8
8
|
|
|
9
|
-
A MetricFlow `semantic_model` maps to an SL source; MetricFlow `measures` map to
|
|
9
|
+
A MetricFlow `semantic_model` maps to an SL source; MetricFlow `measures` map to ktx measures; MetricFlow `entities` map to ktx `joins`; MetricFlow `metrics` (top-level) map to ktx measures OR to cross-model derived measures. Files in one WorkUnit are ALWAYS part of the same logical entity (a connected component, possibly spanning `extends:` + cross-model metric refs). Flatten inheritance and cross-file references at write time.
|
|
10
10
|
|
|
11
11
|
## Mapping table
|
|
12
12
|
|
|
13
|
-
| MetricFlow |
|
|
13
|
+
| MetricFlow | ktx form | Notes |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `semantic_model: X { model: ref('t') }` with measures + dimensions | **Overlay**
|
|
16
|
-
| `semantic_model: X { model: source('s','t') }` | **Overlay**
|
|
15
|
+
| `semantic_model: X { model: ref('t') }` with measures + dimensions | **Overlay** named `X` with `measures`, computed-only `columns`, `column_overrides`, `joins` | The `model:` ref resolves to a manifest table. |
|
|
16
|
+
| `semantic_model: X { model: source('s','t') }` | **Overlay** named `X` over table `t`. | Same shape; `source()` still resolves to a physical table. |
|
|
17
17
|
| `semantic_model: X { model: <literal> }` with no manifest entry | **Standalone** with explicit `sql:`, `grain:`, `columns:` | Happens when the dbt manifest isn't available. |
|
|
18
18
|
| `semantic_model: Y { extends: X }` | **Merge** Y's measures/dimensions/entities into X's overlay, or write a single overlay named for the most-derived child (Y) containing both X's and Y's primitives | Do not emit a second overlay for X - flatten. |
|
|
19
19
|
| `measures: [{ name, agg, expr }]` | `measures: [{ name, expr: "<agg>(<expr>)" }]` | Aggregation inlined. `agg: count_distinct` → `count(distinct ...)`. |
|
|
@@ -23,11 +23,11 @@ A MetricFlow `semantic_model` maps to an SL source; MetricFlow `measures` map to
|
|
|
23
23
|
| `metrics: [{ type: simple, filter: <jinja> }]` | **New measure** on the same source, with the filter translated to SQL and attached via `filter:` | Translate Jinja `{{ Dimension('x__y') }}` to the column name `y`. |
|
|
24
24
|
| `metrics: [{ type: derived, type_params: { expr, metrics } }]` | **Derived measure** on whichever source owns the referenced measures, with `expr:` referencing measure names | If the metric spans models, still write it once on the source owning the "primary" measure (the one the agent judges most central). Mention the cross-model chain in the description. |
|
|
25
25
|
| `metrics: [{ type: ratio, type_params: { numerator, denominator } }]` | Same as derived; `expr: "numerator / NULLIF(denominator, 0)"` if no explicit expr | Safe-division by default. |
|
|
26
|
-
| `metrics: [{ type: cumulative, type_params: { window, grain_to_date } }]` | **Standalone** source with a window-function SQL; reference the resulting column as a normal measure |
|
|
27
|
-
| `metrics: [{ type: conversion }]` | **Flag for human** - do NOT write. Emit a wiki note describing the intended semantics. | No
|
|
26
|
+
| `metrics: [{ type: cumulative, type_params: { window, grain_to_date } }]` | **Standalone** source with a window-function SQL; reference the resulting column as a normal measure | ktx SL has no first-class cumulative primitive (spec Non-goals). |
|
|
27
|
+
| `metrics: [{ type: conversion }]` | **Flag for human** - do NOT write. Emit a wiki note describing the intended semantics. | No ktx equivalent in v1. |
|
|
28
28
|
| Metric not mappable | Wiki page `<metric_name>-definition.md` with the full YAML body quoted | Capture the intent even if we can't emit SL. |
|
|
29
29
|
|
|
30
|
-
Type map: MetricFlow `time` to
|
|
30
|
+
Type map: MetricFlow `time` to ktx `time`; `categorical` to `string`; `number` to `number`; `boolean` to `boolean`. Follow `expr` over `name` when both differ - `expr` is the physical column.
|
|
31
31
|
|
|
32
32
|
Verify each MetricFlow model source table with entity_details before producing
|
|
33
33
|
the corresponding sl_write_source.
|
|
@@ -92,7 +92,7 @@ After every `sl_write_source`, call `sl_validate`. The warehouse will reject inv
|
|
|
92
92
|
|
|
93
93
|
## Cumulative metrics - sql-standalone fallback
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
ktx SL has no first-class `window:` or `grain_to_date:` primitive in v1 (spec Non-goals). Translate a MetricFlow cumulative metric to a standalone SL source with a window-function SQL:
|
|
96
96
|
|
|
97
97
|
```yaml
|
|
98
98
|
# MetricFlow input:
|
|
@@ -105,7 +105,7 @@ metrics:
|
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
```yaml
|
|
108
|
-
#
|
|
108
|
+
# ktx standalone output:
|
|
109
109
|
name: cum_revenue_7d
|
|
110
110
|
source_type: sql
|
|
111
111
|
sql: |
|
|
@@ -143,7 +143,7 @@ Do NOT emit SL for this. Instead:
|
|
|
143
143
|
- Write a wiki page at `wiki/global/<metric_name>-intent.md` quoting the full YAML body and a one-line explanation of the intended semantics (base event → conversion event within window).
|
|
144
144
|
- Call `emit_unmapped_fallback` with `rawPath` set to the MetricFlow file path, `reason: "conversion_metric_unsupported"`, and `fallback: "flagged"`.
|
|
145
145
|
|
|
146
|
-
When
|
|
146
|
+
When ktx SL gains conversion primitives, re-ingesting will find the prior wiki note (via `priorProvenance`) and replace it with an SL source.
|
|
147
147
|
|
|
148
148
|
## Provenance markers
|
|
149
149
|
|
|
@@ -174,7 +174,7 @@ semantic_models:
|
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
```yaml
|
|
177
|
-
#
|
|
177
|
+
# ktx overlay at <connId>/orders.yaml:
|
|
178
178
|
# <!-- from: raw-sources/.../models/orders.yml#L1-10 -->
|
|
179
179
|
name: orders
|
|
180
180
|
descriptions:
|
|
@@ -217,7 +217,7 @@ metrics:
|
|
|
217
217
|
```
|
|
218
218
|
|
|
219
219
|
```yaml
|
|
220
|
-
#
|
|
220
|
+
# ktx overlay at <connId>/orders_ext.yaml (one file; inheritance flattened):
|
|
221
221
|
# <!-- from: raw-sources/.../models/orders.yml#L1-10 -->
|
|
222
222
|
# <!-- from: raw-sources/.../models/orders_ext.yml#L1-8 -->
|
|
223
223
|
# <!-- from: raw-sources/.../metrics/orders_final.yml#L1-10 -->
|
|
@@ -256,7 +256,7 @@ metrics:
|
|
|
256
256
|
metrics: [{name: revenue}, {name: cost}]
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
-
Because the WorkUnit bundles all three files (cross-component union via the metric), write the derived measure on ONE of the two sources - pick the source whose domain "owns" the metric (here, `sales` - margin is inherently a sales metric). Cross-source references aren't native in
|
|
259
|
+
Because the WorkUnit bundles all three files (cross-component union via the metric), write the derived measure on ONE of the two sources - pick the source whose domain "owns" the metric (here, `sales` - margin is inherently a sales metric). Cross-source references aren't native in ktx SL; treat the metric's operands as already-resolvable in the target source's query context OR emit a standalone SQL that joins the two tables:
|
|
260
260
|
|
|
261
261
|
```yaml
|
|
262
262
|
# <connId>/sales.yaml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: notion_synthesize
|
|
3
|
-
description: Synthesize durable
|
|
3
|
+
description: Synthesize durable ktx wiki pages and semantic-layer sources from staged Notion pages, databases, data-source rows, and clustered Notion evidence. Load when a WorkUnit contains Notion raw files or Notion evidence chunks.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
package/dist/skills/sl/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sl
|
|
3
|
-
description:
|
|
3
|
+
description: ktx's semantic layer - a structured catalog of sources (tables/views), measures, joins, and segments expressed as YAML. Covers the schema and how to query it via `sl_query`. Use when the task involves querying pre-defined metrics (ARR, churn, retention, LTV, MAU) or reading SL source YAML to understand the catalog. Capture is handled by the `sl_capture` skill (memory-agent only).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Semantic Layer
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
ktx's semantic layer (SL) is a structured catalog. Each **source** represents a table, a SQL view, or an overlay that enriches a manifest-backed table with measures, computed columns, joins, and named segments. The catalog is the single source of truth for reusable business metrics.
|
|
9
9
|
|
|
10
10
|
This skill covers two parts:
|
|
11
11
|
- **Part 1** - Schema reference (what an SL source looks like).
|
|
@@ -21,7 +21,7 @@ skills must verify warehouse identifiers with `discover_data`,
|
|
|
21
21
|
|
|
22
22
|
## Part 1 - Schema reference
|
|
23
23
|
|
|
24
|
-
An SL source is a YAML file
|
|
24
|
+
An SL source is a YAML file under `semantic-layer/<connectionId>/`. The file's `name:` field is the source's identity — it mirrors the warehouse identifier verbatim (e.g. Snowflake's uppercase `SIGNED_UP`); the filename is only a derived label. Always address sources by name through the `sl_*` tools, never by file path. There are three flavors:
|
|
25
25
|
|
|
26
26
|
### Overlay sources
|
|
27
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sl_capture
|
|
3
|
-
description: How to capture new reusable patterns into
|
|
3
|
+
description: How to capture new reusable patterns into ktx's semantic layer - when a measure, segment, or join belongs in the catalog and how to write it generically so it stays small and useful over time. Loaded by the post-turn memory-agent only. The research agent does not write to the SL.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wiki_capture
|
|
3
|
-
description:
|
|
3
|
+
description: ktx's knowledge base - wiki pages for durable, reusable business knowledge. Covers capture workflow for user preferences, metric definitions, organizational conventions, and cross-references between wiki pages and semantic-layer sources. Loaded by the post-turn memory-agent only. The research agent reads wiki via `wiki_read`/`wiki_search` but does not write it.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
package/dist/source-mapping.js
CHANGED
|
@@ -23,7 +23,7 @@ async function createDefaultLookerClient(project, connectionId) {
|
|
|
23
23
|
return lookerCredentialsFromLocalConnection(lookerConnectionId, project.config.connections[lookerConnectionId]);
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
|
-
return factory.
|
|
26
|
+
return factory.createLookerClient(connectionId);
|
|
27
27
|
}
|
|
28
28
|
function isLookerConnection(project, connectionId) {
|
|
29
29
|
return String(project.config.connections[connectionId]?.driver ?? '').toLowerCase() === 'looker';
|
package/dist/sql.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { executeFederatedQuery } from './connectors/duckdb/federated-executor.js';
|
|
1
2
|
import { loadKtxProject } from './context/project/project.js';
|
|
2
3
|
import type { SqlAnalysisPort } from './context/sql-analysis/ports.js';
|
|
3
4
|
import type { KtxCliIo } from './cli-runtime.js';
|
|
@@ -18,6 +19,7 @@ export interface KtxSqlDeps {
|
|
|
18
19
|
loadProject?: typeof loadKtxProject;
|
|
19
20
|
createSqlAnalysis?: () => SqlAnalysisPort;
|
|
20
21
|
createScanConnector?: typeof createKtxCliScanConnector;
|
|
22
|
+
executeFederated?: typeof executeFederatedQuery;
|
|
21
23
|
}
|
|
22
24
|
export declare function runKtxSql(args: KtxSqlArgs, io?: KtxCliIo, deps?: KtxSqlDeps): Promise<number>;
|
|
23
25
|
export {};
|
package/dist/sql.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { FEDERATED_CONNECTION_ID } from './context/connections/federation.js';
|
|
2
|
+
import { executeProjectReadOnlySql } from './context/connections/project-sql-executor.js';
|
|
3
|
+
import { resolveConfiguredConnection } from './context/connections/resolve-connection.js';
|
|
1
4
|
import { loadKtxProject } from './context/project/project.js';
|
|
5
|
+
import { sqlAnalysisDialectForDriver } from './context/sql-analysis/dialect.js';
|
|
2
6
|
import { resolveOutputMode } from './io/mode.js';
|
|
3
7
|
import { createKtxCliScanConnector } from './local-scan-connectors.js';
|
|
4
8
|
import { createManagedDaemonSqlAnalysisPort } from './managed-python-http.js';
|
|
@@ -8,19 +12,6 @@ import { emitTelemetryEvent, reportException } from './telemetry/index.js';
|
|
|
8
12
|
import { collectTelemetryRedactionSecrets } from './telemetry/redaction-secrets.js';
|
|
9
13
|
import { scrubErrorClass } from './telemetry/scrubber.js';
|
|
10
14
|
profileMark('module:sql');
|
|
11
|
-
function sqlAnalysisDialectForDriver(driver) {
|
|
12
|
-
const normalized = String(driver ?? '').trim().toLowerCase();
|
|
13
|
-
const map = {
|
|
14
|
-
postgres: 'postgres',
|
|
15
|
-
bigquery: 'bigquery',
|
|
16
|
-
snowflake: 'snowflake',
|
|
17
|
-
mysql: 'mysql',
|
|
18
|
-
sqlserver: 'tsql',
|
|
19
|
-
sqlite: 'sqlite',
|
|
20
|
-
clickhouse: 'clickhouse',
|
|
21
|
-
};
|
|
22
|
-
return map[normalized] ?? 'postgres';
|
|
23
|
-
}
|
|
24
15
|
function queryVerb(sql) {
|
|
25
16
|
const first = sql.trim().split(/\s+/, 1)[0]?.toLowerCase();
|
|
26
17
|
if (first === 'select' || first === 'explain' || first === 'show' || first === 'with') {
|
|
@@ -79,11 +70,6 @@ function printSqlResult(output, mode, io) {
|
|
|
79
70
|
}
|
|
80
71
|
printPretty(output, io);
|
|
81
72
|
}
|
|
82
|
-
async function cleanupConnector(connector) {
|
|
83
|
-
if (connector?.cleanup) {
|
|
84
|
-
await connector.cleanup();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
73
|
function resultOutput(connectionId, result) {
|
|
88
74
|
return {
|
|
89
75
|
connectionId,
|
|
@@ -100,12 +86,10 @@ export async function runKtxSql(args, io = process, deps = {}) {
|
|
|
100
86
|
let project;
|
|
101
87
|
try {
|
|
102
88
|
project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir });
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
driver = String(connection.driver ?? 'unknown').toLowerCase();
|
|
108
|
-
demoConnection = isDemoConnection(args.connectionId, connection);
|
|
89
|
+
const isFederated = args.connectionId === FEDERATED_CONNECTION_ID;
|
|
90
|
+
const connection = isFederated ? undefined : resolveConfiguredConnection(project.config, args.connectionId);
|
|
91
|
+
driver = isFederated ? 'duckdb' : String(connection?.driver ?? 'unknown').toLowerCase();
|
|
92
|
+
demoConnection = isFederated ? false : isDemoConnection(args.connectionId, connection);
|
|
109
93
|
const createSqlAnalysis = deps.createSqlAnalysis ??
|
|
110
94
|
(() => createManagedDaemonSqlAnalysisPort({
|
|
111
95
|
cliVersion: args.cliVersion,
|
|
@@ -114,44 +98,42 @@ export async function runKtxSql(args, io = process, deps = {}) {
|
|
|
114
98
|
io,
|
|
115
99
|
}));
|
|
116
100
|
const analysisPort = createSqlAnalysis();
|
|
117
|
-
const dialect = sqlAnalysisDialectForDriver(connection
|
|
101
|
+
const dialect = isFederated ? 'duckdb' : sqlAnalysisDialectForDriver(connection?.driver);
|
|
118
102
|
const validation = await analysisPort.validateReadOnly(args.sql, dialect);
|
|
119
103
|
if (!validation.ok) {
|
|
120
104
|
throw new Error(validation.error ?? 'SQL is not read-only.');
|
|
121
105
|
}
|
|
122
106
|
const referencedTableCount = await safeReferencedTableCount(analysisPort, args.sql, dialect);
|
|
123
107
|
const createScanConnector = deps.createScanConnector ?? createKtxCliScanConnector;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!connector.capabilities.readOnlySql || !connector.executeReadOnly) {
|
|
128
|
-
throw new Error(`Connection "${args.connectionId}" does not support read-only SQL execution.`);
|
|
129
|
-
}
|
|
130
|
-
const result = await connector.executeReadOnly({
|
|
108
|
+
const result = await executeProjectReadOnlySql({
|
|
109
|
+
project,
|
|
110
|
+
input: {
|
|
131
111
|
connectionId: args.connectionId,
|
|
112
|
+
projectDir: args.projectDir,
|
|
113
|
+
connection,
|
|
132
114
|
sql: args.sql,
|
|
133
115
|
maxRows: args.maxRows,
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
116
|
+
},
|
|
117
|
+
createConnector: (connectionId) => createScanConnector(project, connectionId),
|
|
118
|
+
executeFederated: deps.executeFederated,
|
|
119
|
+
runId: 'cli-sql',
|
|
120
|
+
});
|
|
121
|
+
const mode = resolveOutputMode({ explicit: args.output, json: args.json, io });
|
|
122
|
+
printSqlResult(resultOutput(args.connectionId, result), mode, io);
|
|
123
|
+
await emitTelemetryEvent({
|
|
124
|
+
name: 'sql_completed',
|
|
125
|
+
projectDir: args.projectDir,
|
|
126
|
+
io,
|
|
127
|
+
fields: {
|
|
128
|
+
driver,
|
|
129
|
+
isDemoConnection: demoConnection,
|
|
130
|
+
queryVerb: queryVerb(args.sql),
|
|
131
|
+
referencedTableCount,
|
|
132
|
+
durationMs: Math.max(0, performance.now() - startedAt),
|
|
133
|
+
outcome: 'ok',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return 0;
|
|
155
137
|
}
|
|
156
138
|
catch (error) {
|
|
157
139
|
const errorClass = scrubErrorClass(error);
|
package/dist/startup-profile.js
CHANGED
|
@@ -28,7 +28,7 @@ export function installStartupProfileReporter() {
|
|
|
28
28
|
}
|
|
29
29
|
process.once('beforeExit', () => {
|
|
30
30
|
const total = now();
|
|
31
|
-
process.stderr.write('\
|
|
31
|
+
process.stderr.write('\nktx startup profile\n');
|
|
32
32
|
for (const event of events) {
|
|
33
33
|
const elapsed = event.at.toFixed(1).padStart(7);
|
|
34
34
|
if (event.duration === undefined) {
|
package/dist/status-project.d.ts
CHANGED
|
@@ -40,7 +40,6 @@ interface PipelineStatus {
|
|
|
40
40
|
interface StorageStatus {
|
|
41
41
|
state: string;
|
|
42
42
|
search: string;
|
|
43
|
-
gitAutoCommit: boolean;
|
|
44
43
|
gitAuthor: string;
|
|
45
44
|
}
|
|
46
45
|
interface ConfigStatus {
|
|
@@ -133,7 +132,6 @@ export interface ProjectStatus {
|
|
|
133
132
|
maxConcurrency: number;
|
|
134
133
|
failureMode: string;
|
|
135
134
|
};
|
|
136
|
-
memoryAutoCommit: boolean;
|
|
137
135
|
relationshipsDetail?: {
|
|
138
136
|
acceptThreshold: number;
|
|
139
137
|
reviewThreshold: number;
|
package/dist/status-project.js
CHANGED
|
@@ -384,7 +384,6 @@ function buildStorageStatus(config) {
|
|
|
384
384
|
return {
|
|
385
385
|
state: config.storage.state,
|
|
386
386
|
search: config.storage.search,
|
|
387
|
-
gitAutoCommit: config.storage.git.auto_commit,
|
|
388
387
|
gitAuthor: config.storage.git.author,
|
|
389
388
|
};
|
|
390
389
|
}
|
|
@@ -498,9 +497,10 @@ function buildConfigStatus(issues) {
|
|
|
498
497
|
if (list.length === 0) {
|
|
499
498
|
return { status: 'ok', detail: 'ktx.yaml schema valid', issues: [] };
|
|
500
499
|
}
|
|
500
|
+
// Error-severity issues never reach here — the doctor exits on them first.
|
|
501
501
|
return {
|
|
502
502
|
status: 'warn',
|
|
503
|
-
detail:
|
|
503
|
+
detail: `ktx.yaml schema valid · ${list.length} ignored field${list.length === 1 ? '' : 's'}`,
|
|
504
504
|
issues: list,
|
|
505
505
|
};
|
|
506
506
|
}
|
|
@@ -713,7 +713,6 @@ export async function buildProjectStatus(project, options = {}) {
|
|
|
713
713
|
maxConcurrency: config.ingest.workUnits.maxConcurrency,
|
|
714
714
|
failureMode: config.ingest.workUnits.failureMode,
|
|
715
715
|
},
|
|
716
|
-
memoryAutoCommit: config.memory.auto_commit,
|
|
717
716
|
relationshipsDetail: {
|
|
718
717
|
acceptThreshold: config.scan.relationships.acceptThreshold,
|
|
719
718
|
reviewThreshold: config.scan.relationships.reviewThreshold,
|
|
@@ -840,7 +839,7 @@ export function renderProjectStatus(status, options = {}) {
|
|
|
840
839
|
const sym = (level) => color(level, SYMBOL[level]);
|
|
841
840
|
const lines = [];
|
|
842
841
|
const dirStr = abbreviateHome(status.projectDir, env);
|
|
843
|
-
lines.push(`${bold('
|
|
842
|
+
lines.push(`${bold('ktx status')} ${dim('·')} ${status.projectName} ${dim(`(${dirStr})`)}`);
|
|
844
843
|
lines.push('');
|
|
845
844
|
const labelPad = 'Connections'.length;
|
|
846
845
|
const label = (text) => text.padEnd(labelPad);
|
|
@@ -960,8 +959,7 @@ export function renderProjectStatus(status, options = {}) {
|
|
|
960
959
|
lines.push(` ${bold('Relationships')} ${dim(`accept=${r.acceptThreshold}, review=${r.reviewThreshold}, maxLlmTables=${r.maxLlmTablesPerBatch}, concurrency=${r.validationConcurrency}`)}`);
|
|
961
960
|
}
|
|
962
961
|
lines.push(` ${bold('Agent')} ${dim(`max_iterations=${status.pipeline.agentMaxIterations}, tools=${status.pipeline.agentTools.join(', ') || '(none)'}`)}`);
|
|
963
|
-
lines.push(` ${bold('
|
|
964
|
-
lines.push(` ${bold('Git')} ${dim(`auto_commit=${status.storage.gitAutoCommit}, author=${status.storage.gitAuthor}`)}`);
|
|
962
|
+
lines.push(` ${bold('Git')} ${dim(`author=${status.storage.gitAuthor}`)}`);
|
|
965
963
|
lines.push('');
|
|
966
964
|
}
|
|
967
965
|
// Verdict + next steps
|
|
@@ -303,6 +303,7 @@ export declare const telemetryEventSchemas: {
|
|
|
303
303
|
}>;
|
|
304
304
|
durationMs: z.ZodNumber;
|
|
305
305
|
errorClass: z.ZodOptional<z.ZodString>;
|
|
306
|
+
errorDetail: z.ZodOptional<z.ZodString>;
|
|
306
307
|
sampleRate: z.ZodLiteral<1>;
|
|
307
308
|
mcpClientName: z.ZodOptional<z.ZodString>;
|
|
308
309
|
mcpClientVersion: z.ZodOptional<z.ZodString>;
|
|
@@ -357,9 +358,9 @@ export declare const telemetryEventSchemas: {
|
|
|
357
358
|
error: "error";
|
|
358
359
|
}>;
|
|
359
360
|
stage: z.ZodEnum<{
|
|
361
|
+
resolve: "resolve";
|
|
360
362
|
parse: "parse";
|
|
361
363
|
compile: "compile";
|
|
362
|
-
resolve: "resolve";
|
|
363
364
|
transpile: "transpile";
|
|
364
365
|
}>;
|
|
365
366
|
errorClass: z.ZodOptional<z.ZodString>;
|
|
@@ -459,7 +460,7 @@ export declare const telemetryEventCatalog: readonly [{
|
|
|
459
460
|
}, {
|
|
460
461
|
readonly name: "mcp_request_completed";
|
|
461
462
|
readonly description: "Emitted for sampled MCP tool requests.";
|
|
462
|
-
readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "sampleRate", "mcpClientName", "mcpClientVersion"];
|
|
463
|
+
readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "errorDetail", "sampleRate", "mcpClientName", "mcpClientVersion"];
|
|
463
464
|
}, {
|
|
464
465
|
readonly name: "daemon_started";
|
|
465
466
|
readonly description: "Emitted when the long-lived ktx-daemon HTTP server starts.";
|
package/dist/telemetry/events.js
CHANGED
|
@@ -145,6 +145,7 @@ const mcpRequestCompletedSchema = telemetryCommonEnvelopeSchema
|
|
|
145
145
|
outcome: outcomeSchema,
|
|
146
146
|
durationMs: z.number().nonnegative(),
|
|
147
147
|
errorClass: z.string().optional(),
|
|
148
|
+
errorDetail: z.string().max(1000).optional(),
|
|
148
149
|
sampleRate: z.literal(1),
|
|
149
150
|
// Raw, client-tool-controlled identity from the MCP initialize handshake
|
|
150
151
|
// (clientInfo.name/version). Optional: clients may omit clientInfo. Stored
|
|
@@ -326,7 +327,16 @@ export const telemetryEventCatalog = [
|
|
|
326
327
|
{
|
|
327
328
|
name: 'mcp_request_completed',
|
|
328
329
|
description: 'Emitted for sampled MCP tool requests.',
|
|
329
|
-
fields: [
|
|
330
|
+
fields: [
|
|
331
|
+
'toolName',
|
|
332
|
+
'outcome',
|
|
333
|
+
'durationMs',
|
|
334
|
+
'errorClass',
|
|
335
|
+
'errorDetail',
|
|
336
|
+
'sampleRate',
|
|
337
|
+
'mcpClientName',
|
|
338
|
+
'mcpClientVersion',
|
|
339
|
+
],
|
|
330
340
|
},
|
|
331
341
|
{
|
|
332
342
|
name: 'daemon_started',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
import { getKtxCliPackageInfo } from '../cli-runtime.js';
|
|
3
|
+
import { KtxExpectedError } from '../errors.js';
|
|
3
4
|
import { buildCommonEnvelope } from './events.js';
|
|
4
5
|
import { trackTelemetryException } from './emitter.js';
|
|
5
6
|
import { computeTelemetryProjectId, loadTelemetryIdentity } from './identity.js';
|
|
@@ -24,6 +25,16 @@ function consumeHandledPrimitive(value) {
|
|
|
24
25
|
recentHandledPrimitives.splice(index, 1);
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Expected operational errors are surfaced to the caller and recorded by the
|
|
30
|
+
* outcome-tagged telemetry events; they are not ktx faults, so they never reach
|
|
31
|
+
* Error Tracking. ktx marks these with KtxExpectedError at the site that knows
|
|
32
|
+
* the rejection is expected — the classification is never inferred globally from
|
|
33
|
+
* a generic error type, which would also swallow internal/fatal faults.
|
|
34
|
+
*/
|
|
35
|
+
function isExpectedError(error) {
|
|
36
|
+
return error instanceof KtxExpectedError;
|
|
37
|
+
}
|
|
27
38
|
function shouldSkipAsAlreadyReported(error, handled) {
|
|
28
39
|
if ((typeof error === 'object' || typeof error === 'function') && error !== null) {
|
|
29
40
|
if (reportedObjects.has(error)) {
|
|
@@ -116,6 +127,9 @@ function sanitizedError(error, secrets) {
|
|
|
116
127
|
}
|
|
117
128
|
export async function reportException(input) {
|
|
118
129
|
try {
|
|
130
|
+
if (isExpectedError(input.error)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
119
133
|
if (shouldSkipAsAlreadyReported(input.error, input.context.handled)) {
|
|
120
134
|
return;
|
|
121
135
|
}
|
package/dist/text-ingest.js
CHANGED
|
@@ -213,7 +213,7 @@ export async function runKtxTextIngest(args, io, deps = {}) {
|
|
|
213
213
|
const ingestInput = {
|
|
214
214
|
userId: args.userId,
|
|
215
215
|
chatId: `cli-text-ingest-${batchId}-${index + 1}`,
|
|
216
|
-
userMessage: `Ingest external text artifact ${artifactReference(item.label)} into
|
|
216
|
+
userMessage: `Ingest external text artifact ${artifactReference(item.label)} into ktx memory.`,
|
|
217
217
|
assistantMessage: item.content.trim(),
|
|
218
218
|
...(args.connectionId ? { connectionId: args.connectionId } : {}),
|
|
219
219
|
sourceType: 'external_ingest',
|
package/dist/tree-picker-tui.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { Box, Text, render as renderInkRuntime, useApp, useInput } from 'ink';
|
|
4
4
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { filterTree, flattenSelection, hasPartialChildren, isAncestorChecked, reducer, visibleNodeIds, } from './tree-picker-state.js';
|
|
6
|
+
import { TREE_PICKER_NAVIGATION_HINT } from './prompt-navigation.js';
|
|
6
7
|
const COLOR_THEME = {
|
|
7
8
|
text: 'white',
|
|
8
9
|
muted: 'gray',
|
|
@@ -17,7 +18,6 @@ const NO_COLOR_THEME = {
|
|
|
17
18
|
selected: 'white',
|
|
18
19
|
warning: 'white',
|
|
19
20
|
};
|
|
20
|
-
const DEFAULT_TREE_PICKER_HELP_TEXT = 'Up/Down to move, Right/Left to expand or collapse, Tab to select, Type to search, Enter to confirm, Escape to clear search or go back, Ctrl+C to exit.';
|
|
21
21
|
const DEFAULT_SKIP_EMPTY_MESSAGE = 'Nothing selected. Skip this step? Press Enter to skip or Escape to go back.';
|
|
22
22
|
function resolveTheme(env = process.env) {
|
|
23
23
|
return env.NO_COLOR || env.TERM === 'dumb' ? NO_COLOR_THEME : COLOR_THEME;
|
|
@@ -136,7 +136,6 @@ export function TreePickerApp(props) {
|
|
|
136
136
|
const hiddenBelow = Math.max(0, visibleIds.length - rows.offset - rows.items.length);
|
|
137
137
|
const searchMatchCount = filterTree(state).visibleIds.size;
|
|
138
138
|
const width = resolveTreePickerWidth(props.terminalWidth);
|
|
139
|
-
const helpText = props.chrome.helpText ?? DEFAULT_TREE_PICKER_HELP_TEXT;
|
|
140
139
|
const skipEmptyMessage = props.chrome.skipEmptyMessage ?? DEFAULT_SKIP_EMPTY_MESSAGE;
|
|
141
140
|
stateRef.current = state;
|
|
142
141
|
useEffect(() => {
|
|
@@ -177,7 +176,7 @@ export function TreePickerApp(props) {
|
|
|
177
176
|
app.exit();
|
|
178
177
|
}
|
|
179
178
|
});
|
|
180
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.active, children: "\u25C6" }), _jsxs(Text, { bold: true, children: [" ", props.chrome.title] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderTop: false, borderRight: false, borderBottom: false, borderColor: theme.active, paddingLeft: 1, children: [_jsx(Text, { color: theme.muted, children:
|
|
179
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.active, children: "\u25C6" }), _jsxs(Text, { bold: true, children: [" ", props.chrome.title] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderTop: false, borderRight: false, borderBottom: false, borderColor: theme.active, paddingLeft: 1, children: [_jsx(Text, { color: theme.muted, children: TREE_PICKER_NAVIGATION_HINT }), _jsx(Text, { children: " " }), (props.chrome.subtitleLines ?? []).map((line, idx) => (_jsx(Text, { color: theme.muted, children: line }, `subtitle-${idx}`))), (props.chrome.warningLines ?? []).map((line, idx) => (_jsx(Text, { color: theme.warning, children: line }, `chromewarn-${idx}`))), state.preLoadWarnings.map((warning) => (_jsx(Text, { color: theme.warning, children: warning }, warning))), _jsxs(Text, { children: [_jsx(Text, { color: theme.muted, children: "Search: " }), state.isNavigating ? (_jsx(Text, { color: theme.muted, children: state.search.query || '(type to search)' })) : (_jsxs(Text, { children: [state.search.query, _jsx(Text, { inverse: true, children: " " })] })), _jsxs(Text, { color: theme.muted, children: [" (", searchMatchCount, " match", searchMatchCount === 1 ? '' : 'es', ")"] })] }), _jsx(Text, { children: " " }), hiddenAbove > 0 ? _jsxs(Text, { color: theme.muted, children: ["\u2191 ", hiddenAbove, " more"] }) : null, rows.items.map((nodeId) => (_jsx(PickerRow, { state: state, nodeId: nodeId, width: width, theme: theme }, nodeId))), hiddenBelow > 0 ? _jsxs(Text, { color: theme.muted, children: ["\u2193 ", hiddenBelow, " more"] }) : null, state.pendingConfirm === 'save-confirm' ? (_jsx(Text, { color: theme.warning, children: props.chrome.confirmSaveMessage
|
|
181
180
|
? props.chrome.confirmSaveMessage(state)
|
|
182
181
|
: 'Confirm save? Press Enter to confirm or Escape to go back.' })) : null, state.pendingConfirm === 'skip-empty' ? _jsx(Text, { color: theme.warning, children: skipEmptyMessage }) : null, state.transientHint ? _jsx(Text, { color: theme.warning, children: state.transientHint.text }) : null] }), _jsx(Text, { color: theme.active, children: "\u2514" })] }));
|
|
183
182
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaelio/ktx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Standalone ktx context layer for data agents",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kaelio",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@clack/prompts": "1.4.0",
|
|
41
41
|
"@clickhouse/client": "^1.18.5",
|
|
42
42
|
"@commander-js/extra-typings": "14.0.0",
|
|
43
|
+
"@duckdb/node-api": "1.5.3-r.3",
|
|
43
44
|
"@google-cloud/bigquery": "^8.3.1",
|
|
44
45
|
"@looker/sdk": "^26.8.0",
|
|
45
46
|
"@looker/sdk-node": "^26.8.0",
|
|
Binary file
|