From 611f830fe0fdb0cd1e9c660898a19321e47e4c80 Mon Sep 17 00:00:00 2001 From: Luca Martial <48870843+luca-martial@users.noreply.github.com> Date: Mon, 18 May 2026 10:50:34 -0400 Subject: [PATCH 1/3] fix(docs): restore search overlay behavior (#134) --- docs-site/app/api/search/route.ts | 4 ++ docs-site/app/global.css | 10 ++-- docs-site/app/layout.tsx | 4 +- docs-site/tests/docs-index-route.test.mjs | 18 +++++++ docs-site/tests/docs-search-behavior.test.mjs | 53 +++++++++++++++++++ 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 docs-site/app/api/search/route.ts create mode 100644 docs-site/tests/docs-search-behavior.test.mjs diff --git a/docs-site/app/api/search/route.ts b/docs-site/app/api/search/route.ts new file mode 100644 index 00000000..d86bfc5b --- /dev/null +++ b/docs-site/app/api/search/route.ts @@ -0,0 +1,4 @@ +import { source } from "@/lib/source"; +import { createFromSource } from "fumadocs-core/search/server"; + +export const { GET } = createFromSource(source); diff --git a/docs-site/app/global.css b/docs-site/app/global.css index bc4ed8a4..08bd9b83 100644 --- a/docs-site/app/global.css +++ b/docs-site/app/global.css @@ -69,7 +69,11 @@ --color-fd-muted-foreground: #7a8d96; } -html, body { +/* Keep html overflow at the default `visible` so body's overflow + propagates to the viewport (per CSS Overflow spec). That lets + `react-remove-scroll-bar` lock viewport scroll via body alone while + leaving the sticky sidebar placeholder anchored to the viewport. */ +body { overflow-x: clip; } @@ -778,8 +782,8 @@ body::after { mix-blend-mode: overlay; } -/* Make sure content stays above background */ -body > * { +/* Make sure page content stays above the decorative background. */ +.ktx-site-shell { position: relative; z-index: 2; } diff --git a/docs-site/app/layout.tsx b/docs-site/app/layout.tsx index 48e12a3f..7c808130 100644 --- a/docs-site/app/layout.tsx +++ b/docs-site/app/layout.tsx @@ -41,7 +41,9 @@ export default function RootLayout({ children }: { children: ReactNode }) { suppressHydrationWarning > - {children} + +
{children}
+
); diff --git a/docs-site/tests/docs-index-route.test.mjs b/docs-site/tests/docs-index-route.test.mjs index 7d1c62c0..721813ec 100644 --- a/docs-site/tests/docs-index-route.test.mjs +++ b/docs-site/tests/docs-index-route.test.mjs @@ -111,3 +111,21 @@ test("/ktx/docs redirects to the docs introduction", async () => { `${docsBasePath}/docs/getting-started/introduction`, ); }); + +test("/ktx/api/search returns docs search results", async () => { + const response = await fetch( + `${docsSiteUrl}${docsBasePath}/api/search?query=setup`, + ); + + assert.equal(response.status, 200); + + const results = await response.json(); + assert.ok(Array.isArray(results), "search response should be an array"); + assert.ok( + results.some( + (result) => + typeof result.url === "string" && result.url.startsWith("/docs/"), + ), + "search should return at least one docs result", + ); +}); diff --git a/docs-site/tests/docs-search-behavior.test.mjs b/docs-site/tests/docs-search-behavior.test.mjs new file mode 100644 index 00000000..0a96482b --- /dev/null +++ b/docs-site/tests/docs-search-behavior.test.mjs @@ -0,0 +1,53 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; + +const docsSiteDir = join(dirname(fileURLToPath(import.meta.url)), ".."); + +async function readDocsFile(path) { + return readFile(join(docsSiteDir, path), "utf8"); +} + +test("root provider uses the base-path-aware search API", async () => { + const layout = await readDocsFile("app/layout.tsx"); + + assert.match(layout, /search=\{\{/); + assert.match(layout, /api:\s*"\/ktx\/api\/search"/); +}); + +test("site background stacking does not target every body child", async () => { + const css = await readDocsFile("app/global.css"); + + assert.doesNotMatch(css, /body\s*>\s*\*\s*\{[^}]*z-index/s); + assert.match(css, /\.ktx-site-shell\s*\{[^}]*z-index:\s*2/s); +}); + +test("search lock relies on body overflow propagation, not html or sidebar overrides", async () => { + const css = await readDocsFile("app/global.css"); + + // Body still clips horizontal overflow defensively. + assert.match(css, /(^|\s)body\s*\{[^}]*overflow-x:\s*clip/s); + + // html must keep its default `visible` overflow so body's lock + // (`overflow: hidden` from react-remove-scroll-bar) propagates to the + // viewport. Locking html directly breaks `position: sticky` on the + // sidebar placeholder. + assert.doesNotMatch(css, /(^|\s)html\s*,?\s*\{[^}]*overflow(-y|\s*:)\s*(hidden|clip)/s); + assert.doesNotMatch( + css, + /html:has\(body\[data-scroll-locked\]\)[^{]*\{[^}]*overflow:\s*(hidden|clip)/s, + ); + + // No site-specific overrides to body's data-scroll-locked overflow or + // to the sidebar placeholder when locked. + assert.doesNotMatch( + css, + /html\s+body\[data-scroll-locked\][^{]*\{[^}]*overflow:/s, + ); + assert.doesNotMatch( + css, + /body\[data-scroll-locked\]\s+\[data-sidebar-placeholder\][^{]*\{[^}]*position:\s*fixed/s, + ); +}); From 7d156d9a0648b3c68df87a7d47f0986e685d3b2b Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Mon, 18 May 2026 17:41:37 +0200 Subject: [PATCH 2/3] feat(docs): visualize KTX ingestion with ReactFlow diagram (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(docs): visualize KTX ingestion with ReactFlow diagram Reframe the introduction around the two user-facing ingestion outputs (wiki and executable semantic layer) and replace the static product-mechanics card flow with a ReactFlow diagram: sources fan into a sequential ingest pipeline, which forks into wiki and semantic-layer outputs connected by a bidirectional "references" edge. Drop the .ktx/raw-sources internal-implementation rows from the intro table and update the content test to guard the new copy. * Improve KTX docs introduction * feat(docs): animate ingestion flow with running dots Replace static smoothstep edges in the introduction page's ingestion diagram with a custom animated edge that runs glowing cyan dots along each path, conveying the source → stage → output flow. Dot duration scales with path length and is hidden under prefers-reduced-motion. * feat(docs): route ingestion atoms through full source→output journey Replace per-edge dots with full-journey particles: each atom is born at a source, threads the entire stage chain, and lands at either the wiki or semantic layer. Particles are tinted by their source's accent so the origin is legible. Each source produces exactly 2 atoms (8 total) to guarantee every input is visibly active, while the destination and begin offsets are randomized per page load. Particles populate on client mount to avoid hydration mismatch, and are hidden under prefers-reduced-motion. --- docs-site/components/product-mechanics.tsx | 999 ++++++++++-------- .../docs/getting-started/introduction.mdx | 65 +- docs-site/package.json | 7 +- .../tests/product-mechanics-content.test.mjs | 95 +- pnpm-lock.yaml | 184 ++++ 5 files changed, 870 insertions(+), 480 deletions(-) diff --git a/docs-site/components/product-mechanics.tsx b/docs-site/components/product-mechanics.tsx index 1c10e9aa..45baaa31 100644 --- a/docs-site/components/product-mechanics.tsx +++ b/docs-site/components/product-mechanics.tsx @@ -1,115 +1,502 @@ -import type { ReactNode } from "react"; +"use client"; -type SourceInput = { - name: string; - sources: string[]; - detail: string; - signal: string; +import { + Background, + BackgroundVariant, + type Edge, + type EdgeProps, + getSmoothStepPath, + Handle, + MarkerType, + type Node, + type NodeProps, + Position, + ReactFlow, +} from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; +import { useEffect, useMemo, useState } from "react"; + +type SourceNodeData = { accent: string; + body: string; + items: string[]; + title: string; }; -const sourceInputs = [ - { - name: "Database structure", - sources: [ - "Postgres", - "Snowflake", - "BigQuery", - "and many others", - ], - detail: "tables, columns, types, constraints, row counts", - signal: "grounds definitions in live database structure", - accent: "border-fd-primary", - }, - { - name: "BI and usage evidence", - sources: ["Metabase", "Looker"], - detail: "historic SQL, questions, dashboards, usage patterns", - signal: "extracts joins, filters, grain, and trusted examples", - accent: "border-orange-500", - }, - { - name: "Semantic modeling", - sources: ["dbt", "MetricFlow", "LookML"], - detail: "models, metrics, dimensions, explores, joins", - signal: "maps existing modeling logic into semantic entities", - accent: "border-amber-500", - }, - { - name: "Company documentation", - sources: ["Notion"], - detail: "Notion pages, policies, caveats", - signal: "links business language back to semantic references", - accent: "border-slate-500 dark:border-cyan-200", - }, -] satisfies SourceInput[]; +type StageNodeData = { + body: string; + index: number; + title: string; +}; -const ingestSteps = [ +type OutputNodeData = { + accent: string; + body: string; + path: string; + tags: string[]; + title: string; +}; + +type SourceNode = Node; +type StageNode = Node; +type OutputNode = Node; +type FlowNode = SourceNode | StageNode | OutputNode; + +const SOURCE_W = 210; +const SOURCE_H = 200; +const STAGE_W = 280; +const STAGE_H = 120; +const OUTPUT_W = 340; +const OUTPUT_H = 232; + +const ROW_SOURCES_Y = 80; +const ROW_STAGE_START_Y = 360; +const STAGE_GAP = 30; +const ROW_OUTPUTS_Y = 1000; + +const STAGE_CENTER_X = 460; +const STAGE_X = STAGE_CENTER_X - STAGE_W / 2; + +const SOURCE_GAP_X = 24; +const SOURCES_TOTAL = SOURCE_W * 4 + SOURCE_GAP_X * 3; +const SOURCES_START_X = STAGE_CENTER_X - SOURCES_TOTAL / 2; + +const OUTPUT_GAP_X = 180; +const OUTPUTS_TOTAL = OUTPUT_W * 2 + OUTPUT_GAP_X; +const OUTPUTS_START_X = STAGE_CENTER_X - OUTPUTS_TOTAL / 2; + +const EDGE_STROKE = "#94a3b8"; + +const sourceData: SourceNodeData[] = [ { - title: "extract evidence", - body: "Pull structured facts from schemas, SQL, BI metadata, and docs.", + title: "Databases", + body: "Schemas, columns, keys, row counts, and query history.", + items: ["PostgreSQL", "Snowflake", "BigQuery", "SQLite"], + accent: "#3b82f6", }, { - title: "reconcile entities", - body: "Merge names, measures, joins, and caveats into one project model.", + title: "BI tools", + body: "Dashboards, questions, explores, usage, and trusted examples.", + items: ["Metabase", "Looker"], + accent: "#f97316", }, { - title: "validate references", - body: "Check semantic fields and joins against database context before agents use them.", + title: "Modeling code", + body: "Existing metrics, dimensions, models, joins, and entities.", + items: ["dbt", "LookML", "MetricFlow"], + accent: "#f59e0b", + }, + { + title: "Docs and notes", + body: "Policies, caveats, team definitions, and analyst context.", + items: ["Notion", "Any text"], + accent: "#10b981", }, ]; -const artifacts = [ +const stageData: Omit[] = [ { - path: "semantic-layer/*.yaml", - title: "Typed query model", - body: "sources, grain, joins, dimensions, measures, filters, segments", + title: "Source adapters", + body: "Read each configured system in its native shape.", }, { + title: "Context builder", + body: "Turn source evidence into proposed context updates.", + }, + { + title: "Reconciliation", + body: "Merge new evidence with the context that already exists.", + }, + { + title: "Validation", + body: "Check references and semantics before agents rely on them.", + }, +]; + +const outputData: OutputNodeData[] = [ + { + title: "Wiki", path: "wiki/*.md", - title: "Business context", - body: "rules and caveats with sl_refs back to semantic-layer entities", + tags: ["free-form", "auto-maintained"], + body: "Definitions, caveats, policies, analyst notes, and business language that agents can search.", + accent: "#10b981", }, { - path: "raw-sources/", - title: "Evidence trail", - body: "scan artifacts, extracted metadata, relationship evidence", - }, - { - path: ".ktx/", - title: "Local indexes", - body: "embeddings and search indexes, not the source of truth", + title: "Semantic layer", + path: "semantic-layer/*.yaml", + tags: ["structured", "executable", "auto-maintained"], + body: "Metrics, joins, tables, dimensions, filters, and segments that KTX can validate and compile into SQL.", + accent: "#3b82f6", }, ]; -const runtimeSteps = [ - { - title: "Search wiki", - body: "Find business rules, caveats, synonyms, and sl_refs.", - }, - { - title: "Resolve semantic refs", - body: "Map measure and dimension names to approved entities.", - }, - { - title: "Validate fields", - body: "Check source, columns, joins, grain, filters, and segments.", - }, - { - title: "Build query plan", - body: "Create a semantic query plan before SQL is generated.", - }, - { - title: "Compile dialect SQL", - body: "Generate warehouse-shaped SQL instead of copying examples.", - }, - { - title: "Execute with bounds", - body: "Optionally run with bounded rows and return provenance.", - }, +const nodes: FlowNode[] = [ + ...sourceData.map((source, index) => ({ + id: `source-${index}`, + type: "source", + position: { + x: SOURCES_START_X + index * (SOURCE_W + SOURCE_GAP_X), + y: ROW_SOURCES_Y, + }, + data: source, + draggable: false, + selectable: false, + })), + ...stageData.map((stage, index) => ({ + id: `stage-${index}`, + type: "stage", + position: { + x: STAGE_X, + y: ROW_STAGE_START_Y + index * (STAGE_H + STAGE_GAP), + }, + data: { ...stage, index: index + 1 }, + draggable: false, + selectable: false, + })), + ...outputData.map((output, index) => ({ + id: `output-${index}`, + type: "output", + position: { + x: OUTPUTS_START_X + index * (OUTPUT_W + OUTPUT_GAP_X), + y: ROW_OUTPUTS_Y, + }, + data: output, + draggable: false, + selectable: false, + })), ]; +const REF_EDGE_STROKE = "#64748b"; + +const flowEdges = [ + ...sourceData.map((_, index) => ({ + id: `e-source-${index}-stage-0`, + source: `source-${index}`, + target: "stage-0", + })), + ...stageData.slice(0, -1).map((_, index) => ({ + id: `e-stage-${index}-stage-${index + 1}`, + source: `stage-${index}`, + target: `stage-${index + 1}`, + })), + ...outputData.map((_, index) => ({ + id: `e-stage-3-output-${index}`, + source: "stage-3", + target: `output-${index}`, + })), +].map((edge) => ({ + ...edge, + type: "smoothstep" as const, + style: { stroke: EDGE_STROKE, strokeWidth: 1.5 }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: EDGE_STROKE, + width: 16, + height: 16, + }, +})); + +const refsEdge = { + id: "e-output-refs", + source: "output-0", + sourceHandle: "right", + target: "output-1", + targetHandle: "left", + type: "straight" as const, + label: "references", + labelBgPadding: [6, 3] as [number, number], + labelBgBorderRadius: 4, + labelStyle: { + fontSize: 13, + fontWeight: 500, + fill: "var(--color-fd-muted-foreground)", + }, + labelBgStyle: { + fill: "var(--color-fd-background)", + stroke: "var(--color-fd-border)", + strokeWidth: 1, + }, + style: { + stroke: REF_EDGE_STROKE, + strokeWidth: 1.25, + strokeDasharray: "4 4", + }, + markerStart: { + type: MarkerType.ArrowClosed, + color: REF_EDGE_STROKE, + width: 14, + height: 14, + }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: REF_EDGE_STROKE, + width: 14, + height: 14, + }, +}; + + +function SourceNodeView({ data }: NodeProps) { + return ( +
+ +

+ {data.title} +

+

+ {data.body} +

+
+ {data.items.map((item) => ( + + {item} + + ))} +
+ +
+ ); +} + +function StageNodeView({ data }: NodeProps) { + return ( +
+ + + {data.index} + +
+

+ {data.title} +

+

+ {data.body} +

+
+ +
+ ); +} + +function OutputNodeView({ data }: NodeProps) { + return ( +
+ + + +

+ {data.path} +

+

+ {data.title} +

+
+ {data.tags.map((tag) => ( + + {tag} + + ))} +
+

+ {data.body} +

+
+ ); +} + +const PARTICLES_PER_SOURCE = 2; +const PARTICLE_SPEED_PX_PER_SEC = 130; +const PARTICLE_MIN_DURATION_SEC = 4; + +const stageTopY = (i: number) => ROW_STAGE_START_Y + i * (STAGE_H + STAGE_GAP); +const stageBottomY = (i: number) => stageTopY(i) + STAGE_H; + +function buildParticlePath( + sourceIndex: number, + outputIndex: number, +): { d: string; length: number } { + const sourceCenterX = + SOURCES_START_X + sourceIndex * (SOURCE_W + SOURCE_GAP_X) + SOURCE_W / 2; + const sourceBottomYVal = ROW_SOURCES_Y + SOURCE_H; + const outputCenterX = + OUTPUTS_START_X + outputIndex * (OUTPUT_W + OUTPUT_GAP_X) + OUTPUT_W / 2; + + const legs: Array<[number, number, number, number]> = [ + [sourceCenterX, sourceBottomYVal, STAGE_CENTER_X, stageTopY(0)], + [STAGE_CENTER_X, stageBottomY(0), STAGE_CENTER_X, stageTopY(1)], + [STAGE_CENTER_X, stageBottomY(1), STAGE_CENTER_X, stageTopY(2)], + [STAGE_CENTER_X, stageBottomY(2), STAGE_CENTER_X, stageTopY(3)], + [STAGE_CENTER_X, stageBottomY(3), outputCenterX, ROW_OUTPUTS_Y], + ]; + + const segments = legs.map(([sx, sy, tx, ty]) => { + const [segment] = getSmoothStepPath({ + sourceX: sx, + sourceY: sy, + sourcePosition: Position.Bottom, + targetX: tx, + targetY: ty, + targetPosition: Position.Top, + }); + return segment; + }); + + let d = segments[0]; + for (let i = 1; i < segments.length; i += 1) { + d += ` ${segments[i].replace(/^M/, "L")}`; + } + + const length = legs.reduce( + (sum, [sx, sy, tx, ty]) => sum + Math.abs(tx - sx) + Math.abs(ty - sy), + 0, + ); + + return { d, length }; +} + +type ParticleEdgeData = { + d: string; + duration: number; + beginOffset: number; + color: string; +}; + +type ParticleEdge = Edge; + +function ParticleEdgeView({ id, data }: EdgeProps) { + if (!data) return null; + const pathId = `mechanics-particle-path-${id}`; + return ( + <> + + + + + + + + + + + ); +} + +const nodeTypes = { + source: SourceNodeView, + stage: StageNodeView, + output: OutputNodeView, +}; + +const edgeTypes = { + particle: ParticleEdgeView, +}; + +const staticEdges = [...flowEdges, refsEdge]; + +type ParticleSpec = { + id: string; + sourceIndex: number; + outputIndex: number; +}; + +function makeRandomParticles(perSource: number): ParticleSpec[] { + const specs: ParticleSpec[] = []; + for (let sourceIndex = 0; sourceIndex < sourceData.length; sourceIndex += 1) { + for (let n = 0; n < perSource; n += 1) { + specs.push({ + id: `particle-${sourceIndex}-${n}`, + sourceIndex, + outputIndex: Math.floor(Math.random() * outputData.length), + }); + } + } + return specs; +} + +function specToEdge(spec: ParticleSpec): { + id: string; + source: string; + target: string; + type: "particle"; + data: ParticleEdgeData; +} { + const { d, length } = buildParticlePath(spec.sourceIndex, spec.outputIndex); + const duration = Math.max( + PARTICLE_MIN_DURATION_SEC, + length / PARTICLE_SPEED_PX_PER_SEC, + ); + return { + id: spec.id, + source: `source-${spec.sourceIndex}`, + target: `output-${spec.outputIndex}`, + type: "particle", + data: { + d, + duration, + beginOffset: Math.random() * duration, + color: sourceData[spec.sourceIndex].accent, + }, + }; +} + export function ProductMechanics() { + const [particles, setParticles] = useState([]); + + useEffect(() => { + setParticles(makeRandomParticles(PARTICLES_PER_SOURCE)); + }, []); + + const edges = useMemo( + () => [...staticEdges, ...particles.map(specToEdge)], + [particles], + ); + return (
- How KTX works + How ingestion works

- KTX reads source evidence, writes local context files, and gives - agents semantic search, validation, SQL, and provenance. + KTX ingests source evidence, reconciles it with your existing project, + and produces durable context that agents can search, review, and + execute.

-
- - -
+
+
+

+ Ingestion flow +

+

+ From scattered source systems to agent-ready context +

+

+ The inputs can be structured systems or loose team knowledge. The + outputs are the two files agents need: a readable wiki and an + executable semantic layer. +

+
+ +
+ + + +
+
+
); } - -function IngestionDiagram() { - return ( -
- - -
-
- Inputs KTX reads -
- {sourceInputs.map((source) => ( -
-
-

- {source.name} -

-

- {source.detail} -

-

- {source.signal} -

-
- -
- ))} -
-
- -
- KTX transforms evidence -
-
-

- KTX builds the model -

-
    - {ingestSteps.map((step, index) => ( - - ))} -
-
- -
-

- Outputs KTX writes -

-
- {artifacts.map((artifact) => ( - - ))} -
-
-
-
-
-
- ); -} - -function RuntimeDiagram() { - return ( -
- - -
-
- Agent sends - -
connection: warehouse
-
measure: orders.total_revenue
-
dimension: customers.segment
-
filter: orders.created_date >= '2024-01-01'
-
-

- This is the API surface agents should use: compact semantic intent, - not hand-written warehouse SQL. -

-
- -
- KTX planning and execution -
    - {runtimeSteps.map((step, index) => ( - - ))} -
-
-
- -
-
- Semantic query plan -
-

- source:{" "} - orders joined to customers as many_to_one -

-

- measure:{" "} - total_revenue = sum(amount) with refund filter -

-

- grain: segment - group-by with date predicate -

-

- result: dialect - SQL, bounded rows, and provenance -

-
-
- -
- KTX returns - -
select
-
customers.segment,
-
sum(orders.amount) as total_revenue
-
from analytics.orders
-
join analytics.customers
-
on orders.customer_id = customers.id
-
where orders.status != 'refunded'
-
and orders.created_date >= '2024-01-01'
-
group by 1
-
-

- The output can be SQL-only or executed results with provenance, so - the agent can show where the answer came from. -

-
-
-
- ); -} - -function DiagramHeader({ - body, - eyebrow, - id, - title, -}: { - body: string; - eyebrow: string; - id: string; - title: string; -}) { - return ( -
-

- {eyebrow} -

-

- {title} -

-

- {body} -

-
- ); -} - -function Artifact({ - body, - path, - title, -}: { - body: string; - path: string; - title: string; -}) { - return ( -
-

- {path} -

-

{title}

-

- {body} -

-
- ); -} - -function PipelineStep({ - body, - dark = false, - index, - title, -}: { - body: string; - dark?: boolean; - index: number; - title: string; -}) { - return ( -
  • - - {index} - - - - {title} - - - {body} - - -
  • - ); -} - -function ColumnLabel({ children }: { children: ReactNode }) { - return ( -

    - {children} -

    - ); -} - -function SourceList({ - sources, -}: { - sources: string[]; -}) { - return ( -
    -

    - Sources -

    -
    - {sources.map((source) => - source === "and many others" ? ( - - {source} - - ) : ( - - {source} - - ), - )} -
    -
    - ); -} - -function CodeBox({ children }: { children: ReactNode }) { - return ( -
    -
    {children}
    -
    - ); -} diff --git a/docs-site/content/docs/getting-started/introduction.mdx b/docs-site/content/docs/getting-started/introduction.mdx index 499d25a0..d0ee126d 100644 --- a/docs-site/content/docs/getting-started/introduction.mdx +++ b/docs-site/content/docs/getting-started/introduction.mdx @@ -1,6 +1,6 @@ --- title: Introduction -description: What KTX is, how it works, and where to start. +description: KTX is an open-source, self-improving context layer for data agents. --- import { ProductMechanics } from "@/components/product-mechanics"; @@ -23,46 +23,67 @@ import { ProductMechanics } from "@/components/product-mechanics"; Make analytics context usable by agents

    - {'KTX turns warehouse metadata, semantic definitions, BI usage, and team knowledge into local files and runtime tools that database agents can trust.'} + {'KTX is an open-source context layer for database agents. It turns warehouse metadata, BI models, query history, docs, and approved metric definitions into reviewable files agents can search and execute.'}

    -## Why KTX +## Why KTX helps -- Schemas show columns, not business rules. -- Agents need trusted metrics, joins, filters, caveats, and provenance. -- KTX captures that context before agents write SQL, docs, or semantic edits. +KTX gives agents a shared context workspace before they write SQL, answer a +question, or update analytics definitions. -## What KTX creates +- **Context as code.** KTX writes wiki pages and semantic-layer definitions as + git-based files you can review, diff, and merge. +- **Self-improving ingest.** KTX reads warehouses, BI tools, modeling code, + query history, and notes, then reconciles new evidence with accepted context. +- **Executable semantics.** Agents can use approved measures, joins, filters, + dimensions, and segments instead of rebuilding canonical SQL from scratch. +- **Agent-native access.** CLI and MCP tools let agents search context, compile + semantic queries, run read-only SQL, and propose updates. -| Path | What it gives agents | -|------|----------------------| -| `semantic-layer/` | Measures, dimensions, joins, grain, filters, segments | -| `wiki/` | Business definitions, caveats, policies, analyst notes | -| `raw-sources/` | Extracted metadata, scan output, relationship evidence | -| `.ktx/` | Local indexes, embeddings, setup state, runtime data | +KTX complements existing semantic layers by pairing metric definitions with the +surrounding business knowledge, caveats, provenance, and review workflow agents +need for data work. + +## How KTX works + +KTX has two connected sides: it builds and maintains the context layer, then +serves that context to agents at runtime. + +| Side | What KTX does | +|------|---------------| +| **Ingest and auto-maintain knowledge** | Reads your data stack and company knowledge, reconciles new evidence with accepted context, and keeps changes to `semantic-layer/` plus `wiki/` as version-controlled diffs automatically. | +| **Serve agents at runtime** | Helps agents find the right wiki pages and semantic-layer entities, then compile or execute semantic queries through CLI and MCP tools. | ## Use it for -- **Generate SQL** from approved measures, dimensions, joins, and filters -- **Explain provenance** with wiki context and warehouse evidence -- **Repair context** through reviewable YAML and Markdown diffs -- **Work alongside** dbt, LookML, MetricFlow, Looker, Metabase, and warehouses +Use KTX when agents need more than raw database access. Agents can search wiki +context, find semantic-layer entities, compile trusted semantic queries, run +read-only SQL, and use the same tools through MCP. -Databases: SQLite, PostgreSQL, Snowflake, BigQuery, ClickHouse, MySQL, SQL -Server. +- Generate SQL from approved metrics, joins, filters, and dimensions. +- Explain metric provenance with wiki context and source evidence. +- Repair context through reviewable YAML and Markdown diffs. +- Work alongside dbt, MetricFlow, LookML, Looker, Metabase, Notion, and + supported databases. ## Start here +Choose the route that matches what you want to do next. The quickstart is the +best first step for users; contributor setup lives in the community docs. + - Set up KTX and build your first context in under 10 minutes. + Install KTX, run setup, build context, and connect an agent. - - Hands-on workflows for scanning, ingesting, writing, and serving. + + Understand why agents need more than schema access and raw SQL. + + + Refresh context from databases, BI tools, query history, and documents. Edit semantic-layer YAML and wiki Markdown safely. diff --git a/docs-site/package.json b/docs-site/package.json index 229af1e3..4cf896ff 100644 --- a/docs-site/package.json +++ b/docs-site/package.json @@ -10,6 +10,7 @@ "test": "node --test tests/*.test.mjs" }, "dependencies": { + "@xyflow/react": "^12.10.2", "fumadocs-core": "16.8.10", "fumadocs-mdx": "15.0.4", "fumadocs-ui": "16.8.10", @@ -18,11 +19,11 @@ "react-dom": "19.2.6" }, "devDependencies": { + "@tailwindcss/postcss": "^4", "@types/node": "^25.7.0", "@types/react": "^19", "@types/react-dom": "^19", - "typescript": "^6.0", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4" + "tailwindcss": "^4", + "typescript": "^6.0" } } diff --git a/docs-site/tests/product-mechanics-content.test.mjs b/docs-site/tests/product-mechanics-content.test.mjs index bf03abcc..81d716d3 100644 --- a/docs-site/tests/product-mechanics-content.test.mjs +++ b/docs-site/tests/product-mechanics-content.test.mjs @@ -23,7 +23,7 @@ test("docs introduction frames the concept before showing product mechanics", as const heroIndex = introduction.indexOf("Make analytics context"); const whyIndex = introduction.indexOf("## Why KTX"); - const createsIndex = introduction.indexOf("## What KTX creates"); + const worksIndex = introduction.indexOf("## How KTX works"); const mechanicsIndex = introduction.indexOf(""); const useCaseIndex = introduction.indexOf("## Use it for"); const heroSource = introduction.slice(0, mechanicsIndex); @@ -34,12 +34,12 @@ test("docs introduction frames the concept before showing product mechanics", as "problem framing should appear after the hero", ); assert.ok( - createsIndex > whyIndex, - "artifact summary should appear after problem framing", + worksIndex > whyIndex, + "mechanics bridge should appear after problem framing", ); assert.ok( - mechanicsIndex > createsIndex, - "mechanics component should appear after the artifact summary", + mechanicsIndex > worksIndex, + "mechanics component should appear after the mechanics bridge", ); assert.ok( mechanicsIndex < useCaseIndex, @@ -49,49 +49,47 @@ test("docs introduction frames the concept before showing product mechanics", as assert.doesNotMatch(heroSource, /The Context Layer/); assert.doesNotMatch(heroSource, /Building Context/); assert.doesNotMatch(heroSource, /flex flex-wrap gap-3/); + assert.doesNotMatch(introduction, /raw-sources/); + assert.doesNotMatch(introduction, /\.ktx/); }); -test("product mechanics component covers source-specific context and SQL expansion", async () => { +test("product mechanics component explains ingestion outputs", async () => { const component = await readDocsFile("components/product-mechanics.tsx"); for (const expectedText of [ - "How KTX works", - "Build context from source evidence", - "Run agent requests through the model", - "Ingestion", - "Runtime", - "wiki/", - "semantic-layer/", - "raw-sources/", - ".ktx/", - "sl_refs", - "Database structure", - "BI and usage evidence", - "Semantic modeling", - "Company documentation", - "Notion pages", - "Sources", - "KTX transforms evidence", - "KTX builds the model", - "Outputs KTX writes", - "Postgres", + "How ingestion works", + "Ingestion flow", + "From scattered source systems to agent-ready context", + "wiki/*.md", + "semantic-layer/*.yaml", + "Wiki", + "Semantic layer", + "Databases", + "BI tools", + "Modeling code", + "Docs and notes", + "Source adapters", + "Context builder", + "Reconciliation", + "Validation", + "PostgreSQL", "Snowflake", "BigQuery", - "and many others", "Metabase", "Looker", + "dbt", "MetricFlow", "LookML", - "extract evidence", - "reconcile entities", - "validate references", - "semantic query plan", - "dialect SQL", - "bounded rows", - "provenance", - "measure: orders.total_revenue", - "dimension: customers.segment", - "select", + "Notion", + "Any text", + "compile into SQL", + '"use client"', + "@xyflow/react", + "=17' + react-dom: '>=17' + + '@xyflow/system@0.0.76': + resolution: {integrity: sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -3139,6 +3169,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -3325,6 +3358,44 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -5937,6 +6008,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6158,6 +6234,21 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9061,6 +9152,27 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -9191,6 +9303,29 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@xyflow/system': 0.0.76 + classcat: 5.0.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + zustand: 4.5.7(@types/react@19.2.14)(react@19.2.6) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.76': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -9459,6 +9594,8 @@ snapshots: dependencies: clsx: 2.1.1 + classcat@5.0.5: {} + clean-stack@2.2.0: {} clean-stack@5.3.0: @@ -9631,6 +9768,42 @@ snapshots: csstype@3.2.3: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + data-uri-to-buffer@4.0.1: {} debug@4.4.3: @@ -12730,6 +12903,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -12907,4 +13084,11 @@ snapshots: zod@4.4.3: {} + zustand@4.5.7(@types/react@19.2.14)(react@19.2.6): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.6 + zwitch@2.0.4: {} From b507ff171de5d56bcef2002c81233893311d8598 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Mon, 18 May 2026 19:22:19 +0200 Subject: [PATCH 3/3] docs: revamp quickstart and tighten code-block styling (#135) * docs: streamline quickstart * feat(docs): simplify quickstart code-block styling Remove the fake terminal chrome (traffic lights + zsh header) and language pill from bash blocks, and the teal left-accent from output blocks. Bash fences now render as minimal cards; text fences route to a muted "output" preview. Make detectLanguage recursive and enable addLanguageClass in source.config.ts so Shiki tokens carry through to the renderer. Switch Shiki themes to min-light / github-dark and disable monospace ligatures so flag pairs like --agents keep a visible space. * fix(docs): restore quickstart CI snippets --- docs-site/app/global.css | 92 +---- docs-site/components/code-block.tsx | 68 ++-- docs-site/components/copy-button.tsx | 10 +- docs-site/components/docs-page-actions.tsx | 2 +- .../content/docs/cli-reference/ktx-dev.mdx | 2 +- .../content/docs/cli-reference/ktx-ingest.mdx | 4 +- .../docs/getting-started/quickstart.mdx | 346 +++++++----------- .../docs/integrations/agent-clients.mdx | 2 +- docs-site/source.config.ts | 10 +- 9 files changed, 191 insertions(+), 345 deletions(-) diff --git a/docs-site/app/global.css b/docs-site/app/global.css index 08bd9b83..e7e2c5b2 100644 --- a/docs-site/app/global.css +++ b/docs-site/app/global.css @@ -165,6 +165,17 @@ pre { line-height: 1.7 !important; } +/* Disable monospace ligatures so `--flag` keeps a visible space and double + dashes don't fuse into an em-dash glyph. */ +code, +pre, +pre code, +.ktx-code, +.ktx-code code { + font-variant-ligatures: none !important; + font-feature-settings: "liga" 0, "calt" 0 !important; +} + .dark pre { background: transparent !important; } @@ -220,57 +231,10 @@ figure[data-rehype-pretty-code-figure]:has(.ktx-code) { margin: 0; } -/* ── Mode A: Terminal ─────────────────────── */ -.ktx-code-terminal { - background: #0c1417; - border: 1px solid rgba(255, 255, 255, 0.08); - color: #c8c3bc; - box-shadow: - 0 1px 2px rgba(0, 0, 0, 0.1), - 0 12px 32px -16px rgba(0, 0, 0, 0.3); -} - -.ktx-code-terminal:hover { - border-color: rgba(34, 211, 238, 0.2); - box-shadow: - 0 1px 2px rgba(0, 0, 0, 0.1), - 0 14px 32px -12px rgba(34, 211, 238, 0.18); -} - -.ktx-code-terminal-head { - display: flex; - align-items: center; - gap: 6px; - padding: 10px 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.06); - background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent); -} - -.ktx-tl-dot { - width: 11px; - height: 11px; - border-radius: 999px; - flex-shrink: 0; -} - -.ktx-code-terminal-label { - margin-left: 8px; - font-size: 11px; - font-weight: 500; - letter-spacing: 0.02em; - color: rgba(255, 255, 255, 0.4); -} - -.ktx-code-body-terminal { - background: transparent !important; - color: #c8c3bc !important; -} - /* ── Mode D: Output preview (wizard prompts, status output) ── */ .ktx-code-output { background: var(--color-fd-muted); border: 1px solid var(--color-fd-border); - border-left: 3px solid color-mix(in oklch, var(--color-fd-primary) 50%, var(--color-fd-border)); position: relative; box-shadow: 0 1px 2px rgba(27, 27, 24, 0.02); } @@ -278,17 +242,14 @@ figure[data-rehype-pretty-code-figure]:has(.ktx-code) { .dark .ktx-code-output { background: #111a1e; border-color: rgba(255, 255, 255, 0.05); - border-left-color: rgba(34, 211, 238, 0.25); } .ktx-code-output:hover { border-color: color-mix(in oklch, var(--color-fd-primary) 25%, var(--color-fd-border)); - border-left-color: var(--color-fd-primary); } .dark .ktx-code-output:hover { border-color: rgba(255, 255, 255, 0.08); - border-left-color: rgba(34, 211, 238, 0.45); } .ktx-code-output-label { @@ -308,8 +269,8 @@ figure[data-rehype-pretty-code-figure]:has(.ktx-code) { .ktx-code-output-copy { position: absolute !important; - top: 6px !important; - right: 6px !important; + top: 7px !important; + right: 8px !important; opacity: 0; transform: translateY(-4px); transition: opacity 0.2s var(--ktx-ease), transform 0.2s var(--ktx-ease); @@ -449,30 +410,10 @@ figure[data-rehype-pretty-code-figure]:has(.ktx-code) { border-color: rgba(34, 211, 238, 0.2); } -.ktx-code-minimal-lang { - position: absolute; - top: 8px; - left: 14px; - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-fd-muted-foreground); - font-family: var(--font-display), var(--font-sans), sans-serif; - opacity: 0; - transition: opacity 0.2s var(--ktx-ease); - pointer-events: none; - z-index: 1; -} - -.ktx-code-minimal:hover .ktx-code-minimal-lang { - opacity: 0.5; -} - .ktx-code-minimal-copy { position: absolute !important; - top: 6px !important; - right: 6px !important; + top: 7px !important; + right: 8px !important; opacity: 0; transform: translateY(-4px); transition: opacity 0.2s var(--ktx-ease), transform 0.2s var(--ktx-ease); @@ -1062,8 +1003,7 @@ body::after { .pill-badge .pill-dot { animation: none; } .card-lift { transition: none; } .ktx-code, - .ktx-code-minimal-copy, - .ktx-code-minimal-lang { + .ktx-code-minimal-copy { transition: none; } #nd-sidebar div[data-state]:not([class]) > button[data-state] svg { diff --git a/docs-site/components/code-block.tsx b/docs-site/components/code-block.tsx index 7d6a22af..9c9d71ec 100644 --- a/docs-site/components/code-block.tsx +++ b/docs-site/components/code-block.tsx @@ -13,7 +13,7 @@ type Props = ComponentPropsWithoutRef<"pre"> & { "data-language"?: string; }; -const TERMINAL_LANGS = new Set(["bash", "sh", "shell", "zsh"]); +const OUTPUT_LANGS = new Set(["text", "plain", "plaintext", "console", "output"]); const WIZARD_GLYPHS = /^\s*[◆◇◯◐○●]/; function extractText(node: ReactNode): string { @@ -27,6 +27,33 @@ function extractText(node: ReactNode): string { return ""; } +function findLanguageInNode(node: ReactNode): string | null { + if (!isValidElement(node)) return null; + const props = (node as ReactElement<{ + className?: string; + "data-language"?: string; + children?: ReactNode; + }>).props; + + const dataLang = props["data-language"]; + if (typeof dataLang === "string" && dataLang) return dataLang; + + const className = typeof props.className === "string" ? props.className : ""; + const m = className.match(/language-([\w-]+)/); + if (m) return m[1]; + + const children = props.children; + if (Array.isArray(children)) { + for (const child of children) { + const found = findLanguageInNode(child); + if (found) return found; + } + } else if (children) { + return findLanguageInNode(children); + } + return null; +} + function detectLanguage(props: Props, children: ReactNode): string | null { const dataLang = props["data-language"]; if (typeof dataLang === "string" && dataLang) return dataLang; @@ -35,14 +62,7 @@ function detectLanguage(props: Props, children: ReactNode): string | null { const m = className.match(/language-([\w-]+)/); if (m) return m[1]; - if (isValidElement(children)) { - const childProps = (children as ReactElement<{ className?: string }>).props; - const childClass = typeof childProps.className === "string" ? childProps.className : ""; - const cm = childClass.match(/language-([\w-]+)/); - if (cm) return cm[1]; - } - - return null; + return findLanguageInNode(children); } export function CodeBlock(props: Props) { @@ -50,32 +70,11 @@ export function CodeBlock(props: Props) { const language = detectLanguage(props, children); const codeText = extractText(children); - const isTerminal = language !== null && TERMINAL_LANGS.has(language); - const isOutput = !isTerminal && WIZARD_GLYPHS.test(codeText); const hasTitle = typeof title === "string" && title.length > 0; - - // Mode A - Terminal (commands the user types) - if (isTerminal) { - return ( -
    -
    - - - - - {hasTitle ? title : "zsh"} - - -
    -
    -          {children}
    -        
    -
    - ); - } + const isOutput = + !hasTitle && + (WIZARD_GLYPHS.test(codeText) || + (language !== null && OUTPUT_LANGS.has(language))); // Mode D - Output preview (wizard prompts, terminal output) if (isOutput) { @@ -110,7 +109,6 @@ export function CodeBlock(props: Props) { // Mode C - Minimal default return (
    - {language && {language}}
             {children}
    diff --git a/docs-site/components/copy-button.tsx b/docs-site/components/copy-button.tsx
    index 876f5de0..c0dd1f31 100644
    --- a/docs-site/components/copy-button.tsx
    +++ b/docs-site/components/copy-button.tsx
    @@ -25,12 +25,12 @@ export function CopyButton({ text, className = "" }: Props) {
           type="button"
           onClick={onClick}
           aria-label={copied ? "Copied" : "Copy code"}
    -      className={`inline-flex items-center justify-center w-7 h-7 rounded-md transition-all hover:bg-white/5 ${className}`}
    +      className={`inline-flex items-center justify-center w-9 h-9 rounded-md transition-all hover:bg-fd-muted ${className}`}
         >
           {copied ? (
             
           ) : (
              setCopied(false), 1500);
         } catch {
    -      // Clipboard denied — fail silently
    +      // Clipboard denied - fail silently
         }
       };
     
    diff --git a/docs-site/content/docs/cli-reference/ktx-dev.mdx b/docs-site/content/docs/cli-reference/ktx-dev.mdx
    index 50d4ea77..efa3d74b 100644
    --- a/docs-site/content/docs/cli-reference/ktx-dev.mdx
    +++ b/docs-site/content/docs/cli-reference/ktx-dev.mdx
    @@ -36,7 +36,7 @@ directory. Use it from any directory to generate editor or agent schema files.
     
     | Flag | Description | Default |
     |------|-------------|---------|
    -| `--output ` | Write the schema to a file instead of stdout | — |
    +| `--output ` | Write the schema to a file instead of stdout | - |
     
     ## `dev runtime` Subcommands
     
    diff --git a/docs-site/content/docs/cli-reference/ktx-ingest.mdx b/docs-site/content/docs/cli-reference/ktx-ingest.mdx
    index 9d94cd88..49485d10 100644
    --- a/docs-site/content/docs/cli-reference/ktx-ingest.mdx
    +++ b/docs-site/content/docs/cli-reference/ktx-ingest.mdx
    @@ -33,7 +33,7 @@ connections when you use `--all`.
     | `--plain` | Print plain text output | `true` |
     | `--json` | Print JSON output | `false` |
     | `--yes` | Install required managed runtime features without prompting | `false` |
    -| `--no-input` | Disable interactive terminal input | — |
    +| `--no-input` | Disable interactive terminal input | - |
     
     `--fast` and `--deep` are mutually exclusive. Depth flags apply only to
     database connections. Query-history flags apply only to database connections
    @@ -60,7 +60,7 @@ read one item from stdin.
     | Flag | Description | Default |
     |------|-------------|---------|
     | `--text ` | Text content to ingest; repeat for a batch | `[]` |
    -| `--connection-id ` | Optional KTX connection id for semantic-layer capture | — |
    +| `--connection-id ` | Optional KTX connection id for semantic-layer capture | - |
     | `--user-id ` | Memory user id for capture attribution | `local-cli` |
     | `--json` | Print JSON output | `false` |
     | `--fail-fast` | Stop after the first failed text item | `false` |
    diff --git a/docs-site/content/docs/getting-started/quickstart.mdx b/docs-site/content/docs/getting-started/quickstart.mdx
    index 0118522c..23c77827 100644
    --- a/docs-site/content/docs/getting-started/quickstart.mdx
    +++ b/docs-site/content/docs/getting-started/quickstart.mdx
    @@ -1,135 +1,93 @@
     ---
     title: Quickstart
    -description: Set up KTX, build local context, and connect your coding agent.
    +description: Install KTX, run setup, and connect your coding agent.
     ---
     
    -This guide gets a local analytics project ready for KTX. You will install the
    -CLI, run the setup wizard, connect a database, build context, and install agent
    -rules that teach your coding assistant which KTX commands to run.
    +This guide takes a local analytics project from empty to agent-ready. You'll
    +install the CLI, run one guided setup command, and hand the context to a
    +coding assistant.
     
    -If you are a coding assistant choosing a docs route, start with the
    -[Agent Quickstart](/docs/ai-resources/agent-quickstart). This page is the
    -human setup walkthrough.
    +If you're a coding assistant choosing a docs route, start with the
    +[Agent Quickstart](/docs/ai-resources/agent-quickstart) instead.
     
    -## What setup does
    -
    -`ktx setup` is the main project workflow. It can create or resume `ktx.yaml`,
    -configure model and embedding providers, add database connections, add optional
    -context sources, build the first context artifacts, and install agent
    -integration.
    -
    -When you run bare `ktx` in an interactive terminal outside a KTX project, the
    -CLI opens the same setup experience. Inside an existing project, `ktx setup`
    -resumes incomplete work or opens a menu for changing setup, connecting an
    -agent, checking status, or exploring a demo project.
    +
    +
    + No warehouse handy? +
    +
    + Try KTX against a real data stack - Postgres, dbt, Metabase, and Notion + pre-loaded with the Orbit demo corpus. The page lists demo credentials + you can paste straight into `ktx setup`. +
    + + Get demo credentials at kaelio.com/start → + +
    ## Install the CLI -Install the published `@kaelio/ktx` package: +Install the published package globally: ```bash npm install -g @kaelio/ktx ``` -Then run setup from the analytics project directory: +KTX is open source. If you'd like to hack on it or run from a local checkout, +the source lives at [github.com/kaelio/ktx](https://github.com/kaelio/ktx) - +see [Contributing](/docs/community/contributing) to get set up. + +## Run setup + +From your project directory, run: ```bash ktx setup ``` -The local checkout workflow is only for KTX contributors. See -[Contributing](/docs/community/contributing) for that path. +The wizard walks you through everything KTX needs in one pass: -## Step 1: Choose the project +1. **Project** - creates or resumes `ktx.yaml` in the current directory. +2. **LLM** - picks a Claude backend. The default uses your local Claude Code + session, so no API key is required. You can also use an Anthropic API key + or Vertex AI. +3. **Embeddings** - picks an embeddings backend. Choose OpenAI for hosted + embeddings or `sentence-transformers` to run locally without an API key. +4. **Database** - adds at least one primary connection. Supported drivers: + SQLite, PostgreSQL, MySQL, ClickHouse, SQL Server, BigQuery, and Snowflake. +5. **Context sources** - optionally adds dbt, MetricFlow, LookML, Looker, + Metabase, or Notion. You can skip and add them later. +6. **Build** - runs the first ingest so semantic-layer sources and wiki pages + are ready for agents. +7. **Agent integration** - installs project-local rules for Claude Code, + Codex, Cursor, OpenCode, or universal `.agents`. -In an interactive terminal, setup can create a new KTX project or resume the -nearest existing project. The main project file is `ktx.yaml`. - -For scripted setup, pass the project directory explicitly: - -```bash -ktx setup --project-dir ./analytics -``` - -If setup exits early, rerun `ktx setup` in the same directory. KTX keeps local -setup progress under `.ktx/setup/` and resumes from the remaining work. - -## Step 2: Configure the LLM - -KTX uses a Claude model for ingest agents that turn schemas, SQL, BI metadata, -and documents into semantic-layer sources and wiki context. - -Setup supports three LLM provider paths: - -| Provider | Use when | Credential model | -|----------|----------|------------------| -| Claude subscription (Pro/Max) | You want KTX to use your local Claude Code session | Claude Code local authentication | -| Anthropic API key | You have an Anthropic API key | `ANTHROPIC_API_KEY` or a local `file:` secret | -| Google Vertex AI for Anthropic Claude | Your organization runs Claude through Google Cloud | Application Default Credentials plus Vertex project and location | - -For Anthropic API, setup can read the key from the environment or save a pasted -key to `.ktx/secrets/anthropic-api-key`. `ktx.yaml` stores an `env:` or `file:` -reference, not the raw key. - -For Vertex AI, setup uses Google Application Default Credentials. It can read -your active `gcloud` project, list visible projects, or accept explicit -`--vertex-project` and `--vertex-location` values. - -To use your local Claude Code session instead of an API key, set: - -```yaml -llm: - provider: - backend: claude-code - models: - default: sonnet - triage: haiku - candidateExtraction: sonnet - curator: sonnet - reconcile: sonnet - repair: sonnet -``` - -`claude-code` uses the Claude Code authentication already configured on your -machine. It doesn't use `ANTHROPIC_API_KEY`, Vertex credentials, AI Gateway -tokens, or Bedrock credentials. In non-interactive setup, pass -`--llm-model opus`, `--llm-model sonnet`, `--llm-model haiku`, or a full Claude -model ID to select the Claude Code model. - -Setup checks the selected model before saving. Anthropic API setup fetches live -Claude model choices when possible and falls back to bundled defaults if model -discovery is unavailable. - -## Step 3: Configure embeddings - -KTX uses embeddings for semantic search over semantic-layer sources, wiki -context, schema metadata, and relationship evidence. - -| Backend | Default model | Notes | -|---------|---------------|-------| -| OpenAI | `text-embedding-3-small` | Recommended for hosted embeddings. Requires an OpenAI API key. | -| Local sentence-transformers | `all-MiniLM-L6-v2` | Runs through the KTX-managed Python runtime. No hosted embedding key is required. | - -OpenAI setup reads `OPENAI_API_KEY` or saves a local secret file. Local -sentence-transformers setup can install and start the managed runtime during -setup. To prepare that runtime before setup, run: +If you choose local `sentence-transformers` embeddings, KTX uses the managed +Python runtime. To prepare it before setup, run: ```bash ktx dev runtime install --feature local-embeddings --yes ktx dev runtime start --feature local-embeddings ``` -## Step 4: Add a database - -KTX needs at least one primary database connection before it can build database -context. The wizard supports SQLite, PostgreSQL, MySQL, ClickHouse, SQL Server, -BigQuery, and Snowflake. - -You can usually enter connection fields interactively or provide a URL. Secret -URLs can be stored as local files under `.ktx/secrets/` or referenced with -`env:NAME` in `ktx.yaml`. - -After saving a connection, setup tests it and builds fast schema context: +During the database step, setup tests the saved connection and builds initial +schema context: ```text Testing warehouse @@ -137,114 +95,24 @@ Testing warehouse Building schema context for warehouse Running fast database ingest - -Database ready - warehouse - PostgreSQL - schema context complete ``` -PostgreSQL, BigQuery, and Snowflake can also enable query-history ingest. Query -history helps KTX learn common query patterns, joins, service-account filters, -and warehouse-specific usage. BigQuery and Snowflake support a lookback window; -Postgres reads the current `pg_stat_statements` aggregate data instead. +If setup exits early, rerun `ktx setup` in the same directory. KTX keeps +progress under `.ktx/setup/` and resumes from the remaining work. -## Step 5: Add context sources +> **Note:** Running bare `ktx` in an interactive terminal outside a KTX +> project opens the same wizard. Inside a project, it opens a menu for +> resuming setup, connecting an agent, checking status, or exploring a +> pre-built demo project. -Context sources are optional, but they make the first context layer much richer. -Setup can add: +## Verify -| Source | Typical input | What KTX learns | -|--------|---------------|-----------------| -| dbt | Local project or Git repo | Models, columns, tests, descriptions, tags | -| MetricFlow | Local project or Git repo | Semantic models, metrics, dimensions, entities | -| LookML | Local files or Git repo | Views, explores, dimensions, measures, joins | -| Looker | API URL and credentials | Explores, looks, dashboards, model metadata | -| Metabase | API URL and key | Questions, dashboards, BI database mappings | -| Notion | Integration token and crawl settings | Business docs and knowledge pages | - -Setup maps BI and source metadata back to your primary warehouse connection so -generated context points at the right tables. - -You can skip this step and add sources later by rerunning `ktx setup`. - -## Step 6: Build context - -The context build turns configured databases and sources into local artifacts -agents can read. It runs database ingest first, then source ingest and memory -updates. - -Fast database ingest records deterministic schema grounding. Deep ingest adds -AI-enriched descriptions, embeddings, relationship evidence, and query-history -context when configured. - -When the build finishes, setup verifies that agent-ready context exists: - -```text -KTX context is ready for agents. - -Databases: - warehouse: deep context complete - -Context sources: - dbt_main: memory update complete - -Verification: - Agent context: ready - Semantic search: ready -``` - -If a foreground build is interrupted, rerun `ktx setup` or build the same target -with `ktx ingest `. - -## Step 7: Install agent integration - -The final setup step installs project-local rules for your coding assistant. -Supported targets are Claude Code, Codex, Cursor, OpenCode, and universal -`.agents`. - -You can also run this step later: - -```bash -ktx setup --agents --target codex -``` - -Claude Code and Codex also support global installs: - -```bash -ktx setup --agents --target codex --global -``` - -Agent rules are CLI-based. They point agents at the KTX CLI path that created -the file, so agents do not need a separate `ktx` binary in `PATH`. If the CLI -path changes after reinstalling or moving a checkout, rerun `ktx setup --agents`. - -## Generated files - -KTX writes plain files so people and agents can inspect changes in git. - -| Path | Purpose | -|------|---------| -| `ktx.yaml` | Project configuration for LLMs, embeddings, connections, context sources, and query-history settings | -| `.ktx/secrets/*` | Local secret files referenced from `ktx.yaml`; do not commit these | -| `.ktx/setup/*` | Local setup and context-build state | -| `.ktx/agents/install-manifest.json` | Manifest used to manage installed agent files | -| `semantic-layer//*.yaml` | Semantic source definitions used for SQL generation | -| `wiki/global/*.md` | Shared business context and metric definitions | -| `wiki/user//*.md` | User-scoped notes and local context | -| `.claude/skills/ktx/SKILL.md` | Claude Code project skill | -| `.agents/skills/ktx/SKILL.md` | Codex or universal project skill | -| `.cursor/rules/ktx.mdc` | Cursor project rule | -| `.opencode/commands/ktx.md` | OpenCode project command | - -## Verify setup - -Run: +When setup finishes, check readiness: ```bash ktx status ``` -Example output: - ```text KTX project: /home/user/analytics Project ready: yes @@ -256,15 +124,49 @@ KTX context built: yes Agent integration ready: yes (codex:project) ``` -Use JSON when an agent or script needs a structured readiness check: +For a structured check inside scripts, use `ktx status --json`. -```bash -ktx status --json +When setup builds deep context, its final context check looks like: + +```text +KTX context is ready for agents. + +Databases: + warehouse: deep context complete + +Context sources: + dbt_main: memory update complete ``` -## Scripted setup example +## Connect a coding agent -Use non-interactive setup when creating repeatable fixtures or automation: +The setup wizard installs project-local agent rules in the last step. To +install or change targets later: + +```bash +ktx setup --agents +``` + +Claude Code and Codex also support global installs with `--global`. Agent +rules point at the KTX CLI path that created them, so agents don't need a +separate `ktx` binary on `PATH`. If the CLI path changes, rerun +`ktx setup --agents`. + +## What setup writes + +KTX writes plain files so people and agents can review changes in git. + +| Path | Purpose | +|------|---------| +| `ktx.yaml` | Project configuration | +| `.ktx/secrets/*` | Local secret files referenced from `ktx.yaml` - do not commit | +| `semantic-layer//*.yaml` | Semantic sources for SQL generation | +| `wiki/global/*.md` | Shared business context and metric definitions | +| `.claude/skills/ktx/`, `.agents/skills/ktx/`, `.cursor/rules/ktx.mdc`, `.opencode/commands/ktx.md` | Installed agent rules | + +## Scripted setup + +For repeatable fixtures and automation, skip prompts with flags: ```bash ktx setup \ @@ -287,23 +189,21 @@ ktx ingest warehouse --fast See [ktx setup](/docs/cli-reference/ktx-setup) for the full automation flag surface. -## Common errors +## Common issues -| Symptom | Likely cause | Recovery | -|---------|--------------|----------| -| `ktx: command not found` | The global package is not installed or your shell cannot find it | Reinstall `@kaelio/ktx` and open a new shell | -| Setup resumes the wrong project | `KTX_PROJECT_DIR` or the nearest `ktx.yaml` points somewhere else | Pass `--project-dir ` | -| Anthropic health check fails | API key, model id, or access is invalid | Fix `ANTHROPIC_API_KEY` or rerun setup with a different key or model | -| Vertex AI health check fails | Vertex API, Claude access, project, location, or IAM permissions are missing | Check the project, location, Application Default Credentials, and Vertex AI permissions | -| OpenAI embeddings fail | `OPENAI_API_KEY` is missing or invalid | Export the key or choose local sentence-transformers embeddings | -| Local embeddings fail | Managed Python runtime cannot install or start | Run `ktx dev runtime status`, then install the local embeddings runtime | -| Database test fails | Credentials, network access, database, warehouse, or schema is wrong | Test the same values with the database's native client, then rerun setup | -| Context is not built | Setup saved configuration but skipped or interrupted the build | Run `ktx setup` or `ktx ingest --all` | -| Agent integration is incomplete | Setup skipped the agents step or installed a different target | Run `ktx setup --agents --target ` | +| Symptom | Fix | +|---------|-----| +| `ktx: command not found` | Reinstall `@kaelio/ktx` and open a new shell | +| Setup resumes the wrong project | Pass `--project-dir ` | +| LLM or embeddings health check fails | Rerun setup and pick a different credential, model, or backend | +| Database test fails | Verify the same connection with the database's native client, then rerun setup | +| Agent integration is incomplete | Run `ktx setup --agents --target ` | ## Next steps -- Build and refresh context with [Building Context](/docs/guides/building-context). -- Edit semantic sources and wiki pages with [Writing Context](/docs/guides/writing-context). +- Refresh context with [Building Context](/docs/guides/building-context). +- Edit semantic sources and wiki pages with + [Writing Context](/docs/guides/writing-context). - Connect more tools with [Agent Clients](/docs/integrations/agent-clients). -- Read [The Context Layer](/docs/concepts/the-context-layer) to understand the architecture. +- Read [The Context Layer](/docs/concepts/the-context-layer) to understand + the architecture. diff --git a/docs-site/content/docs/integrations/agent-clients.mdx b/docs-site/content/docs/integrations/agent-clients.mdx index 1934c978..82c25220 100644 --- a/docs-site/content/docs/integrations/agent-clients.mdx +++ b/docs-site/content/docs/integrations/agent-clients.mdx @@ -183,7 +183,7 @@ plugin ZIP for the analytics skill: skill. After `ktx setup`, restart Claude Desktop so it picks up the new MCP server -entry, then install the plugin ZIP. No daemon needs to be running — Claude +entry, then install the plugin ZIP. No daemon needs to be running - Claude Desktop spawns the MCP server itself per session. Claude Desktop does not introspect local stdio MCP servers, so the per-tool diff --git a/docs-site/source.config.ts b/docs-site/source.config.ts index 71c0f614..5af0fa2b 100644 --- a/docs-site/source.config.ts +++ b/docs-site/source.config.ts @@ -5,5 +5,13 @@ export const docs = defineDocs({ }); export default defineConfig({ - mdxOptions: {}, + mdxOptions: { + rehypeCodeOptions: { + addLanguageClass: true, + themes: { + light: "min-light", + dark: "github-dark", + }, + }, + }, });