From 3ce510b55bcfacc103c370b4c2be6779ec4d03b1 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Sun, 10 May 2026 23:51:24 +0200 Subject: [PATCH] rename klo to ktx --- .github/workflows/ci.yml | 6 +- .gitignore | 2 +- AGENTS.md | 24 +- README.md | 62 +- examples/README.md | 8 +- examples/local-warehouse/README.md | 4 +- .../local-warehouse/{klo.yaml => ktx.yaml} | 2 +- .../orbit-relationship-verification/README.md | 6 +- .../{klo.yaml => ktx.yaml} | 2 +- examples/package-artifacts/README.md | 12 +- examples/postgres-historic/README.md | 36 +- .../postgres-historic/init/001-schema.sql | 8 +- examples/postgres-historic/scripts/smoke.sh | 46 +- package.json | 8 +- packages/cli/package.json | 26 +- packages/cli/scripts/build-demo-assets.mjs | 4 +- packages/cli/src/agent-runtime.test.ts | 12 +- packages/cli/src/agent-runtime.ts | 50 +- .../cli/src/agent-search-readiness.test.ts | 28 +- packages/cli/src/agent-search-readiness.ts | 26 +- packages/cli/src/agent.test.ts | 66 +-- packages/cli/src/agent.ts | 46 +- packages/cli/src/bin.ts | 6 +- packages/cli/src/clack.ts | 4 +- packages/cli/src/cli-program.ts | 60 +- packages/cli/src/cli-runtime.ts | 92 +-- packages/cli/src/commands/agent-commands.ts | 20 +- .../cli/src/commands/completion-commands.ts | 4 +- .../cli/src/commands/connection-commands.ts | 34 +- .../src/commands/connection-mapping.test.ts | 86 +-- .../cli/src/commands/connection-mapping.ts | 56 +- .../commands/connection-metabase-commands.ts | 26 +- .../connection-metabase-setup.test.ts | 114 ++-- .../src/commands/connection-metabase-setup.ts | 104 ++-- .../commands/connection-notion-commands.ts | 14 +- .../src/commands/connection-notion-tui.tsx | 6 +- .../src/commands/connection-notion.test.ts | 58 +- .../cli/src/commands/connection-notion.ts | 44 +- packages/cli/src/commands/demo-commands.ts | 26 +- packages/cli/src/commands/doctor-commands.ts | 14 +- packages/cli/src/commands/ingest-commands.ts | 32 +- .../cli/src/commands/knowledge-commands.ts | 12 +- .../src/commands/public-ingest-commands.ts | 28 +- packages/cli/src/commands/scan-commands.ts | 54 +- packages/cli/src/commands/serve-commands.ts | 12 +- packages/cli/src/commands/setup-commands.ts | 42 +- packages/cli/src/commands/sl-commands.ts | 22 +- packages/cli/src/commands/status-commands.ts | 8 +- packages/cli/src/completion.ts | 42 +- packages/cli/src/connection.test.ts | 130 ++--- packages/cli/src/connection.ts | 118 ++-- packages/cli/src/context-build-view.test.ts | 20 +- packages/cli/src/context-build-view.ts | 38 +- packages/cli/src/demo-assets.test.ts | 26 +- packages/cli/src/demo-assets.ts | 18 +- packages/cli/src/demo-full.test.ts | 28 +- packages/cli/src/demo-full.ts | 38 +- packages/cli/src/demo-interaction.test.ts | 4 +- packages/cli/src/demo-interaction.ts | 10 +- packages/cli/src/demo-metrics.test.ts | 2 +- packages/cli/src/demo-metrics.ts | 2 +- packages/cli/src/demo-progress.test.ts | 2 +- packages/cli/src/demo-progress.ts | 6 +- packages/cli/src/demo-replay-store.test.ts | 6 +- packages/cli/src/demo-replay-store.ts | 2 +- packages/cli/src/demo-scan.test.ts | 2 +- packages/cli/src/demo-scan.ts | 52 +- packages/cli/src/demo-seeded-inspect.test.ts | 18 +- packages/cli/src/demo-seeded-inspect.ts | 12 +- packages/cli/src/demo-seeded.test.ts | 6 +- packages/cli/src/demo-seeded.ts | 2 +- packages/cli/src/demo.test.ts | 138 ++--- packages/cli/src/demo.ts | 116 ++-- packages/cli/src/dev.test.ts | 94 ++-- packages/cli/src/dev.ts | 10 +- packages/cli/src/doctor.test.ts | 70 +-- packages/cli/src/doctor.ts | 100 ++-- packages/cli/src/example-smoke.test.ts | 6 +- packages/cli/src/historic-sql-doctor.test.ts | 16 +- packages/cli/src/historic-sql-doctor.ts | 24 +- packages/cli/src/index.test.ts | 332 +++++------ packages/cli/src/index.ts | 56 +- packages/cli/src/ingest-report-file.test.ts | 2 +- packages/cli/src/ingest-report-file.ts | 2 +- packages/cli/src/ingest.test.ts | 260 ++++----- packages/cli/src/ingest.ts | 76 +-- packages/cli/src/io/mode.test.ts | 20 +- packages/cli/src/io/mode.ts | 16 +- packages/cli/src/io/print-list.test.ts | 4 +- packages/cli/src/io/print-list.ts | 8 +- packages/cli/src/knowledge.test.ts | 24 +- packages/cli/src/knowledge.ts | 16 +- packages/cli/src/local-adapters.ts | 78 +-- .../cli/src/local-scan-connectors.test.ts | 60 +- packages/cli/src/local-scan-connectors.ts | 60 +- packages/cli/src/memory-flow-hud.tsx | 14 +- .../cli/src/memory-flow-interactive.test.ts | 2 +- packages/cli/src/memory-flow-interactive.ts | 32 +- packages/cli/src/memory-flow-tui.test.tsx | 22 +- packages/cli/src/memory-flow-tui.tsx | 18 +- packages/cli/src/next-steps.test.ts | 82 +-- packages/cli/src/next-steps.ts | 60 +- packages/cli/src/project-dir.test.ts | 48 +- packages/cli/src/project-resolver.test.ts | 36 +- packages/cli/src/project-resolver.ts | 16 +- packages/cli/src/prompt-navigation.test.ts | 12 +- packages/cli/src/public-ingest.test.ts | 46 +- packages/cli/src/public-ingest.ts | 112 ++-- packages/cli/src/scan.test.ts | 284 +++++----- packages/cli/src/scan.ts | 178 +++--- packages/cli/src/serve.test.ts | 70 +-- packages/cli/src/serve.ts | 66 +-- packages/cli/src/setup-agents.test.ts | 70 +-- packages/cli/src/setup-agents.ts | 156 +++--- packages/cli/src/setup-context.test.ts | 62 +- packages/cli/src/setup-context.ts | 212 +++---- packages/cli/src/setup-databases.test.ts | 160 +++--- packages/cli/src/setup-databases.ts | 222 ++++---- packages/cli/src/setup-embeddings.test.ts | 62 +- packages/cli/src/setup-embeddings.ts | 128 ++--- packages/cli/src/setup-interrupt.test.ts | 6 +- packages/cli/src/setup-interrupt.ts | 12 +- packages/cli/src/setup-models.test.ts | 116 ++-- packages/cli/src/setup-models.ts | 92 +-- packages/cli/src/setup-project.test.ts | 106 ++-- packages/cli/src/setup-project.ts | 102 ++-- packages/cli/src/setup-ready-menu.test.ts | 20 +- packages/cli/src/setup-ready-menu.ts | 28 +- packages/cli/src/setup-secrets.test.ts | 8 +- packages/cli/src/setup-secrets.ts | 2 +- packages/cli/src/setup-sources.test.ts | 90 +-- packages/cli/src/setup-sources.ts | 216 +++---- packages/cli/src/setup.test.ts | 204 +++---- packages/cli/src/setup.ts | 282 +++++----- packages/cli/src/sl.test.ts | 56 +- packages/cli/src/sl.ts | 26 +- packages/cli/src/standalone-smoke.test.ts | 66 +-- packages/cli/src/startup-profile.ts | 4 +- packages/cli/src/viz-fallback.ts | 22 +- packages/connector-bigquery/package.json | 6 +- .../connector-bigquery/src/connector.test.ts | 52 +- packages/connector-bigquery/src/connector.ts | 140 ++--- .../connector-bigquery/src/dialect.test.ts | 6 +- packages/connector-bigquery/src/dialect.ts | 10 +- packages/connector-bigquery/src/index.ts | 28 +- .../src/live-database-introspection.ts | 18 +- .../src/package-exports.test.ts | 6 +- packages/connector-clickhouse/package.json | 6 +- .../src/connector.test.ts | 20 +- .../connector-clickhouse/src/connector.ts | 140 ++--- .../connector-clickhouse/src/dialect.test.ts | 8 +- packages/connector-clickhouse/src/dialect.ts | 10 +- packages/connector-clickhouse/src/index.ts | 24 +- .../src/live-database-introspection.ts | 22 +- .../src/package-exports.test.ts | 6 +- packages/connector-mysql/package.json | 6 +- .../connector-mysql/src/connector.test.ts | 20 +- packages/connector-mysql/src/connector.ts | 144 ++--- packages/connector-mysql/src/dialect.test.ts | 8 +- packages/connector-mysql/src/dialect.ts | 10 +- packages/connector-mysql/src/index.ts | 22 +- .../src/live-database-introspection.ts | 22 +- .../src/package-exports.test.ts | 8 +- packages/connector-postgres/package.json | 6 +- .../connector-postgres/src/connector.test.ts | 22 +- packages/connector-postgres/src/connector.ts | 166 +++--- .../connector-postgres/src/dialect.test.ts | 8 +- packages/connector-postgres/src/dialect.ts | 10 +- .../src/historic-sql-query-client.test.ts | 12 +- .../src/historic-sql-query-client.ts | 14 +- packages/connector-postgres/src/index.ts | 30 +- .../src/live-database-introspection.ts | 22 +- .../src/package-exports.test.ts | 10 +- packages/connector-posthog/package.json | 6 +- .../connector-posthog/src/connector.test.ts | 24 +- packages/connector-posthog/src/connector.ts | 160 +++--- .../connector-posthog/src/dialect.test.ts | 6 +- packages/connector-posthog/src/dialect.ts | 14 +- packages/connector-posthog/src/index.ts | 26 +- .../src/live-database-introspection.ts | 14 +- .../src/package-exports.test.ts | 8 +- .../src/schema-descriptions.ts | 6 +- packages/connector-snowflake/package.json | 6 +- .../connector-snowflake/src/connector.test.ts | 26 +- packages/connector-snowflake/src/connector.ts | 158 +++--- .../connector-snowflake/src/dialect.test.ts | 6 +- packages/connector-snowflake/src/dialect.ts | 10 +- packages/connector-snowflake/src/index.ts | 28 +- .../src/live-database-introspection.ts | 22 +- .../src/package-exports.test.ts | 6 +- packages/connector-sqlite/package.json | 6 +- .../connector-sqlite/src/connector.test.ts | 26 +- packages/connector-sqlite/src/connector.ts | 94 ++-- packages/connector-sqlite/src/dialect.test.ts | 8 +- packages/connector-sqlite/src/dialect.ts | 10 +- packages/connector-sqlite/src/index.ts | 16 +- .../src/live-database-introspection.ts | 12 +- .../src/package-exports.test.ts | 8 +- packages/connector-sqlserver/package.json | 6 +- .../connector-sqlserver/src/connector.test.ts | 26 +- packages/connector-sqlserver/src/connector.ts | 164 +++--- .../connector-sqlserver/src/dialect.test.ts | 8 +- packages/connector-sqlserver/src/dialect.ts | 10 +- packages/connector-sqlserver/src/index.ts | 26 +- .../src/live-database-introspection.ts | 22 +- .../src/package-exports.test.ts | 6 +- packages/context/package.json | 4 +- .../memory_agent_bundle_ingest_work_unit.md | 4 +- .../prompts/memory_agent_external_ingest.md | 4 +- .../scripts/pglite-hybrid-search-spike.mjs | 8 +- .../pglite-owner-process-prototype.mjs | 8 +- .../scripts/pglite-sl-search-prototype.mjs | 8 +- .../scripts/relationship-benchmark-report.mjs | 44 +- packages/context/skills/dbt_ingest/SKILL.md | 6 +- .../context/skills/knowledge_capture/SKILL.md | 2 +- .../context/skills/looker_ingest/SKILL.md | 6 +- .../context/skills/lookml_ingest/SKILL.md | 12 +- .../context/skills/metabase_ingest/SKILL.md | 8 +- .../context/skills/metricflow_ingest/SKILL.md | 26 +- .../context/skills/notion_synthesize/SKILL.md | 2 +- packages/context/skills/sl/SKILL.md | 4 +- packages/context/skills/sl_capture/SKILL.md | 2 +- .../context/src/agent/agent-runner.service.ts | 22 +- packages/context/src/connections/index.ts | 12 +- .../src/connections/local-query-executor.ts | 16 +- .../connections/local-warehouse-descriptor.ts | 8 +- .../src/connections/notion-config.test.ts | 2 +- .../context/src/connections/notion-config.ts | 24 +- .../postgres-query-executor.test.ts | 2 +- .../connections/postgres-query-executor.ts | 14 +- .../context/src/connections/query-executor.ts | 12 +- .../src/connections/read-only-sql.test.ts | 2 +- .../context/src/connections/read-only-sql.ts | 2 +- .../connections/sqlite-query-executor.test.ts | 12 +- .../src/connections/sqlite-query-executor.ts | 14 +- .../context/src/core/config-reference.test.ts | 22 +- packages/context/src/core/config-reference.ts | 6 +- packages/context/src/core/config.ts | 20 +- packages/context/src/core/embedding.ts | 2 +- packages/context/src/core/file-store.ts | 20 +- .../git.service.assert-worktree-clean.test.ts | 4 +- .../git.service.delete-directories.test.ts | 4 +- .../src/core/git.service.reset-hard.test.ts | 4 +- packages/context/src/core/git.service.test.ts | 4 +- packages/context/src/core/git.service.ts | 16 +- packages/context/src/core/index.ts | 24 +- packages/context/src/core/redaction.ts | 22 +- .../src/core/session-worktree.service.test.ts | 6 +- .../src/core/session-worktree.service.ts | 10 +- .../src/daemon/semantic-layer-compute.ts | 62 +- packages/context/src/index.test.ts | 8 +- packages/context/src/index.ts | 206 +++---- .../dbt-descriptions/match-tables.test.ts | 12 +- .../adapters/dbt-descriptions/match-tables.ts | 4 +- .../adapters/dbt-descriptions/parse-schema.ts | 8 +- .../to-description-updates.ts | 14 +- .../dbt-descriptions/to-metadata-updates.ts | 10 +- .../to-relationship-updates.ts | 6 +- .../src/ingest/adapters/dbt/dbt.adapter.ts | 2 +- .../src/ingest/adapters/dbt/fetch.test.ts | 2 +- .../postgres-pgss-query-history-reader.ts | 8 +- .../historic-sql/stage-pgss-golden.test.ts | 4 +- .../adapters/historic-sql/stage-pgss.test.ts | 4 +- .../adapters/historic-sql/stage-pgss.ts | 6 +- .../src/ingest/adapters/historic-sql/types.ts | 8 +- .../adapters/live-database/chunk.test.ts | 10 +- .../ingest/adapters/live-database/chunk.ts | 6 +- .../live-database/daemon-introspection.ts | 54 +- .../live-database/extracted-schema.test.ts | 10 +- .../live-database/extracted-schema.ts | 6 +- .../live-database.adapter.test.ts | 2 +- .../adapters/live-database/stage.test.ts | 10 +- .../ingest/adapters/live-database/stage.ts | 18 +- .../ingest/adapters/live-database/types.ts | 4 +- .../daemon-table-identifier-parser.test.ts | 2 +- .../looker/daemon-table-identifier-parser.ts | 12 +- .../ingest/adapters/looker/factory.test.ts | 2 +- .../adapters/looker/local-looker.adapter.ts | 8 +- .../looker/local-runtime-store.test.ts | 22 +- .../adapters/looker/local-runtime-store.ts | 42 +- .../ingest/adapters/looker/mapping.test.ts | 36 +- .../src/ingest/adapters/looker/mapping.ts | 30 +- .../src/ingest/adapters/looker/types.test.ts | 4 +- .../adapters/metabase/client-boundary.test.ts | 6 +- .../ingest/adapters/metabase/client.test.ts | 6 +- .../src/ingest/adapters/metabase/client.ts | 4 +- .../ingest/adapters/metabase/fetch.test.ts | 2 +- .../src/ingest/adapters/metabase/fetch.ts | 2 +- .../metabase/local-metabase.adapter.test.ts | 14 +- .../metabase/local-metabase.adapter.ts | 14 +- .../metabase/local-source-state-store.test.ts | 14 +- .../metabase/local-source-state-store.ts | 6 +- .../ingest/adapters/metabase/mapping.test.ts | 20 +- .../src/ingest/adapters/metabase/mapping.ts | 34 +- .../ingest/adapters/metricflow/deep-parse.ts | 6 +- .../ingest/adapters/notion/cluster.test.ts | 6 +- .../src/ingest/adapters/notion/cluster.ts | 4 +- .../candidate-dedup.service.ts | 6 +- .../context-candidate-carryforward.service.ts | 6 +- .../curator-pagination.service.ts | 10 +- .../context-evidence-index.service.ts | 6 +- .../sqlite-context-evidence-store.test.ts | 4 +- packages/context/src/ingest/index.ts | 24 +- .../src/ingest/ingest-bundle.runner.test.ts | 24 +- .../src/ingest/ingest-bundle.runner.ts | 4 +- .../context/src/ingest/ingest-prompts.test.ts | 6 +- .../src/ingest/ingest-runtime-assets.test.ts | 8 +- .../context/src/ingest/local-adapters.test.ts | 24 +- packages/context/src/ingest/local-adapters.ts | 34 +- .../src/ingest/local-bundle-ingest.test.ts | 46 +- .../src/ingest/local-bundle-runtime.test.ts | 28 +- .../src/ingest/local-bundle-runtime.ts | 82 +-- ...cal-embedding-provider.integration.test.ts | 10 +- packages/context/src/ingest/local-ingest.ts | 60 +- .../ingest/local-mapping-reconcile.test.ts | 36 +- .../src/ingest/local-mapping-reconcile.ts | 10 +- .../src/ingest/local-metabase-ingest.test.ts | 24 +- .../src/ingest/local-stage-ingest.test.ts | 42 +- .../context/src/ingest/local-stage-ingest.ts | 28 +- .../src/ingest/memory-flow/acceptance.test.ts | 4 +- .../src/ingest/memory-flow/events.test.ts | 8 +- .../ingest/memory-flow/interaction.test.ts | 2 +- .../memory-flow/interactive-render.test.ts | 4 +- .../ingest/memory-flow/package-export.test.ts | 2 +- .../src/ingest/memory-flow/render.test.ts | 4 +- .../src/ingest/memory-flow/view-model.test.ts | 4 +- .../src/ingest/memory-flow/view-model.ts | 2 +- .../src/ingest/memory-flow/visuals.test.ts | 2 +- .../context/src/ingest/metabase-mapping.ts | 2 +- .../page-triage/page-triage.service.test.ts | 4 +- .../ingest/page-triage/page-triage.service.ts | 12 +- packages/context/src/ingest/ports.ts | 14 +- .../context/src/ingest/repo-fetch.test.ts | 4 +- .../ingest/sqlite-bundle-ingest-store.test.ts | 4 +- .../ingest/sqlite-local-ingest-store.test.ts | 4 +- .../src/ingest/stages/stage-3-work-units.ts | 6 +- .../ingest/stages/stage-4-reconciliation.ts | 6 +- .../src/ingest/stages/validate-wu-sources.ts | 2 +- packages/context/src/ingest/types.ts | 4 +- .../src/llm/debug-request-recorder.test.ts | 14 +- .../context/src/llm/debug-request-recorder.ts | 30 +- .../context/src/llm/embedding-port.test.ts | 12 +- packages/context/src/llm/embedding-port.ts | 18 +- packages/context/src/llm/generation.ts | 22 +- packages/context/src/llm/index.ts | 24 +- packages/context/src/llm/local-config.test.ts | 60 +- packages/context/src/llm/local-config.ts | 64 +-- packages/context/src/mcp/context-tools.ts | 46 +- packages/context/src/mcp/index.ts | 58 +- .../src/mcp/local-project-ports.test.ts | 54 +- .../context/src/mcp/local-project-ports.ts | 84 +-- packages/context/src/mcp/server.test.ts | 76 +-- packages/context/src/mcp/server.ts | 22 +- packages/context/src/mcp/types.ts | 154 ++--- .../context/src/memory/local-memory-runs.ts | 2 +- .../context/src/memory/local-memory.test.ts | 22 +- packages/context/src/memory/local-memory.ts | 70 +-- .../src/memory/memory-agent.service.ts | 4 +- .../src/memory/memory-runtime-assets.test.ts | 6 +- packages/context/src/memory/types.ts | 16 +- packages/context/src/package-exports.test.ts | 130 ++--- packages/context/src/project/config.test.ts | 72 +-- packages/context/src/project/config.ts | 128 ++--- packages/context/src/project/index.ts | 30 +- .../src/project/local-git-file-store.test.ts | 14 +- .../src/project/local-git-file-store.ts | 26 +- .../context/src/project/local-state-db.ts | 6 +- .../src/project/mappings-yaml-schema.test.ts | 6 +- .../src/project/mappings-yaml-schema.ts | 12 +- packages/context/src/project/project.test.ts | 24 +- packages/context/src/project/project.ts | 74 +-- .../context/src/project/setup-config.test.ts | 38 +- packages/context/src/project/setup-config.ts | 16 +- .../src/prompts/prompt.service.test.ts | 4 +- .../context/src/prompts/prompt.service.ts | 6 +- packages/context/src/scan/credentials.test.ts | 56 +- packages/context/src/scan/credentials.ts | 36 +- .../context/src/scan/data-dictionary.test.ts | 120 ++-- packages/context/src/scan/data-dictionary.ts | 26 +- .../src/scan/description-generation.test.ts | 36 +- .../src/scan/description-generation.ts | 146 ++--- .../context/src/scan/embedding-text.test.ts | 10 +- packages/context/src/scan/embedding-text.ts | 8 +- .../context/src/scan/enrichment-state.test.ts | 24 +- packages/context/src/scan/enrichment-state.ts | 58 +- .../src/scan/enrichment-summary.test.ts | 20 +- .../context/src/scan/enrichment-summary.ts | 16 +- .../context/src/scan/enrichment-types.test.ts | 42 +- packages/context/src/scan/enrichment-types.ts | 90 +-- packages/context/src/scan/index.ts | 530 +++++++++--------- .../scan/local-enrichment-artifacts.test.ts | 30 +- .../src/scan/local-enrichment-artifacts.ts | 68 +-- .../context/src/scan/local-enrichment.test.ts | 72 +-- packages/context/src/scan/local-enrichment.ts | 234 ++++---- packages/context/src/scan/local-scan.test.ts | 54 +- packages/context/src/scan/local-scan.ts | 140 ++--- .../scan/local-structural-artifacts.test.ts | 28 +- .../src/scan/local-structural-artifacts.ts | 32 +- .../context/src/scan/orchestrator.test.ts | 56 +- packages/context/src/scan/orchestrator.ts | 180 +++--- .../src/scan/relationship-artifacts.test.ts | 36 +- .../src/scan/relationship-artifacts.ts | 32 +- .../relationship-benchmark-report.test.ts | 60 +- .../src/scan/relationship-benchmark-report.ts | 78 +-- .../src/scan/relationship-benchmarks.test.ts | 136 ++--- .../src/scan/relationship-benchmarks.ts | 258 ++++----- .../src/scan/relationship-budget.test.ts | 22 +- .../context/src/scan/relationship-budget.ts | 24 +- .../src/scan/relationship-candidates.test.ts | 72 +-- .../src/scan/relationship-candidates.ts | 246 ++++---- .../relationship-composite-candidates.test.ts | 24 +- .../scan/relationship-composite-candidates.ts | 160 +++--- .../src/scan/relationship-diagnostics.test.ts | 38 +- .../src/scan/relationship-diagnostics.ts | 130 ++--- .../src/scan/relationship-discovery.test.ts | 92 +-- .../src/scan/relationship-discovery.ts | 152 ++--- .../relationship-feedback-calibration.test.ts | 34 +- .../scan/relationship-feedback-calibration.ts | 64 +-- .../scan/relationship-feedback-export.test.ts | 20 +- .../src/scan/relationship-feedback-export.ts | 54 +- .../scan/relationship-formal-metadata.test.ts | 12 +- .../src/scan/relationship-formal-metadata.ts | 20 +- .../scan/relationship-graph-resolver.test.ts | 46 +- .../src/scan/relationship-graph-resolver.ts | 142 ++--- .../scan/relationship-llm-proposal.test.ts | 36 +- .../src/scan/relationship-llm-proposal.ts | 112 ++-- .../src/scan/relationship-locality.test.ts | 8 +- .../context/src/scan/relationship-locality.ts | 44 +- .../scan/relationship-name-similarity.test.ts | 40 +- .../src/scan/relationship-name-similarity.ts | 26 +- .../src/scan/relationship-profiling.test.ts | 60 +- .../src/scan/relationship-profiling.ts | 134 ++--- .../scan/relationship-review-apply.test.ts | 22 +- .../src/scan/relationship-review-apply.ts | 36 +- .../relationship-review-decisions.test.ts | 38 +- .../src/scan/relationship-review-decisions.ts | 64 +-- .../src/scan/relationship-scoring.test.ts | 20 +- .../context/src/scan/relationship-scoring.ts | 76 +-- .../relationship-threshold-advice.test.ts | 32 +- .../src/scan/relationship-threshold-advice.ts | 62 +- .../src/scan/relationship-validation.test.ts | 62 +- .../src/scan/relationship-validation.ts | 98 ++-- .../sqlite-local-enrichment-state-store.ts | 30 +- .../src/scan/type-normalization.test.ts | 20 +- .../context/src/scan/type-normalization.ts | 18 +- packages/context/src/scan/types.test.ts | 68 +-- packages/context/src/scan/types.ts | 218 +++---- .../src/search/backend-conformance.test.ts | 18 +- .../src/search/pglite-owner-process.test.ts | 20 +- .../src/search/pglite-owner-process.ts | 12 +- .../search/pglite-runtime-boundary.test.ts | 14 +- .../context/src/search/pglite-spike.test.ts | 2 +- .../src/skills/skills-registry.service.ts | 6 +- packages/context/src/sl/index.ts | 4 +- packages/context/src/sl/local-query.test.ts | 24 +- packages/context/src/sl/local-query.ts | 16 +- packages/context/src/sl/local-sl.test.ts | 22 +- packages/context/src/sl/local-sl.ts | 32 +- .../src/sl/pglite-sl-search-prototype.test.ts | 14 +- .../src/sl/pglite-sl-search-prototype.ts | 36 +- packages/context/src/sl/ports.ts | 10 +- packages/context/src/sl/schemas.ts | 2 +- .../context/src/sl/semantic-layer.service.ts | 8 +- .../src/sl/sl-dictionary-profile.test.ts | 16 +- .../context/src/sl/sl-dictionary-profile.ts | 12 +- packages/context/src/sl/sl-search.service.ts | 6 +- .../src/sl/sqlite-sl-sources-index.test.ts | 2 +- .../src/sl/tools/sl-warehouse-validation.ts | 4 +- .../sql-analysis/http-sql-analysis-port.ts | 6 +- packages/context/src/sql-analysis/index.ts | 2 +- .../context/src/test/make-local-git-repo.ts | 4 +- packages/context/src/tools/base-tool.ts | 6 +- .../src/tools/context-candidate-write.tool.ts | 4 +- .../src/tools/context-evidence-search.tool.ts | 4 +- .../src/tools/context-evidence-tools.test.ts | 18 +- packages/context/src/tools/tool-session.ts | 4 +- .../src/wiki/knowledge-wiki.service.ts | 10 +- .../context/src/wiki/local-knowledge.test.ts | 10 +- packages/context/src/wiki/local-knowledge.ts | 36 +- packages/context/src/wiki/ports.ts | 4 +- .../src/wiki/sqlite-knowledge-index.test.ts | 2 +- packages/llm/package.json | 4 +- packages/llm/src/embedding-health.test.ts | 12 +- packages/llm/src/embedding-health.ts | 24 +- packages/llm/src/embedding-provider.test.ts | 24 +- packages/llm/src/embedding-provider.ts | 50 +- packages/llm/src/index.ts | 48 +- packages/llm/src/message-builder.test.ts | 18 +- packages/llm/src/message-builder.ts | 32 +- packages/llm/src/model-health.test.ts | 8 +- packages/llm/src/model-health.ts | 24 +- packages/llm/src/model-provider.test.ts | 24 +- packages/llm/src/model-provider.ts | 50 +- packages/llm/src/package-exports.test.ts | 10 +- packages/llm/src/repair.test.ts | 10 +- packages/llm/src/repair.ts | 6 +- packages/llm/src/types.ts | 66 +-- pnpm-lock.yaml | 158 ++---- pyproject.toml | 14 +- python/klo-daemon/src/klo_daemon/__init__.py | 6 - python/klo-daemon/tests/test_package.py | 6 - python/{klo-daemon => ktx-daemon}/README.md | 26 +- .../{klo-daemon => ktx-daemon}/pyproject.toml | 12 +- python/ktx-daemon/src/ktx_daemon/__init__.py | 6 + .../src/ktx_daemon}/__main__.py | 20 +- .../src/ktx_daemon}/app.py | 20 +- .../src/ktx_daemon}/code_execution.py | 2 +- .../src/ktx_daemon}/database_introspection.py | 4 +- .../src/ktx_daemon}/embeddings.py | 2 +- .../src/ktx_daemon}/lookml.py | 0 .../src/ktx_daemon}/semantic_layer.py | 2 +- .../src/ktx_daemon}/source_generation.py | 4 +- .../src/ktx_daemon}/table_identifier.py | 0 .../tests/test_app.py | 4 +- .../tests/test_cli.py | 26 +- .../tests/test_code_execution.py | 2 +- .../tests/test_database_introspection.py | 2 +- .../tests/test_embeddings.py | 2 +- .../tests/test_lookml.py | 2 +- python/ktx-daemon/tests/test_package.py | 6 + .../tests/test_semantic_layer.py | 2 +- .../tests/test_source_generation.py | 2 +- python/{klo-sl => ktx-sl}/AGENTS.md | 0 python/{klo-sl => ktx-sl}/CLAUDE.md | 0 python/{klo-sl => ktx-sl}/README.md | 0 .../demos/complex_cte_join.yaml | 0 .../demos/run_complex_cte_join.sh | 0 python/{klo-sl => ktx-sl}/pyproject.toml | 2 +- .../scripts/gen_b2b_saas_model.py | 14 +- python/{klo-sl => ktx-sl}/scripts/slquery.py | 2 +- .../{klo-sl => ktx-sl}/scripts/tpch_runner.py | 0 .../semantic_layer/__init__.py | 0 .../semantic_layer/__main__.py | 0 .../{klo-sl => ktx-sl}/semantic_layer/cli.py | 0 .../semantic_layer/duplicate_check.py | 0 .../semantic_layer/engine.py | 0 .../semantic_layer/generator.py | 0 .../semantic_layer/graph.py | 0 .../semantic_layer/loader.py | 0 .../semantic_layer/manifest.py | 0 .../semantic_layer/models.py | 0 .../semantic_layer/parser.py | 0 .../semantic_layer/planner.py | 0 .../semantic_layer/sql_table_extractor.py | 0 .../semantic_layer/table_identifier_parser.py | 0 .../sources/b2b_saas/abm_engagements.yaml | 0 .../b2b_saas/account_intent_signals.yaml | 0 .../sources/b2b_saas/accounts.yaml | 0 .../sources/b2b_saas/activities.yaml | 0 .../sources/b2b_saas/ad_accounts.yaml | 0 .../sources/b2b_saas/ad_ad_stats.yaml | 0 .../sources/b2b_saas/ad_campaigns.yaml | 0 .../sources/b2b_saas/ad_creative_stats.yaml | 0 .../sources/b2b_saas/ad_creatives.yaml | 0 .../sources/b2b_saas/ad_groups.yaml | 0 .../sources/b2b_saas/ad_stats.yaml | 0 .../sources/b2b_saas/ads.yaml | 0 .../sources/b2b_saas/ap_bills.yaml | 0 .../sources/b2b_saas/approvals.yaml | 0 .../sources/b2b_saas/attribution_credits.yaml | 0 .../sources/b2b_saas/budgets.yaml | 0 .../sources/b2b_saas/calls.yaml | 0 .../sources/b2b_saas/campaign_members.yaml | 0 .../sources/b2b_saas/campaigns.yaml | 0 .../sources/b2b_saas/card_transactions.yaml | 0 .../sources/b2b_saas/cash_balances.yaml | 0 .../sources/b2b_saas/charges.yaml | 0 .../sources/b2b_saas/churn_risk.yaml | 0 .../sources/b2b_saas/contacts.yaml | 0 .../sources/b2b_saas/content_assets.yaml | 0 .../sources/b2b_saas/content_touches.yaml | 0 .../sources/b2b_saas/contracts.yaml | 0 .../sources/b2b_saas/crm_notes.yaml | 0 .../sources/b2b_saas/currencies.yaml | 0 .../sources/b2b_saas/departments_hr.yaml | 0 .../sources/b2b_saas/disputes.yaml | 0 .../sources/b2b_saas/email_events.yaml | 0 .../sources/b2b_saas/email_sends.yaml | 0 .../sources/b2b_saas/employees.yaml | 0 .../sources/b2b_saas/etl_runs.yaml | 0 .../sources/b2b_saas/fiscal_calendar.yaml | 0 .../sources/b2b_saas/forecast_snapshots.yaml | 0 .../sources/b2b_saas/fx_rates.yaml | 0 .../sources/b2b_saas/ga4_event_params.yaml | 0 .../sources/b2b_saas/ga4_events.yaml | 0 .../sources/b2b_saas/gl_accounts.yaml | 0 .../sources/b2b_saas/identities.yaml | 0 .../sources/b2b_saas/identity_links.yaml | 0 .../sources/b2b_saas/invoice_lines.yaml | 0 .../sources/b2b_saas/invoices.yaml | 0 .../sources/b2b_saas/journal_entries.yaml | 0 .../sources/b2b_saas/journal_lines.yaml | 0 .../sources/b2b_saas/keyword_rankings.yaml | 0 .../sources/b2b_saas/lead_status_history.yaml | 0 .../sources/b2b_saas/leads.yaml | 0 .../sources/b2b_saas/meeting_bookings.yaml | 0 .../sources/b2b_saas/open_roles.yaml | 0 .../sources/b2b_saas/opportunities.yaml | 0 .../b2b_saas/opportunity_contact_roles.yaml | 0 .../b2b_saas/opportunity_line_items.yaml | 0 .../b2b_saas/opportunity_stage_history.yaml | 0 .../sources/b2b_saas/payment_intents.yaml | 0 .../sources/b2b_saas/payments.yaml | 0 .../sources/b2b_saas/payroll_runs.yaml | 0 .../sources/b2b_saas/pricebook_entries.yaml | 0 .../sources/b2b_saas/pricebooks.yaml | 0 .../sources/b2b_saas/product_costs.yaml | 0 .../sources/b2b_saas/product_usage.yaml | 0 .../sources/b2b_saas/products.yaml | 0 .../sources/b2b_saas/quotas.yaml | 0 .../sources/b2b_saas/quote_line_items.yaml | 0 .../sources/b2b_saas/quotes.yaml | 0 .../sources/b2b_saas/refunds.yaml | 0 .../sources/b2b_saas/revenue_schedules.yaml | 0 .../sources/b2b_saas/reverse_etl_jobs.yaml | 0 .../sources/b2b_saas/sales_reps.yaml | 0 .../sources/b2b_saas/sales_teams.yaml | 0 .../b2b_saas/search_console_stats.yaml | 0 .../b2b_saas/sequence_enrollments.yaml | 0 .../sources/b2b_saas/sequence_steps.yaml | 0 .../sources/b2b_saas/sequence_touches.yaml | 0 .../sources/b2b_saas/sequences.yaml | 0 .../sources/b2b_saas/stage_weights.yaml | 0 .../sources/b2b_saas/subscription_items.yaml | 0 .../sources/b2b_saas/subscriptions.yaml | 0 .../sources/b2b_saas/support_tickets.yaml | 0 .../sources/b2b_saas/target_accounts.yaml | 0 .../sources/b2b_saas/touchpoints.yaml | 0 .../sources/b2b_saas/vendors.yaml | 0 .../sources/b2b_saas/web_events.yaml | 0 .../sources/b2b_saas/web_sessions.yaml | 0 .../sources/b2b_saas/webinar_attendance.yaml | 0 .../b2b_saas/webinar_registrations.yaml | 0 .../sources/b2b_saas/webinars.yaml | 0 .../sources/ecommerce/churn_risk.yaml | 0 .../sources/ecommerce/customers.yaml | 0 .../sources/ecommerce/order_items.yaml | 0 .../sources/ecommerce/orders.yaml | 0 .../sources/ecommerce/products.yaml | 0 .../sources/ecommerce/regions.yaml | 0 .../sources/tpch/customer.yaml | 0 .../sources/tpch/lineitem.yaml | 0 .../sources/tpch/nation.yaml | 0 .../sources/tpch/orders.yaml | 0 .../{klo-sl => ktx-sl}/sources/tpch/part.yaml | 0 .../sources/tpch/partsupp.yaml | 0 .../sources/tpch/region.yaml | 0 .../sources/tpch/supplier.yaml | 0 python/{klo-sl => ktx-sl}/tests/__init__.py | 0 python/{klo-sl => ktx-sl}/tests/conftest.py | 0 .../tests/test_aggregate_locality.py | 0 python/{klo-sl => ktx-sl}/tests/test_cli.py | 0 .../tests/test_computed_columns.py | 0 .../tests/test_corner_case_regressions.py | 0 .../tests/test_coverage_gaps.py | 0 .../tests/test_duplicate_check.py | 0 .../{klo-sl => ktx-sl}/tests/test_engine.py | 0 .../tests/test_generator.py | 0 python/{klo-sl => ktx-sl}/tests/test_graph.py | 0 .../{klo-sl => ktx-sl}/tests/test_loader.py | 0 .../{klo-sl => ktx-sl}/tests/test_manifest.py | 0 .../{klo-sl => ktx-sl}/tests/test_models.py | 0 .../{klo-sl => ktx-sl}/tests/test_parser.py | 0 .../{klo-sl => ktx-sl}/tests/test_planner.py | 0 .../{klo-sl => ktx-sl}/tests/test_segments.py | 0 .../tests/test_snowflake.py | 0 .../tests/test_sql_join_coverage.py | 0 .../tests/test_table_identifier_parser.py | 0 python/{klo-sl => ktx-sl}/tests/test_tpch.py | 0 .../tests/test_validator.py | 0 release-policy.json | 24 +- ...acquire-public-benchmark-fixtures.test.mjs | 2 +- scripts/anti-fixture-conditional.test.mjs | 6 +- scripts/build-adventureworks-oltp-fixture.mjs | 8 +- scripts/build-benchmark-snapshot.test.mjs | 2 +- ...d-evidence-fusion-adversarial-fixtures.mjs | 4 +- scripts/check-boundaries.mjs | 10 +- scripts/check-boundaries.test.mjs | 16 +- scripts/ci-artifact-upload.test.mjs | 32 +- scripts/examples-docs.test.mjs | 22 +- scripts/installed-live-database-smoke.mjs | 54 +- .../installed-live-database-smoke.test.mjs | 30 +- scripts/link-dev-cli.mjs | 14 +- scripts/link-dev-cli.test.mjs | 30 +- scripts/package-artifacts.mjs | 404 ++++++------- scripts/package-artifacts.test.mjs | 244 ++++---- scripts/precommit-check.mjs | 38 +- scripts/precommit-check.test.mjs | 14 +- scripts/prepare-cli-bin.mjs | 8 +- scripts/published-package-smoke-config.mjs | 18 +- scripts/published-package-smoke.mjs | 14 +- scripts/published-package-smoke.test.mjs | 72 +-- scripts/relationship-orbit-verification.mjs | 34 +- .../relationship-orbit-verification.test.mjs | 34 +- scripts/release-readiness.mjs | 6 +- scripts/release-readiness.test.mjs | 70 +-- scripts/{run-klo.mjs => run-ktx.mjs} | 14 +- .../{run-klo.test.mjs => run-ktx.test.mjs} | 76 +-- scripts/setup-dev.mjs | 8 +- scripts/setup-dev.test.mjs | 4 +- scripts/standalone-ci-workflow.test.mjs | 14 +- scripts/validate-llm-debug-jsonl.mjs | 2 +- scripts/validate-llm-debug-jsonl.test.mjs | 4 +- uv.lock | 20 +- 704 files changed, 10205 insertions(+), 10255 deletions(-) rename examples/local-warehouse/{klo.yaml => ktx.yaml} (91%) rename examples/orbit-relationship-verification/{klo.yaml => ktx.yaml} (94%) delete mode 100644 python/klo-daemon/src/klo_daemon/__init__.py delete mode 100644 python/klo-daemon/tests/test_package.py rename python/{klo-daemon => ktx-daemon}/README.md (81%) rename python/{klo-daemon => ktx-daemon}/pyproject.toml (81%) create mode 100644 python/ktx-daemon/src/ktx_daemon/__init__.py rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/__main__.py (90%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/app.py (94%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/code_execution.py (99%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/database_introspection.py (98%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/embeddings.py (98%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/lookml.py (100%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/semantic_layer.py (98%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/source_generation.py (98%) rename python/{klo-daemon/src/klo_daemon => ktx-daemon/src/ktx_daemon}/table_identifier.py (100%) rename python/{klo-daemon => ktx-daemon}/tests/test_app.py (99%) rename python/{klo-daemon => ktx-daemon}/tests/test_cli.py (94%) rename python/{klo-daemon => ktx-daemon}/tests/test_code_execution.py (99%) rename python/{klo-daemon => ktx-daemon}/tests/test_database_introspection.py (99%) rename python/{klo-daemon => ktx-daemon}/tests/test_embeddings.py (98%) rename python/{klo-daemon => ktx-daemon}/tests/test_lookml.py (98%) create mode 100644 python/ktx-daemon/tests/test_package.py rename python/{klo-daemon => ktx-daemon}/tests/test_semantic_layer.py (97%) rename python/{klo-daemon => ktx-daemon}/tests/test_source_generation.py (99%) rename python/{klo-sl => ktx-sl}/AGENTS.md (100%) rename python/{klo-sl => ktx-sl}/CLAUDE.md (100%) rename python/{klo-sl => ktx-sl}/README.md (100%) rename python/{klo-sl => ktx-sl}/demos/complex_cte_join.yaml (100%) rename python/{klo-sl => ktx-sl}/demos/run_complex_cte_join.sh (100%) rename python/{klo-sl => ktx-sl}/pyproject.toml (98%) rename python/{klo-sl => ktx-sl}/scripts/gen_b2b_saas_model.py (93%) rename python/{klo-sl => ktx-sl}/scripts/slquery.py (98%) rename python/{klo-sl => ktx-sl}/scripts/tpch_runner.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/__init__.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/__main__.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/cli.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/duplicate_check.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/engine.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/generator.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/graph.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/loader.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/manifest.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/models.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/parser.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/planner.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/sql_table_extractor.py (100%) rename python/{klo-sl => ktx-sl}/semantic_layer/table_identifier_parser.py (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/abm_engagements.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/account_intent_signals.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/accounts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/activities.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_accounts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_ad_stats.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_campaigns.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_creative_stats.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_creatives.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_groups.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ad_stats.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ads.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ap_bills.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/approvals.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/attribution_credits.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/budgets.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/calls.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/campaign_members.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/campaigns.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/card_transactions.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/cash_balances.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/charges.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/churn_risk.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/contacts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/content_assets.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/content_touches.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/contracts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/crm_notes.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/currencies.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/departments_hr.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/disputes.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/email_events.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/email_sends.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/employees.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/etl_runs.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/fiscal_calendar.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/forecast_snapshots.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/fx_rates.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ga4_event_params.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/ga4_events.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/gl_accounts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/identities.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/identity_links.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/invoice_lines.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/invoices.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/journal_entries.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/journal_lines.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/keyword_rankings.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/lead_status_history.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/leads.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/meeting_bookings.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/open_roles.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/opportunities.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/opportunity_contact_roles.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/opportunity_line_items.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/opportunity_stage_history.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/payment_intents.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/payments.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/payroll_runs.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/pricebook_entries.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/pricebooks.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/product_costs.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/product_usage.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/products.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/quotas.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/quote_line_items.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/quotes.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/refunds.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/revenue_schedules.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/reverse_etl_jobs.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sales_reps.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sales_teams.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/search_console_stats.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sequence_enrollments.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sequence_steps.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sequence_touches.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/sequences.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/stage_weights.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/subscription_items.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/subscriptions.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/support_tickets.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/target_accounts.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/touchpoints.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/vendors.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/web_events.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/web_sessions.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/webinar_attendance.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/webinar_registrations.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/b2b_saas/webinars.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/churn_risk.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/customers.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/order_items.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/orders.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/products.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/ecommerce/regions.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/customer.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/lineitem.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/nation.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/orders.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/part.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/partsupp.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/region.yaml (100%) rename python/{klo-sl => ktx-sl}/sources/tpch/supplier.yaml (100%) rename python/{klo-sl => ktx-sl}/tests/__init__.py (100%) rename python/{klo-sl => ktx-sl}/tests/conftest.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_aggregate_locality.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_cli.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_computed_columns.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_corner_case_regressions.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_coverage_gaps.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_duplicate_check.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_engine.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_generator.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_graph.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_loader.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_manifest.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_models.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_parser.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_planner.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_segments.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_snowflake.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_sql_join_coverage.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_table_identifier_parser.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_tpch.py (100%) rename python/{klo-sl => ktx-sl}/tests/test_validator.py (100%) rename scripts/{run-klo.mjs => run-ktx.mjs} (91%) rename scripts/{run-klo.test.mjs => run-ktx.test.mjs} (73%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 184826a3..d37c6812 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: KLO CI +name: KTX CI on: push: @@ -11,7 +11,7 @@ permissions: contents: read concurrency: - group: klo-ci-${{ github.ref }} + group: ktx-ci-${{ github.ref }} cancel-in-progress: true jobs: @@ -62,7 +62,7 @@ jobs: - name: Upload package artifacts uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: - name: klo-package-artifacts-${{ github.sha }} + name: ktx-package-artifacts-${{ github.sha }} path: | dist/artifacts/manifest.json dist/artifacts/npm/*.tgz diff --git a/.gitignore b/.gitignore index c3f7e607..8665ccec 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ yarn-error.log* .pnpm-debug.log* # Local project runtime state -.klo/ +.ktx/ *.db *.sqlite *.sqlite3 diff --git a/AGENTS.md b/AGENTS.md index 8b35d70b..e8062dcb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ database migrations, ORPC contracts, or `python-service/` layout exist here. - **MUST**: Remove dead code; do not leave commented-out code, unused wrappers, or empty directories. - **MUST**: Keep package/public API changes intentional. Do not add compatibility - wrappers for old KLO names unless the user explicitly asks for a migration + wrappers for old KTX names unless the user explicitly asks for a migration bridge. ### Absolute Prohibitions @@ -65,14 +65,14 @@ KTX is a pnpm + uv workspace. - Core context package: `packages/context` - LLM package: `packages/llm` - Database connectors: `packages/connector-*` -- Python semantic layer: `python/klo-sl` -- Python daemon: `python/klo-daemon` +- Python semantic layer: `python/ktx-sl` +- Python daemon: `python/ktx-daemon` - Examples and fixtures: `examples/` - Workspace scripts: `scripts/` - Local agent skills are private overlays. Do not commit `.agents/` or `.claude/` to this public repository. -Some package names still contain `klo` during the split. Do not mass-rename +Some package names still contain `ktx` during the split. Do not mass-rename symbols, package names, paths, or docs to `ktx` unless the task asks for that rename. @@ -86,7 +86,7 @@ pnpm run build pnpm run type-check pnpm run test pnpm run check -pnpm --filter @klo/cli run smoke +pnpm --filter @ktx/cli run smoke pnpm --filter './packages/*' run build pnpm --filter './packages/*' run test pnpm --filter './packages/*' run type-check @@ -97,8 +97,8 @@ pnpm --filter './packages/*' run type-check ```bash uv sync --all-groups uv run pytest -q -uv run pytest python/klo-sl/tests -q -uv run pytest python/klo-daemon/tests -q +uv run pytest python/ktx-sl/tests -q +uv run pytest python/ktx-daemon/tests -q uv run pre-commit run --files [FILES] ``` @@ -127,8 +127,8 @@ shared contracts or package exports are affected. - Build/export changes: `pnpm run build` - Workspace scripts: `node --test scripts/*.test.mjs` or the specific script test file -- Python semantic layer: `uv run pytest python/klo-sl/tests -q` -- Python daemon: `uv run pytest python/klo-daemon/tests -q` +- Python semantic layer: `uv run pytest python/ktx-sl/tests -q` +- Python daemon: `uv run pytest python/ktx-daemon/tests -q` - Python files: also run `uv run pre-commit run --files [FILES]` when pre-commit is configured @@ -178,15 +178,15 @@ use `PascalCase` without the suffix. - Use `pathlib` instead of `os.path`. - Use `logger.exception()` when catching and logging exceptions. - Prefer explicit exception types over broad `except Exception`. -- Keep `python/klo-sl` focused on semantic-layer planning and SQL generation. -- Keep `python/klo-daemon` focused on portable daemon/API behavior around the +- Keep `python/ktx-sl` focused on semantic-layer planning and SQL generation. +- Keep `python/ktx-daemon` focused on portable daemon/API behavior around the semantic layer. ### SQL and Structured Parsing - Prefer AST-based parsing over regex for structured input. - For SQL, use `sqlglot`; it is already a dependency. -- In `python/klo-sl`, follow the local `python/klo-sl/AGENTS.md` guidance: +- In `python/ktx-sl`, follow the local `python/ktx-sl/AGENTS.md` guidance: parse expressions with sqlglot, quote reserved identifiers before parsing, and generate postgres-shaped SQL before final dialect transpilation. - Regex may be used for non-structural sanitization, but not to interpret SQL diff --git a/README.md b/README.md index 71a8af77..014ac600 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# KLO +# KTX -KLO is a workspace-first context layer for database agents. It stores warehouse +KTX is a workspace-first context layer for database agents. It stores warehouse memory in a project directory, generates and validates semantic-layer YAML, indexes knowledge, scans database schemas, and exposes the result through a CLI and MCP server. -KLO projects are plain files: YAML, Markdown, SQLite state, and generated +KTX projects are plain files: YAML, Markdown, SQLite state, and generated artifacts. You can inspect them, commit them, and serve them to any MCP client. -## What KLO provides +## What KTX provides - Durable warehouse memory with semantic-layer sources and knowledge pages. - Native scan connectors for SQLite, Postgres, MySQL, ClickHouse, SQL Server, @@ -25,8 +25,8 @@ Run the pre-seeded demo from the repository root: ```bash pnpm install pnpm run setup:dev -pnpm run klo -- setup demo --no-input -pnpm run klo -- setup demo inspect +pnpm run ktx -- setup demo --no-input +pnpm run ktx -- setup demo inspect ``` The default demo uses packaged sample data and prebuilt context. It does not @@ -35,7 +35,7 @@ require API keys, network access, or an LLM provider. To replay the packaged ingest run, use: ```bash -pnpm run klo -- setup demo --mode replay --no-input +pnpm run ktx -- setup demo --mode replay --no-input ``` To run the full agentic demo with an LLM provider, set a provider key for the @@ -43,11 +43,11 @@ current process: ```bash ANTHROPIC_API_KEY=$YOUR_ANTHROPIC_API_KEY \ - pnpm run klo -- setup demo --mode full --no-input + pnpm run ktx -- setup demo --mode full --no-input ``` Interactive full-demo setup can prompt for a provider key without writing the -key to `klo.yaml`. +key to `ktx.yaml`. ## Build a local project @@ -57,8 +57,8 @@ Create a project from the repository root: uv sync --all-packages source .venv/bin/activate -PROJECT_DIR="$(mktemp -d)/klo-demo" -pnpm run klo -- init "$PROJECT_DIR" --name klo-demo +PROJECT_DIR="$(mktemp -d)/ktx-demo" +pnpm run ktx -- init "$PROJECT_DIR" --name ktx-demo ``` Create a SQLite warehouse: @@ -88,11 +88,11 @@ conn.close() PY ``` -Replace the generated `klo.yaml`: +Replace the generated `ktx.yaml`: ```bash -cat > "$PROJECT_DIR/klo.yaml" < "$PROJECT_DIR/ktx.yaml" <" + author: "ktx " memory: auto_commit: true YAML @@ -112,7 +112,7 @@ YAML Write and validate a semantic-layer source: ```bash -pnpm run klo -- sl write accounts --project-dir "$PROJECT_DIR" \ +pnpm run ktx -- sl write accounts --project-dir "$PROJECT_DIR" \ --connection-id warehouse --yaml 'name: accounts table: accounts description: CRM accounts with segmentation attributes. @@ -133,14 +133,14 @@ measures: joins: [] ' -pnpm run klo -- sl validate accounts --project-dir "$PROJECT_DIR" \ +pnpm run ktx -- sl validate accounts --project-dir "$PROJECT_DIR" \ --connection-id warehouse ``` Generate SQL and execute the query: ```bash -pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \ +pnpm run ktx -- sl query --project-dir "$PROJECT_DIR" \ --connection-id warehouse \ --measure accounts.account_count \ --dimension accounts.segment \ @@ -148,7 +148,7 @@ pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \ --limit 5 \ --format sql -pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \ +pnpm run ktx -- sl query --project-dir "$PROJECT_DIR" \ --connection-id warehouse \ --measure accounts.account_count \ --dimension accounts.segment \ @@ -161,8 +161,8 @@ pnpm run klo -- sl query --project-dir "$PROJECT_DIR" \ List and test the warehouse connection: ```bash -pnpm run klo -- connection list --project-dir "$PROJECT_DIR" -pnpm run klo -- connection test warehouse --project-dir "$PROJECT_DIR" +pnpm run ktx -- connection list --project-dir "$PROJECT_DIR" +pnpm run ktx -- connection test warehouse --project-dir "$PROJECT_DIR" ``` The connection test prints the configured driver and discovered table count: @@ -179,11 +179,11 @@ Scan artifacts are written under ```bash -SCAN_OUTPUT="$(pnpm run klo -- scan warehouse --project-dir "$PROJECT_DIR")" +SCAN_OUTPUT="$(pnpm run ktx -- scan warehouse --project-dir "$PROJECT_DIR")" printf '%s\n' "$SCAN_OUTPUT" SCAN_RUN_ID="$(printf '%s\n' "$SCAN_OUTPUT" | awk '/^Run: / { print $2 }')" -pnpm run klo -- scan status --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID" -pnpm run klo -- scan report --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID" +pnpm run ktx -- scan status --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID" +pnpm run ktx -- scan report --project-dir "$PROJECT_DIR" "$SCAN_RUN_ID" ``` For non-SQLite drivers, prefer credential references such as `--url env:NAME` @@ -195,13 +195,13 @@ Start the Python compute daemon in one terminal: ```bash source .venv/bin/activate -uv run klo-daemon serve-http --host 127.0.0.1 --port 8765 +uv run ktx-daemon serve-http --host 127.0.0.1 --port 8765 ``` Start the stdio MCP server in another terminal: ```bash -pnpm run klo -- serve --mcp stdio --project-dir "$PROJECT_DIR" \ +pnpm run ktx -- serve --mcp stdio --project-dir "$PROJECT_DIR" \ --user-id local \ --semantic-compute-url http://127.0.0.1:8765 \ --execute-queries @@ -225,8 +225,8 @@ The MCP server exposes `connection_list`, `knowledge_search`, - `packages/connector-snowflake`: Snowflake scan connector. - `packages/connector-sqlite`: SQLite scan connector. - `packages/connector-sqlserver`: SQL Server scan connector. -- `python/klo-sl`: semantic-layer engine. -- `python/klo-daemon`: portable compute service for semantic-layer operations. +- `python/ktx-sl`: semantic-layer engine. +- `python/ktx-daemon`: portable compute service for semantic-layer operations. ## Development @@ -240,11 +240,11 @@ source .venv/bin/activate uv run pytest ``` -Use the optional development binary when you want a local `klo-dev` command: +Use the optional development binary when you want a local `ktx-dev` command: ```bash pnpm run link:dev -klo-dev --help +ktx-dev --help ``` The repository uses `pnpm` for TypeScript packages and `uv` for Python @@ -267,4 +267,4 @@ pnpm run release:readiness ## License -KLO is licensed under the Apache License, Version 2.0. See `LICENSE`. +KTX is licensed under the Apache License, Version 2.0. See `LICENSE`. diff --git a/examples/README.md b/examples/README.md index da5a4916..134b4c24 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,15 +1,15 @@ -# klo examples +# ktx examples ## local-warehouse -`local-warehouse/` is a runnable standalone KLO project for local CLI and MCP +`local-warehouse/` is a runnable standalone KTX project for local CLI and MCP smoke testing. It uses the fake ingest adapter and does not require a database or external app server. Copy it before running commands: ```bash -pnpm --filter @klo/cli run build +pnpm --filter @ktx/cli run build EXAMPLE_DIR="$(mktemp -d)/local-warehouse" cp -R examples/local-warehouse "$EXAMPLE_DIR" node packages/cli/dist/bin.js knowledge list --project-dir "$EXAMPLE_DIR" @@ -21,7 +21,7 @@ The copied project initializes its own Git repository on first use. ## orbit-relationship-verification -`orbit-relationship-verification/` is a checked-in KLO project used by +`orbit-relationship-verification/` is a checked-in KTX project used by `pnpm run relationships:verify-orbit`. It points the `orbit` SQLite connection at the Orbit-style no-declared-constraint relationship fixture and verifies that relationship enrichment writes nine accepted joins without requiring a local diff --git a/examples/local-warehouse/README.md b/examples/local-warehouse/README.md index a8a58ff2..a90538fc 100644 --- a/examples/local-warehouse/README.md +++ b/examples/local-warehouse/README.md @@ -1,13 +1,13 @@ # Local Warehouse Example -This example is a standalone KLO project that can be copied to a temp directory +This example is a standalone KTX project that can be copied to a temp directory and used with the local CLI and stdio MCP server. It uses the `fake` ingest adapter so it does not require a database or external app server. Run the example from the repository root after building the CLI: ```bash -pnpm --filter @klo/cli run build +pnpm --filter @ktx/cli run build EXAMPLE_DIR="$(mktemp -d)/local-warehouse" cp -R examples/local-warehouse "$EXAMPLE_DIR" node packages/cli/dist/bin.js knowledge list --project-dir "$EXAMPLE_DIR" diff --git a/examples/local-warehouse/klo.yaml b/examples/local-warehouse/ktx.yaml similarity index 91% rename from examples/local-warehouse/klo.yaml rename to examples/local-warehouse/ktx.yaml index 959c8fa8..7ca51365 100644 --- a/examples/local-warehouse/klo.yaml +++ b/examples/local-warehouse/ktx.yaml @@ -8,7 +8,7 @@ storage: search: sqlite-fts5 git: auto_commit: true - author: "klo " + author: "ktx " ingest: adapters: - fake diff --git a/examples/orbit-relationship-verification/README.md b/examples/orbit-relationship-verification/README.md index 7f0bde1f..245411b6 100644 --- a/examples/orbit-relationship-verification/README.md +++ b/examples/orbit-relationship-verification/README.md @@ -1,11 +1,11 @@ # Orbit-style relationship discovery verification -This KLO project backs the default `relationships:verify-orbit` command. It uses +This KTX project backs the default `relationships:verify-orbit` command. It uses the checked-in Orbit-style SQLite fixture from the relationship discovery benchmark corpus, with no declared primary keys or foreign keys in the database schema. -Run from the KLO workspace root: +Run from the KTX workspace root: ```bash pnpm run relationships:verify-orbit @@ -29,5 +29,5 @@ examples/orbit-relationship-verification/reports/orbit-verification.md Use a real local Orbit project by overriding the project directory: ```bash -KLO_ORBIT_PROJECT_DIR=/path/to/orbit-project pnpm run relationships:verify-orbit +KTX_ORBIT_PROJECT_DIR=/path/to/orbit-project pnpm run relationships:verify-orbit ``` diff --git a/examples/orbit-relationship-verification/klo.yaml b/examples/orbit-relationship-verification/ktx.yaml similarity index 94% rename from examples/orbit-relationship-verification/klo.yaml rename to examples/orbit-relationship-verification/ktx.yaml index db124bab..5f826daf 100644 --- a/examples/orbit-relationship-verification/klo.yaml +++ b/examples/orbit-relationship-verification/ktx.yaml @@ -9,7 +9,7 @@ storage: search: sqlite-fts5 git: auto_commit: true - author: "klo " + author: "ktx " ingest: adapters: - live-database diff --git a/examples/package-artifacts/README.md b/examples/package-artifacts/README.md index 0e7c05a0..2db3817b 100644 --- a/examples/package-artifacts/README.md +++ b/examples/package-artifacts/README.md @@ -1,17 +1,17 @@ # Package artifact smoke checks The package artifact smoke checks create temporary projects instead of storing -sample projects in this directory. Run the checks from `klo/`: +sample projects in this directory. Run the checks from `ktx/`: ```bash source .venv/bin/activate pnpm run artifacts:check ``` -The npm smoke project installs the generated `@klo/context` and `@klo/cli` -tarballs, imports public package entry points, and runs installed `klo` +The npm smoke project installs the generated `@ktx/context` and `@ktx/cli` +tarballs, imports public package entry points, and runs installed `ktx` commands against a generated local project. -The Python smoke project installs `klo-daemon` through the local artifact -directory, imports `semantic_layer` and `klo_daemon`, and runs -`python -m klo_daemon semantic-validate`. +The Python smoke project installs `ktx-daemon` through the local artifact +directory, imports `semantic_layer` and `ktx_daemon`, and runs +`python -m ktx_daemon semantic-validate`. diff --git a/examples/postgres-historic/README.md b/examples/postgres-historic/README.md index 04c943be..3e27b462 100644 --- a/examples/postgres-historic/README.md +++ b/examples/postgres-historic/README.md @@ -2,7 +2,7 @@ This example is a manual smoke for Postgres historic-SQL ingest through `pg_stat_statements`. It starts Postgres 14 with the extension preloaded, -generates query workload under separate users, runs `klo setup` with +generates query workload under separate users, runs `ktx setup` with `--enable-historic-sql`, and verifies three local ingest runs: - first run creates a fresh PGSS baseline @@ -12,30 +12,30 @@ generates query workload under separate users, runs `klo setup` with ## Prerequisites - Docker with Compose v2 -- Node and pnpm matching the KLO workspace -- `python-service/.venv` already created, or `KLO_SQL_ANALYSIS_URL` pointing at +- Node and pnpm matching the KTX workspace +- `python-service/.venv` already created, or `KTX_SQL_ANALYSIS_URL` pointing at a running service that exposes `/api/sql/analyze-for-fingerprint` ## Run -From the KLO repository root: +From the KTX repository root: ```bash examples/postgres-historic/scripts/smoke.sh ``` -The smoke creates a temporary KLO project, starts Postgres on +The smoke creates a temporary KTX project, starts Postgres on `127.0.0.1:55432`, and uses this connection URL: ```bash -postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics # pragma: allowlist secret +postgresql://ktx_reader:ktx_reader@127.0.0.1:55432/analytics # pragma: allowlist secret ``` -Set `KLO_POSTGRES_HISTORIC_KEEP_DOCKER=1` to leave the container running after +Set `KTX_POSTGRES_HISTORIC_KEEP_DOCKER=1` to leave the container running after the script exits. The smoke validates the historic-SQL raw snapshot path without requiring LLM -credentials. It uses KLO's local stage-only ingest API after `klo setup` so the +credentials. It uses KTX's local stage-only ingest API after `ktx setup` so the PGSS baseline and delta behavior can be checked independently from curation. ## Manual Commands @@ -50,9 +50,9 @@ examples/postgres-historic/scripts/generate-workload.sh base Create a project and enable historic SQL: ```bash -export WAREHOUSE_DATABASE_URL=postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics # pragma: allowlist secret -pnpm --filter @klo/cli run build -node packages/cli/dist/bin.js --project-dir /tmp/klo-postgres-historic setup \ +export WAREHOUSE_DATABASE_URL=postgresql://ktx_reader:ktx_reader@127.0.0.1:55432/analytics # pragma: allowlist secret +pnpm --filter @ktx/cli run build +node packages/cli/dist/bin.js --project-dir /tmp/ktx-postgres-historic setup \ --new \ --skip-agents \ --skip-llm \ @@ -71,11 +71,11 @@ node packages/cli/dist/bin.js --project-dir /tmp/klo-postgres-historic setup \ ### Readiness check ```bash -pnpm run klo -- dev doctor --project-dir /tmp/klo-postgres-historic --no-input +pnpm run ktx -- dev doctor --project-dir /tmp/ktx-postgres-historic --no-input ``` -The installed CLI form is `klo dev doctor --project-dir -/tmp/klo-postgres-historic --no-input`. Expected output includes `PASS Postgres +The installed CLI form is `ktx dev doctor --project-dir +/tmp/ktx-postgres-historic --no-input`. Expected output includes `PASS Postgres Historic SQL (warehouse)` when `pg_stat_statements` is installed, `pg_read_all_stats` is granted, tracking is enabled, and `pg_stat_statements.max` is at least 5000. @@ -83,7 +83,7 @@ Historic SQL (warehouse)` when `pg_stat_statements` is installed, Run local historic-SQL ingest: ```bash -node packages/cli/dist/bin.js --project-dir /tmp/klo-postgres-historic dev ingest run \ +node packages/cli/dist/bin.js --project-dir /tmp/ktx-postgres-historic dev ingest run \ --connection-id warehouse \ --adapter historic-sql \ --plain \ @@ -96,7 +96,7 @@ configured LLM provider. Inspect the latest manifest: ```bash -find /tmp/klo-postgres-historic/raw-sources/warehouse/historic-sql -name manifest.json | sort | tail -n 1 +find /tmp/ktx-postgres-historic/raw-sources/warehouse/historic-sql -name manifest.json | sort | tail -n 1 ``` The manifest should have `dialect: "postgres"`, `degraded: true`, @@ -108,8 +108,8 @@ The manifest should have `dialect: "postgres"`, `degraded: true`, - Missing extension: confirm `shared_preload_libraries=pg_stat_statements` and `CREATE EXTENSION pg_stat_statements;` both happened in the `analytics` database. -- Missing grants: confirm `GRANT pg_read_all_stats TO klo_reader;`. +- Missing grants: confirm `GRANT pg_read_all_stats TO ktx_reader;`. - Empty templates: rerun `scripts/generate-workload.sh base` and keep `--historic-sql-min-calls 2` for the smoke. -- SQL-analysis failures: set `KLO_SQL_ANALYSIS_URL` to the running service URL +- SQL-analysis failures: set `KTX_SQL_ANALYSIS_URL` to the running service URL or create `python-service/.venv` before running `scripts/smoke.sh`. diff --git a/examples/postgres-historic/init/001-schema.sql b/examples/postgres-historic/init/001-schema.sql index 8eae7eaf..e60ba85a 100644 --- a/examples/postgres-historic/init/001-schema.sql +++ b/examples/postgres-historic/init/001-schema.sql @@ -2,9 +2,9 @@ CREATE EXTENSION IF NOT EXISTS pg_stat_statements; CREATE ROLE app_user LOGIN PASSWORD 'app_pass'; CREATE ROLE etl_user LOGIN PASSWORD 'etl_pass'; -CREATE ROLE klo_reader LOGIN PASSWORD 'klo_reader'; +CREATE ROLE ktx_reader LOGIN PASSWORD 'ktx_reader'; -GRANT pg_read_all_stats TO klo_reader; +GRANT pg_read_all_stats TO ktx_reader; CREATE TABLE customers ( id integer PRIMARY KEY, @@ -47,5 +47,5 @@ INSERT INTO events (id, customer_id, event_name, occurred_at) VALUES (4, 3, 'sync_completed', now() - interval '6 hours'), (5, 4, 'dashboard_viewed', now() - interval '5 hours'); -GRANT USAGE ON SCHEMA public TO app_user, etl_user, klo_reader; -GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user, etl_user, klo_reader; +GRANT USAGE ON SCHEMA public TO app_user, etl_user, ktx_reader; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user, etl_user, ktx_reader; diff --git a/examples/postgres-historic/scripts/smoke.sh b/examples/postgres-historic/scripts/smoke.sh index 4fec1e4b..d948cf8e 100755 --- a/examples/postgres-historic/scripts/smoke.sh +++ b/examples/postgres-historic/scripts/smoke.sh @@ -3,12 +3,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" EXAMPLE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" -KLO_ROOT="$(cd "$EXAMPLE_DIR/../.." && pwd)" -REPO_ROOT="$(cd "$KLO_ROOT/.." && pwd)" +KTX_ROOT="$(cd "$EXAMPLE_DIR/../.." && pwd)" +REPO_ROOT="$(cd "$KTX_ROOT/.." && pwd)" COMPOSE_FILE="$EXAMPLE_DIR/docker-compose.yml" -PROJECT_PARENT="${KLO_POSTGRES_HISTORIC_PROJECT_PARENT:-$(mktemp -d)}" -PROJECT_DIR="$PROJECT_PARENT/postgres-historic-klo" -KLO_BIN="$KLO_ROOT/packages/cli/dist/bin.js" +PROJECT_PARENT="${KTX_POSTGRES_HISTORIC_PROJECT_PARENT:-$(mktemp -d)}" +PROJECT_DIR="$PROJECT_PARENT/postgres-historic-ktx" +KTX_BIN="$KTX_ROOT/packages/cli/dist/bin.js" PYTHON_SERVICE_LOG="$PROJECT_PARENT/python-service.log" PYTHON_SERVICE_PID="" @@ -16,18 +16,18 @@ cleanup() { if [[ -n "$PYTHON_SERVICE_PID" ]]; then kill "$PYTHON_SERVICE_PID" >/dev/null 2>&1 || true fi - if [[ "${KLO_POSTGRES_HISTORIC_KEEP_DOCKER:-0}" != "1" ]]; then + if [[ "${KTX_POSTGRES_HISTORIC_KEEP_DOCKER:-0}" != "1" ]]; then docker compose -f "$COMPOSE_FILE" down -v >/dev/null 2>&1 || true fi } trap cleanup EXIT start_sql_analysis_if_needed() { - if [[ -n "${KLO_SQL_ANALYSIS_URL:-}" ]]; then + if [[ -n "${KTX_SQL_ANALYSIS_URL:-}" ]]; then return fi if [[ ! -d "$REPO_ROOT/python-service/.venv" ]]; then - echo "Set KLO_SQL_ANALYSIS_URL or create python-service/.venv before running this smoke." >&2 + echo "Set KTX_SQL_ANALYSIS_URL or create python-service/.venv before running this smoke." >&2 exit 1 fi ( @@ -36,9 +36,9 @@ start_sql_analysis_if_needed() { uvicorn app.main:app --host 127.0.0.1 --port 18081 >"$PYTHON_SERVICE_LOG" 2>&1 ) & PYTHON_SERVICE_PID="$!" - export KLO_SQL_ANALYSIS_URL="http://127.0.0.1:18081" + export KTX_SQL_ANALYSIS_URL="http://127.0.0.1:18081" for _ in $(seq 1 60); do - if curl -fsS "$KLO_SQL_ANALYSIS_URL/health" >/dev/null 2>&1; then + if curl -fsS "$KTX_SQL_ANALYSIS_URL/health" >/dev/null 2>&1; then return fi sleep 1 @@ -74,18 +74,18 @@ NODE run_historic_stage_only() { local job_id="$1" - node - "$KLO_ROOT" "$PROJECT_DIR" "$job_id" <<'NODE' + node - "$KTX_ROOT" "$PROJECT_DIR" "$job_id" <<'NODE' const { join } = await import('node:path'); -const kloRoot = process.argv[2]; +const ktxRoot = process.argv[2]; const projectDir = process.argv[3]; const jobId = process.argv[4]; -const { loadKloProject } = await import(join(kloRoot, 'packages/context/dist/project/index.js')); -const { runLocalStageOnlyIngest } = await import(join(kloRoot, 'packages/context/dist/ingest/index.js')); -const { createKloCliLocalIngestAdapters } = await import(join(kloRoot, 'packages/cli/dist/local-adapters.js')); +const { loadKtxProject } = await import(join(ktxRoot, 'packages/context/dist/project/index.js')); +const { runLocalStageOnlyIngest } = await import(join(ktxRoot, 'packages/context/dist/ingest/index.js')); +const { createKtxCliLocalIngestAdapters } = await import(join(ktxRoot, 'packages/cli/dist/local-adapters.js')); -const project = await loadKloProject({ projectDir }); -const adapters = createKloCliLocalIngestAdapters(project, { historicSqlConnectionId: 'warehouse' }); +const project = await loadKtxProject({ projectDir }); +const adapters = createKtxCliLocalIngestAdapters(project, { historicSqlConnectionId: 'warehouse' }); const adapter = adapters.find((candidate) => candidate.source === 'historic-sql'); if (!adapter) throw new Error('historic-sql adapter was not registered for local run'); const record = await runLocalStageOnlyIngest({ @@ -102,22 +102,22 @@ await adapter.onPullSucceeded?.({ syncId: record.syncId, trigger: 'manual_resync', completedAt: new Date(record.completedAt), - stagedDir: join(project.projectDir, '.klo/cache/local-ingest', jobId, 'staged'), + stagedDir: join(project.projectDir, '.ktx/cache/local-ingest', jobId, 'staged'), }); console.log(record.syncId); NODE } -cd "$KLO_ROOT" -pnpm --filter @klo/context run build -pnpm --filter @klo/cli run build +cd "$KTX_ROOT" +pnpm --filter @ktx/context run build +pnpm --filter @ktx/cli run build start_sql_analysis_if_needed docker compose -f "$COMPOSE_FILE" up -d --wait "$EXAMPLE_DIR/scripts/generate-workload.sh" base -export WAREHOUSE_DATABASE_URL="${WAREHOUSE_DATABASE_URL:-postgresql://klo_reader:klo_reader@127.0.0.1:55432/analytics}" # pragma: allowlist secret -node "$KLO_BIN" --project-dir "$PROJECT_DIR" setup \ +export WAREHOUSE_DATABASE_URL="${WAREHOUSE_DATABASE_URL:-postgresql://ktx_reader:ktx_reader@127.0.0.1:55432/analytics}" # pragma: allowlist secret +node "$KTX_BIN" --project-dir "$PROJECT_DIR" setup \ --new \ --skip-agents \ --skip-llm \ diff --git a/package.json b/package.json index 681c9fb3..08fda8a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "klo-workspace", + "name": "ktx-workspace", "version": "0.0.0-private", - "description": "Workspace root for klo packages", + "description": "Workspace root for ktx packages", "private": true, "type": "module", "packageManager": "pnpm@10.28.0", @@ -18,7 +18,7 @@ "artifacts:verify-manifest": "node scripts/package-artifacts.mjs verify-manifest", "build": "pnpm --filter './packages/*' run build", "check": "node scripts/check-boundaries.mjs && node --test scripts/*.test.mjs && pnpm --filter './packages/*' run build && pnpm --filter './packages/*' run test", - "klo": "node scripts/run-klo.mjs", + "ktx": "node scripts/run-ktx.mjs", "link:dev": "node scripts/link-dev-cli.mjs", "native:rebuild": "pnpm -r rebuild better-sqlite3", "setup:dev": "node scripts/setup-dev.mjs", @@ -28,7 +28,7 @@ "relationships:rebuild-public-snapshots": "node scripts/build-benchmark-snapshot.mjs --rebuild-all", "relationships:build-adventureworks-oltp": "node scripts/build-adventureworks-oltp-fixture.mjs", "relationships:verify-orbit": "node scripts/relationship-orbit-verification.mjs", - "smoke": "pnpm run build && pnpm --filter @klo/cli run smoke", + "smoke": "pnpm run build && pnpm --filter @ktx/cli run smoke", "test": "node --test scripts/*.test.mjs && pnpm --filter './packages/*' run test", "type-check": "pnpm --filter './packages/*' run type-check" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 498c7305..0cc4d6e9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,14 +1,14 @@ { - "name": "@klo/cli", + "name": "@ktx/cli", "version": "0.0.0-private", - "description": "CLI wrapper for klo context packages", + "description": "CLI wrapper for ktx context packages", "private": true, "type": "module", "engines": { "node": ">=22.0.0" }, "bin": { - "klo": "./dist/bin.js" + "ktx": "./dist/bin.js" }, "main": "dist/index.js", "types": "dist/index.d.ts", @@ -34,16 +34,16 @@ "dependencies": { "@clack/prompts": "1.3.0", "@commander-js/extra-typings": "14.0.0", - "@klo/connector-bigquery": "workspace:*", - "@klo/connector-clickhouse": "workspace:*", - "@klo/connector-mysql": "workspace:*", - "@klo/connector-postgres": "workspace:*", - "@klo/connector-posthog": "workspace:*", - "@klo/connector-snowflake": "workspace:*", - "@klo/connector-sqlite": "workspace:*", - "@klo/connector-sqlserver": "workspace:*", - "@klo/context": "workspace:*", - "@klo/llm": "workspace:*", + "@ktx/connector-bigquery": "workspace:*", + "@ktx/connector-clickhouse": "workspace:*", + "@ktx/connector-mysql": "workspace:*", + "@ktx/connector-postgres": "workspace:*", + "@ktx/connector-posthog": "workspace:*", + "@ktx/connector-snowflake": "workspace:*", + "@ktx/connector-sqlite": "workspace:*", + "@ktx/connector-sqlserver": "workspace:*", + "@ktx/context": "workspace:*", + "@ktx/llm": "workspace:*", "@modelcontextprotocol/sdk": "^1.27.1", "commander": "14.0.3", "ink": "^7.0.1", diff --git a/packages/cli/scripts/build-demo-assets.mjs b/packages/cli/scripts/build-demo-assets.mjs index 036ac79b..2e135b1c 100644 --- a/packages/cli/scripts/build-demo-assets.mjs +++ b/packages/cli/scripts/build-demo-assets.mjs @@ -7,7 +7,7 @@ import Database from 'better-sqlite3'; const packageRoot = dirname(dirname(fileURLToPath(import.meta.url))); const repoRoot = resolve(packageRoot, '../..'); const defaultDemoSource = resolve(repoRoot, '../../../orbit-demo-source'); -const sourceRoot = resolve(process.env.KLO_DEMO_SOURCE_DIR ?? defaultDemoSource); +const sourceRoot = resolve(process.env.KTX_DEMO_SOURCE_DIR ?? defaultDemoSource); const assetDir = join(packageRoot, 'assets/demo/orbit'); const dbPath = join(assetDir, 'demo.db'); const exampleDbtProjectDir = ['dbt', `${'kae'}lio_demo`].join('/'); @@ -330,7 +330,7 @@ async function pathExists(path) { async function assertReadable(path, label) { if (!(await pathExists(path))) { throw new Error( - `${label} not found at ${path}. Set KLO_DEMO_SOURCE_DIR to the Orbit demo source directory.`, + `${label} not found at ${path}. Set KTX_DEMO_SOURCE_DIR to the Orbit demo source directory.`, ); } } diff --git a/packages/cli/src/agent-runtime.test.ts b/packages/cli/src/agent-runtime.test.ts index c376edb7..a7634103 100644 --- a/packages/cli/src/agent-runtime.test.ts +++ b/packages/cli/src/agent-runtime.test.ts @@ -3,8 +3,8 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - KLO_AGENT_MAX_ROWS_CAP, - createKloAgentRuntime, + KTX_AGENT_MAX_ROWS_CAP, + createKtxAgentRuntime, parseAgentMaxRows, readAgentJsonFile, writeAgentJson, @@ -28,7 +28,7 @@ describe('agent runtime helpers', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-agent-runtime-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-agent-runtime-')); }); afterEach(async () => { @@ -69,13 +69,13 @@ describe('agent runtime helpers', () => { expect(parseAgentMaxRows(100)).toBe(100); expect(() => parseAgentMaxRows(undefined)).toThrow('maxRows is required'); expect(() => parseAgentMaxRows(0)).toThrow('positive integer'); - expect(() => parseAgentMaxRows(KLO_AGENT_MAX_ROWS_CAP + 1)).toThrow(String(KLO_AGENT_MAX_ROWS_CAP)); + expect(() => parseAgentMaxRows(KTX_AGENT_MAX_ROWS_CAP + 1)).toThrow(String(KTX_AGENT_MAX_ROWS_CAP)); }); it('constructs local context ports with semantic compute and query executor', async () => { const project = { projectDir: tempDir, - configPath: join(tempDir, 'klo.yaml'), + configPath: join(tempDir, 'ktx.yaml'), config: { project: 'revenue', connections: {} }, coreConfig: {}, git: {}, @@ -88,7 +88,7 @@ describe('agent runtime helpers', () => { const createContextTools = vi.fn(() => ports); await expect( - createKloAgentRuntime( + createKtxAgentRuntime( { projectDir: tempDir, enableSemanticCompute: true, enableQueryExecution: true }, { loadProject, diff --git a/packages/cli/src/agent-runtime.ts b/packages/cli/src/agent-runtime.ts index a9cc5cce..98ebcb3a 100644 --- a/packages/cli/src/agent-runtime.ts +++ b/packages/cli/src/agent-runtime.ts @@ -1,38 +1,38 @@ import { readFile } from 'node:fs/promises'; -import { createDefaultLocalQueryExecutor, type KloSqlQueryExecutorPort } from '@klo/context/connections'; -import { createPythonSemanticLayerComputePort, type KloSemanticLayerComputePort } from '@klo/context/daemon'; -import { createLocalProjectMcpContextPorts, type KloMcpContextPorts } from '@klo/context/mcp'; -import { type KloLocalProject, loadKloProject } from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; +import { createDefaultLocalQueryExecutor, type KtxSqlQueryExecutorPort } from '@ktx/context/connections'; +import { createPythonSemanticLayerComputePort, type KtxSemanticLayerComputePort } from '@ktx/context/daemon'; +import { createLocalProjectMcpContextPorts, type KtxMcpContextPorts } from '@ktx/context/mcp'; +import { type KtxLocalProject, loadKtxProject } from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; -export const KLO_AGENT_MAX_ROWS_CAP = 1000; +export const KTX_AGENT_MAX_ROWS_CAP = 1000; -export interface KloAgentRuntimeOptions { +export interface KtxAgentRuntimeOptions { projectDir: string; enableSemanticCompute: boolean; enableQueryExecution: boolean; } -export interface KloAgentRuntime { - project: KloLocalProject; - ports: KloMcpContextPorts; - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: KloSqlQueryExecutorPort; +export interface KtxAgentRuntime { + project: KtxLocalProject; + ports: KtxMcpContextPorts; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: KtxSqlQueryExecutorPort; } -export interface KloAgentRuntimeDeps { - loadProject?: typeof loadKloProject; +export interface KtxAgentRuntimeDeps { + loadProject?: typeof loadKtxProject; createContextTools?: typeof createLocalProjectMcpContextPorts; - createSemanticLayerCompute?: () => KloSemanticLayerComputePort; - createQueryExecutor?: () => KloSqlQueryExecutorPort; + createSemanticLayerCompute?: () => KtxSemanticLayerComputePort; + createQueryExecutor?: () => KtxSqlQueryExecutorPort; } -export function writeAgentJson(io: KloCliIo, value: unknown): void { +export function writeAgentJson(io: KtxCliIo, value: unknown): void { io.stdout.write(`${JSON.stringify(value, null, 2)}\n`); } export function writeAgentJsonError( - io: KloCliIo, + io: KtxCliIo, message: string, detail: Record = {}, ): void { @@ -51,17 +51,17 @@ export function parseAgentMaxRows(value: number | undefined): number { if (!Number.isInteger(value) || value === undefined || value <= 0) { throw new Error('maxRows is required and must be a positive integer.'); } - if (value > KLO_AGENT_MAX_ROWS_CAP) { - throw new Error(`maxRows must be less than or equal to ${KLO_AGENT_MAX_ROWS_CAP}.`); + if (value > KTX_AGENT_MAX_ROWS_CAP) { + throw new Error(`maxRows must be less than or equal to ${KTX_AGENT_MAX_ROWS_CAP}.`); } return value; } -export async function createKloAgentRuntime( - options: KloAgentRuntimeOptions, - deps: KloAgentRuntimeDeps = {}, -): Promise { - const project = await (deps.loadProject ?? loadKloProject)({ projectDir: options.projectDir }); +export async function createKtxAgentRuntime( + options: KtxAgentRuntimeOptions, + deps: KtxAgentRuntimeDeps = {}, +): Promise { + const project = await (deps.loadProject ?? loadKtxProject)({ projectDir: options.projectDir }); const semanticLayerCompute = options.enableSemanticCompute ? (deps.createSemanticLayerCompute ?? createPythonSemanticLayerComputePort)() : undefined; diff --git a/packages/cli/src/agent-search-readiness.test.ts b/packages/cli/src/agent-search-readiness.test.ts index e8699892..e7975c7e 100644 --- a/packages/cli/src/agent-search-readiness.test.ts +++ b/packages/cli/src/agent-search-readiness.test.ts @@ -9,40 +9,40 @@ import { describe('agent semantic-layer search readiness guidance', () => { it('formats missing project guidance with exact recovery commands', () => { - expect(missingProjectSlSearchReadiness('/tmp/klo-search', 'gross revenue')).toEqual({ + expect(missingProjectSlSearchReadiness('/tmp/ktx-search', 'gross revenue')).toEqual({ code: 'agent_sl_search_missing_project', - message: 'Semantic-layer search needs an initialized KLO project at /tmp/klo-search.', + message: 'Semantic-layer search needs an initialized KTX project at /tmp/ktx-search.', nextSteps: [ - 'klo demo', - 'klo setup --project-dir /tmp/klo-search', - 'klo ingest ', - 'klo agent sl list --json --query "gross revenue" --project-dir /tmp/klo-search', + 'ktx demo', + 'ktx setup --project-dir /tmp/ktx-search', + 'ktx ingest ', + 'ktx agent sl list --json --query "gross revenue" --project-dir /tmp/ktx-search', ], }); }); it('formats no-connection and no-index guidance without hiding the project path', () => { - expect(noConnectionsSlSearchReadiness('/tmp/klo-search', 'revenue')).toMatchObject({ + expect(noConnectionsSlSearchReadiness('/tmp/ktx-search', 'revenue')).toMatchObject({ code: 'agent_sl_search_no_connections', - message: 'Semantic-layer search found no configured connections in /tmp/klo-search.', + message: 'Semantic-layer search found no configured connections in /tmp/ktx-search.', }); - expect(noIndexedSourcesSlSearchReadiness('/tmp/klo-search', 'orders')).toMatchObject({ + expect(noIndexedSourcesSlSearchReadiness('/tmp/ktx-search', 'orders')).toMatchObject({ code: 'agent_sl_search_no_indexed_sources', - message: 'Semantic-layer search found no indexed semantic-layer sources in /tmp/klo-search.', + message: 'Semantic-layer search found no indexed semantic-layer sources in /tmp/ktx-search.', }); }); it('formats unknown connection guidance', () => { - expect(missingConnectionSlSearchReadiness('/tmp/klo-search', 'warehouse', 'revenue')).toMatchObject({ + expect(missingConnectionSlSearchReadiness('/tmp/ktx-search', 'warehouse', 'revenue')).toMatchObject({ code: 'agent_sl_search_unknown_connection', - message: 'Semantic-layer search connection "warehouse" is not configured in /tmp/klo-search.', + message: 'Semantic-layer search connection "warehouse" is not configured in /tmp/ktx-search.', }); }); - it('detects missing klo.yaml read errors', () => { + it('detects missing ktx.yaml read errors', () => { const error = Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT', - path: '/tmp/klo-search/klo.yaml', + path: '/tmp/ktx-search/ktx.yaml', }); expect(isMissingProjectConfigError(error)).toBe(true); diff --git a/packages/cli/src/agent-search-readiness.ts b/packages/cli/src/agent-search-readiness.ts index 2e019b73..263676c1 100644 --- a/packages/cli/src/agent-search-readiness.ts +++ b/packages/cli/src/agent-search-readiness.ts @@ -1,11 +1,11 @@ -export type KloAgentSlSearchReadinessCode = +export type KtxAgentSlSearchReadinessCode = | 'agent_sl_search_missing_project' | 'agent_sl_search_no_connections' | 'agent_sl_search_unknown_connection' | 'agent_sl_search_no_indexed_sources'; -export interface KloAgentSlSearchReadinessDetail { - code: KloAgentSlSearchReadinessCode; +export interface KtxAgentSlSearchReadinessDetail { + code: KtxAgentSlSearchReadinessCode; message: string; nextSteps: string[]; } @@ -16,14 +16,14 @@ function queryForCommand(query: string | undefined): string { } function projectSearchCommand(projectDir: string, query: string | undefined): string { - return `klo agent sl list --json --query ${JSON.stringify(queryForCommand(query))} --project-dir ${projectDir}`; + return `ktx agent sl list --json --query ${JSON.stringify(queryForCommand(query))} --project-dir ${projectDir}`; } function baseNextSteps(projectDir: string, query: string | undefined): string[] { return [ - 'klo demo', - `klo setup --project-dir ${projectDir}`, - 'klo ingest ', + 'ktx demo', + `ktx setup --project-dir ${projectDir}`, + 'ktx ingest ', projectSearchCommand(projectDir, query), ]; } @@ -31,10 +31,10 @@ function baseNextSteps(projectDir: string, query: string | undefined): string[] export function missingProjectSlSearchReadiness( projectDir: string, query: string | undefined, -): KloAgentSlSearchReadinessDetail { +): KtxAgentSlSearchReadinessDetail { return { code: 'agent_sl_search_missing_project', - message: `Semantic-layer search needs an initialized KLO project at ${projectDir}.`, + message: `Semantic-layer search needs an initialized KTX project at ${projectDir}.`, nextSteps: baseNextSteps(projectDir, query), }; } @@ -42,7 +42,7 @@ export function missingProjectSlSearchReadiness( export function noConnectionsSlSearchReadiness( projectDir: string, query: string | undefined, -): KloAgentSlSearchReadinessDetail { +): KtxAgentSlSearchReadinessDetail { return { code: 'agent_sl_search_no_connections', message: `Semantic-layer search found no configured connections in ${projectDir}.`, @@ -54,7 +54,7 @@ export function missingConnectionSlSearchReadiness( projectDir: string, connectionId: string, query: string | undefined, -): KloAgentSlSearchReadinessDetail { +): KtxAgentSlSearchReadinessDetail { return { code: 'agent_sl_search_unknown_connection', message: `Semantic-layer search connection "${connectionId}" is not configured in ${projectDir}.`, @@ -65,7 +65,7 @@ export function missingConnectionSlSearchReadiness( export function noIndexedSourcesSlSearchReadiness( projectDir: string, query: string | undefined, -): KloAgentSlSearchReadinessDetail { +): KtxAgentSlSearchReadinessDetail { return { code: 'agent_sl_search_no_indexed_sources', message: `Semantic-layer search found no indexed semantic-layer sources in ${projectDir}.`, @@ -90,5 +90,5 @@ function errorPath(error: unknown): string | undefined { } export function isMissingProjectConfigError(error: unknown): boolean { - return errorCode(error) === 'ENOENT' && (errorPath(error)?.endsWith('klo.yaml') ?? false); + return errorCode(error) === 'ENOENT' && (errorPath(error)?.endsWith('ktx.yaml') ?? false); } diff --git a/packages/cli/src/agent.test.ts b/packages/cli/src/agent.test.ts index 981a5f4c..a57e7d04 100644 --- a/packages/cli/src/agent.test.ts +++ b/packages/cli/src/agent.test.ts @@ -1,10 +1,10 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { buildDefaultKloProjectConfig } from '@klo/context/project'; +import { buildDefaultKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloAgent } from './agent.js'; -import type { KloAgentRuntime } from './agent-runtime.js'; +import { runKtxAgent } from './agent.js'; +import type { KtxAgentRuntime } from './agent-runtime.js'; function makeIo() { let stdout = ''; @@ -19,21 +19,21 @@ function makeIo() { }; } -function runtime(overrides: Record = {}): KloAgentRuntime { - const config = buildDefaultKloProjectConfig('revenue'); +function runtime(overrides: Record = {}): KtxAgentRuntime { + const config = buildDefaultKtxProjectConfig('revenue'); return { project: { projectDir: '/tmp/revenue', - configPath: '/tmp/revenue/klo.yaml', + configPath: '/tmp/revenue/ktx.yaml', config: { ...config, connections: { warehouse: { driver: 'sqlite', path: 'warehouse.sqlite', readonly: true as const }, }, }, - coreConfig: {} as KloAgentRuntime['project']['coreConfig'], - git: {} as KloAgentRuntime['project']['git'], - fileStore: {} as KloAgentRuntime['project']['fileStore'], + coreConfig: {} as KtxAgentRuntime['project']['coreConfig'], + git: {} as KtxAgentRuntime['project']['git'], + fileStore: {} as KtxAgentRuntime['project']['fileStore'], }, ports: { connections: { list: vi.fn(async () => [{ id: 'warehouse', name: 'warehouse', connectionType: 'sqlite' }]) }, @@ -86,7 +86,7 @@ function runtime(overrides: Record = {}): KloAgentRuntime { }; } -function runtimeWithoutConnections(): KloAgentRuntime { +function runtimeWithoutConnections(): KtxAgentRuntime { const base = runtime(); return { ...base, @@ -107,11 +107,11 @@ function runtimeWithoutConnections(): KloAgentRuntime { }; } -describe('runKloAgent', () => { +describe('runKtxAgent', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-agent-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-agent-')); }); afterEach(async () => { @@ -121,7 +121,7 @@ describe('runKloAgent', () => { it('prints tool discovery with every stable command', async () => { const io = makeIo(); - await expect(runKloAgent({ command: 'tools', projectDir: tempDir, json: true }, io.io)).resolves.toBe(0); + await expect(runKtxAgent({ command: 'tools', projectDir: tempDir, json: true }, io.io)).resolves.toBe(0); const body = JSON.parse(io.stdout()); expect(body.projectDir).toBe(tempDir); @@ -143,7 +143,7 @@ describe('runKloAgent', () => { const readSetupStatus = vi.fn(async () => ({ project: { path: tempDir, ready: true }, agents: [] })); await expect( - runKloAgent({ command: 'context', projectDir: tempDir, json: true }, io.io, { createRuntime, readSetupStatus }), + runKtxAgent({ command: 'context', projectDir: tempDir, json: true }, io.io, { createRuntime, readSetupStatus }), ).resolves.toBe(0); expect(JSON.parse(io.stdout())).toMatchObject({ @@ -168,7 +168,7 @@ describe('runKloAgent', () => { { command: 'wiki-read' as const, projectDir: tempDir, json: true as const, pageId: 'page-1' }, ]) { const io = makeIo(); - await expect(runKloAgent(args, io.io, { createRuntime: async () => runtime() })).resolves.toBe(0); + await expect(runKtxAgent(args, io.io, { createRuntime: async () => runtime() })).resolves.toBe(0); expect(JSON.parse(io.stdout())).toBeTruthy(); expect(io.stderr()).toBe(''); } @@ -199,7 +199,7 @@ describe('runKloAgent', () => { const io = makeIo(); await expect( - runKloAgent({ command: 'wiki-search', projectDir: tempDir, json: true, query: 'paid order', limit: 5 }, io.io, { + runKtxAgent({ command: 'wiki-search', projectDir: tempDir, json: true, query: 'paid order', limit: 5 }, io.io, { createRuntime: async () => fakeRuntime, }), ).resolves.toBe(0); @@ -222,7 +222,7 @@ describe('runKloAgent', () => { await writeFile(queryFile, '{"measures":["total_revenue"],"dimensions":[]}', 'utf-8'); await expect( - runKloAgent( + runKtxAgent( { command: 'sl-query', projectDir: tempDir, @@ -247,7 +247,7 @@ describe('runKloAgent', () => { await writeFile(sqlFile, 'select 1', 'utf-8'); await expect( - runKloAgent( + runKtxAgent( { command: 'sql-execute', projectDir: tempDir, @@ -274,11 +274,11 @@ describe('runKloAgent', () => { const io = makeIo(); const missingProjectError = Object.assign(new Error('ENOENT: no such file or directory'), { code: 'ENOENT', - path: join(tempDir, 'klo.yaml'), + path: join(tempDir, 'ktx.yaml'), }); await expect( - runKloAgent( + runKtxAgent( { command: 'sl-list', projectDir: tempDir, json: true, query: 'gross revenue' }, io.io, { createRuntime: vi.fn(async () => Promise.reject(missingProjectError)) }, @@ -289,12 +289,12 @@ describe('runKloAgent', () => { ok: false, error: { code: 'agent_sl_search_missing_project', - message: `Semantic-layer search needs an initialized KLO project at ${tempDir}.`, + message: `Semantic-layer search needs an initialized KTX project at ${tempDir}.`, nextSteps: [ - 'klo demo', - `klo setup --project-dir ${tempDir}`, - 'klo ingest ', - `klo agent sl list --json --query "gross revenue" --project-dir ${tempDir}`, + 'ktx demo', + `ktx setup --project-dir ${tempDir}`, + 'ktx ingest ', + `ktx agent sl list --json --query "gross revenue" --project-dir ${tempDir}`, ], }, }); @@ -305,7 +305,7 @@ describe('runKloAgent', () => { const io = makeIo(); await expect( - runKloAgent( + runKtxAgent( { command: 'sl-list', projectDir: tempDir, json: true, query: 'revenue' }, io.io, { createRuntime: async () => runtimeWithoutConnections() }, @@ -318,10 +318,10 @@ describe('runKloAgent', () => { code: 'agent_sl_search_no_connections', message: `Semantic-layer search found no configured connections in ${tempDir}.`, nextSteps: [ - 'klo demo', - `klo setup --project-dir ${tempDir}`, - 'klo ingest ', - `klo agent sl list --json --query "revenue" --project-dir ${tempDir}`, + 'ktx demo', + `ktx setup --project-dir ${tempDir}`, + 'ktx ingest ', + `ktx agent sl list --json --query "revenue" --project-dir ${tempDir}`, ], }, }); @@ -331,7 +331,7 @@ describe('runKloAgent', () => { const io = makeIo(); await expect( - runKloAgent( + runKtxAgent( { command: 'sl-list', projectDir: tempDir, json: true, connectionId: 'missing', query: 'revenue' }, io.io, { createRuntime: async () => runtime() }, @@ -357,7 +357,7 @@ describe('runKloAgent', () => { const io = makeIo(); await expect( - runKloAgent( + runKtxAgent( { command: 'sl-list', projectDir: tempDir, json: true, connectionId: 'warehouse', query: 'revenue' }, io.io, { createRuntime: async () => fakeRuntime }, @@ -377,7 +377,7 @@ describe('runKloAgent', () => { const io = makeIo(); await expect( - runKloAgent({ command: 'wiki-read', projectDir: tempDir, json: true, pageId: 'missing' }, io.io, { + runKtxAgent({ command: 'wiki-read', projectDir: tempDir, json: true, pageId: 'missing' }, io.io, { createRuntime: async () => runtime({ ports: { knowledge: { read: vi.fn(async () => null) } }, diff --git a/packages/cli/src/agent.ts b/packages/cli/src/agent.ts index 072e7901..ea2a224e 100644 --- a/packages/cli/src/agent.ts +++ b/packages/cli/src/agent.ts @@ -1,13 +1,13 @@ import { readFile } from 'node:fs/promises'; -import type { KloCliIo } from './cli-runtime.js'; +import type { KtxCliIo } from './cli-runtime.js'; import { - createKloAgentRuntime, + createKtxAgentRuntime, parseAgentMaxRows, readAgentJsonFile, writeAgentJson, writeAgentJsonError, - type KloAgentRuntime, - type KloAgentRuntimeDeps, + type KtxAgentRuntime, + type KtxAgentRuntimeDeps, } from './agent-runtime.js'; import { isMissingProjectConfigError, @@ -15,11 +15,11 @@ import { missingProjectSlSearchReadiness, noConnectionsSlSearchReadiness, noIndexedSourcesSlSearchReadiness, - type KloAgentSlSearchReadinessDetail, + type KtxAgentSlSearchReadinessDetail, } from './agent-search-readiness.js'; -import { readKloSetupStatus, type KloSetupStatus } from './setup.js'; +import { readKtxSetupStatus, type KtxSetupStatus } from './setup.js'; -export type KloAgentArgs = +export type KtxAgentArgs = | { command: 'tools'; projectDir: string; json: true } | { command: 'context'; projectDir: string; json: true } | { command: 'sl-list'; projectDir: string; json: true; connectionId?: string; query?: string } @@ -37,38 +37,38 @@ export type KloAgentArgs = | { command: 'wiki-read'; projectDir: string; json: true; pageId: string } | { command: 'sql-execute'; projectDir: string; json: true; connectionId: string; sqlFile: string; maxRows?: number }; -export interface KloAgentDeps extends KloAgentRuntimeDeps { +export interface KtxAgentDeps extends KtxAgentRuntimeDeps { createRuntime?: (options: { projectDir: string; enableSemanticCompute: boolean; enableQueryExecution: boolean; - }) => Promise; + }) => Promise; readSetupStatus?: ( projectDir: string, - ) => Promise; + ) => Promise; } const AGENT_TOOLS = [ - { name: 'context', command: 'klo agent context --json' }, - { name: 'sl.list', command: 'klo agent sl list --json [--connection-id ] [--query ]' }, - { name: 'sl.read', command: 'klo agent sl read --json [--connection-id ]' }, + { name: 'context', command: 'ktx agent context --json' }, + { name: 'sl.list', command: 'ktx agent sl list --json [--connection-id ] [--query ]' }, + { name: 'sl.read', command: 'ktx agent sl read --json [--connection-id ]' }, { name: 'sl.query', - command: 'klo agent sl query --json --connection-id --query-file --execute --max-rows 100', + command: 'ktx agent sl query --json --connection-id --query-file --execute --max-rows 100', }, - { name: 'wiki.search', command: 'klo agent wiki search --json [--limit 10]' }, - { name: 'wiki.read', command: 'klo agent wiki read --json' }, + { name: 'wiki.search', command: 'ktx agent wiki search --json [--limit 10]' }, + { name: 'wiki.read', command: 'ktx agent wiki read --json' }, { name: 'sql.execute', - command: 'klo agent sql execute --json --connection-id --sql-file --max-rows 100', + command: 'ktx agent sql execute --json --connection-id --sql-file --max-rows 100', }, ] as const; -function writeAgentSlSearchReadinessError(io: KloCliIo, detail: KloAgentSlSearchReadinessDetail): void { +function writeAgentSlSearchReadinessError(io: KtxCliIo, detail: KtxAgentSlSearchReadinessDetail): void { writeAgentJsonError(io, detail.message, { code: detail.code, nextSteps: detail.nextSteps }); } -async function runtimeFor(args: KloAgentArgs, deps: KloAgentDeps): Promise { +async function runtimeFor(args: KtxAgentArgs, deps: KtxAgentDeps): Promise { const needsSemanticCompute = args.command === 'sl-query'; const needsQueryExecution = args.command === 'sql-execute' || (args.command === 'sl-query' && args.execute); return deps.createRuntime @@ -77,7 +77,7 @@ async function runtimeFor(args: KloAgentArgs, deps: KloAgentDeps): Promise { +export async function runKtxAgent(args: KtxAgentArgs, io: KtxCliIo, deps: KtxAgentDeps = {}): Promise { try { if (args.command === 'tools') { writeAgentJson(io, { projectDir: args.projectDir, tools: AGENT_TOOLS }); @@ -105,7 +105,7 @@ export async function runKloAgent(args: KloAgentArgs, io: KloCliIo, deps: KloAge if (args.command === 'context') { const [status, connections, semanticLayer] = await Promise.all([ - (deps.readSetupStatus ?? readKloSetupStatus)(args.projectDir), + (deps.readSetupStatus ?? readKtxSetupStatus)(args.projectDir), runtime.ports.connections?.list() ?? [], runtime.ports.semanticLayer?.listSources({}) ?? { sources: [], totalSources: 0 }, ]); diff --git a/packages/cli/src/bin.ts b/packages/cli/src/bin.ts index 11a4dbd1..c5986940 100644 --- a/packages/cli/src/bin.ts +++ b/packages/cli/src/bin.ts @@ -4,6 +4,6 @@ import { installStartupProfileReporter, profileMark, profileSpan } from './start installStartupProfileReporter(); profileMark('bin:entry'); -const { runKloCli } = await profileSpan('import ./cli-runtime.js', () => import('./cli-runtime.js')); -profileMark('bin:runKloCli'); -process.exitCode = await runKloCli(process.argv.slice(2)); +const { runKtxCli } = await profileSpan('import ./cli-runtime.js', () => import('./cli-runtime.js')); +profileMark('bin:runKtxCli'); +process.exitCode = await runKtxCli(process.argv.slice(2)); diff --git a/packages/cli/src/clack.ts b/packages/cli/src/clack.ts index 668ccee6..e7083df9 100644 --- a/packages/cli/src/clack.ts +++ b/packages/cli/src/clack.ts @@ -1,11 +1,11 @@ import { spinner } from '@clack/prompts'; -export interface KloCliSpinner { +export interface KtxCliSpinner { start(message: string): void; stop(message: string): void; error(message: string): void; } -export function createClackSpinner(): KloCliSpinner { +export function createClackSpinner(): KtxCliSpinner { return spinner(); } diff --git a/packages/cli/src/cli-program.ts b/packages/cli/src/cli-program.ts index 74ae4f16..f96e6f1c 100644 --- a/packages/cli/src/cli-program.ts +++ b/packages/cli/src/cli-program.ts @@ -1,5 +1,5 @@ import { Command, InvalidArgumentError } from '@commander-js/extra-typings'; -import type { KloCliDeps, KloCliIo, KloCliPackageInfo } from './cli-runtime.js'; +import type { KtxCliDeps, KtxCliIo, KtxCliPackageInfo } from './cli-runtime.js'; import { registerAgentCommands } from './commands/agent-commands.js'; import { registerConnectionCommands } from './commands/connection-commands.js'; import { registerWikiCommands } from './commands/knowledge-commands.js'; @@ -9,16 +9,16 @@ import { registerSetupCommands } from './commands/setup-commands.js'; import { registerSlCommands } from './commands/sl-commands.js'; import { registerStatusCommands } from './commands/status-commands.js'; import { registerDevCommands } from './dev.js'; -import { findNearestKloProjectDir, resolveKloProjectDir } from './project-resolver.js'; +import { findNearestKtxProjectDir, resolveKtxProjectDir } from './project-resolver.js'; import { profileMark, profileSpan } from './startup-profile.js'; profileMark('module:cli-program'); -export interface KloCliCommandContext { - io: KloCliIo; - deps: KloCliDeps; +export interface KtxCliCommandContext { + io: KtxCliIo; + deps: KtxCliDeps; setExitCode: (code: number) => void; - runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KloCliIo) => Promise; + runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KtxCliIo) => Promise; writeDebug?: (command: string, commandContext: CommandWithGlobalOptions) => void; } @@ -29,13 +29,13 @@ export interface OutputModeOptions { input?: boolean; } -interface KloCommanderProgramOptions { - runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KloCliIo) => Promise; +interface KtxCommanderProgramOptions { + runInit: (args: { projectDir: string; projectName?: string; force: boolean }, io: KtxCliIo) => Promise; } type CommanderExitLike = { exitCode: number; code: string; message: string }; -interface KloGlobalOptionValues { +interface KtxGlobalOptionValues { projectDir?: string; debug?: boolean; } @@ -104,7 +104,7 @@ export function parseNonEmptyAssignmentOption(value: string): { key: string; val }; } -function optionsWithGlobals(command: CommandWithGlobalOptions): KloGlobalOptionValues { +function optionsWithGlobals(command: CommandWithGlobalOptions): KtxGlobalOptionValues { const options = command.optsWithGlobals ? command.optsWithGlobals() : command.opts(); const values = options as { projectDir?: unknown; debug?: unknown }; return { @@ -114,25 +114,25 @@ function optionsWithGlobals(command: CommandWithGlobalOptions): KloGlobalOptionV } export function resolveCommandProjectDir(command: CommandWithGlobalOptions): string { - return resolveKloProjectDir({ explicitProjectDir: optionsWithGlobals(command).projectDir }); + return resolveKtxProjectDir({ explicitProjectDir: optionsWithGlobals(command).projectDir }); } export function resolveCommandProjectDirOverride(command: CommandWithGlobalOptions): string | undefined { - return optionsWithGlobals(command).projectDir ?? process.env.KLO_PROJECT_DIR; + return optionsWithGlobals(command).projectDir ?? process.env.KTX_PROJECT_DIR; } -function createBaseProgram(info: KloCliPackageInfo, io: KloCliIo): Command { +function createBaseProgram(info: KtxCliPackageInfo, io: KtxCliIo): Command { return new Command() - .name('klo') - .description('Standalone KLO developer CLI') - .option('--project-dir ', 'KLO project directory (default: KLO_PROJECT_DIR, nearest klo.yaml, or cwd)') + .name('ktx') + .description('Standalone KTX developer CLI') + .option('--project-dir ', 'KTX project directory (default: KTX_PROJECT_DIR, nearest ktx.yaml, or cwd)') .option('--debug', 'Enable diagnostic logging to stderr') .version(`${info.name} ${info.version}`, '-v, --version', 'Show CLI version') .helpOption('-h, --help', 'Show this help text') .configureHelp({ showGlobalOptions: true }) .addHelpText( 'after', - '\nAdvanced:\n klo dev Low-level diagnostics, scans, adapter commands, and mapping tools.\n', + '\nAdvanced:\n ktx dev Low-level diagnostics, scans, adapter commands, and mapping tools.\n', ) .showHelpAfterError() .exitOverride() @@ -143,7 +143,7 @@ function createBaseProgram(info: KloCliPackageInfo, io: KloCliIo): Command { }); } -function writeDebug(io: KloCliIo, commandContext: CommandWithGlobalOptions, command: string): void { +function writeDebug(io: KtxCliIo, commandContext: CommandWithGlobalOptions, command: string): void { const global = optionsWithGlobals(commandContext); if (global.debug !== true) { return; @@ -158,18 +158,18 @@ function formatCliError(error: unknown): string { async function runBareInteractiveCommand( program: Command, - io: KloCliIo, - context: KloCliCommandContext, + io: KtxCliIo, + context: KtxCliCommandContext, ): Promise { - const nearestProjectDir = findNearestKloProjectDir(process.cwd()); - const envProjectDir = process.env.KLO_PROJECT_DIR; - const runner = context.deps.setup ?? (await import('./setup.js')).runKloSetup; + const nearestProjectDir = findNearestKtxProjectDir(process.cwd()); + const envProjectDir = process.env.KTX_PROJECT_DIR; + const runner = context.deps.setup ?? (await import('./setup.js')).runKtxSetup; if (!nearestProjectDir && !envProjectDir) { return await runner( { command: 'run', - projectDir: resolveKloProjectDir(), + projectDir: resolveKtxProjectDir(), mode: 'auto', agents: false, agentScope: 'project', @@ -191,18 +191,18 @@ async function runBareInteractiveCommand( return 0; } -export async function runCommanderKloCli( +export async function runCommanderKtxCli( argv: string[], - io: KloCliIo, - deps: KloCliDeps, - info: KloCliPackageInfo, - options: KloCommanderProgramOptions, + io: KtxCliIo, + deps: KtxCliDeps, + info: KtxCliPackageInfo, + options: KtxCommanderProgramOptions, ): Promise { profileMark('commander:entry'); let exitCode = 0; const program = createBaseProgram(info, io); profileMark('commander:base-program'); - const context: KloCliCommandContext = { + const context: KtxCliCommandContext = { io, deps, setExitCode: (code: number) => { diff --git a/packages/cli/src/cli-runtime.ts b/packages/cli/src/cli-runtime.ts index 993fcf31..60129922 100644 --- a/packages/cli/src/cli-runtime.ts +++ b/packages/cli/src/cli-runtime.ts @@ -1,67 +1,67 @@ -import type { KloConnectionMetabaseSetupArgs } from './commands/connection-metabase-setup.js'; -import type { KloConnectionNotionArgs } from './commands/connection-notion.js'; -import type { KloAgentArgs } from './agent.js'; -import type { KloConnectionArgs } from './connection.js'; -import type { KloDemoArgs } from './demo.js'; -import type { KloDoctorArgs } from './doctor.js'; -import type { KloIngestArgs } from './ingest.js'; -import type { KloKnowledgeArgs } from './knowledge.js'; -import type { KloPublicIngestArgs } from './public-ingest.js'; -import type { KloScanArgs } from './scan.js'; -import type { KloServeArgs } from './serve.js'; -import type { KloSetupArgs } from './setup.js'; -import type { KloSlArgs } from './sl.js'; +import type { KtxConnectionMetabaseSetupArgs } from './commands/connection-metabase-setup.js'; +import type { KtxConnectionNotionArgs } from './commands/connection-notion.js'; +import type { KtxAgentArgs } from './agent.js'; +import type { KtxConnectionArgs } from './connection.js'; +import type { KtxDemoArgs } from './demo.js'; +import type { KtxDoctorArgs } from './doctor.js'; +import type { KtxIngestArgs } from './ingest.js'; +import type { KtxKnowledgeArgs } from './knowledge.js'; +import type { KtxPublicIngestArgs } from './public-ingest.js'; +import type { KtxScanArgs } from './scan.js'; +import type { KtxServeArgs } from './serve.js'; +import type { KtxSetupArgs } from './setup.js'; +import type { KtxSlArgs } from './sl.js'; import { profileMark, profileSpan } from './startup-profile.js'; profileMark('module:cli-runtime'); -export interface KloCliPackageInfo { - name: '@klo/cli'; +export interface KtxCliPackageInfo { + name: '@ktx/cli'; version: '0.0.0-private'; - contextPackageName: '@klo/context'; + contextPackageName: '@ktx/context'; } -export interface KloCliIo { +export interface KtxCliIo { stdout: { isTTY?: boolean; write(chunk: string): void }; stderr: { write(chunk: string): void }; } -export interface KloCliDeps { - serveStdio?: (args: KloServeArgs) => Promise; - setup?: (args: KloSetupArgs, io: KloCliIo) => Promise; - agent?: (args: KloAgentArgs, io: KloCliIo) => Promise; - connection?: (args: KloConnectionArgs, io: KloCliIo) => Promise; - connectionNotion?: (args: KloConnectionNotionArgs, io: KloCliIo) => Promise; - connectionMetabaseSetup?: (args: KloConnectionMetabaseSetupArgs, io: KloCliIo) => Promise; - demo?: (args: KloDemoArgs, io: KloCliIo) => Promise; - doctor?: (args: KloDoctorArgs, io: KloCliIo) => Promise; - ingest?: (args: KloIngestArgs, io: KloCliIo) => Promise; - publicIngest?: (args: KloPublicIngestArgs, io: KloCliIo) => Promise; - scan?: (args: KloScanArgs, io: KloCliIo) => Promise; - knowledge?: (args: KloKnowledgeArgs, io: KloCliIo) => Promise; - sl?: (args: KloSlArgs, io: KloCliIo) => Promise; +export interface KtxCliDeps { + serveStdio?: (args: KtxServeArgs) => Promise; + setup?: (args: KtxSetupArgs, io: KtxCliIo) => Promise; + agent?: (args: KtxAgentArgs, io: KtxCliIo) => Promise; + connection?: (args: KtxConnectionArgs, io: KtxCliIo) => Promise; + connectionNotion?: (args: KtxConnectionNotionArgs, io: KtxCliIo) => Promise; + connectionMetabaseSetup?: (args: KtxConnectionMetabaseSetupArgs, io: KtxCliIo) => Promise; + demo?: (args: KtxDemoArgs, io: KtxCliIo) => Promise; + doctor?: (args: KtxDoctorArgs, io: KtxCliIo) => Promise; + ingest?: (args: KtxIngestArgs, io: KtxCliIo) => Promise; + publicIngest?: (args: KtxPublicIngestArgs, io: KtxCliIo) => Promise; + scan?: (args: KtxScanArgs, io: KtxCliIo) => Promise; + knowledge?: (args: KtxKnowledgeArgs, io: KtxCliIo) => Promise; + sl?: (args: KtxSlArgs, io: KtxCliIo) => Promise; } -export function getKloCliPackageInfo(): KloCliPackageInfo { +export function getKtxCliPackageInfo(): KtxCliPackageInfo { return { - name: '@klo/cli', + name: '@ktx/cli', version: '0.0.0-private', - contextPackageName: '@klo/context', + contextPackageName: '@ktx/context', }; } async function runInit( args: { projectDir: string; projectName?: string; force: boolean }, - io: KloCliIo, + io: KtxCliIo, ): Promise { - const { initKloProject } = await import('@klo/context/project'); - const result = await initKloProject({ + const { initKtxProject } = await import('@ktx/context/project'); + const result = await initKtxProject({ projectDir: args.projectDir, projectName: args.projectName, force: args.force, }); - io.stdout.write(`Initialized KLO project at ${result.projectDir}\n`); + io.stdout.write(`Initialized KTX project at ${result.projectDir}\n`); io.stdout.write(`Config: ${result.configPath}\n`); io.stdout.write(`Commit: ${result.commitHash ?? 'none'}\n`); return 0; @@ -69,21 +69,21 @@ async function runInit( export async function runInitForCommander( args: { projectDir: string; projectName?: string; force: boolean }, - io: KloCliIo, + io: KtxCliIo, ): Promise { return await runInit(args, io); } -export async function runKloCli( +export async function runKtxCli( argv = process.argv.slice(2), - io: KloCliIo = process, - deps: KloCliDeps = {}, + io: KtxCliIo = process, + deps: KtxCliDeps = {}, ): Promise { - const info = getKloCliPackageInfo(); - profileMark('runtime:runKloCli'); - const { runCommanderKloCli } = await profileSpan('import ./cli-program.js', () => import('./cli-program.js')); + const info = getKtxCliPackageInfo(); + profileMark('runtime:runKtxCli'); + const { runCommanderKtxCli } = await profileSpan('import ./cli-program.js', () => import('./cli-program.js')); - return await runCommanderKloCli(argv, io, deps, info, { + return await runCommanderKtxCli(argv, io, deps, info, { runInit: runInitForCommander, }); } diff --git a/packages/cli/src/commands/agent-commands.ts b/packages/cli/src/commands/agent-commands.ts index 7f47da50..57cc94c3 100644 --- a/packages/cli/src/commands/agent-commands.ts +++ b/packages/cli/src/commands/agent-commands.ts @@ -1,10 +1,10 @@ import { Option, type Command } from '@commander-js/extra-typings'; -import type { KloAgentArgs } from '../agent.js'; -import type { KloCliCommandContext } from '../cli-program.js'; +import type { KtxAgentArgs } from '../agent.js'; +import type { KtxCliCommandContext } from '../cli-program.js'; import { parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js'; -async function runAgent(context: KloCliCommandContext, args: KloAgentArgs): Promise { - const runner = context.deps.agent ?? (await import('../agent.js')).runKloAgent; +async function runAgent(context: KtxCliCommandContext, args: KtxAgentArgs): Promise { + const runner = context.deps.agent ?? (await import('../agent.js')).runKtxAgent; context.setExitCode(await runner(args, context.io)); } @@ -12,10 +12,10 @@ function jsonOption(): Option { return new Option('--json', 'Print JSON output').makeOptionMandatory(); } -export function registerAgentCommands(program: Command, context: KloCliCommandContext): void { +export function registerAgentCommands(program: Command, context: KtxCliCommandContext): void { const agent = program .command('agent', { hidden: true }) - .description('Machine-readable KLO commands for coding agents') + .description('Machine-readable KTX commands for coding agents') .showHelpAfterError(); agent.hook('preAction', (_thisCommand, actionCommand) => { @@ -24,7 +24,7 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo agent .command('tools') - .description('Print available agent-facing KLO tools') + .description('Print available agent-facing KTX tools') .addOption(jsonOption()) .action(async (_options, command) => { await runAgent(context, { command: 'tools', projectDir: resolveCommandProjectDir(command), json: true }); @@ -91,10 +91,10 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo }, ); - const wiki = agent.command('wiki').description('KLO wiki agent commands'); + const wiki = agent.command('wiki').description('KTX wiki agent commands'); wiki .command('search') - .description('Search KLO wiki pages') + .description('Search KTX wiki pages') .argument('') .addOption(jsonOption()) .option('--limit ', 'Maximum search results', parsePositiveIntegerOption, 10) @@ -109,7 +109,7 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo }); wiki .command('read') - .description('Read one KLO wiki page') + .description('Read one KTX wiki page') .argument('') .addOption(jsonOption()) .action(async (pageId: string, _options, command) => { diff --git a/packages/cli/src/commands/completion-commands.ts b/packages/cli/src/commands/completion-commands.ts index 234ed0e7..23c45429 100644 --- a/packages/cli/src/commands/completion-commands.ts +++ b/packages/cli/src/commands/completion-commands.ts @@ -1,10 +1,10 @@ import type { CommandUnknownOpts } from '@commander-js/extra-typings'; -import type { KloCliCommandContext } from '../cli-program.js'; +import type { KtxCliCommandContext } from '../cli-program.js'; import { completeCommanderInput, installZshCompletion, zshCompletionScript } from '../completion.js'; export function registerCompletionCommands( program: CommandUnknownOpts, - context: KloCliCommandContext, + context: KtxCliCommandContext, completionRoot: CommandUnknownOpts = program, ): void { program diff --git a/packages/cli/src/commands/connection-commands.ts b/packages/cli/src/commands/connection-commands.ts index f1330118..f3c87709 100644 --- a/packages/cli/src/commands/connection-commands.ts +++ b/packages/cli/src/commands/connection-commands.ts @@ -1,7 +1,7 @@ import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'; import { collectOption, - type KloCliCommandContext, + type KtxCliCommandContext, parseBooleanStringOption, parseNonEmptyAssignmentOption, parseNonNegativeIntegerOption, @@ -10,9 +10,9 @@ import { resolveCommandProjectDir, } from '../cli-program.js'; import { connectionAddCommandSchema } from '../command-schemas.js'; -import type { KloConnectionArgs } from '../connection.js'; +import type { KtxConnectionArgs } from '../connection.js'; import { profileMark } from '../startup-profile.js'; -import type { KloConnectionMappingArgs } from './connection-mapping.js'; +import type { KtxConnectionMappingArgs } from './connection-mapping.js'; import { registerConnectionMetabaseCommands } from './connection-metabase-commands.js'; import { registerConnectionNotionCommands } from './connection-notion-commands.js'; @@ -42,24 +42,24 @@ function parseMappingFieldOption(value: string): 'databaseMappings' | 'connectio throw new InvalidArgumentError('must be databaseMappings or connectionMappings'); } -async function runConnectionArgs(context: KloCliCommandContext, args: KloConnectionArgs): Promise { - const runner = context.deps.connection ?? (await import('../connection.js')).runKloConnection; +async function runConnectionArgs(context: KtxCliCommandContext, args: KtxConnectionArgs): Promise { + const runner = context.deps.connection ?? (await import('../connection.js')).runKtxConnection; context.setExitCode(await runner(args, context.io)); } -async function runMappingArgs(context: KloCliCommandContext, args: KloConnectionMappingArgs): Promise { - const { runKloConnectionMapping } = await import('./connection-mapping.js'); - context.setExitCode(await runKloConnectionMapping(args, context.io)); +async function runMappingArgs(context: KtxCliCommandContext, args: KtxConnectionMappingArgs): Promise { + const { runKtxConnectionMapping } = await import('./connection-mapping.js'); + context.setExitCode(await runKtxConnectionMapping(args, context.io)); } -export function registerConnectionCommands(program: Command, context: KloCliCommandContext, commandName = 'connection'): void { +export function registerConnectionCommands(program: Command, context: KtxCliCommandContext, commandName = 'connection'): void { const connection = program .command(commandName) .description('Add, list, test, and map data sources') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the nearest klo.yaml or current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the nearest ktx.yaml or current working directory.\n', ); connection.hook('preAction', (_thisCommand, actionCommand) => { context.writeDebug?.(commandName, actionCommand); @@ -75,7 +75,7 @@ export function registerConnectionCommands(program: Command, context: KloCliComm connection .command('test') .description('Test a configured connection') - .argument('', 'KLO connection id') + .argument('', 'KTX connection id') .action(async (connectionId: string, _options: unknown, command) => { await runConnectionArgs(context, { command: 'test', @@ -88,12 +88,12 @@ export function registerConnectionCommands(program: Command, context: KloCliComm .command('add') .description('Add or replace a configured connection') .argument('', 'Connection driver') - .argument('', 'KLO connection id') + .argument('', 'KTX connection id') .option('--url ', 'Connection URL, env:NAME, or file:/path reference') .option('--schema ', 'Schema to include; repeatable', collectOption, []) .option('--readonly', 'Mark the connection as read-only', false) .option('--force', 'Replace an existing connection', false) - .option('--allow-literal-credentials', 'Allow writing a literal credential URL to klo.yaml', false) + .option('--allow-literal-credentials', 'Allow writing a literal credential URL to ktx.yaml', false) .addOption(new Option('--token-env ', 'Environment variable containing Notion auth token').conflicts('tokenFile')) .addOption(new Option('--token-file ', 'File containing Notion auth token').conflicts('tokenEnv')) .addOption( @@ -155,8 +155,8 @@ export function registerConnectionCommands(program: Command, context: KloCliComm connection .command('remove') - .description('Remove a configured connection from klo.yaml') - .argument('', 'KLO connection id') + .description('Remove a configured connection from ktx.yaml') + .argument('', 'KTX connection id') .option('--force', 'Remove without prompting', false) .option('--no-input', 'Disable interactive terminal input') .action(async (connectionId: string, options: { force?: boolean; input?: boolean }, command) => { @@ -188,14 +188,14 @@ export function registerConnectionCommands(program: Command, context: KloCliComm registerConnectionNotionCommands(connection, context); } -export function registerConnectionMappingCommands(connection: Command, context: KloCliCommandContext): void { +export function registerConnectionMappingCommands(connection: Command, context: KtxCliCommandContext): void { const mapping = connection .command('mapping') .description('Manage Metabase warehouse mappings') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ); mapping diff --git a/packages/cli/src/commands/connection-mapping.test.ts b/packages/cli/src/commands/connection-mapping.test.ts index 1ef5d1c5..7d76cc9d 100644 --- a/packages/cli/src/commands/connection-mapping.test.ts +++ b/packages/cli/src/commands/connection-mapping.test.ts @@ -1,10 +1,10 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { LocalMetabaseSourceStateReader } from '@klo/context/ingest'; -import { initKloProject, loadKloProject, serializeKloProjectConfig } from '@klo/context/project'; +import { LocalMetabaseSourceStateReader } from '@ktx/context/ingest'; +import { initKtxProject, loadKtxProject, serializeKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloConnectionMapping } from './connection-mapping.js'; +import { runKtxConnectionMapping } from './connection-mapping.js'; function makeIo() { let stdout = ''; @@ -27,18 +27,18 @@ function makeIo() { }; } -describe('runKloConnectionMapping', () => { +describe('runKtxConnectionMapping', () => { let tempDir: string; let projectDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-metabase-mapping-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-metabase-mapping-')); projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'mapping' }); - const project = await loadKloProject({ projectDir }); + await initKtxProject({ projectDir, projectName: 'mapping' }); + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig({ + 'ktx.yaml', + serializeKtxProjectConfig({ ...project.config, connections: { 'prod-metabase': { @@ -53,22 +53,22 @@ describe('runKloConnectionMapping', () => { }, }, }), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed Metabase mapping test connections', ); }); async function replaceConnections(connections: Record) { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig({ + 'ktx.yaml', + serializeKtxProjectConfig({ ...project.config, connections, }), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Replace mapping test connections', ); } @@ -80,7 +80,7 @@ describe('runKloConnectionMapping', () => { it('sets, lists, disables, and clears local Metabase mappings', async () => { const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'set', projectDir, @@ -95,13 +95,13 @@ describe('runKloConnectionMapping', () => { const listIo = makeIo(); await expect( - runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, listIo.io), + runKtxConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, listIo.io), ).resolves.toBe(0); expect(listIo.stdout()).toContain('1 -> prod-warehouse'); expect(listIo.stdout()).toContain('unhydrated'); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'set-sync-enabled', projectDir, @@ -114,7 +114,7 @@ describe('runKloConnectionMapping', () => { ).resolves.toBe(0); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'clear', projectDir, @@ -127,12 +127,12 @@ describe('runKloConnectionMapping', () => { }); it('lists Metabase yaml mapping bootstrap rows before any SQLite command writes', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-yaml-mapping-')); - await initKloProject({ projectDir, projectName: 'yaml-mapping' }); - const project = await loadKloProject({ projectDir }); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-cli-yaml-mapping-')); + await initKtxProject({ projectDir, projectName: 'yaml-mapping' }); + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig({ + 'ktx.yaml', + serializeKtxProjectConfig({ ...project.config, connections: { 'prod-metabase': { @@ -145,21 +145,21 @@ describe('runKloConnectionMapping', () => { 'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' }, }, }), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed yaml mappings', ); const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, io.io, ), ).resolves.toBe(0); expect(io.stdout()).toContain('1 -> prod-warehouse'); - expect(io.stdout()).toContain('source: klo.yaml'); + expect(io.stdout()).toContain('source: ktx.yaml'); }); it('refreshes Metabase discovery metadata through the injected runtime client', async () => { @@ -178,7 +178,7 @@ describe('runKloConnectionMapping', () => { const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'refresh', projectDir, @@ -194,7 +194,7 @@ describe('runKloConnectionMapping', () => { expect(io.stdout()).toContain('Discovery: 1 database'); expect(client.cleanup).toHaveBeenCalledTimes(1); - const store = new LocalMetabaseSourceStateReader({ dbPath: join(projectDir, '.klo', 'db.sqlite') }); + const store = new LocalMetabaseSourceStateReader({ dbPath: join(projectDir, '.ktx', 'db.sqlite') }); await expect(store.listDatabaseMappings('prod-metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 1, metabaseDatabaseName: 'Analytics', source: 'refresh' }, ]); @@ -215,7 +215,7 @@ describe('runKloConnectionMapping', () => { const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'set', projectDir, @@ -228,7 +228,7 @@ describe('runKloConnectionMapping', () => { ), ).resolves.toBe(0); await expect( - runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-looker', json: false }, io.io), + runKtxConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-looker', json: false }, io.io), ).resolves.toBe(0); expect(io.stdout()).toContain('analytics -> prod-warehouse'); @@ -242,7 +242,7 @@ describe('runKloConnectionMapping', () => { const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'set', projectDir, @@ -273,7 +273,7 @@ describe('runKloConnectionMapping', () => { const io = makeIo(); await expect( - runKloConnectionMapping( + runKtxConnectionMapping( { command: 'refresh', projectDir, connectionId: 'prod-looker', autoAccept: true }, io.io, { @@ -298,12 +298,12 @@ describe('runKloConnectionMapping', () => { }); it('validates Looker mappings through the canonical local warehouse descriptor', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-descriptor-validation-')); - await initKloProject({ projectDir, projectName: 'descriptor-validation' }); - const project = await loadKloProject({ projectDir }); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-cli-descriptor-validation-')); + await initKtxProject({ projectDir, projectName: 'descriptor-validation' }); + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig({ + 'ktx.yaml', + serializeKtxProjectConfig({ ...project.config, connections: { 'prod-looker': { @@ -313,14 +313,14 @@ describe('runKloConnectionMapping', () => { 'prod-warehouse': { driver: 'postgresql', url: 'postgresql://readonly@db.test/analytics' }, }, }), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed descriptor validation', ); const io = makeIo(); await expect( - runKloConnectionMapping({ command: 'validate', projectDir, connectionId: 'prod-looker' }, io.io), + runKtxConnectionMapping({ command: 'validate', projectDir, connectionId: 'prod-looker' }, io.io), ).resolves.toBe(0); expect(io.stdout()).toContain('Mapping validation passed: prod-looker'); diff --git a/packages/cli/src/commands/connection-mapping.ts b/packages/cli/src/commands/connection-mapping.ts index dcf84a7d..b35bf40f 100644 --- a/packages/cli/src/commands/connection-mapping.ts +++ b/packages/cli/src/commands/connection-mapping.ts @@ -1,5 +1,5 @@ import { readFile } from 'node:fs/promises'; -import { localConnectionToWarehouseDescriptor } from '@klo/context/connections'; +import { localConnectionToWarehouseDescriptor } from '@ktx/context/connections'; import { DEFAULT_METABASE_CLIENT_CONFIG, DefaultLookerConnectionClientFactory, @@ -12,20 +12,20 @@ import { discoverMetabaseDatabases, lookerCredentialsFromLocalConnection, metabaseRuntimeConfigFromLocalConnection, - seedLocalMappingStateFromKloYaml, + seedLocalMappingStateFromKtxYaml, validateLookerMappings, validateMappingPhysicalMatch, type LookerMappingClient, type MetabaseRuntimeClient, type MetabaseSyncMode, -} from '@klo/context/ingest'; -import { type KloLocalProject, kloLocalStateDbPath, loadKloProject } from '@klo/context/project'; -import type { KloCliIo } from '../index.js'; +} from '@ktx/context/ingest'; +import { type KtxLocalProject, ktxLocalStateDbPath, loadKtxProject } from '@ktx/context/project'; +import type { KtxCliIo } from '../index.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/connection-mapping'); -export type KloConnectionMappingArgs = +export type KtxConnectionMappingArgs = | { command: 'list'; projectDir: string; connectionId: string; json: boolean } | { command: 'set'; @@ -57,13 +57,13 @@ export type KloConnectionMappingArgs = | { command: 'validate'; projectDir: string; connectionId: string } | { command: 'clear'; projectDir: string; connectionId: string; metabaseDatabaseId?: number; mappingKey?: string }; -interface KloConnectionMappingDeps { +interface KtxConnectionMappingDeps { createMetabaseClient?: ( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ) => Promise>; createLookerClient?: ( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ) => Promise & { cleanup?(): Promise }>; } @@ -85,7 +85,7 @@ function parseId(value: string, label: string): number { } async function createDefaultMetabaseClient( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ): Promise> { const factory = new DefaultMetabaseConnectionClientFactory( @@ -97,7 +97,7 @@ async function createDefaultMetabaseClient( } async function createDefaultLookerClient( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ): Promise & { cleanup?(): Promise }> { const factory = new DefaultLookerConnectionClientFactory({ @@ -110,30 +110,30 @@ async function createDefaultLookerClient( }; } -function isLookerConnection(project: KloLocalProject, connectionId: string): boolean { +function isLookerConnection(project: KtxLocalProject, connectionId: string): boolean { return String(project.config.connections[connectionId]?.driver ?? '').toLowerCase() === 'looker'; } -function assertLookerConnection(project: KloLocalProject, connectionId: string): void { +function assertLookerConnection(project: KtxLocalProject, connectionId: string): void { if (!isLookerConnection(project, connectionId)) { throw new Error(`Connection "${connectionId}" is not a Looker connection`); } } -function assertMetabaseConnection(project: KloLocalProject, connectionId: string): void { +function assertMetabaseConnection(project: KtxLocalProject, connectionId: string): void { const connection = project.config.connections[connectionId]; if (!connection || String(connection.driver).toLowerCase() !== 'metabase') { throw new Error(`Connection "${connectionId}" is not a Metabase connection`); } } -function assertTargetConnection(project: KloLocalProject, connectionId: string): void { +function assertTargetConnection(project: KtxLocalProject, connectionId: string): void { if (!project.config.connections[connectionId]) { throw new Error(`Target connection "${connectionId}" does not exist`); } } -function targetPhysicalInfo(project: KloLocalProject, connectionId: string) { +function targetPhysicalInfo(project: KtxLocalProject, connectionId: string) { const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]); if (!descriptor) { return { connection_type: 'UNKNOWN' }; @@ -160,22 +160,22 @@ function renderMapping( } function renderLookerMapping(row: Awaited>[number]): string { - const target = row.kloConnectionId ?? '[unmapped]'; + const target = row.ktxConnectionId ?? '[unmapped]'; const metadata = [row.lookerDialect, row.lookerHost, row.lookerDatabase].filter(Boolean).join(', '); return `${row.lookerConnectionName} -> ${target}${metadata ? ` (${metadata}, source: ${row.source})` : ` (source: ${row.source})`}`; } -export async function runKloConnectionMapping( - args: KloConnectionMappingArgs, - io: KloCliIo = process, - deps: KloConnectionMappingDeps = {}, +export async function runKtxConnectionMapping( + args: KtxConnectionMappingArgs, + io: KtxCliIo = process, + deps: KtxConnectionMappingDeps = {}, ): Promise { try { - const project = await loadKloProject({ projectDir: args.projectDir }); - await seedLocalMappingStateFromKloYaml(project, args.connectionId); + const project = await loadKtxProject({ projectDir: args.projectDir }); + await seedLocalMappingStateFromKtxYaml(project, args.connectionId); if (isLookerConnection(project, args.connectionId)) { assertLookerConnection(project, args.connectionId); - const store = new LocalLookerRuntimeStore({ dbPath: kloLocalStateDbPath(project) }); + const store = new LocalLookerRuntimeStore({ dbPath: ktxLocalStateDbPath(project) }); if (args.command === 'list') { const rows = await store.listConnectionMappings(args.connectionId); @@ -191,7 +191,7 @@ export async function runKloConnectionMapping( await store.upsertConnectionMapping({ lookerConnectionId: args.connectionId, lookerConnectionName: args.key, - kloConnectionId: args.value, + ktxConnectionId: args.value, source: 'cli', }); io.stdout.write(`Set connectionMappings.${args.key} = ${args.value}\n`); @@ -219,13 +219,13 @@ export async function runKloConnectionMapping( } if (args.command === 'validate') { - const knownKloConnectionIds = new Set(Object.keys(project.config.connections)); + const knownKtxConnectionIds = new Set(Object.keys(project.config.connections)); const knownConnectionTypes = new Map( Object.entries(project.config.connections).map(([id, _config]) => [id, targetPhysicalInfo(project, id).connection_type]), ); const validation = validateLookerMappings({ mappings: await store.readMappings(args.connectionId), - knownKloConnectionIds, + knownKtxConnectionIds, knownConnectionTypes, }); if (!validation.ok) { @@ -255,7 +255,7 @@ export async function runKloConnectionMapping( } assertMetabaseConnection(project, args.connectionId); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) }); if (args.command === 'list') { const rows = await store.listDatabaseMappings(args.connectionId); diff --git a/packages/cli/src/commands/connection-metabase-commands.ts b/packages/cli/src/commands/connection-metabase-commands.ts index 236ef780..1a07be3a 100644 --- a/packages/cli/src/commands/connection-metabase-commands.ts +++ b/packages/cli/src/commands/connection-metabase-commands.ts @@ -1,17 +1,17 @@ import { type Command, Option } from '@commander-js/extra-typings'; import { - type KloCliCommandContext, + type KtxCliCommandContext, parseNonEmptyAssignmentOption, parsePositiveIntegerOption, parseSafeConnectionIdOption, resolveCommandProjectDir, } from '../cli-program.js'; import { - type KloConnectionMetabaseSetupArgs, + type KtxConnectionMetabaseSetupArgs, type MetabaseSetupMappingAssignment, type MetabaseSetupSyncMode, - runKloConnectionMetabaseSetup, + runKtxConnectionMetabaseSetup, } from './connection-metabase-setup.js'; const SYNC_MODE_CHOICES = ['ALL', 'ONLY', 'EXCEPT'] as const satisfies readonly MetabaseSetupSyncMode[]; @@ -51,21 +51,21 @@ function collectMappingOption( } async function runMetabaseSetupArgs( - context: KloCliCommandContext, - args: KloConnectionMetabaseSetupArgs, + context: KtxCliCommandContext, + args: KtxConnectionMetabaseSetupArgs, ): Promise { - const runner = context.deps.connectionMetabaseSetup ?? runKloConnectionMetabaseSetup; + const runner = context.deps.connectionMetabaseSetup ?? runKtxConnectionMetabaseSetup; context.setExitCode(await runner(args, context.io)); } -export function registerConnectionMetabaseCommands(connection: Command, context: KloCliCommandContext): void { +export function registerConnectionMetabaseCommands(connection: Command, context: KtxCliCommandContext): void { const metabase = connection .command('metabase') .description('Configure Metabase connections') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ); metabase.action(() => { @@ -76,7 +76,7 @@ export function registerConnectionMetabaseCommands(connection: Command, context: metabase .command('setup') .description('Guided setup for a Metabase connection') - .option('--id ', 'KLO connection id to write', parseSafeConnectionIdOption) + .option('--id ', 'KTX connection id to write', parseSafeConnectionIdOption) .option('--url ', 'Metabase API URL') .addOption(new Option('--api-key ', 'Metabase API key').conflicts('mintApiKey')) .option('--mint-api-key', 'Mint a Metabase API key with credentials', false) @@ -85,10 +85,10 @@ export function registerConnectionMetabaseCommands(connection: Command, context: .addHelpText( 'after', '\nGuided equivalent of:\n' + - ' klo connection mapping refresh --auto-accept\n' + - ' klo connection mapping set databaseMappings =\n' + - ' klo connection mapping set-sync-enabled --enabled true\n' + - ' klo ingest \n', + ' ktx connection mapping refresh --auto-accept\n' + + ' ktx connection mapping set databaseMappings =\n' + + ' ktx connection mapping set-sync-enabled --enabled true\n' + + ' ktx ingest \n', ) .option( '--map ', diff --git a/packages/cli/src/commands/connection-metabase-setup.test.ts b/packages/cli/src/commands/connection-metabase-setup.test.ts index e1dd1063..cd94565a 100644 --- a/packages/cli/src/commands/connection-metabase-setup.test.ts +++ b/packages/cli/src/commands/connection-metabase-setup.test.ts @@ -1,11 +1,11 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { LocalMetabaseSourceStateReader } from '@klo/context/ingest'; -import { initKloProject, kloLocalStateDbPath, loadKloProject, serializeKloProjectConfig } from '@klo/context/project'; +import { LocalMetabaseSourceStateReader } from '@ktx/context/ingest'; +import { initKtxProject, ktxLocalStateDbPath, loadKtxProject, serializeKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloConnectionMetabaseSetup } from './connection-metabase-setup.js'; +import { runKtxConnectionMetabaseSetup } from './connection-metabase-setup.js'; const CANCEL_PROMPT = Symbol('cancel'); @@ -135,7 +135,7 @@ function makeIo(options: { isTTY?: boolean; stdinIsTTY?: boolean } = {}) { }; } -describe('runKloConnectionMetabaseSetup', () => { +describe('runKtxConnectionMetabaseSetup', () => { const fakeMetabaseCredential = 'mb_example'; const existingMetabaseCredential = 'mb_existing'; const fakeAdminCredential = 'pw'; @@ -144,9 +144,9 @@ describe('runKloConnectionMetabaseSetup', () => { let projectDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-metabase-setup-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-metabase-setup-')); projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'metabase-setup' }); + await initKtxProject({ projectDir, projectName: 'metabase-setup' }); }); afterEach(async () => { @@ -154,15 +154,15 @@ describe('runKloConnectionMetabaseSetup', () => { }); async function writeConnections(connections: Record) { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig({ + 'ktx.yaml', + serializeKtxProjectConfig({ ...project.config, connections, }), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed Metabase setup test connections', ); } @@ -208,7 +208,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -230,17 +230,17 @@ describe('runKloConnectionMetabaseSetup', () => { expect(io.stdout()).toContain('Connection: metabase'); expect(io.stdout()).toContain('Discovered 1 database'); - expect(io.stdout()).toContain(`klo ingest metabase --project-dir ${projectDir}`); + expect(io.stdout()).toContain(`ktx ingest metabase --project-dir ${projectDir}`); expect(io.stdout()).not.toContain('mb_example'); expect(io.stderr()).not.toContain('mb_example'); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('driver: metabase'); expect(config).toContain('api_url: http://metabase.example.test:3000'); expect(config).toContain('api_key: mb_example'); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, @@ -275,7 +275,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -295,8 +295,8 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true }, ]); @@ -314,7 +314,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -350,7 +350,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -370,8 +370,8 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true }, ]); @@ -384,7 +384,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -412,7 +412,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -440,7 +440,7 @@ describe('runKloConnectionMetabaseSetup', () => { const missingUsernameIo = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -462,7 +462,7 @@ describe('runKloConnectionMetabaseSetup', () => { const missingPasswordIo = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -500,7 +500,7 @@ describe('runKloConnectionMetabaseSetup', () => { const mintingIo = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -537,7 +537,7 @@ describe('runKloConnectionMetabaseSetup', () => { expect(mintingIo.stdout()).not.toContain(fakeAdminCredential); expect(mintingIo.stderr()).not.toContain(fakeAdminCredential); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('driver: metabase'); expect(config).toContain('api_url: http://metabase.example.test:3000'); expect(config).toContain(`api_key: ${mintedMetabaseCredential}`); @@ -548,7 +548,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -590,7 +590,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -640,7 +640,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -660,8 +660,8 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 1, targetConnectionId: 'orbit', syncEnabled: true }, { metabaseDatabaseId: 2, targetConnectionId: null, syncEnabled: false }, @@ -676,7 +676,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -712,7 +712,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -759,7 +759,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -782,12 +782,12 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(1); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('driver: metabase'); - expect(io.stderr()).toContain(`klo ingest metabase --project-dir ${projectDir}`); + expect(io.stderr()).toContain(`ktx ingest metabase --project-dir ${projectDir}`); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, targetConnectionId: 'orbit' }, ]); @@ -810,7 +810,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -857,7 +857,7 @@ describe('runKloConnectionMetabaseSetup', () => { const interactiveMetabaseCredential = 'mb_interactive_fixture'; await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -882,13 +882,13 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('driver: metabase'); expect(config).toContain('api_url: http://metabase.example.test:3000'); expect(config).toContain(`api_key: ${interactiveMetabaseCredential}`); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, @@ -931,7 +931,7 @@ describe('runKloConnectionMetabaseSetup', () => { const events: string[] = []; await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -958,8 +958,8 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([ { metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true }, { metabaseDatabaseId: 3, targetConnectionId: 'warehouse2', syncEnabled: false }, @@ -997,7 +997,7 @@ describe('runKloConnectionMetabaseSetup', () => { const events: string[] = []; await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -1023,7 +1023,7 @@ describe('runKloConnectionMetabaseSetup', () => { ), ).resolves.toBe(0); - expect(events).toContain('intro:KLO Metabase setup'); + expect(events).toContain('intro:KTX Metabase setup'); expect(events.some((event) => event.startsWith('spinner.start:Testing Metabase connection'))).toBe(true); expect(events.some((event) => event.startsWith('spinner.stop:Metabase reachable'))).toBe(true); expect(events.some((event) => event.startsWith('spinner.start:Discovering Metabase databases'))).toBe(true); @@ -1053,7 +1053,7 @@ describe('runKloConnectionMetabaseSetup', () => { const io = makeIo(); await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -1081,7 +1081,7 @@ describe('runKloConnectionMetabaseSetup', () => { }, }); - const beforeConfig = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const beforeConfig = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); const metabaseClient = makeMetabaseClient({ testConnectionSuccess: true, databases: [ @@ -1098,7 +1098,7 @@ describe('runKloConnectionMetabaseSetup', () => { const cancelMetabaseCredential = 'mb_cancel_fixture'; await expect( - runKloConnectionMetabaseSetup( + runKtxConnectionMetabaseSetup( { command: 'setup', projectDir, @@ -1126,11 +1126,11 @@ describe('runKloConnectionMetabaseSetup', () => { expect(io.stderr()).toContain('Setup cancelled.'); expect(io.stderr()).not.toContain(cancelMetabaseCredential); - const afterConfig = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const afterConfig = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(afterConfig).toBe(beforeConfig); - const updatedProject = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await expect(store.listDatabaseMappings('metabase')).resolves.toEqual([]); }); }); diff --git a/packages/cli/src/commands/connection-metabase-setup.ts b/packages/cli/src/commands/connection-metabase-setup.ts index 62f1d086..9b5e21d7 100644 --- a/packages/cli/src/commands/connection-metabase-setup.ts +++ b/packages/cli/src/commands/connection-metabase-setup.ts @@ -12,7 +12,7 @@ import { select, text, } from '@clack/prompts'; -import { localConnectionToWarehouseDescriptor } from '@klo/context/connections'; +import { localConnectionToWarehouseDescriptor } from '@ktx/context/connections'; import { DEFAULT_METABASE_CLIENT_CONFIG, DefaultMetabaseConnectionClientFactory, @@ -23,21 +23,21 @@ import { type MetabaseSyncMode, metabaseRuntimeConfigFromLocalConnection, validateMappingPhysicalMatch, -} from '@klo/context/ingest'; +} from '@ktx/context/ingest'; import { - type KloLocalProject, - type KloProjectConnectionConfig, - kloLocalStateDbPath, - loadKloProject, - serializeKloProjectConfig, -} from '@klo/context/project'; + type KtxLocalProject, + type KtxProjectConnectionConfig, + ktxLocalStateDbPath, + loadKtxProject, + serializeKtxProjectConfig, +} from '@ktx/context/project'; -import { createClackSpinner, type KloCliSpinner } from '../clack.js'; -import type { KloCliIo } from '../cli-runtime.js'; +import { createClackSpinner, type KtxCliSpinner } from '../clack.js'; +import type { KtxCliIo } from '../cli-runtime.js'; import { withMenuOptionsSpacing, withMultiselectNavigation } from '../prompt-navigation.js'; -import { type KloPublicIngestArgs, runKloPublicIngest } from '../public-ingest.js'; +import { type KtxPublicIngestArgs, runKtxPublicIngest } from '../public-ingest.js'; -export type KloMetabaseSetupInputMode = 'auto' | 'disabled'; +export type KtxMetabaseSetupInputMode = 'auto' | 'disabled'; export type MetabaseSetupSyncMode = MetabaseSyncMode; @@ -56,7 +56,7 @@ export interface MetabaseSetupPromptAdapter { outro(message?: string): void; note(message: string, title: string): void; log: MetabaseSetupLogger; - spinner(): KloCliSpinner; + spinner(): KtxCliSpinner; select(options: { message: string; options: Array> }): Promise; multiselect(options: { message: string; @@ -71,7 +71,7 @@ export interface MetabaseSetupPromptAdapter { cancel(message: string): void; } -type KloMetabaseSetupInteractiveIo = KloCliIo & { +type KtxMetabaseSetupInteractiveIo = KtxCliIo & { stdin?: { isTTY?: boolean }; }; @@ -86,9 +86,9 @@ export interface MintMetabaseApiKeyArgs { password: string; } -export type MintMetabaseApiKey = (args: MintMetabaseApiKeyArgs, io: KloCliIo) => Promise; +export type MintMetabaseApiKey = (args: MintMetabaseApiKeyArgs, io: KtxCliIo) => Promise; -export interface KloConnectionMetabaseSetupArgs { +export interface KtxConnectionMetabaseSetupArgs { command: 'setup'; projectDir: string; connectionId?: string; @@ -102,20 +102,20 @@ export interface KloConnectionMetabaseSetupArgs { syncMode: MetabaseSetupSyncMode; runIngest: boolean; yes: boolean; - inputMode: KloMetabaseSetupInputMode; + inputMode: KtxMetabaseSetupInputMode; } -export interface KloConnectionMetabaseSetupDeps { +export interface KtxConnectionMetabaseSetupDeps { createMetabaseClient?: ( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ) => Promise>; mintMetabaseApiKey?: MintMetabaseApiKey; prompts?: MetabaseSetupPromptAdapter; - runPublicIngest?: (args: Extract, io: KloCliIo) => Promise; + runPublicIngest?: (args: Extract, io: KtxCliIo) => Promise; } -function isMetabaseConnection(connection: KloProjectConnectionConfig | undefined): boolean { +function isMetabaseConnection(connection: KtxProjectConnectionConfig | undefined): boolean { return ( String(connection?.driver ?? '') .trim() @@ -131,22 +131,22 @@ function uniqueSorted(values: number[]): number[] { return [...new Set(values)].sort((a, b) => a - b); } -function resolveMetabaseUrl(connection: KloProjectConnectionConfig | undefined): string | undefined { +function resolveMetabaseUrl(connection: KtxProjectConnectionConfig | undefined): string | undefined { return stringField(connection?.api_url) ?? stringField(connection?.apiUrl) ?? stringField(connection?.url); } -function resolveLiteralMetabaseApiKey(connection: KloProjectConnectionConfig | undefined): string | undefined { +function resolveLiteralMetabaseApiKey(connection: KtxProjectConnectionConfig | undefined): string | undefined { return stringField(connection?.api_key) ?? stringField(connection?.apiKey); } -function listMetabaseConnectionIds(project: KloLocalProject): string[] { +function listMetabaseConnectionIds(project: KtxLocalProject): string[] { return Object.entries(project.config.connections) .filter(([_connectionId, connection]) => isMetabaseConnection(connection)) .map(([connectionId]) => connectionId) .sort(); } -function listWarehouseConnectionIds(project: KloLocalProject): string[] { +function listWarehouseConnectionIds(project: KtxLocalProject): string[] { return Object.entries(project.config.connections) .filter(([connectionId, connection]) => localConnectionToWarehouseDescriptor(connectionId, connection) != null) .map(([connectionId]) => connectionId) @@ -165,7 +165,7 @@ function redactSecrets(message: string, secrets: string[]): string { } async function createDefaultMetabaseClient( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ): Promise> { const factory = new DefaultMetabaseConnectionClientFactory( @@ -192,7 +192,7 @@ async function defaultMintMetabaseApiKey(args: MintMetabaseApiKeyArgs): Promise< const mintedKey = await sessionClient.createApiKey({ groupId: adminGroup.id, - name: `KLO CLI ${new Date().toISOString()}`, + name: `KTX CLI ${new Date().toISOString()}`, }); const trimmedKey = stringField(mintedKey); if (!trimmedKey) { @@ -237,7 +237,7 @@ export function createClackMetabaseSetupPromptAdapter(): MetabaseSetupPromptAdap log.error(message); }, }, - spinner(): KloCliSpinner { + spinner(): KtxCliSpinner { return createClackSpinner(); }, async select(options: { @@ -271,8 +271,8 @@ export function createClackMetabaseSetupPromptAdapter(): MetabaseSetupPromptAdap } function isInteractiveMetabaseSetupIo( - args: Pick, - io: KloMetabaseSetupInteractiveIo, + args: Pick, + io: KtxMetabaseSetupInteractiveIo, ): boolean { return args.inputMode !== 'disabled' && io.stdin?.isTTY === true && io.stdout.isTTY === true; } @@ -295,7 +295,7 @@ function normalizeDiscoveredDatabases(databases: MetabaseDatabase[]): Array<{ })); } -function targetPhysicalInfo(project: KloLocalProject, connectionId: string) { +function targetPhysicalInfo(project: KtxLocalProject, connectionId: string) { const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]); if (!descriptor) { return { connection_type: 'UNKNOWN' }; @@ -338,23 +338,23 @@ function noteMetabaseSetupSummary(options: { ); } -export async function runKloConnectionMetabaseSetup( - args: KloConnectionMetabaseSetupArgs, - io: KloCliIo, - deps: KloConnectionMetabaseSetupDeps = {}, +export async function runKtxConnectionMetabaseSetup( + args: KtxConnectionMetabaseSetupArgs, + io: KtxCliIo, + deps: KtxConnectionMetabaseSetupDeps = {}, ): Promise { let apiKeyForRedaction = args.apiKey; let passwordForRedaction = args.metabasePassword; - const interactiveIo = io as KloMetabaseSetupInteractiveIo; + const interactiveIo = io as KtxMetabaseSetupInteractiveIo; const isInteractive = isInteractiveMetabaseSetupIo(args, interactiveIo); const prompts = deps.prompts ?? (isInteractive ? createClackMetabaseSetupPromptAdapter() : undefined); try { if (isInteractive && prompts) { - prompts.intro('KLO Metabase setup'); + prompts.intro('KTX Metabase setup'); } - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); const existingMetabaseConnectionIds = listMetabaseConnectionIds(project); let connectionId: string; @@ -491,7 +491,7 @@ export async function runKloConnectionMetabaseSetup( throw new Error('Metabase API key is required (use --api-key)'); } - const transientConnectionConfig: KloProjectConnectionConfig = { + const transientConnectionConfig: KtxProjectConnectionConfig = { ...(existingConnection ?? {}), driver: 'metabase', api_url: url, @@ -504,7 +504,7 @@ export async function runKloConnectionMetabaseSetup( [connectionId]: transientConnectionConfig, }, }; - const discoveryProject: KloLocalProject = { ...project, config: configWithTransient }; + const discoveryProject: KtxLocalProject = { ...project, config: configWithTransient }; for (const mapping of args.mappings) { if (!configWithTransient.connections[mapping.targetConnectionId]) { @@ -618,7 +618,7 @@ export async function runKloConnectionMetabaseSetup( } const targetConnectionId = await prompts.select({ - message: `Map Metabase database ${database.id} ("${database.name}") to which KLO connection?`, + message: `Map Metabase database ${database.id} ("${database.name}") to which KTX connection?`, options: warehouseConnectionIds.map((warehouseId) => ({ value: warehouseId, label: warehouseId })), }); resolvedMappings.push({ metabaseDatabaseId: databaseId, targetConnectionId }); @@ -641,7 +641,7 @@ export async function runKloConnectionMetabaseSetup( syncEnabledDatabaseIds: resolvedSyncEnabledDatabaseIds, }); const confirmed = await prompts.confirm({ - message: 'Write changes to klo.yaml and enable sync?', + message: 'Write changes to ktx.yaml and enable sync?', initialValue: true, }); if (!confirmed) { @@ -675,15 +675,15 @@ export async function runKloConnectionMetabaseSetup( } await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig(configWithTransient), - 'klo', - 'klo@example.com', + 'ktx.yaml', + serializeKtxProjectConfig(configWithTransient), + 'ktx', + 'ktx@example.com', `Setup Metabase connection ${connectionId}`, ); - const updatedProject = await loadKloProject({ projectDir: args.projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) }); + const updatedProject = await loadKtxProject({ projectDir: args.projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) }); await store.refreshDiscoveredDatabases({ connectionId, discovered }); @@ -716,7 +716,7 @@ export async function runKloConnectionMetabaseSetup( const unhydrated = await store.getUnhydratedSyncEnabledMappingIds(connectionId); if (unhydrated.length > 0) { io.stderr.write( - `Sync-enabled mappings are missing discovery metadata; run klo connection mapping refresh ${connectionId} --auto-accept\n`, + `Sync-enabled mappings are missing discovery metadata; run ktx connection mapping refresh ${connectionId} --auto-accept\n`, ); return 1; } @@ -743,10 +743,10 @@ export async function runKloConnectionMetabaseSetup( io.stdout.write(`Connection: ${connectionId}\n`); io.stdout.write(`Discovered ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}\n`); - io.stdout.write(`Next: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`); + io.stdout.write(`Next: ktx ingest ${connectionId} --project-dir ${args.projectDir}\n`); if (args.runIngest) { - const ingestRunner = deps.runPublicIngest ?? runKloPublicIngest; + const ingestRunner = deps.runPublicIngest ?? runKtxPublicIngest; const exitCode = await ingestRunner( { command: 'run', @@ -759,7 +759,7 @@ export async function runKloConnectionMetabaseSetup( io, ); if (exitCode !== 0) { - io.stderr.write(`Ingest failed; re-run: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`); + io.stderr.write(`Ingest failed; re-run: ktx ingest ${connectionId} --project-dir ${args.projectDir}\n`); return 1; } } diff --git a/packages/cli/src/commands/connection-notion-commands.ts b/packages/cli/src/commands/connection-notion-commands.ts index fc3ccb32..8f021ad9 100644 --- a/packages/cli/src/commands/connection-notion-commands.ts +++ b/packages/cli/src/commands/connection-notion-commands.ts @@ -1,6 +1,6 @@ import { type Command, InvalidArgumentError } from '@commander-js/extra-typings'; -import { collectOption, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; -import type { KloConnectionNotionArgs } from './connection-notion.js'; +import { collectOption, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; +import type { KtxConnectionNotionArgs } from './connection-notion.js'; interface NotionPickOptions { input?: boolean; @@ -36,7 +36,7 @@ function normalizeNotionPageId(value: string): string { return `${lower.slice(0, 8)}-${lower.slice(8, 12)}-${lower.slice(12, 16)}-${lower.slice(16, 20)}-${lower.slice(20)}`; } -function buildPickArgs(connectionId: string, projectDir: string, options: NotionPickOptions): KloConnectionNotionArgs { +function buildPickArgs(connectionId: string, projectDir: string, options: NotionPickOptions): KtxConnectionNotionArgs { if (options.input !== false) { return { command: 'pick', @@ -59,19 +59,19 @@ function buildPickArgs(connectionId: string, projectDir: string, options: Notion }; } -async function runConnectionNotionArgs(context: KloCliCommandContext, args: KloConnectionNotionArgs): Promise { - const runner = context.deps.connectionNotion ?? (await import('./connection-notion.js')).runKloConnectionNotion; +async function runConnectionNotionArgs(context: KtxCliCommandContext, args: KtxConnectionNotionArgs): Promise { + const runner = context.deps.connectionNotion ?? (await import('./connection-notion.js')).runKtxConnectionNotion; context.setExitCode(await runner(args, context.io)); } -export function registerConnectionNotionCommands(connect: Command, context: KloCliCommandContext): void { +export function registerConnectionNotionCommands(connect: Command, context: KtxCliCommandContext): void { const notion = connect .command('notion') .description('Configure Notion source selection') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ); notion.action(() => { diff --git a/packages/cli/src/commands/connection-notion-tui.tsx b/packages/cli/src/commands/connection-notion-tui.tsx index a624d0c7..c0a4746a 100644 --- a/packages/cli/src/commands/connection-notion-tui.tsx +++ b/packages/cli/src/commands/connection-notion-tui.tsx @@ -10,7 +10,7 @@ import { type PickerCommand, type PickerState, } from './connection-notion-tree.js'; -import type { KloCliIo } from '../index.js'; +import type { KtxCliIo } from '../index.js'; const COLOR_THEME = { text: 'white', @@ -28,9 +28,9 @@ const NO_COLOR_THEME = { type NotionPickerTheme = Record; -export interface NotionPickerTuiIo extends KloCliIo { +export interface NotionPickerTuiIo extends KtxCliIo { stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void }; - stdout: KloCliIo['stdout'] & { isTTY?: boolean; columns?: number; rows?: number }; + stdout: KtxCliIo['stdout'] & { isTTY?: boolean; columns?: number; rows?: number }; } interface InkKey { diff --git a/packages/cli/src/commands/connection-notion.test.ts b/packages/cli/src/commands/connection-notion.test.ts index 4024c40a..b03484d4 100644 --- a/packages/cli/src/commands/connection-notion.test.ts +++ b/packages/cli/src/commands/connection-notion.test.ts @@ -2,11 +2,11 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { - initKloProject, - loadKloProject, - serializeKloProjectConfig, - type KloProjectConfig, -} from '@klo/context/project'; + initKtxProject, + loadKtxProject, + serializeKtxProjectConfig, + type KtxProjectConfig, +} from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { applyNotionPickerWriteback, @@ -14,7 +14,7 @@ import { notionPickerPageFromSearchResult, normalizeNotionPageId, resolveNotionWorkspaceLabel, - runKloConnectionNotion, + runKtxConnectionNotion, type NotionPickerApi, type PickerRenderInput, type PickerRenderResult, @@ -91,24 +91,24 @@ describe('normalizeNotionPageId', () => { }); }); -describe('runKloConnectionNotion', () => { +describe('runKtxConnectionNotion', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-notion-pick-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-notion-pick-')); }); afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); }); - async function writeProjectConfig(projectDir: string, config: KloProjectConfig): Promise { - const project = await loadKloProject({ projectDir }); + async function writeProjectConfig(projectDir: string, config: KtxProjectConfig): Promise { + const project = await loadKtxProject({ projectDir }); await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig(config), - 'klo', - 'klo@example.com', + 'ktx.yaml', + serializeKtxProjectConfig(config), + 'ktx', + 'ktx@example.com', 'seed test config', ); } @@ -120,7 +120,7 @@ describe('runKloConnectionNotion', () => { }); await expect( - runKloConnectionNotion( + runKtxConnectionNotion( { command: 'pick', projectDir: '/tmp/project', @@ -138,7 +138,7 @@ describe('runKloConnectionNotion', () => { it('writes selected root_page_ids while preserving every other Notion connection field', async () => { const projectDir = join(tempDir, 'project'); - const initialized = await initKloProject({ projectDir, projectName: 'warehouse' }); + const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeProjectConfig(projectDir, { ...initialized.config, connections: { @@ -160,7 +160,7 @@ describe('runKloConnectionNotion', () => { const io = makeIo(); await expect( - runKloConnectionNotion( + runKtxConnectionNotion( { command: 'pick', projectDir, @@ -175,7 +175,7 @@ describe('runKloConnectionNotion', () => { ), ).resolves.toBe(0); - const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('crawl_mode: selected_roots'); expect(yaml).toContain('root_page_ids:'); expect(yaml).toContain('11111111-2222-3333-4444-555555555555'); @@ -193,7 +193,7 @@ describe('runKloConnectionNotion', () => { it('rejects empty writeback, missing connections, and non-Notion connections', async () => { const projectDir = join(tempDir, 'project'); - const initialized = await initKloProject({ projectDir, projectName: 'warehouse' }); + const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeProjectConfig(projectDir, { ...initialized.config, connections: { @@ -204,7 +204,7 @@ describe('runKloConnectionNotion', () => { }, }, }); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await expect(applyNotionPickerWriteback(project, 'warehouse', [])).rejects.toThrow( 'connection notion pick requires at least one root page id', @@ -297,7 +297,7 @@ describe('runKloConnectionNotion', () => { it('runs interactive discovery, warns about stale roots, renders the TUI, and saves selected roots', async () => { const projectDir = join(tempDir, 'project'); - const initialized = await initKloProject({ projectDir, projectName: 'warehouse' }); + const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeProjectConfig(projectDir, { ...initialized.config, connections: { @@ -330,7 +330,7 @@ describe('runKloConnectionNotion', () => { const io = makeIo(); await expect( - runKloConnectionNotion( + runKtxConnectionNotion( { command: 'pick', projectDir, @@ -346,7 +346,7 @@ describe('runKloConnectionNotion', () => { ), ).resolves.toBe(0); - const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('crawl_mode: selected_roots'); expect(yaml).toContain(PAGE_IDS.engineering); expect(yaml).not.toContain(PAGE_IDS.stale); @@ -357,7 +357,7 @@ describe('runKloConnectionNotion', () => { it('passes partial-discovery warnings into the TUI banner state', async () => { const projectDir = join(tempDir, 'project'); - const initialized = await initKloProject({ projectDir, projectName: 'warehouse' }); + const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeProjectConfig(projectDir, { ...initialized.config, connections: { @@ -394,7 +394,7 @@ describe('runKloConnectionNotion', () => { const io = makeIo(); await expect( - runKloConnectionNotion( + runKtxConnectionNotion( { command: 'pick', projectDir, @@ -422,7 +422,7 @@ describe('runKloConnectionNotion', () => { it('quits interactive mode without writing when the TUI returns quit', async () => { const projectDir = join(tempDir, 'project'); - const initialized = await initKloProject({ projectDir, projectName: 'warehouse' }); + const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeProjectConfig(projectDir, { ...initialized.config, connections: { @@ -440,11 +440,11 @@ describe('runKloConnectionNotion', () => { }, }, }); - const before = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const before = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); const io = makeIo(); await expect( - runKloConnectionNotion( + runKtxConnectionNotion( { command: 'pick', projectDir, @@ -460,7 +460,7 @@ describe('runKloConnectionNotion', () => { ), ).resolves.toBe(0); - await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toBe(before); + await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toBe(before); expect(io.stdout()).toContain('No changes saved.'); }); }); diff --git a/packages/cli/src/commands/connection-notion.ts b/packages/cli/src/commands/connection-notion.ts index 91c6a294..ecdf0f36 100644 --- a/packages/cli/src/commands/connection-notion.ts +++ b/packages/cli/src/commands/connection-notion.ts @@ -1,12 +1,12 @@ -import { parseNotionConnectionConfig, resolveNotionAuthToken } from '@klo/context/connections'; -import { type NotionApi, type NotionBotInfo, NotionClient } from '@klo/context/ingest'; +import { parseNotionConnectionConfig, resolveNotionAuthToken } from '@ktx/context/connections'; +import { type NotionApi, type NotionBotInfo, NotionClient } from '@ktx/context/ingest'; import { - type KloLocalProject, - type KloProjectConnectionConfig, - loadKloProject, - serializeKloProjectConfig, -} from '@klo/context/project'; -import type { KloCliIo } from '../index.js'; + type KtxLocalProject, + type KtxProjectConnectionConfig, + loadKtxProject, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import type { KtxCliIo } from '../index.js'; import { profileMark } from '../startup-profile.js'; import { buildInitialState, buildPickerTree, type NotionPickerPageInput } from './connection-notion-tree.js'; import { @@ -18,7 +18,7 @@ import { profileMark('module:commands/connection-notion'); -export type KloConnectionNotionArgs = +export type KtxConnectionNotionArgs = | { command: 'pick'; projectDir: string; @@ -36,9 +36,9 @@ export type KloConnectionNotionArgs = export type NotionPickerApi = Pick; export type { PickerRenderInput, PickerRenderResult }; -interface KloConnectionNotionDeps { +interface KtxConnectionNotionDeps { env?: Record; - loadProject?: typeof loadKloProject; + loadProject?: typeof loadKtxProject; createNotionApi?: (authToken: string) => NotionPickerApi; renderPicker?: (input: PickerRenderInput, io: NotionPickerTuiIo) => Promise; } @@ -168,7 +168,7 @@ export async function resolveNotionWorkspaceLabel(api: NotionPickerApi, connecti } } -function notionConnection(project: KloLocalProject, connectionId: string): KloProjectConnectionConfig { +function notionConnection(project: KtxLocalProject, connectionId: string): KtxProjectConnectionConfig { const connection = project.config.connections[connectionId]; if (!connection) { throw new Error(`Connection "${connectionId}" not found`); @@ -180,7 +180,7 @@ function notionConnection(project: KloLocalProject, connectionId: string): KloPr } export async function applyNotionPickerWriteback( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, rootPageIds: string[], ): Promise { @@ -202,22 +202,22 @@ export async function applyNotionPickerWriteback( }; await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig(nextConfig), - 'klo', - 'klo@example.com', + 'ktx.yaml', + serializeKtxProjectConfig(nextConfig), + 'ktx', + 'ktx@example.com', `Pick Notion roots: ${connectionId} (${rootPageIds.length} pages)`, ); } -export async function runKloConnectionNotion( - args: KloConnectionNotionArgs, - io: KloCliIo = process, - deps: KloConnectionNotionDeps = {}, +export async function runKtxConnectionNotion( + args: KtxConnectionNotionArgs, + io: KtxCliIo = process, + deps: KtxConnectionNotionDeps = {}, ): Promise { try { assertSafeConnectionId(args.connectionId); - const loadProject = deps.loadProject ?? loadKloProject; + const loadProject = deps.loadProject ?? loadKtxProject; if (args.mode === 'interactive') { const project = await loadProject({ projectDir: args.projectDir }); diff --git a/packages/cli/src/commands/demo-commands.ts b/packages/cli/src/commands/demo-commands.ts index 7fa16d24..fe9dd614 100644 --- a/packages/cli/src/commands/demo-commands.ts +++ b/packages/cli/src/commands/demo-commands.ts @@ -1,14 +1,14 @@ import { type Command, Option } from '@commander-js/extra-typings'; import { type CommandWithGlobalOptions, - type KloCliCommandContext, + type KtxCliCommandContext, resolveCommandProjectDirOverride, } from '../cli-program.js'; import { - type KloDemoArgs, - type KloDemoInputMode, - type KloDemoMode, - type KloDemoOutputMode, + type KtxDemoArgs, + type KtxDemoInputMode, + type KtxDemoMode, + type KtxDemoOutputMode, } from '../demo.js'; import { defaultDemoProjectDir } from '../demo-assets.js'; import { resolveProjectDir } from '../project-dir.js'; @@ -23,7 +23,7 @@ interface DemoOptions { projectDir?: string; } -function demoOutputMode(options: { plain?: boolean; json?: boolean }): KloDemoOutputMode { +function demoOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode { if (options.json === true) { return 'json'; } @@ -37,14 +37,14 @@ function demoDoctorOutputMode(options: { json?: boolean }): 'plain' | 'json' { return options.json === true ? 'json' : 'plain'; } -function demoInspectOutputMode(options: { plain?: boolean; json?: boolean }): KloDemoOutputMode { +function demoInspectOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode { if (options.json === true) { return 'json'; } return 'plain'; } -function demoInputMode(options: { input?: boolean }): { inputMode?: KloDemoInputMode } { +function demoInputMode(options: { input?: boolean }): { inputMode?: KtxDemoInputMode } { return options.input === false ? { inputMode: 'disabled' } : {}; } @@ -118,19 +118,19 @@ export function resolveDemoCommandOptions(command: { opts: () => T; optsWithG return { ...inherited, ...definedOptions(command.opts() as Record, inherited, command) } as T; } -async function runDemoArgs(context: KloCliCommandContext, args: KloDemoArgs): Promise { - const runner = context.deps.demo ?? (await import('../demo.js')).runKloDemo; +async function runDemoArgs(context: KtxCliCommandContext, args: KtxDemoArgs): Promise { + const runner = context.deps.demo ?? (await import('../demo.js')).runKtxDemo; context.setExitCode(await runner(args, context.io)); } export function registerDemoCommands( program: Command, - context: KloCliCommandContext, + context: KtxCliCommandContext, options: { description?: string } = {}, ): void { const demo = program .command('demo') - .description(options.description ?? 'Run the pre-seeded KLO demo or a full LLM-backed demo') + .description(options.description ?? 'Run the pre-seeded KTX demo or a full LLM-backed demo') .addOption( new Option('--mode ', 'Demo mode: seeded (default), replay, or full') .choices(['seeded', 'replay', 'full']) @@ -260,7 +260,7 @@ export function registerDemoCommands( .addOption(new Option('--plain', 'Print plain text output instead of the visual demo').conflicts('json')) .addOption(new Option('--json', 'Print JSON output').conflicts('plain')) .option('--no-input', 'Disable interactive terminal input') - .action(async (_options, command: { opts: () => { mode: KloDemoMode } & DemoOptions }) => { + .action(async (_options, command: { opts: () => { mode: KtxDemoMode } & DemoOptions }) => { const options = resolveDemoCommandOptions(command); await runDemoArgs(context, { command: 'ingest', diff --git a/packages/cli/src/commands/doctor-commands.ts b/packages/cli/src/commands/doctor-commands.ts index f2cb9a61..280f9d5a 100644 --- a/packages/cli/src/commands/doctor-commands.ts +++ b/packages/cli/src/commands/doctor-commands.ts @@ -1,6 +1,6 @@ import type { Command } from '@commander-js/extra-typings'; -import { type CommandWithGlobalOptions, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; -import type { KloDoctorArgs } from '../doctor.js'; +import { type CommandWithGlobalOptions, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; +import type { KtxDoctorArgs } from '../doctor.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/doctor-commands'); @@ -13,15 +13,15 @@ function inputMode(options: { input?: boolean }): { inputMode?: 'disabled' } { return options.input === false ? { inputMode: 'disabled' } : {}; } -async function runDoctorArgs(context: KloCliCommandContext, args: KloDoctorArgs): Promise { - const runner = context.deps.doctor ?? (await import('../doctor.js')).runKloDoctor; +async function runDoctorArgs(context: KtxCliCommandContext, args: KtxDoctorArgs): Promise { + const runner = context.deps.doctor ?? (await import('../doctor.js')).runKtxDoctor; context.setExitCode(await runner(args, context.io)); } -export function registerDoctorCommands(program: Command, context: KloCliCommandContext): void { +export function registerDoctorCommands(program: Command, context: KtxCliCommandContext): void { const doctor = program .command('doctor') - .description('Check KLO setup, project, and demo readiness') + .description('Check KTX setup, project, and demo readiness') .option('--json', 'Print JSON output', false) .option('--no-input', 'Disable interactive terminal input') .action(async (options: { json?: boolean; input?: boolean }, command) => { @@ -35,7 +35,7 @@ export function registerDoctorCommands(program: Command, context: KloCliCommandC doctor .command('setup') - .description('Check KLO install, build, and local runtime readiness') + .description('Check KTX install, build, and local runtime readiness') .option('--json', 'Print JSON output', false) .option('--no-input', 'Disable interactive terminal input') .action( diff --git a/packages/cli/src/commands/ingest-commands.ts b/packages/cli/src/commands/ingest-commands.ts index 90a50ab2..e546c4c4 100644 --- a/packages/cli/src/commands/ingest-commands.ts +++ b/packages/cli/src/commands/ingest-commands.ts @@ -1,22 +1,22 @@ import { resolve } from 'node:path'; import { type Command, Option } from '@commander-js/extra-typings'; -import { type KloCliCommandContext, type OutputModeOptions, resolveCommandProjectDir } from '../cli-program.js'; -import type { KloCliDeps, KloCliIo } from '../index.js'; -import type { KloIngestArgs, KloIngestOutputMode } from '../ingest.js'; +import { type KtxCliCommandContext, type OutputModeOptions, resolveCommandProjectDir } from '../cli-program.js'; +import type { KtxCliDeps, KtxCliIo } from '../index.js'; +import type { KtxIngestArgs, KtxIngestOutputMode } from '../ingest.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/ingest-commands'); interface IngestCommandOptions { runIngestWithProgress: ( - args: KloIngestArgs, - io: KloCliIo, - deps: KloCliDeps, - defaultRunIngest: (args: KloIngestArgs, io: KloCliIo) => Promise, + args: KtxIngestArgs, + io: KtxCliIo, + deps: KtxCliDeps, + defaultRunIngest: (args: KtxIngestArgs, io: KtxCliIo) => Promise, ) => Promise; } -function outputMode(options: OutputModeOptions): KloIngestOutputMode { +function outputMode(options: OutputModeOptions): KtxIngestOutputMode { if (options.json === true) { return 'json'; } @@ -26,7 +26,7 @@ function outputMode(options: OutputModeOptions): KloIngestOutputMode { return 'plain'; } -function watchOutputMode(options: OutputModeOptions): KloIngestOutputMode { +function watchOutputMode(options: OutputModeOptions): KtxIngestOutputMode { if (options.json === true) { return 'json'; } @@ -36,22 +36,22 @@ function watchOutputMode(options: OutputModeOptions): KloIngestOutputMode { return 'viz'; } -function inputMode(options: OutputModeOptions): Pick { +function inputMode(options: OutputModeOptions): Pick { return options.input === false ? { inputMode: 'disabled' } : {}; } async function runIngestArgs( - context: KloCliCommandContext, - args: KloIngestArgs, + context: KtxCliCommandContext, + args: KtxIngestArgs, options: IngestCommandOptions, ): Promise { - const { runKloIngest } = await import('../ingest.js'); - context.setExitCode(await options.runIngestWithProgress(args, context.io, context.deps, runKloIngest)); + const { runKtxIngest } = await import('../ingest.js'); + context.setExitCode(await options.runIngestWithProgress(args, context.io, context.deps, runKtxIngest)); } export function registerIngestCommands( program: Command, - context: KloCliCommandContext, + context: KtxCliCommandContext, commandOptions: IngestCommandOptions, ): void { const ingest = program @@ -66,7 +66,7 @@ export function registerIngestCommands( ingest .command('run') .description('Run local ingest for one configured connection and source adapter') - .requiredOption('--connection-id ', 'KLO connection id') + .requiredOption('--connection-id ', 'KTX connection id') .requiredOption('--adapter ', 'Ingest source adapter name') .option('--source-dir ', 'Directory containing source files') .option('--database-introspection-url ', 'Daemon URL for live-database introspection') diff --git a/packages/cli/src/commands/knowledge-commands.ts b/packages/cli/src/commands/knowledge-commands.ts index 5ef9f58f..c85a118c 100644 --- a/packages/cli/src/commands/knowledge-commands.ts +++ b/packages/cli/src/commands/knowledge-commands.ts @@ -1,24 +1,24 @@ import { type Command, Option } from '@commander-js/extra-typings'; -import { collectOption, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; +import { collectOption, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; import { wikiWriteCommandSchema } from '../command-schemas.js'; -import type { KloKnowledgeArgs } from '../knowledge.js'; +import type { KtxKnowledgeArgs } from '../knowledge.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/knowledge-commands'); -async function runKnowledgeArgs(context: KloCliCommandContext, args: KloKnowledgeArgs): Promise { - const runner = context.deps.knowledge ?? (await import('../knowledge.js')).runKloKnowledge; +async function runKnowledgeArgs(context: KtxCliCommandContext, args: KtxKnowledgeArgs): Promise { + const runner = context.deps.knowledge ?? (await import('../knowledge.js')).runKtxKnowledge; context.setExitCode(await runner(args, context.io)); } -export function registerWikiCommands(program: Command, context: KloCliCommandContext): void { +export function registerWikiCommands(program: Command, context: KtxCliCommandContext): void { const wiki = program .command('wiki') .description('List, read, search, or write local wiki pages') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ); wiki diff --git a/packages/cli/src/commands/public-ingest-commands.ts b/packages/cli/src/commands/public-ingest-commands.ts index 1bc138a2..dfe63c42 100644 --- a/packages/cli/src/commands/public-ingest-commands.ts +++ b/packages/cli/src/commands/public-ingest-commands.ts @@ -1,7 +1,7 @@ import { InvalidArgumentError, type Command } from '@commander-js/extra-typings'; -import { type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; +import { type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; import { publicIngestReadCommandSchema, publicIngestRunCommandSchema } from '../command-schemas.js'; -import type { KloPublicIngestArgs, KloPublicIngestInputMode } from '../public-ingest.js'; +import type { KtxPublicIngestArgs, KtxPublicIngestInputMode } from '../public-ingest.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/public-ingest-commands'); @@ -12,26 +12,26 @@ interface PublicIngestOptions { input?: boolean; } -function inputMode(options: { input?: boolean }): KloPublicIngestInputMode { +function inputMode(options: { input?: boolean }): KtxPublicIngestInputMode { return options.input === false ? 'disabled' : 'auto'; } -async function runPublicIngestArgs(context: KloCliCommandContext, args: KloPublicIngestArgs): Promise { - const runner = context.deps.publicIngest ?? (await import('../public-ingest.js')).runKloPublicIngest; +async function runPublicIngestArgs(context: KtxCliCommandContext, args: KtxPublicIngestArgs): Promise { + const runner = context.deps.publicIngest ?? (await import('../public-ingest.js')).runKtxPublicIngest; context.setExitCode(await runner(args, context.io)); } function parsePublicIngestConnectionId(value: string): string { if (value === 'run') { - throw new InvalidArgumentError('run is reserved; use klo dev ingest run for low-level adapter syntax'); + throw new InvalidArgumentError('run is reserved; use ktx dev ingest run for low-level adapter syntax'); } return value; } -export function registerPublicIngestCommands(program: Command, context: KloCliCommandContext): void { +export function registerPublicIngestCommands(program: Command, context: KtxCliCommandContext): void { const ingest = program .command('ingest') - .description('Build and refresh KLO context from configured sources') + .description('Build and refresh KTX context from configured sources') .usage('[options] [connectionId]') .argument('[connectionId]', 'Connection id to ingest', parsePublicIngestConnectionId) .option('--all', 'Ingest every eligible configured source', false) @@ -42,12 +42,12 @@ export function registerPublicIngestCommands(program: Command, context: KloCliCo [ '', 'Examples:', - ' klo ingest [options]', - ' klo ingest --all [options]', - ' klo ingest status [runId] [options]', - ' klo ingest watch [runId] [options]', + ' ktx ingest [options]', + ' ktx ingest --all [options]', + ' ktx ingest status [runId] [options]', + ' ktx ingest watch [runId] [options]', '', - 'Project directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.', + 'Project directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.', '', ].join('\n'), ) @@ -58,7 +58,7 @@ export function registerPublicIngestCommands(program: Command, context: KloCliCo .action(async (connectionId: string | undefined, _options: PublicIngestOptions, command) => { const options = command.opts(); if (options.all === true && connectionId) { - throw new Error('klo ingest accepts either --all or , not both'); + throw new Error('ktx ingest accepts either --all or , not both'); } const args = publicIngestRunCommandSchema.parse({ command: 'run', diff --git a/packages/cli/src/commands/scan-commands.ts b/packages/cli/src/commands/scan-commands.ts index c67511d4..9f3d35f7 100644 --- a/packages/cli/src/commands/scan-commands.ts +++ b/packages/cli/src/commands/scan-commands.ts @@ -1,35 +1,35 @@ import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'; -import { type KloCliCommandContext, parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js'; -import type { KloScanArgs } from '../scan.js'; +import { type KtxCliCommandContext, parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js'; +import type { KtxScanArgs } from '../scan.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/scan-commands'); -async function runScanArgs(context: KloCliCommandContext, args: KloScanArgs): Promise { - const runner = context.deps.scan ?? (await import('../scan.js')).runKloScan; +async function runScanArgs(context: KtxCliCommandContext, args: KtxScanArgs): Promise { + const runner = context.deps.scan ?? (await import('../scan.js')).runKtxScan; context.setExitCode(await runner(args, context.io)); } -type KloScanModeOption = Extract['mode']; +type KtxScanModeOption = Extract['mode']; -function parseScanModeOption(value: string): KloScanModeOption { +function parseScanModeOption(value: string): KtxScanModeOption { if (value === 'structural' || value === 'enriched' || value === 'relationships') { return value; } throw new InvalidArgumentError('Allowed choices are structural, enriched, relationships'); } -type KloRelationshipStatusOption = Extract['status']; -type KloRelationshipFeedbackDecisionOption = Extract['decision']; +type KtxRelationshipStatusOption = Extract['status']; +type KtxRelationshipFeedbackDecisionOption = Extract['decision']; -function parseRelationshipStatusOption(value: string): KloRelationshipStatusOption { +function parseRelationshipStatusOption(value: string): KtxRelationshipStatusOption { if (value === 'accepted' || value === 'review' || value === 'rejected' || value === 'skipped' || value === 'all') { return value; } throw new InvalidArgumentError('Allowed choices are accepted, review, rejected, skipped, all'); } -function parseRelationshipFeedbackDecisionOption(value: string): KloRelationshipFeedbackDecisionOption { +function parseRelationshipFeedbackDecisionOption(value: string): KtxRelationshipFeedbackDecisionOption { if (value === 'accepted' || value === 'rejected' || value === 'all') { return value; } @@ -58,7 +58,7 @@ function relationshipDecisionArgs(options: { note?: string; json?: boolean; }): Pick< - Extract, + Extract, 'candidateId' | 'decision' | 'reviewer' | 'note' | 'json' > | null { const decisionCount = [options.accept !== undefined, options.reject !== undefined].filter(Boolean).length; @@ -69,7 +69,7 @@ function relationshipDecisionArgs(options: { return { candidateId: options.accept, decision: 'accepted', - reviewer: options.reviewer ?? 'klo', + reviewer: options.reviewer ?? 'ktx', note: options.note ?? null, json: options.json === true, }; @@ -78,7 +78,7 @@ function relationshipDecisionArgs(options: { return { candidateId: options.reject, decision: 'rejected', - reviewer: options.reviewer ?? 'klo', + reviewer: options.reviewer ?? 'ktx', note: options.note ?? null, json: options.json === true, }; @@ -90,11 +90,11 @@ function collectRelationshipCandidateOption(value: string, previous: string[]): return [...previous, parseNonEmptyOption(value)]; } -export function registerScanCommands(program: Command, context: KloCliCommandContext): void { +export function registerScanCommands(program: Command, context: KtxCliCommandContext): void { const scan = program .command('scan') .description('Run or inspect standalone connection scans') - .argument('[connectionId]', 'KLO connection id to scan') + .argument('[connectionId]', 'KTX connection id to scan') .option( '--mode ', 'Scan mode: structural, enriched, relationships (default: structural)', @@ -105,7 +105,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ) .hook('preAction', (_thisCommand, actionCommand) => { context.writeDebug?.('scan', actionCommand); @@ -113,7 +113,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .action(async (connectionId: string | undefined, options, command) => { if (!connectionId) { scan.outputHelp(); - context.io.stderr.write('klo dev scan requires or a subcommand\n'); + context.io.stderr.write('ktx dev scan requires or a subcommand\n'); context.setExitCode(1); return; } @@ -135,7 +135,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .argument('', 'Local scan run id') .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (runId: string, _options: unknown, command) => { await runScanArgs(context, { @@ -152,7 +152,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .option('--json', 'Print the raw scan report JSON', false) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (runId: string, options, command) => { await runScanArgs(context, { @@ -189,7 +189,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .option('--json', 'Print relationship artifacts as JSON', false) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (runId: string, options, command) => { const decision = relationshipDecisionArgs(options); @@ -231,7 +231,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .option('--json', 'Print the apply result as JSON', false) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (runId: string, options, command) => { const parentOptions = command.parent?.opts() as { dryRun?: boolean } | undefined; @@ -249,7 +249,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon scan .command('relationship-feedback') .description('Export persisted relationship review decisions as calibration labels') - .option('--connection ', 'Only export labels for one KLO connection') + .option('--connection ', 'Only export labels for one KTX connection') .option( '--decision ', 'Relationship feedback decision: accepted, rejected, all', @@ -260,7 +260,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .addOption(new Option('--jsonl', 'Print labels as newline-delimited JSON').default(false).conflicts('json')) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (options, command) => { await runScanArgs(context, { @@ -276,7 +276,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon scan .command('relationship-calibration') .description('Summarize relationship feedback labels against current score thresholds') - .option('--connection ', 'Only calibrate labels for one KLO connection') + .option('--connection ', 'Only calibrate labels for one KTX connection') .option( '--decision ', 'Relationship feedback decision: accepted, rejected, all', @@ -298,7 +298,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .option('--json', 'Print the calibration report as JSON', false) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (options, command) => { await runScanArgs(context, { @@ -315,7 +315,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon scan .command('relationship-thresholds') .description('Evaluate relationship feedback labels for offline threshold advice') - .option('--connection ', 'Only evaluate labels for one KLO connection') + .option('--connection ', 'Only evaluate labels for one KTX connection') .option( '--min-total-labels ', 'Minimum scored labels before advice can be ready', @@ -337,7 +337,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon .option('--json', 'Print the threshold advice report as JSON', false) .addHelpText( 'after', - '\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n', + '\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n', ) .action(async (options, command) => { await runScanArgs(context, { diff --git a/packages/cli/src/commands/serve-commands.ts b/packages/cli/src/commands/serve-commands.ts index bf26d31a..b7e659fb 100644 --- a/packages/cli/src/commands/serve-commands.ts +++ b/packages/cli/src/commands/serve-commands.ts @@ -1,6 +1,6 @@ import { type Command, InvalidArgumentError } from '@commander-js/extra-typings'; -import { type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; -import type { KloServeArgs } from '../serve.js'; +import { type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js'; +import type { KtxServeArgs } from '../serve.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/serve-commands'); @@ -12,10 +12,10 @@ function parseMcp(value: string): 'stdio' { throw new InvalidArgumentError('Only stdio is supported in this phase'); } -export function registerServeCommands(program: Command, context: KloCliCommandContext): void { +export function registerServeCommands(program: Command, context: KtxCliCommandContext): void { program .command('serve') - .description('Run standalone KLO services such as MCP stdio') + .description('Run standalone KTX services such as MCP stdio') .requiredOption('--mcp ', 'MCP transport mode', parseMcp) .option('--user-id ', 'Local user id', 'local') .option('--semantic-compute', 'Enable semantic-layer compute', false) @@ -30,7 +30,7 @@ export function registerServeCommands(program: Command, context: KloCliCommandCo if (options.executeQueries === true && !semanticCompute) { throw new Error('--execute-queries requires --semantic-compute'); } - const args: KloServeArgs = { + const args: KtxServeArgs = { mcp: options.mcp, projectDir: resolveCommandProjectDir(command), userId: options.userId, @@ -41,7 +41,7 @@ export function registerServeCommands(program: Command, context: KloCliCommandCo memoryCapture: options.memoryCapture === true, memoryModel: options.memoryModel, }; - const runner = context.deps.serveStdio ?? (await import('../serve.js')).runKloServeStdio; + const runner = context.deps.serveStdio ?? (await import('../serve.js')).runKtxServeStdio; context.setExitCode(await runner(args)); }); } diff --git a/packages/cli/src/commands/setup-commands.ts b/packages/cli/src/commands/setup-commands.ts index 5d7c0aad..1d5a933a 100644 --- a/packages/cli/src/commands/setup-commands.ts +++ b/packages/cli/src/commands/setup-commands.ts @@ -1,15 +1,15 @@ import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'; -import type { KloCliCommandContext } from '../cli-program.js'; +import type { KtxCliCommandContext } from '../cli-program.js'; import { resolveCommandProjectDir } from '../cli-program.js'; -import type { KloSetupDatabaseDriver } from '../setup-databases.js'; -import type { KloSetupSourceType } from '../setup-sources.js'; +import type { KtxSetupDatabaseDriver } from '../setup-databases.js'; +import type { KtxSetupSourceType } from '../setup-sources.js'; import { registerDemoCommands } from './demo-commands.js'; async function runSetupArgs( - context: KloCliCommandContext, + context: KtxCliCommandContext, args: Parameters>[0], ) { - const runner = context.deps.setup ?? (await import('../setup.js')).runKloSetup; + const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup; context.setExitCode(await runner(args, context.io)); } @@ -28,7 +28,7 @@ function embeddingBackend(value: string): 'openai' | 'sentence-transformers' { throw new InvalidArgumentError(`invalid choice '${value}'`); } -function databaseDriver(value: string): KloSetupDatabaseDriver { +function databaseDriver(value: string): KtxSetupDatabaseDriver { if ( value === 'sqlite' || value === 'postgres' || @@ -43,7 +43,7 @@ function databaseDriver(value: string): KloSetupDatabaseDriver { throw new InvalidArgumentError(`invalid choice '${value}'`); } -function sourceType(value: string): KloSetupSourceType { +function sourceType(value: string): KtxSetupSourceType { if ( value === 'dbt' || value === 'metricflow' || @@ -109,7 +109,7 @@ function shouldShowSetupEntryMenu( embeddingApiKeyEnv?: string; embeddingApiKeyFile?: string; skipEmbeddings?: boolean; - database?: KloSetupDatabaseDriver[]; + database?: KtxSetupDatabaseDriver[]; databaseConnectionId?: string[]; newDatabaseConnectionId?: string; databaseUrl?: string; @@ -121,7 +121,7 @@ function shouldShowSetupEntryMenu( historicSqlServiceAccountPattern?: string[]; historicSqlRedactionPattern?: string[]; skipDatabases?: boolean; - source?: KloSetupSourceType; + source?: KtxSetupSourceType; sourceConnectionId?: string; sourcePath?: string; sourceGitUrl?: string; @@ -210,13 +210,13 @@ function shouldShowSetupEntryMenu( ].some((optionName) => optionWasSpecified(command, optionName)); } -export function registerSetupCommands(program: Command, context: KloCliCommandContext): void { +export function registerSetupCommands(program: Command, context: KtxCliCommandContext): void { const setup = program .command('setup') - .description('Set up or resume a local KLO project') - .option('--project-dir ', 'KLO project directory') - .option('--new', 'Create a new KLO project before setup', false) - .option('--existing', 'Use an existing KLO project', false) + .description('Set up or resume a local KTX project') + .option('--project-dir ', 'KTX project directory') + .option('--new', 'Create a new KTX project before setup', false) + .option('--existing', 'Use an existing KTX project', false) .option('--agents', 'Install agent integration only', false) .addOption( new Option('--target ', 'Agent target').choices([ @@ -247,10 +247,10 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo .option( '--database ', 'Database driver to configure; repeatable', - (value, previous: KloSetupDatabaseDriver[]) => { + (value, previous: KtxSetupDatabaseDriver[]) => { return [...previous, databaseDriver(value)]; }, - [] as KloSetupDatabaseDriver[], + [] as KtxSetupDatabaseDriver[], ) .option( '--database-connection-id ', @@ -291,7 +291,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo (value, previous: string[]) => [...previous, value], [], ) - .option('--skip-databases', 'Leave database setup incomplete; KLO cannot work until a primary source is added', false) + .option('--skip-databases', 'Leave database setup incomplete; KTX cannot work until a primary source is added', false) .addOption(new Option('--source ', 'Source connector type').argParser(sourceType)) .option('--source-connection-id ', 'Connection id for source setup') .option('--source-path ', 'Local source path for dbt, MetricFlow, or LookML') @@ -421,9 +421,9 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo }); }); - registerDemoCommands(setup, context, { description: 'Run the packaged KLO demo from setup' }); + registerDemoCommands(setup, context, { description: 'Run the packaged KTX demo from setup' }); - const setupContext = setup.command('context').description('Build, inspect, and recover setup-managed KLO context'); + const setupContext = setup.command('context').description('Build, inspect, and recover setup-managed KTX context'); function setupContextInputMode(command: { optsWithGlobals?: () => unknown; @@ -435,7 +435,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo setupContext .command('build') - .description('Build agent-ready KLO context for setup') + .description('Build agent-ready KTX context for setup') .option('--no-input', 'Disable interactive terminal input') .action(async (options: { input?: boolean }, command) => { await runSetupArgs(context, { @@ -505,7 +505,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo setup .command('status') - .description('Show setup readiness for the resolved KLO project') + .description('Show setup readiness for the resolved KTX project') .option('--json', 'Print JSON output', false) .action(async (options: { json?: boolean }, command) => { await runSetupArgs(context, { diff --git a/packages/cli/src/commands/sl-commands.ts b/packages/cli/src/commands/sl-commands.ts index 03dd4e6e..24ab95aa 100644 --- a/packages/cli/src/commands/sl-commands.ts +++ b/packages/cli/src/commands/sl-commands.ts @@ -1,12 +1,12 @@ import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'; import { collectOption, - type KloCliCommandContext, + type KtxCliCommandContext, parsePositiveIntegerOption, resolveCommandProjectDir, } from '../cli-program.js'; import { slQueryCommandSchema } from '../command-schemas.js'; -import type { KloSlArgs } from '../sl.js'; +import type { KtxSlArgs } from '../sl.js'; import { profileMark } from '../startup-profile.js'; profileMark('module:commands/sl-commands'); @@ -32,24 +32,24 @@ function collectOrderBy( return [...previous, parseOrderBy(value)]; } -async function runSlArgs(context: KloCliCommandContext, args: KloSlArgs): Promise { - const runner = context.deps.sl ?? (await import('../sl.js')).runKloSl; +async function runSlArgs(context: KtxCliCommandContext, args: KtxSlArgs): Promise { + const runner = context.deps.sl ?? (await import('../sl.js')).runKtxSl; context.setExitCode(await runner(args, context.io)); } -export function registerSlCommands(program: Command, context: KloCliCommandContext, commandName = 'sl'): void { +export function registerSlCommands(program: Command, context: KtxCliCommandContext, commandName = 'sl'): void { const sl = program .command(commandName) .description('List, read, validate, query, or write local semantic-layer sources') .showHelpAfterError() .addHelpText( 'after', - '\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n', + '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n', ); sl.command('list') .description('List semantic-layer sources') - .option('--connection-id ', 'KLO connection id') + .option('--connection-id ', 'KTX connection id') .addOption( new Option('--output ', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([ 'pretty', @@ -71,7 +71,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte sl.command('read') .description('Read a semantic-layer source') .argument('', 'Semantic-layer source name') - .requiredOption('--connection-id ', 'KLO connection id') + .requiredOption('--connection-id ', 'KTX connection id') .action(async (sourceName: string, options: { connectionId: string }, command) => { await runSlArgs(context, { command: 'read', @@ -84,7 +84,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte sl.command('validate') .description('Validate a semantic-layer source') .argument('', 'Semantic-layer source name') - .requiredOption('--connection-id ', 'KLO connection id') + .requiredOption('--connection-id ', 'KTX connection id') .action(async (sourceName: string, options: { connectionId: string }, command) => { await runSlArgs(context, { command: 'validate', @@ -97,7 +97,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte sl.command('write') .description('Write a semantic-layer source') .argument('', 'Semantic-layer source name') - .requiredOption('--connection-id ', 'KLO connection id') + .requiredOption('--connection-id ', 'KTX connection id') .requiredOption('--yaml ', 'Semantic-layer source YAML') .action(async (sourceName: string, options: { connectionId: string; yaml: string }, command) => { await runSlArgs(context, { @@ -111,7 +111,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte sl.command('query') .description('Compile or execute a semantic-layer query') - .option('--connection-id ', 'KLO connection id') + .option('--connection-id ', 'KTX connection id') .option('--measure ', 'Measure to query; repeatable', collectOption, []) .option('--dimension ', 'Dimension to include; repeatable', collectOption, []) .option('--filter ', 'Filter expression; repeatable', collectOption, []) diff --git a/packages/cli/src/commands/status-commands.ts b/packages/cli/src/commands/status-commands.ts index 4eb08798..fe48cf52 100644 --- a/packages/cli/src/commands/status-commands.ts +++ b/packages/cli/src/commands/status-commands.ts @@ -1,14 +1,14 @@ import type { Command } from '@commander-js/extra-typings'; -import type { KloCliCommandContext } from '../cli-program.js'; +import type { KtxCliCommandContext } from '../cli-program.js'; import { resolveCommandProjectDir } from '../cli-program.js'; -export function registerStatusCommands(program: Command, context: KloCliCommandContext): void { +export function registerStatusCommands(program: Command, context: KtxCliCommandContext): void { program .command('status') - .description('Show current KLO project setup status') + .description('Show current KTX project setup status') .option('--json', 'Print JSON output', false) .action(async (options: { json?: boolean }, command) => { - const runner = context.deps.setup ?? (await import('../setup.js')).runKloSetup; + const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup; context.setExitCode( await runner( { diff --git a/packages/cli/src/completion.ts b/packages/cli/src/completion.ts index 70f03675..10e787f6 100644 --- a/packages/cli/src/completion.ts +++ b/packages/cli/src/completion.ts @@ -28,10 +28,10 @@ export interface ZshCompletionInstallResult { zshrcPath: string; } -const KLO_COMPLETION_BLOCK_START = '# >>> klo completion >>>'; -const KLO_COMPLETION_BLOCK_END = '# <<< klo completion <<<'; -const KLO_COMPLETION_BLOCK_PATTERN = new RegExp( - `\\n?${escapeRegExp(KLO_COMPLETION_BLOCK_START)}[\\s\\S]*?${escapeRegExp(KLO_COMPLETION_BLOCK_END)}\\n?`, +const KTX_COMPLETION_BLOCK_START = '# >>> ktx completion >>>'; +const KTX_COMPLETION_BLOCK_END = '# <<< ktx completion <<<'; +const KTX_COMPLETION_BLOCK_PATTERN = new RegExp( + `\\n?${escapeRegExp(KTX_COMPLETION_BLOCK_START)}[\\s\\S]*?${escapeRegExp(KTX_COMPLETION_BLOCK_END)}\\n?`, 'g', ); @@ -39,27 +39,27 @@ export function zshCompletionScript(): string { const zshWords = '$' + '{words[@]}'; const zshCompletionCapture = [ '$', - `{(@f)$("${'$'}{klo_completion_command[@]}" dev __complete --shell zsh --position "$CURRENT" -- "${zshWords}" 2>/dev/null)}`, + `{(@f)$("${'$'}{ktx_completion_command[@]}" dev __complete --shell zsh --position "$CURRENT" -- "${zshWords}" 2>/dev/null)}`, ].join(''); const zshCompletionsCount = '$' + '{#completions[@]}'; - const zshCompletionCommand = '$' + '(eval "print -r -- $' + '{KLO_COMPLETION_COMMAND:-klo}")'; + const zshCompletionCommand = '$' + '(eval "print -r -- $' + '{KTX_COMPLETION_COMMAND:-ktx}")'; return [ - '#compdef klo', + '#compdef ktx', '', - '_klo() {', + '_ktx() {', ' local -a completions', - ' local -a klo_completion_command', - ` klo_completion_command=("\${(@z)${zshCompletionCommand}}")`, + ' local -a ktx_completion_command', + ` ktx_completion_command=("\${(@z)${zshCompletionCommand}}")`, ` completions=("${zshCompletionCapture}")`, ` if (( ${zshCompletionsCount} )); then`, - " _describe 'klo completions' completions", + " _describe 'ktx completions' completions", ' else', ' _files', ' fi', '}', '', - 'compdef _klo klo', + 'compdef _ktx ktx', '', ].join('\n'); } @@ -68,7 +68,7 @@ export async function installZshCompletion(): Promise { } function updateZshrcCompletionBlock(contents: string): string { - const withoutManagedBlock = contents.replace(KLO_COMPLETION_BLOCK_PATTERN, normalizeTrailingNewline); + const withoutManagedBlock = contents.replace(KTX_COMPLETION_BLOCK_PATTERN, normalizeTrailingNewline); const hasCompinit = /^.*\bcompinit\b.*$/m.test(withoutManagedBlock); const block = zshrcCompletionBlock({ includeCompinit: !hasCompinit }); @@ -313,23 +313,23 @@ function updateZshrcCompletionBlock(contents: string): string { function zshrcCompletionBlock(options: { includeCompinit: boolean }): string { return [ - KLO_COMPLETION_BLOCK_START, - '_klo_completion_command() {', + KTX_COMPLETION_BLOCK_START, + '_ktx_completion_command() {', ' local dir="$PWD"', ' while [[ "$dir" != "/" ]]; do', - ` if [[ -f "$dir/package.json" ]] && command grep -q '"name": "klo-workspace"' "$dir/package.json" 2>/dev/null; then`, - ' print -r -- "node $dir/scripts/run-klo.mjs --"', + ` if [[ -f "$dir/package.json" ]] && command grep -q '"name": "ktx-workspace"' "$dir/package.json" 2>/dev/null; then`, + ' print -r -- "node $dir/scripts/run-ktx.mjs --"', ' return', ' fi', ' dir="' + '$' + '{dir:h}"', ' done', - ' print -r -- "klo"', + ' print -r -- "ktx"', '}', - "export KLO_COMPLETION_COMMAND='$(_klo_completion_command)'", + "export KTX_COMPLETION_COMMAND='$(_ktx_completion_command)'", 'setopt complete_aliases', 'fpath=("$HOME/.zfunc" $fpath)', ...(options.includeCompinit ? ['autoload -Uz compinit', 'compinit'] : []), - KLO_COMPLETION_BLOCK_END, + KTX_COMPLETION_BLOCK_END, ].join('\n'); } diff --git a/packages/cli/src/connection.test.ts b/packages/cli/src/connection.test.ts index 94f07b86..a54280be 100644 --- a/packages/cli/src/connection.test.ts +++ b/packages/cli/src/connection.test.ts @@ -1,11 +1,11 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject, parseKloProjectConfig } from '@klo/context/project'; -import type { KloConnectionDriver, KloScanConnector, KloSchemaSnapshot } from '@klo/context/scan'; +import { initKtxProject, parseKtxProjectConfig } from '@ktx/context/project'; +import type { KtxConnectionDriver, KtxScanConnector, KtxSchemaSnapshot } from '@ktx/context/scan'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloConnection } from './connection.js'; -import { runKloCli, type KloCliIo } from './index.js'; +import { runKtxConnection } from './connection.js'; +import { runKtxCli, type KtxCliIo } from './index.js'; function makeIo(options: { stdoutIsTty?: boolean; stdinIsTty?: boolean } = {}) { let stdout = ''; @@ -32,7 +32,7 @@ function makeIo(options: { stdoutIsTty?: boolean; stdinIsTty?: boolean } = {}) { }; } -function snapshotFor(driver: KloConnectionDriver, tableNames: string[]): KloSchemaSnapshot { +function snapshotFor(driver: KtxConnectionDriver, tableNames: string[]): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver, @@ -52,10 +52,10 @@ function snapshotFor(driver: KloConnectionDriver, tableNames: string[]): KloSche }; } -function nativeConnector(driver: KloConnectionDriver, tableNames: string[]) { +function nativeConnector(driver: KtxConnectionDriver, tableNames: string[]) { const introspect = vi.fn(async () => snapshotFor(driver, tableNames)); const cleanup = vi.fn(async () => undefined); - const connector: KloScanConnector = { + const connector: KtxScanConnector = { id: `${driver}:warehouse`, driver, capabilities: { @@ -75,11 +75,11 @@ function nativeConnector(driver: KloConnectionDriver, tableNames: string[]) { return { connector, introspect, cleanup }; } -describe('runKloConnection', () => { +describe('runKtxConnection', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-connection-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-connection-')); }); afterEach(async () => { @@ -88,11 +88,11 @@ describe('runKloConnection', () => { it('adds and lists env-referenced connections without resolving secrets', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'add', projectDir, @@ -109,18 +109,18 @@ describe('runKloConnection', () => { ).resolves.toBe(0); expect(io.stdout()).toContain('Connection: warehouse'); - await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain('url: env:DATABASE_URL'); + await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('url: env:DATABASE_URL'); const listIo = makeIo(); - await expect(runKloConnection({ command: 'list', projectDir }, listIo.io)).resolves.toBe(0); + await expect(runKtxConnection({ command: 'list', projectDir }, listIo.io)).resolves.toBe(0); expect(listIo.stdout()).toContain('warehouse'); expect(listIo.stdout()).toContain('postgres'); }); - it('removes a configured connection from klo.yaml without deleting local artifacts when forced', async () => { + it('removes a configured connection from ktx.yaml without deleting local artifacts when forced', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -134,14 +134,14 @@ describe('runKloConnection', () => { }, makeIo().io, ); - const artifactPath = join(projectDir, '.klo', 'artifacts', 'warehouse.txt'); - await mkdir(join(projectDir, '.klo', 'artifacts'), { recursive: true }); + const artifactPath = join(projectDir, '.ktx', 'artifacts', 'warehouse.txt'); + await mkdir(join(projectDir, '.ktx', 'artifacts'), { recursive: true }); await writeFile(artifactPath, 'keep me', 'utf-8'); const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'remove', projectDir, @@ -153,20 +153,20 @@ describe('runKloConnection', () => { ), ).resolves.toBe(0); - const parsed = parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf-8')); + const parsed = parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf-8')); expect(parsed.connections.warehouse).toBeUndefined(); await expect(readFile(artifactPath, 'utf-8')).resolves.toBe('keep me'); - expect(io.stdout()).toContain('Connection removed from klo.yaml.'); + expect(io.stdout()).toContain('Connection removed from ktx.yaml.'); expect(io.stdout()).toContain( - 'Ingested artifacts from this connection remain in .klo/. Run klo dev artifacts to inspect.', + 'Ingested artifacts from this connection remain in .ktx/. Run ktx dev artifacts to inspect.', ); expect(io.stderr()).toBe(''); }); it('requires --force when removing in non-interactive mode', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -183,7 +183,7 @@ describe('runKloConnection', () => { const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'remove', projectDir, @@ -200,11 +200,11 @@ describe('runKloConnection', () => { it('returns a clear error when removing an unknown connection', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'remove', projectDir, @@ -216,13 +216,13 @@ describe('runKloConnection', () => { ), ).resolves.toBe(1); - expect(io.stderr()).toContain('Connection "missing" is not configured in klo.yaml'); + expect(io.stderr()).toContain('Connection "missing" is not configured in ktx.yaml'); }); it('asks for confirmation before removing in an interactive terminal', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -243,7 +243,7 @@ describe('runKloConnection', () => { }; await expect( - runKloConnection( + runKtxConnection( { command: 'remove', projectDir, @@ -256,14 +256,14 @@ describe('runKloConnection', () => { ).resolves.toBe(0); expect(prompts.confirm).toHaveBeenCalledWith({ - message: 'Remove connection "warehouse" from klo.yaml? Ingested artifacts will remain in .klo/.', + message: 'Remove connection "warehouse" from ktx.yaml? Ingested artifacts will remain in .ktx/.', initialValue: false, }); }); it('runs public connect map as refresh, validate, and list over the low-level mapping runner', async () => { const io = makeIo(); - const runMapping = vi.fn(async (argv: string[], mappingIo: KloCliIo) => { + const runMapping = vi.fn(async (argv: string[], mappingIo: KtxCliIo) => { if (argv[0] === 'refresh') { mappingIo.stdout.write('Discovery: 1 database\n'); mappingIo.stdout.write('Unmapped discovered: 1\n'); @@ -282,7 +282,7 @@ describe('runKloConnection', () => { }); await expect( - runKloConnection( + runKtxConnection( { command: 'map', projectDir: '/tmp/project', sourceConnectionId: 'prod-metabase', json: false }, io.io, { runMapping }, @@ -309,14 +309,14 @@ describe('runKloConnection', () => { expect(io.stdout()).toContain('Mappings:'); expect(io.stdout()).toContain('1 -> [unmapped]'); expect(io.stdout()).toContain('Next:'); - expect(io.stdout()).toContain('klo ingest prod-metabase'); - expect(io.stdout()).toContain('klo dev mapping'); + expect(io.stdout()).toContain('ktx ingest prod-metabase'); + expect(io.stdout()).toContain('ktx dev mapping'); expect(io.stderr()).toBe(''); }); it('prints stable JSON for public connect map without leaking low-level stdout', async () => { const io = makeIo(); - const runMapping = vi.fn(async (argv: string[], mappingIo: KloCliIo) => { + const runMapping = vi.fn(async (argv: string[], mappingIo: KtxCliIo) => { if (argv[0] === 'refresh') { mappingIo.stdout.write('Discovery: 1 connection\nUnmapped discovered: 0\nStale mappings: 0\n'); return 0; @@ -332,8 +332,8 @@ describe('runKloConnection', () => { [ { lookerConnectionName: 'analytics', - kloConnectionId: 'prod-warehouse', - source: 'klo.yaml', + ktxConnectionId: 'prod-warehouse', + source: 'ktx.yaml', }, ], null, @@ -346,7 +346,7 @@ describe('runKloConnection', () => { }); await expect( - runKloConnection( + runKtxConnection( { command: 'map', projectDir: '/tmp/project', sourceConnectionId: 'prod-looker', json: true }, io.io, { runMapping }, @@ -357,7 +357,7 @@ describe('runKloConnection', () => { connectionId: string; refresh: { ok: boolean; output: string[] }; validation: { ok: boolean; output: string[] }; - mappings: Array<{ lookerConnectionName: string; kloConnectionId: string; source: string }>; + mappings: Array<{ lookerConnectionName: string; ktxConnectionId: string; source: string }>; }; expect(parsed).toEqual({ connectionId: 'prod-looker', @@ -372,8 +372,8 @@ describe('runKloConnection', () => { mappings: [ { lookerConnectionName: 'analytics', - kloConnectionId: 'prod-warehouse', - source: 'klo.yaml', + ktxConnectionId: 'prod-warehouse', + source: 'ktx.yaml', }, ], }); @@ -382,7 +382,7 @@ describe('runKloConnection', () => { it('returns the refresh failure when public connect map cannot discover source metadata', async () => { const io = makeIo(); - const runMapping = vi.fn(async (argv: string[], mappingIo: KloCliIo) => { + const runMapping = vi.fn(async (argv: string[], mappingIo: KtxCliIo) => { if (argv[0] === 'refresh') { mappingIo.stderr.write('Metabase API key is not configured\n'); return 1; @@ -391,7 +391,7 @@ describe('runKloConnection', () => { }); await expect( - runKloConnection( + runKtxConnection( { command: 'map', projectDir: '/tmp/project', sourceConnectionId: 'prod-metabase', json: false }, io.io, { runMapping }, @@ -405,11 +405,11 @@ describe('runKloConnection', () => { it('rejects literal credential URLs unless explicitly allowed', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'add', projectDir, @@ -430,12 +430,12 @@ describe('runKloConnection', () => { it('warns before writing explicitly allowed literal credential URLs without echoing the URL', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); const literalUrl = 'postgres://localhost:5432/warehouse'; await expect( - runKloConnection( + runKtxConnection( { command: 'add', projectDir, @@ -452,19 +452,19 @@ describe('runKloConnection', () => { ).resolves.toBe(0); expect(io.stderr()).toContain( - 'Warning: writing a literal credential URL to klo.yaml for connection "warehouse". Prefer env:NAME or file:/path references.', + 'Warning: writing a literal credential URL to ktx.yaml for connection "warehouse". Prefer env:NAME or file:/path references.', ); expect(io.stderr()).not.toContain(literalUrl); - await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain(literalUrl); + await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain(literalUrl); }); it('adds a Notion connection without writing token values', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); await expect( - runKloConnection( + runKtxConnection( { command: 'add', projectDir, @@ -490,7 +490,7 @@ describe('runKloConnection', () => { ), ).resolves.toBe(0); - const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('driver: notion'); expect(yaml).toContain('auth_token_ref: env:NOTION_AUTH_TOKEN'); expect(yaml).toContain('crawl_mode: all_accessible'); @@ -502,8 +502,8 @@ describe('runKloConnection', () => { it('runs connection notion pick --no-input through the public connection entrypoint', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -530,7 +530,7 @@ describe('runKloConnection', () => { const io = makeIo(); await expect( - runKloCli( + runKtxCli( [ 'connection', 'notion', @@ -546,7 +546,7 @@ describe('runKloConnection', () => { ), ).resolves.toBe(0); - const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('crawl_mode: selected_roots'); expect(yaml).toContain('11111111-2222-3333-4444-555555555555'); expect(yaml).toContain('database-1'); @@ -556,8 +556,8 @@ describe('runKloConnection', () => { it('tests a configured connection through the native scan connector', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -576,7 +576,7 @@ describe('runKloConnection', () => { const io = makeIo(); await expect( - runKloConnection({ command: 'test', projectDir, connectionId: 'warehouse' }, io.io, { + runKtxConnection({ command: 'test', projectDir, connectionId: 'warehouse' }, io.io, { createScanConnector, }), ).resolves.toBe(0); @@ -600,8 +600,8 @@ describe('runKloConnection', () => { it('cleans up the native scan connector when connection testing fails', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - await runKloConnection( + await initKtxProject({ projectDir, projectName: 'warehouse' }); + await runKtxConnection( { command: 'add', projectDir, @@ -616,7 +616,7 @@ describe('runKloConnection', () => { makeIo().io, ); const cleanup = vi.fn(async () => undefined); - const connector: KloScanConnector = { + const connector: KtxScanConnector = { id: 'sqlite:warehouse', driver: 'sqlite', capabilities: { @@ -638,7 +638,7 @@ describe('runKloConnection', () => { const io = makeIo(); await expect( - runKloConnection({ command: 'test', projectDir, connectionId: 'warehouse' }, io.io, { + runKtxConnection({ command: 'test', projectDir, connectionId: 'warehouse' }, io.io, { createScanConnector: vi.fn(async () => connector), }), ).resolves.toBe(1); diff --git a/packages/cli/src/connection.ts b/packages/cli/src/connection.ts index 421c3230..aa6de7c2 100644 --- a/packages/cli/src/connection.ts +++ b/packages/cli/src/connection.ts @@ -1,14 +1,14 @@ import { cancel, confirm, isCancel } from '@clack/prompts'; -import { type KloLocalProject, loadKloProject, serializeKloProjectConfig } from '@klo/context/project'; -import type { KloScanConnector } from '@klo/context/scan'; -import type { KloConnectionMappingArgs } from './commands/connection-mapping.js'; -import type { KloCliIo } from './index.js'; -import { createKloCliScanConnector } from './local-scan-connectors.js'; +import { type KtxLocalProject, loadKtxProject, serializeKtxProjectConfig } from '@ktx/context/project'; +import type { KtxScanConnector } from '@ktx/context/scan'; +import type { KtxConnectionMappingArgs } from './commands/connection-mapping.js'; +import type { KtxCliIo } from './index.js'; +import { createKtxCliScanConnector } from './local-scan-connectors.js'; import { profileMark } from './startup-profile.js'; profileMark('module:connection'); -interface KloNotionConnectionCliConfig { +interface KtxNotionConnectionCliConfig { authTokenRef: string; crawlMode: 'all_accessible' | 'selected_roots'; rootPageIds: string[]; @@ -19,9 +19,9 @@ interface KloNotionConnectionCliConfig { maxKnowledgeUpdatesPerRun?: number; } -type KloConnectionInputMode = 'disabled'; +type KtxConnectionInputMode = 'disabled'; -export type KloConnectionArgs = +export type KtxConnectionArgs = | { command: 'list'; projectDir: string } | { command: 'add'; @@ -33,7 +33,7 @@ export type KloConnectionArgs = readonly: boolean; force: boolean; allowLiteralCredentials: boolean; - notion?: KloNotionConnectionCliConfig; + notion?: KtxNotionConnectionCliConfig; } | { command: 'test'; projectDir: string; connectionId: string } | { @@ -41,7 +41,7 @@ export type KloConnectionArgs = projectDir: string; connectionId: string; force: boolean; - inputMode?: KloConnectionInputMode; + inputMode?: KtxConnectionInputMode; } | { command: 'map'; @@ -50,19 +50,19 @@ export type KloConnectionArgs = json: boolean; }; -interface KloConnectionPromptAdapter { +interface KtxConnectionPromptAdapter { confirm(options: { message: string; initialValue?: boolean }): Promise; cancel(message: string): void; } -interface KloConnectionIo extends KloCliIo { +interface KtxConnectionIo extends KtxCliIo { stdin?: { isTTY?: boolean }; } -interface KloConnectionDeps { - createScanConnector?: typeof createKloCliScanConnector; - runMapping?: (argv: string[], io: KloCliIo) => Promise; - prompts?: KloConnectionPromptAdapter; +interface KtxConnectionDeps { + createScanConnector?: typeof createKtxCliScanConnector; + runMapping?: (argv: string[], io: KtxCliIo) => Promise; + prompts?: KtxConnectionPromptAdapter; } function assertSafeConnectionId(connectionId: string): void { @@ -76,10 +76,10 @@ function isCredentialReference(value: string): boolean { } function literalCredentialWarning(connectionId: string): string { - return `Warning: writing a literal credential URL to klo.yaml for connection "${connectionId}". Prefer env:NAME or file:/path references.`; + return `Warning: writing a literal credential URL to ktx.yaml for connection "${connectionId}". Prefer env:NAME or file:/path references.`; } -function createClackConnectionPromptAdapter(): KloConnectionPromptAdapter { +function createClackConnectionPromptAdapter(): KtxConnectionPromptAdapter { return { async confirm(options: { message: string; initialValue?: boolean }): Promise { const value = await confirm(options); @@ -92,24 +92,24 @@ function createClackConnectionPromptAdapter(): KloConnectionPromptAdapter { } function isInteractiveConnectionIo( - args: Extract, - io: KloConnectionIo, + args: Extract, + io: KtxConnectionIo, ): boolean { return args.inputMode !== 'disabled' && io.stdin?.isTTY === true && io.stdout.isTTY === true; } -async function cleanupConnector(connector: KloScanConnector | null): Promise { +async function cleanupConnector(connector: KtxScanConnector | null): Promise { if (connector?.cleanup) { await connector.cleanup(); } } async function testNativeConnection( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, - createScanConnector: typeof createKloCliScanConnector, + createScanConnector: typeof createKtxCliScanConnector, ): Promise<{ driver: string; tableCount: number }> { - let connector: KloScanConnector | null = null; + let connector: KtxScanConnector | null = null; try { connector = await createScanConnector(project, connectionId); const snapshot = await connector.introspect( @@ -131,7 +131,7 @@ async function testNativeConnection( } } -interface BufferedIo extends KloCliIo { +interface BufferedIo extends KtxCliIo { stdoutText(): string; stderrText(): string; } @@ -167,17 +167,17 @@ function splitOutputLines(output: string): string[] { } async function runLowLevelMapping( - args: KloConnectionMappingArgs, + args: KtxConnectionMappingArgs, argv: string[], - io: KloCliIo, - deps: KloConnectionDeps, + io: KtxCliIo, + deps: KtxConnectionDeps, ): Promise { if (deps.runMapping) { return await deps.runMapping(argv, io); } - const { runKloConnectionMapping } = await import('./commands/connection-mapping.js'); - return await runKloConnectionMapping(args, io); + const { runKtxConnectionMapping } = await import('./commands/connection-mapping.js'); + return await runKtxConnectionMapping(args, io); } function parseMappingListJson(output: string): unknown[] { @@ -190,12 +190,12 @@ function parseMappingListJson(output: string): unknown[] { } async function runPublicConnectionMap( - args: Extract, - io: KloCliIo, - deps: KloConnectionDeps, + args: Extract, + io: KtxCliIo, + deps: KtxConnectionDeps, ): Promise { const refreshIo = createBufferedIo(); - const refreshArgs: KloConnectionMappingArgs = { + const refreshArgs: KtxConnectionMappingArgs = { command: 'refresh', projectDir: args.projectDir, connectionId: args.sourceConnectionId, @@ -217,7 +217,7 @@ async function runPublicConnectionMap( } const validationIo = createBufferedIo(); - const validationArgs: KloConnectionMappingArgs = { + const validationArgs: KtxConnectionMappingArgs = { command: 'validate', projectDir: args.projectDir, connectionId: args.sourceConnectionId, @@ -237,7 +237,7 @@ async function runPublicConnectionMap( const listIo = createBufferedIo(); const listArgv = ['list', args.sourceConnectionId, '--project-dir', args.projectDir]; - const listArgs: KloConnectionMappingArgs = { + const listArgs: KtxConnectionMappingArgs = { command: 'list', projectDir: args.projectDir, connectionId: args.sourceConnectionId, @@ -271,26 +271,26 @@ async function runPublicConnectionMap( io.stdout.write('\nMappings:\n'); io.stdout.write(listIo.stdoutText().trim() ? listIo.stdoutText() : 'No mappings found.\n'); io.stdout.write('\nNext:\n'); - io.stdout.write(` klo ingest ${args.sourceConnectionId}\n`); - io.stdout.write(` klo dev mapping list ${args.sourceConnectionId}\n`); + io.stdout.write(` ktx ingest ${args.sourceConnectionId}\n`); + io.stdout.write(` ktx dev mapping list ${args.sourceConnectionId}\n`); return 0; } -export async function runKloConnection( - args: KloConnectionArgs, - io: KloConnectionIo = process, - deps: KloConnectionDeps = {}, +export async function runKtxConnection( + args: KtxConnectionArgs, + io: KtxConnectionIo = process, + deps: KtxConnectionDeps = {}, ): Promise { try { if (args.command === 'map') { return await runPublicConnectionMap(args, io, deps); } - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if (args.command === 'list') { const entries = Object.entries(project.config.connections).sort(([a], [b]) => a.localeCompare(b)); if (entries.length === 0) { - io.stdout.write('No connections configured. Run `klo connection add --driver ` to add one.\n'); + io.stdout.write('No connections configured. Run `ktx connection add --driver ` to add one.\n'); return 0; } const idWidth = Math.max('ID'.length, ...entries.map(([id]) => id.length)); @@ -348,11 +348,11 @@ export async function runKloConnection( }, }; await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig(nextConfig), - 'klo', - 'klo@example.com', - `Update KLO connection: ${args.connectionId}`, + 'ktx.yaml', + serializeKtxProjectConfig(nextConfig), + 'ktx', + 'ktx@example.com', + `Update KTX connection: ${args.connectionId}`, ); io.stdout.write(`Connection: ${args.connectionId}\n`); io.stdout.write(`Driver: ${args.driver}\n`); @@ -361,7 +361,7 @@ export async function runKloConnection( if (args.command === 'remove') { if (!project.config.connections[args.connectionId]) { - throw new Error(`Connection "${args.connectionId}" is not configured in klo.yaml`); + throw new Error(`Connection "${args.connectionId}" is not configured in ktx.yaml`); } if (!args.force) { @@ -373,7 +373,7 @@ export async function runKloConnection( const prompts = deps.prompts ?? createClackConnectionPromptAdapter(); const confirmed = await prompts.confirm({ - message: `Remove connection "${args.connectionId}" from klo.yaml? Ingested artifacts will remain in .klo/.`, + message: `Remove connection "${args.connectionId}" from ktx.yaml? Ingested artifacts will remain in .ktx/.`, initialValue: false, }); if (!confirmed) { @@ -388,21 +388,21 @@ export async function runKloConnection( connections, }; await project.fileStore.writeFile( - 'klo.yaml', - serializeKloProjectConfig(nextConfig), - 'klo', - 'klo@example.com', - `Remove KLO connection: ${args.connectionId}`, + 'ktx.yaml', + serializeKtxProjectConfig(nextConfig), + 'ktx', + 'ktx@example.com', + `Remove KTX connection: ${args.connectionId}`, ); - io.stdout.write('Connection removed from klo.yaml.\n'); - io.stdout.write('Ingested artifacts from this connection remain in .klo/. Run klo dev artifacts to inspect.\n'); + io.stdout.write('Connection removed from ktx.yaml.\n'); + io.stdout.write('Ingested artifacts from this connection remain in .ktx/. Run ktx dev artifacts to inspect.\n'); return 0; } const result = await testNativeConnection( project, args.connectionId, - deps.createScanConnector ?? createKloCliScanConnector, + deps.createScanConnector ?? createKtxCliScanConnector, ); io.stdout.write(`Connection test passed: ${args.connectionId}\n`); io.stdout.write(`Driver: ${result.driver}\n`); diff --git a/packages/cli/src/context-build-view.test.ts b/packages/cli/src/context-build-view.test.ts index 1176113c..c14102ec 100644 --- a/packages/cli/src/context-build-view.test.ts +++ b/packages/cli/src/context-build-view.test.ts @@ -1,6 +1,6 @@ -import { buildDefaultKloProjectConfig, type KloProjectConfig } from '@klo/context/project'; +import { buildDefaultKtxProjectConfig, type KtxProjectConfig } from '@ktx/context/project'; import { describe, expect, it, vi } from 'vitest'; -import type { KloPublicIngestProject, KloPublicIngestTargetResult } from './public-ingest.js'; +import type { KtxPublicIngestProject, KtxPublicIngestTargetResult } from './public-ingest.js'; import { extractProgressMessage, initViewState, @@ -32,17 +32,17 @@ function makeIo(options: { isTTY?: boolean } = {}) { }; } -function projectWithConnections(connections: KloProjectConfig['connections']): KloPublicIngestProject { +function projectWithConnections(connections: KtxProjectConfig['connections']): KtxPublicIngestProject { return { projectDir: '/tmp/project', config: { - ...buildDefaultKloProjectConfig('warehouse'), + ...buildDefaultKtxProjectConfig('warehouse'), connections, }, }; } -function successResult(connectionId: string, driver: string, operation: 'scan' | 'source-ingest'): KloPublicIngestTargetResult { +function successResult(connectionId: string, driver: string, operation: 'scan' | 'source-ingest'): KtxPublicIngestTargetResult { return { connectionId, driver, @@ -55,7 +55,7 @@ function successResult(connectionId: string, driver: string, operation: 'scan' | }; } -function failedResult(connectionId: string, driver: string, operation: 'scan' | 'source-ingest'): KloPublicIngestTargetResult { +function failedResult(connectionId: string, driver: string, operation: 'scan' | 'source-ingest'): KtxPublicIngestTargetResult { return { connectionId, driver, @@ -78,7 +78,7 @@ describe('extractProgressMessage', () => { }); it('returns null for non-progress output', () => { - expect(extractProgressMessage('KLO scan completed\n')).toBeNull(); + expect(extractProgressMessage('KTX scan completed\n')).toBeNull(); }); }); @@ -137,7 +137,7 @@ describe('renderContextBuildView', () => { ]); const output = renderContextBuildView(state, { styled: false }); - expect(output).toContain('Building KLO context'); + expect(output).toContain('Building KTX context'); expect(output).toContain('Primary sources:'); expect(output).toContain('warehouse'); expect(output).toContain('queued'); @@ -237,7 +237,7 @@ describe('runContextBuild', () => { ); const output = io.stdout(); - expect(output).toContain('Building KLO context'); + expect(output).toContain('Building KTX context'); expect(output).toContain('Primary sources:'); expect(output).toContain('warehouse'); expect(output).toContain('Context sources:'); @@ -297,7 +297,7 @@ describe('runContextBuild', () => { expect(mockExit).toHaveBeenCalledWith(0); expect(io.stdout()).toContain('Context build continuing in the background.'); - expect(io.stdout()).toContain('Resume: klo setup --project-dir /tmp/project'); + expect(io.stdout()).toContain('Resume: ktx setup --project-dir /tmp/project'); mockExit.mockRestore(); }); }); diff --git a/packages/cli/src/context-build-view.ts b/packages/cli/src/context-build-view.ts index 28a7c5b7..2e39537c 100644 --- a/packages/cli/src/context-build-view.ts +++ b/packages/cli/src/context-build-view.ts @@ -1,12 +1,12 @@ import { spawn } from 'node:child_process'; import { mkdirSync, openSync } from 'node:fs'; import { join, resolve } from 'node:path'; -import type { KloCliIo } from './index.js'; +import type { KtxCliIo } from './index.js'; import type { - KloPublicIngestArgs, - KloPublicIngestPlanTarget, - KloPublicIngestProject, - KloPublicIngestTargetResult, + KtxPublicIngestArgs, + KtxPublicIngestPlanTarget, + KtxPublicIngestProject, + KtxPublicIngestTargetResult, } from './public-ingest.js'; import { buildPublicIngestPlan, executePublicIngestTarget } from './public-ingest.js'; import { formatDuration } from './demo-metrics.js'; @@ -18,7 +18,7 @@ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', const ESC = String.fromCharCode(0x1b); export interface ContextBuildTargetState { - target: KloPublicIngestPlanTarget; + target: KtxPublicIngestPlanTarget; status: 'queued' | 'running' | 'done' | 'failed'; detailLine: string | null; summaryText: string | null; @@ -131,7 +131,7 @@ function renderTargetGroup( } function resumeCommand(projectDir?: string): string { - return projectDir ? `klo setup --project-dir ${projectDir}` : 'klo setup'; + return projectDir ? `ktx setup --project-dir ${projectDir}` : 'ktx setup'; } export function renderContextBuildView( @@ -142,7 +142,7 @@ export function renderContextBuildView( const width = columnWidth(state); const lines: string[] = [ '', - 'Building KLO context', + 'Building KTX context', '─────────────────────', ...renderTargetGroup('Primary sources', state.primarySources, state.frame, styled, width), ...renderTargetGroup('Context sources', state.contextSources, state.frame, styled, width), @@ -184,7 +184,7 @@ export function parseIngestSummary(output: string): string | null { } interface CapturedIo { - io: KloCliIo; + io: KtxCliIo; captured(): string; } @@ -212,7 +212,7 @@ function createCaptureIo(onProgress: (message: string) => void, isTTY: boolean): // --- Repaint --- -function createRepainter(io: KloCliIo) { +function createRepainter(io: KtxCliIo) { let lastLineCount = 0; return { @@ -229,7 +229,7 @@ function createRepainter(io: KloCliIo) { // --- Background build --- -function resolveKloEntryScript(): string | null { +function resolveKtxEntryScript(): string | null { const argv1 = process.argv[1]; if (argv1 && (argv1.endsWith('.js') || argv1.endsWith('.ts') || argv1.endsWith('.mjs'))) { return argv1; @@ -238,11 +238,11 @@ function resolveKloEntryScript(): string | null { } function spawnBackgroundBuild(projectDir: string): { logPath: string } | null { - const entryScript = resolveKloEntryScript(); + const entryScript = resolveKtxEntryScript(); if (!entryScript) return null; const resolvedDir = resolve(projectDir); - const logDir = join(resolvedDir, '.klo', 'setup'); + const logDir = join(resolvedDir, '.ktx', 'setup'); mkdirSync(logDir, { recursive: true }); const logPath = join(logDir, 'context-build.log'); const logFd = openSync(logPath, 'w'); @@ -280,11 +280,11 @@ function defaultSetupKeystroke(onDetach: () => void, onCtrlC: () => void): (() = // --- Orchestration --- -function makeTargetState(target: KloPublicIngestPlanTarget): ContextBuildTargetState { +function makeTargetState(target: KtxPublicIngestPlanTarget): ContextBuildTargetState { return { target, status: 'queued', detailLine: null, summaryText: null, startedAt: null, elapsedMs: 0 }; } -export function initViewState(targets: KloPublicIngestPlanTarget[]): ContextBuildViewState { +export function initViewState(targets: KtxPublicIngestPlanTarget[]): ContextBuildViewState { return { primarySources: targets.filter((t) => t.operation === 'scan').map(makeTargetState), contextSources: targets.filter((t) => t.operation === 'source-ingest').map(makeTargetState), @@ -293,9 +293,9 @@ export function initViewState(targets: KloPublicIngestPlanTarget[]): ContextBuil } export async function runContextBuild( - project: KloPublicIngestProject, + project: KtxPublicIngestProject, args: ContextBuildArgs, - io: KloCliIo, + io: KtxCliIo, deps: ContextBuildDeps = {}, ): Promise { const plan = buildPublicIngestPlan(project, { projectDir: args.projectDir, all: true }); @@ -339,7 +339,7 @@ export async function runContextBuild( const bg = spawnBackgroundBuild(args.projectDir); io.stdout.write('\n\nContext build continuing in the background.\n'); if (bg) io.stdout.write(`Log: ${bg.logPath}\n`); - io.stdout.write(`Status: klo setup context status --project-dir ${resolve(args.projectDir)}\n`); + io.stdout.write(`Status: ktx setup context status --project-dir ${resolve(args.projectDir)}\n`); io.stdout.write(`Resume: ${resumeCommand(args.projectDir)}\n`); process.exit(0); }, @@ -351,7 +351,7 @@ export async function runContextBuild( }, ); } - const runArgs: Extract = { + const runArgs: Extract = { command: 'run', projectDir: args.projectDir, all: true, diff --git a/packages/cli/src/demo-assets.test.ts b/packages/cli/src/demo-assets.test.ts index ebadff4e..d8307c66 100644 --- a/packages/cli/src/demo-assets.test.ts +++ b/packages/cli/src/demo-assets.test.ts @@ -28,7 +28,7 @@ async function readPackagedJson(relativePath: string): Promise { } describe('demo assets', () => { - const projectDir = join(tmpdir(), `klo-demo-assets-${process.pid}`); + const projectDir = join(tmpdir(), `ktx-demo-assets-${process.pid}`); afterEach(async () => { await rm(projectDir, { recursive: true, force: true }); @@ -36,8 +36,8 @@ describe('demo assets', () => { it('resolves the default demo root under the OS temp directory', () => { const dir = defaultDemoProjectDir(); - expect(dir.startsWith(join(tmpdir(), 'klo-demo-'))).toBe(true); - expect(dir).toMatch(/klo-demo-[a-f0-9]{8}$/); + expect(dir.startsWith(join(tmpdir(), 'ktx-demo-'))).toBe(true); + expect(dir).toMatch(/ktx-demo-[a-f0-9]{8}$/); }); it('exports the packaged Orbit demo identity', () => { @@ -124,7 +124,7 @@ describe('demo assets', () => { await expect(access(join(projectDir, 'raw-sources'))).resolves.toBeUndefined(); await expect(access(join(projectDir, '_schema'))).rejects.toMatchObject({ code: 'ENOENT' }); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('backend: anthropic'); expect(config).toContain('api_key: env:ANTHROPIC_API_KEY'); expect(config).not.toContain('sk-ant-'); @@ -187,7 +187,7 @@ describe('demo assets', () => { await expect(inspectDemoProjectState(projectDir)).resolves.toEqual({ status: 'missing', projectDir, - missing: ['klo.yaml', 'demo.db', 'state.sqlite', 'replays/replay.memory-flow.v1.json'], + missing: ['ktx.yaml', 'demo.db', 'state.sqlite', 'replays/replay.memory-flow.v1.json'], }); await ensureDemoProject({ projectDir, force: false }); @@ -210,7 +210,7 @@ describe('demo assets', () => { await rm(join(projectDir, 'demo.db'), { force: true }); await expect(resetDemoProject({ projectDir, force: false })).rejects.toThrow( - `klo setup demo reset is destructive; pass --force to recreate ${projectDir}`, + `ktx setup demo reset is destructive; pass --force to recreate ${projectDir}`, ); await expect(resetDemoProject({ projectDir, force: true })).resolves.toMatchObject({ projectDir }); @@ -218,10 +218,10 @@ describe('demo assets', () => { await expect(inspectDemoProjectState(projectDir)).resolves.toMatchObject({ status: 'ready' }); }); - it('preserves a user-edited klo.yaml across reset --force', async () => { + it('preserves a user-edited ktx.yaml across reset --force', async () => { await ensureDemoProject({ projectDir, force: false }); const customConfig = [ - 'project: klo-demo-orbit', + 'project: ktx-demo-orbit', 'connections:', ` ${DEMO_CONNECTION_ID}:`, ' driver: sqlite', @@ -232,7 +232,7 @@ describe('demo assets', () => { ' search: sqlite-fts5', ' git:', ' auto_commit: true', - ' author: klo ', + ' author: ktx ', 'llm:', ' provider:', ' backend: vertex', @@ -253,20 +253,20 @@ describe('demo assets', () => { ' failureMode: continue', '', ].join('\n'); - await writeFile(join(projectDir, 'klo.yaml'), customConfig, 'utf-8'); + await writeFile(join(projectDir, 'ktx.yaml'), customConfig, 'utf-8'); await resetDemoProject({ projectDir, force: true }); - const preserved = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const preserved = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(preserved).toBe(customConfig); expect(preserved).toContain('backend: vertex'); expect(preserved).not.toContain('backend: anthropic'); await expect(inspectDemoProjectState(projectDir)).resolves.toMatchObject({ status: 'ready' }); }); - it('still writes the default klo.yaml on reset when none exists', async () => { + it('still writes the default ktx.yaml on reset when none exists', async () => { await resetDemoProject({ projectDir, force: true }); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('backend: anthropic'); }); }); diff --git a/packages/cli/src/demo-assets.ts b/packages/cli/src/demo-assets.ts index ee4867c1..c0127d41 100644 --- a/packages/cli/src/demo-assets.ts +++ b/packages/cli/src/demo-assets.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { randomBytes } from 'node:crypto'; -import type { MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { loadDemoReplayFile, loadLatestDemoReplay } from './demo-replay-store.js'; interface DemoProjectResult { @@ -33,7 +33,7 @@ export const DEMO_REPLAY_FILE = 'replay.memory-flow.v1.json'; export const DEMO_FULL_JOB_ID = 'demo-full-ingest'; const REQUIRED_BASE_PROJECT_PATHS = [ - 'klo.yaml', + 'ktx.yaml', 'demo.db', 'state.sqlite', join('replays', DEMO_REPLAY_FILE), @@ -70,7 +70,7 @@ async function exists(path: string): Promise { export function defaultDemoProjectDir(): string { const suffix = randomBytes(4).toString('hex'); - return join(tmpdir(), `klo-demo-${suffix}`); + return join(tmpdir(), `ktx-demo-${suffix}`); } export async function inspectDemoProjectState(projectDir: string): Promise { @@ -97,10 +97,10 @@ export async function inspectDemoProjectState(projectDir: string): Promise { const projectDir = resolve(options.projectDir); if (!options.force) { - throw new Error(`klo setup demo reset is destructive; pass --force to recreate ${projectDir}`); + throw new Error(`ktx setup demo reset is destructive; pass --force to recreate ${projectDir}`); } - const preservedConfig = await readExistingConfig(join(projectDir, 'klo.yaml')); + const preservedConfig = await readExistingConfig(join(projectDir, 'ktx.yaml')); const result = await ensureDemoProject({ projectDir, force: true }); if (preservedConfig !== null) { await writeFile(result.configPath, preservedConfig, 'utf-8'); @@ -118,7 +118,7 @@ async function readExistingConfig(configPath: string): Promise { function demoConfig(databasePath: string): string { return [ - 'project: klo-demo-orbit', + 'project: ktx-demo-orbit', 'connections:', ` ${DEMO_CONNECTION_ID}:`, ' driver: sqlite', @@ -129,7 +129,7 @@ function demoConfig(databasePath: string): string { ' search: sqlite-fts5', ' git:', ' auto_commit: true', - ' author: klo ', + ' author: ktx ', 'llm:', ' provider:', ' backend: anthropic', @@ -185,7 +185,7 @@ async function assertPackagedSeededAssetsPresent(): Promise { export async function ensureDemoProject(options: EnsureDemoProjectOptions): Promise { const projectDir = resolve(options.projectDir); - const configPath = join(projectDir, 'klo.yaml'); + const configPath = join(projectDir, 'ktx.yaml'); if (!options.force && (await exists(configPath))) { throw new Error(`Demo project already exists at ${projectDir}; pass --force to recreate it`); } @@ -237,7 +237,7 @@ export async function ensureSeededDemoProject(options: EnsureDemoProjectOptions) if (!options.force && error instanceof Error && error.message.includes('Demo project already exists')) { return { projectDir, - configPath: join(projectDir, 'klo.yaml'), + configPath: join(projectDir, 'ktx.yaml'), databasePath: join(projectDir, 'demo.db'), replayPath: join(projectDir, 'replays', DEMO_REPLAY_FILE), }; diff --git a/packages/cli/src/demo-full.test.ts b/packages/cli/src/demo-full.test.ts index b1911897..7d3887e8 100644 --- a/packages/cli/src/demo-full.test.ts +++ b/packages/cli/src/demo-full.test.ts @@ -1,7 +1,7 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { IngestReportSnapshot, LocalIngestResult, RunLocalIngestOptions } from '@klo/context/ingest'; +import type { IngestReportSnapshot, LocalIngestResult, RunLocalIngestOptions } from '@ktx/context/ingest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { DEMO_ADAPTER, DEMO_CONNECTION_ID, DEMO_FULL_JOB_ID, ensureDemoProject } from './demo-assets.js'; import { @@ -69,7 +69,7 @@ describe('full demo helpers', () => { let projectDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-demo-full-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-demo-full-')); projectDir = join(tempDir, 'demo'); await ensureDemoProject({ projectDir, force: false }); }); @@ -79,18 +79,18 @@ describe('full demo helpers', () => { }); it('fails full mode with exact Anthropic env guidance when the key is missing', async () => { - const project = await import('@klo/context/project').then((mod) => mod.loadKloProject({ projectDir })); + const project = await import('@ktx/context/project').then((mod) => mod.loadKtxProject({ projectDir })); expect(() => assertFullDemoCredentials(project, {})).toThrow( - 'klo setup demo --mode full needs ANTHROPIC_API_KEY. Export ANTHROPIC_API_KEY and rerun `klo setup demo --mode full --no-input`, or run `klo setup demo --mode seeded --no-input` without credentials.', + 'ktx setup demo --mode full needs ANTHROPIC_API_KEY. Export ANTHROPIC_API_KEY and rerun `ktx setup demo --mode full --no-input`, or run `ktx setup demo --mode seeded --no-input` without credentials.', ); }); it('respects an existing gateway provider project for full mode', async () => { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ - 'project: klo-demo-orbit', + 'project: ktx-demo-orbit', 'connections:', ' orbit_demo:', ' driver: sqlite', @@ -109,22 +109,22 @@ describe('full demo helpers', () => { ].join('\n'), 'utf-8', ); - const project = await import('@klo/context/project').then((mod) => mod.loadKloProject({ projectDir })); + const project = await import('@ktx/context/project').then((mod) => mod.loadKtxProject({ projectDir })); expect(() => assertFullDemoCredentials(project, {})).not.toThrow(); expect(fullDemoCredentialStatus(project, {})).toEqual({ status: 'ready' }); }); it('reports full-demo credential status without throwing', async () => { - const project = await import('@klo/context/project').then((mod) => mod.loadKloProject({ projectDir })); + const project = await import('@ktx/context/project').then((mod) => mod.loadKtxProject({ projectDir })); expect(fullDemoCredentialStatus(project, {})).toEqual({ status: 'missing-anthropic-key' }); expect(fullDemoCredentialStatus(project, { ANTHROPIC_API_KEY: 'sk-ant-test' })).toEqual({ status: 'ready' }); // pragma: allowlist secret await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ - 'project: klo-demo-orbit', + 'project: ktx-demo-orbit', 'connections:', ' orbit_demo:', ' driver: sqlite', @@ -136,7 +136,7 @@ describe('full demo helpers', () => { ].join('\n'), 'utf-8', ); - const disabledProject = await import('@klo/context/project').then((mod) => mod.loadKloProject({ projectDir })); + const disabledProject = await import('@ktx/context/project').then((mod) => mod.loadKtxProject({ projectDir })); expect(fullDemoCredentialStatus(disabledProject, {})).toEqual({ status: 'unsupported-provider', provider: 'none' }); }); @@ -192,9 +192,9 @@ describe('full demo helpers', () => { expect(summary).toContain('Full demo ingest: done'); expect(summary).toContain('Saved memory: 1 wiki, 1 semantic layer'); expect(summary).toContain('Provenance rows: 2'); - expect(summary).toContain('Next: klo setup demo inspect'); - expect(summary).toContain('Shows the files, semantic-layer sources, and memory KLO just produced.'); - expect(summary).toContain('Next: klo setup demo replay'); + expect(summary).toContain('Next: ktx setup demo inspect'); + expect(summary).toContain('Shows the files, semantic-layer sources, and memory KTX just produced.'); + expect(summary).toContain('Next: ktx setup demo replay'); expect(summary).toContain('Replays the same visual story without calling the LLM again.'); expect(summary).not.toContain('--viz'); }); diff --git a/packages/cli/src/demo-full.ts b/packages/cli/src/demo-full.ts index 3d6beff8..6b483d73 100644 --- a/packages/cli/src/demo-full.ts +++ b/packages/cli/src/demo-full.ts @@ -1,4 +1,4 @@ -import { resolveKloConfigReference } from '@klo/context/core'; +import { resolveKtxConfigReference } from '@ktx/context/core'; import { createMemoryFlowLiveBuffer, ingestReportToMemoryFlowReplay, @@ -7,12 +7,12 @@ import { type LocalIngestResult, type MemoryFlowReplayInput, type RunLocalIngestOptions, -} from '@klo/context/ingest'; -import { loadKloProject, type KloLocalProject } from '@klo/context/project'; -import { runLocalScan, type LocalScanRunResult } from '@klo/context/scan'; +} from '@ktx/context/ingest'; +import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project'; +import { runLocalScan, type LocalScanRunResult } from '@ktx/context/scan'; import { DEMO_ADAPTER, DEMO_CONNECTION_ID, DEMO_FULL_JOB_ID, ensureDemoProject } from './demo-assets.js'; import { runDemoScan } from './demo-scan.js'; -import { createKloCliLocalIngestAdapters } from './local-adapters.js'; +import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; import { formatNextStepLines } from './next-steps.js'; interface DemoFullOptions { @@ -24,7 +24,7 @@ interface DemoFullOptions { } export interface DemoFullResult { - project: KloLocalProject; + project: KtxLocalProject; scan: LocalScanRunResult; ingest: LocalIngestResult; report: IngestReportSnapshot; @@ -54,7 +54,7 @@ function savedCounts(report: IngestReportSnapshot): { wikiCount: number; slCount } export function fullDemoCredentialStatus( - project: KloLocalProject, + project: KtxLocalProject, env: NodeJS.ProcessEnv = process.env, ): FullDemoCredentialStatus { const llm = project.config.llm; @@ -62,14 +62,14 @@ export function fullDemoCredentialStatus( return { status: 'unsupported-provider', provider: llm.provider.backend }; } - if (llm.provider.backend === 'anthropic' && !resolveKloConfigReference(llm.provider.anthropic?.api_key, env)) { + if (llm.provider.backend === 'anthropic' && !resolveKtxConfigReference(llm.provider.anthropic?.api_key, env)) { return { status: 'missing-anthropic-key' }; } return { status: 'ready' }; } -export function assertFullDemoCredentials(project: KloLocalProject, env: NodeJS.ProcessEnv = process.env): void { +export function assertFullDemoCredentials(project: KtxLocalProject, env: NodeJS.ProcessEnv = process.env): void { const llm = project.config.llm; const status = fullDemoCredentialStatus(project, env); if (status.status === 'ready') { @@ -78,13 +78,13 @@ export function assertFullDemoCredentials(project: KloLocalProject, env: NodeJS. if (status.status === 'unsupported-provider') { throw new Error( - 'klo setup demo --mode full requires llm.provider.backend: anthropic, vertex, or gateway. Run `klo setup demo init --force --no-input` to recreate the demo config, or run `klo setup demo --mode seeded --no-input` without credentials.', + 'ktx setup demo --mode full requires llm.provider.backend: anthropic, vertex, or gateway. Run `ktx setup demo init --force --no-input` to recreate the demo config, or run `ktx setup demo --mode seeded --no-input` without credentials.', ); } if (llm.provider.backend === 'anthropic') { throw new Error( - 'klo setup demo --mode full needs ANTHROPIC_API_KEY. Export ANTHROPIC_API_KEY and rerun `klo setup demo --mode full --no-input`, or run `klo setup demo --mode seeded --no-input` without credentials.', + 'ktx setup demo --mode full needs ANTHROPIC_API_KEY. Export ANTHROPIC_API_KEY and rerun `ktx setup demo --mode full --no-input`, or run `ktx setup demo --mode seeded --no-input` without credentials.', ); } } @@ -110,7 +110,7 @@ function initialFullReplay(projectDir: string): MemoryFlowReplayInput { export async function runDemoFull(options: DemoFullOptions): Promise { await ensureDemoProjectForReuse(options.projectDir); - const project = await loadKloProject({ projectDir: options.projectDir }); + const project = await loadKtxProject({ projectDir: options.projectDir }); assertFullDemoCredentials(project, options.env); const { result: scan } = await runDemoScan({ @@ -125,7 +125,7 @@ export async function runDemoFull(options: DemoFullOptions): Promise wu.actions.length > 0).length; - const lines: string[] = ['', '★ KLO finished ingesting your data', '']; + const lines: string[] = ['', '★ KTX finished ingesting your data', '']; if (areasAnalyzed > 0) { lines.push(` ✓ Analyzed ${areasAnalyzed} business area${areasAnalyzed === 1 ? '' : 's'}`); @@ -187,7 +187,7 @@ export function formatCleanDemoSummary(report: IngestReportSnapshot, projectDir: lines.push(''); if (counts.slCount > 0 || counts.wikiCount > 0) { - lines.push(' KLO created:'); + lines.push(' KTX created:'); if (counts.slCount > 0) lines.push(` 📊 ${counts.slCount} query definition${counts.slCount === 1 ? '' : 's'} — so agents can write accurate SQL for your data`); if (counts.wikiCount > 0) lines.push(` 📝 ${counts.wikiCount} knowledge page${counts.wikiCount === 1 ? '' : 's'} — so agents understand your business context`); lines.push(''); @@ -206,7 +206,7 @@ export function formatCleanDemoSummary(report: IngestReportSnapshot, projectDir: lines.push(' What to do next:'); lines.push(...formatNextStepLines()); lines.push(''); - lines.push(` Your KLO project files are at: ${projectDir}`); + lines.push(` Your KTX project files are at: ${projectDir}`); lines.push(''); return lines.join('\n'); diff --git a/packages/cli/src/demo-interaction.test.ts b/packages/cli/src/demo-interaction.test.ts index 6c1f6828..4ab89adc 100644 --- a/packages/cli/src/demo-interaction.test.ts +++ b/packages/cli/src/demo-interaction.test.ts @@ -21,7 +21,7 @@ describe('demo interaction decisions', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-demo-interaction-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-demo-interaction-')); }); afterEach(async () => { @@ -53,7 +53,7 @@ describe('demo interaction decisions', () => { prompts: createTestDemoPromptAdapter({ choices: [] }), }), ).rejects.toThrow( - `Demo project is not ready at ${tempDir}: missing demo.db. Run klo setup demo reset --project-dir ${tempDir} --force --no-input`, + `Demo project is not ready at ${tempDir}: missing demo.db. Run ktx setup demo reset --project-dir ${tempDir} --force --no-input`, ); }); diff --git a/packages/cli/src/demo-interaction.ts b/packages/cli/src/demo-interaction.ts index b8e05f60..7702498c 100644 --- a/packages/cli/src/demo-interaction.ts +++ b/packages/cli/src/demo-interaction.ts @@ -2,7 +2,7 @@ import { cancel, confirm, isCancel, password, select, text } from '@clack/prompt import type { Option as ClackOption } from '@clack/prompts'; import { resolve } from 'node:path'; import { inspectDemoProjectState } from './demo-assets.js'; -import type { KloDemoInputMode } from './demo.js'; +import type { KtxDemoInputMode } from './demo.js'; import { withMenuOptionsSpacing } from './prompt-navigation.js'; type DemoPromptOption = ClackOption; @@ -29,7 +29,7 @@ type FullCredentialDecision = | { action: 'run-mode'; mode: 'seeded' | 'replay' } | { action: 'cancel' }; -function isInteractive(inputMode: KloDemoInputMode | undefined, io: DemoInteractiveIo): boolean { +function isInteractive(inputMode: KtxDemoInputMode | undefined, io: DemoInteractiveIo): boolean { return inputMode !== 'disabled' && io.stdin?.isTTY === true && io.stdout.isTTY === true; } @@ -97,7 +97,7 @@ export function createTestDemoPromptAdapter(options: { export async function chooseDemoProjectForInteractiveRun(options: { projectDir: string; - inputMode?: KloDemoInputMode; + inputMode?: KtxDemoInputMode; io: DemoInteractiveIo; prompts?: DemoPromptAdapter; }): Promise { @@ -108,7 +108,7 @@ export async function chooseDemoProjectForInteractiveRun(options: { if (!isInteractive(options.inputMode, options.io)) { if (state.status === 'corrupt') { throw new Error( - `Demo project is not ready at ${projectDir}: missing ${state.missing.join(', ')}. Run klo setup demo reset --project-dir ${projectDir} --force --no-input`, + `Demo project is not ready at ${projectDir}: missing ${state.missing.join(', ')}. Run ktx setup demo reset --project-dir ${projectDir} --force --no-input`, ); } return { action: 'use', projectDir, reset: false }; @@ -163,7 +163,7 @@ export async function chooseDemoProjectForInteractiveRun(options: { export async function resolveFullCredentialDecision(options: { needsAnthropicKey: boolean; - inputMode?: KloDemoInputMode; + inputMode?: KtxDemoInputMode; io: DemoInteractiveIo; env: NodeJS.ProcessEnv; prompts?: DemoPromptAdapter; diff --git a/packages/cli/src/demo-metrics.test.ts b/packages/cli/src/demo-metrics.test.ts index c7fd11f7..42be0cde 100644 --- a/packages/cli/src/demo-metrics.test.ts +++ b/packages/cli/src/demo-metrics.test.ts @@ -1,4 +1,4 @@ -import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { describe, expect, it } from 'vitest'; import { buildDemoMetrics, diff --git a/packages/cli/src/demo-metrics.ts b/packages/cli/src/demo-metrics.ts index dba511ec..4fee8de3 100644 --- a/packages/cli/src/demo-metrics.ts +++ b/packages/cli/src/demo-metrics.ts @@ -1,4 +1,4 @@ -import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; const DEFAULT_INPUT_TOKENS_PER_STEP = 4500; const DEFAULT_OUTPUT_TOKENS_PER_STEP = 700; diff --git a/packages/cli/src/demo-progress.test.ts b/packages/cli/src/demo-progress.test.ts index 4cfd1cc9..a7605990 100644 --- a/packages/cli/src/demo-progress.test.ts +++ b/packages/cli/src/demo-progress.test.ts @@ -1,4 +1,4 @@ -import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { describe, expect, it } from 'vitest'; import { createPlainProgressEmitter, formatMemoryFlowEventLine } from './demo-progress.js'; diff --git a/packages/cli/src/demo-progress.ts b/packages/cli/src/demo-progress.ts index 8cce9e87..82d01163 100644 --- a/packages/cli/src/demo-progress.ts +++ b/packages/cli/src/demo-progress.ts @@ -1,5 +1,5 @@ -import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; -import type { KloDemoIo } from './demo.js'; +import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; +import type { KtxDemoIo } from './demo.js'; function plural(n: number, one: string, many = `${one}s`): string { return `${n} ${n === 1 ? one : many}`; @@ -62,7 +62,7 @@ export function formatMemoryFlowEventLine(event: MemoryFlowEvent): string | null } } -export function createPlainProgressEmitter(io: KloDemoIo): (snapshot: MemoryFlowReplayInput) => void { +export function createPlainProgressEmitter(io: KtxDemoIo): (snapshot: MemoryFlowReplayInput) => void { let printed = 0; return (snapshot) => { while (printed < snapshot.events.length) { diff --git a/packages/cli/src/demo-replay-store.test.ts b/packages/cli/src/demo-replay-store.test.ts index 9422fcbf..4ec9e236 100644 --- a/packages/cli/src/demo-replay-store.test.ts +++ b/packages/cli/src/demo-replay-store.test.ts @@ -1,7 +1,7 @@ import { mkdtemp, readFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { type MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import { type MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { describe, expect, it } from 'vitest'; import { DEMO_LATEST_REPLAY_FILE, loadLatestDemoReplay, writeDemoReplay } from './demo-replay-store.js'; @@ -35,7 +35,7 @@ function replay(overrides: Partial = {}): MemoryFlowRepla describe('demo replay store', () => { it('writes a versioned replay file and updates latest', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-demo-replay-store-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-demo-replay-store-')); const saved = await writeDemoReplay(projectDir, replay(), { label: 'full' }); @@ -53,7 +53,7 @@ describe('demo replay store', () => { }); it('returns null when no latest local replay exists', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-demo-replay-store-empty-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-demo-replay-store-empty-')); await expect(loadLatestDemoReplay(projectDir)).resolves.toBeNull(); }); diff --git a/packages/cli/src/demo-replay-store.ts b/packages/cli/src/demo-replay-store.ts index 61a9b5bb..46e067bd 100644 --- a/packages/cli/src/demo-replay-store.ts +++ b/packages/cli/src/demo-replay-store.ts @@ -1,7 +1,7 @@ import { constants as fsConstants } from 'node:fs'; import { access, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; -import { parseMemoryFlowReplayInput, type MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import { parseMemoryFlowReplayInput, type MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; interface StoredMemoryFlowReplayFile { memoryFlowReplaySchemaVersion: 1; diff --git a/packages/cli/src/demo-scan.test.ts b/packages/cli/src/demo-scan.test.ts index e33334a9..3b7a8e43 100644 --- a/packages/cli/src/demo-scan.test.ts +++ b/packages/cli/src/demo-scan.test.ts @@ -5,7 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest'; import { findLatestDemoScanReport, runDemoScan } from './demo-scan.js'; describe('demo scan helpers', () => { - const projectDir = join(tmpdir(), `klo-demo-scan-${process.pid}`); + const projectDir = join(tmpdir(), `ktx-demo-scan-${process.pid}`); afterEach(async () => { await rm(projectDir, { recursive: true, force: true }); diff --git a/packages/cli/src/demo-scan.ts b/packages/cli/src/demo-scan.ts index 1237d328..5dd4b182 100644 --- a/packages/cli/src/demo-scan.ts +++ b/packages/cli/src/demo-scan.ts @@ -1,9 +1,9 @@ -import { getLocalIngestStatus, type IngestReportSnapshot, type MemoryFlowReplayInput } from '@klo/context/ingest'; -import { loadKloProject, type KloLocalProject } from '@klo/context/project'; -import { runLocalScan, type KloScanReport, type LocalScanRunResult } from '@klo/context/scan'; +import { getLocalIngestStatus, type IngestReportSnapshot, type MemoryFlowReplayInput } from '@ktx/context/ingest'; +import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project'; +import { runLocalScan, type KtxScanReport, type LocalScanRunResult } from '@ktx/context/scan'; import { DEMO_ADAPTER, DEMO_CONNECTION_ID, DEMO_FULL_JOB_ID, ensureDemoProject } from './demo-assets.js'; import { loadLatestDemoReplay } from './demo-replay-store.js'; -import { createKloCliLocalIngestAdapters } from './local-adapters.js'; +import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; interface DemoScanOptions { projectDir: string; @@ -13,13 +13,13 @@ interface DemoScanOptions { } interface DemoScanResult { - project: KloLocalProject; + project: KtxLocalProject; result: LocalScanRunResult; } interface DemoInspectSummary { projectDir: string; - scanReport: KloScanReport | null; + scanReport: KtxScanReport | null; fullReport: IngestReportSnapshot | null; semanticLayerFileCount: number; knowledgeFileCount: number; @@ -28,7 +28,7 @@ interface DemoInspectSummary { } interface DemoInspectDeps { - findFullReport?: (project: KloLocalProject) => Promise; + findFullReport?: (project: KtxLocalProject) => Promise; } async function ensureDemoProjectForReuse(projectDir: string): Promise { @@ -40,36 +40,36 @@ async function ensureDemoProjectForReuse(projectDir: string): Promise { }); } -async function loadReadyDemoProject(projectDir: string): Promise { +async function loadReadyDemoProject(projectDir: string): Promise { try { - return await loadKloProject({ projectDir }); + return await loadKtxProject({ projectDir }); } catch (error) { const reason = error instanceof Error ? error.message : String(error); throw new Error( - `Demo project is not ready at ${projectDir}: ${reason}. Run klo setup demo init --project-dir ${projectDir} --force --no-input to recreate it.`, + `Demo project is not ready at ${projectDir}: ${reason}. Run ktx setup demo init --project-dir ${projectDir} --force --no-input to recreate it.`, ); } } -function reportDiff(report: KloScanReport): string { +function reportDiff(report: KtxScanReport): string { return `+${report.diffSummary.tablesAdded}/~${report.diffSummary.tablesModified}/-${report.diffSummary.tablesDeleted}/=${report.diffSummary.tablesUnchanged}`; } -function jsonReport(raw: string, path: string): KloScanReport { +function jsonReport(raw: string, path: string): KtxScanReport { try { - return JSON.parse(raw) as KloScanReport; + return JSON.parse(raw) as KtxScanReport; } catch (error) { const reason = error instanceof Error ? error.message : String(error); throw new Error(`Invalid demo scan report at ${path}: ${reason}`); } } -async function countFiles(project: KloLocalProject, root: string, predicate: (path: string) => boolean): Promise { +async function countFiles(project: KtxLocalProject, root: string, predicate: (path: string) => boolean): Promise { const { files } = await project.fileStore.listFiles(root, true); return files.filter(predicate).length; } -async function findFullDemoReport(project: KloLocalProject): Promise { +async function findFullDemoReport(project: KtxLocalProject): Promise { return getLocalIngestStatus(project, DEMO_FULL_JOB_ID); } @@ -92,13 +92,13 @@ export async function runDemoScan(options: DemoScanOptions): Promise { +export async function findLatestDemoScanReport(projectDir: string): Promise { const project = await loadReadyDemoProject(projectDir); const root = `raw-sources/${DEMO_CONNECTION_ID}/${DEMO_ADAPTER}`; const { files } = await project.fileStore.listFiles(root, true); @@ -117,7 +117,7 @@ export async function findLatestDemoScanReport(projectDir: string): Promise { const project = projectOverride ?? (await loadReadyDemoProject(projectDir)); @@ -143,7 +143,7 @@ export async function inspectDemoProject( }; } -export function formatDemoScanSummary(report: KloScanReport): string { +export function formatDemoScanSummary(report: KtxScanReport): string { return [ 'Demo scan: done', `Connection: ${report.connectionId}`, @@ -152,7 +152,7 @@ export function formatDemoScanSummary(report: KloScanReport): string { `Tables: ${reportDiff(report)}`, `Semantic-layer artifacts: ${report.artifactPaths.manifestShards.length}`, `Report: ${report.artifactPaths.reportPath ?? 'none'}`, - 'Next: klo setup demo inspect', + 'Next: ktx setup demo inspect', ' Shows the files and semantic-layer draft created from the database scan.', '', ].join('\n'); @@ -190,22 +190,22 @@ export function formatDemoInspect(summary: DemoInspectSummary): string { : [report ? 'Memory synthesis: full mode not run' : 'Memory synthesis: not run']; const next = fullReport ? [ - `Next: klo ingest watch ${fullReport.runId} --project-dir ${summary.projectDir}`, + `Next: ktx ingest watch ${fullReport.runId} --project-dir ${summary.projectDir}`, ' Opens the captured run timeline and lets you inspect what happened.', - 'Next: klo setup demo replay', + 'Next: ktx setup demo replay', ' Replays the same visual story without calling the LLM again.', ] : report ? [ - 'Next: klo setup demo --mode full', + 'Next: ktx setup demo --mode full', ' Runs the full AI-backed pass with your LLM provider.', - 'Next: klo setup demo replay', + 'Next: ktx setup demo replay', ' Replays the packaged visual story without calling the LLM.', ] : [ - 'Next: klo setup demo --no-input', + 'Next: ktx setup demo --no-input', ' Runs the pre-seeded demo without calling the LLM.', - 'Next: klo setup demo --mode full', + 'Next: ktx setup demo --mode full', ' Runs the full AI-backed pass with your LLM provider.', ]; diff --git a/packages/cli/src/demo-seeded-inspect.test.ts b/packages/cli/src/demo-seeded-inspect.test.ts index 3e0fc3ee..35c76861 100644 --- a/packages/cli/src/demo-seeded-inspect.test.ts +++ b/packages/cli/src/demo-seeded-inspect.test.ts @@ -4,10 +4,10 @@ import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { runDemoSeeded } from './demo-seeded.js'; import { formatSeededInspect, inspectSeededProject } from './demo-seeded-inspect.js'; -import { KLO_NEXT_STEP_COMMANDS } from './next-steps.js'; +import { KTX_NEXT_STEP_COMMANDS } from './next-steps.js'; describe('seeded demo inspect contract', () => { - const projectDir = join(tmpdir(), `klo-demo-seeded-inspect-${process.pid}`); + const projectDir = join(tmpdir(), `ktx-demo-seeded-inspect-${process.pid}`); afterEach(async () => { await rm(projectDir, { recursive: true, force: true }); @@ -59,7 +59,7 @@ describe('seeded demo inspect contract', () => { reports: { primaryPath: 'reports/seeded-demo-report.json', fileCount: 1 }, replays: { primaryPath: 'replays/replay.memory-flow.v1.json', latestPath: 'replays/latest.memory-flow.v1.json' }, }, - nextCommands: KLO_NEXT_STEP_COMMANDS, + nextCommands: KTX_NEXT_STEP_COMMANDS, }); expect(inspect.generatedOutputs.replays.fileCount).toBeGreaterThanOrEqual(3); @@ -89,13 +89,13 @@ describe('seeded demo inspect contract', () => { expect(output).toContain('Report: reports/seeded-demo-report.json'); expect(output).toContain('Replay: replays/replay.memory-flow.v1.json'); expect(output).toContain('Latest replay: seeded (packaged, prebuilt)'); - expect(output).toContain(' $ klo agent tools --json'); - expect(output).toContain(' $ klo agent context --json'); - expect(output).toContain(' $ klo serve --mcp stdio --user-id local'); - expect(output.indexOf('klo agent tools --json')).toBeLessThan( - output.indexOf('klo serve --mcp stdio --user-id local'), + expect(output).toContain(' $ ktx agent tools --json'); + expect(output).toContain(' $ ktx agent context --json'); + expect(output).toContain(' $ ktx serve --mcp stdio --user-id local'); + expect(output.indexOf('ktx agent tools --json')).toBeLessThan( + output.indexOf('ktx serve --mcp stdio --user-id local'), ); - expect(output).not.toContain('klo ask'); + expect(output).not.toContain('ktx ask'); expect(output).not.toContain('deterministic mode'); }); diff --git a/packages/cli/src/demo-seeded-inspect.ts b/packages/cli/src/demo-seeded-inspect.ts index ea9ebe79..0081e4b8 100644 --- a/packages/cli/src/demo-seeded-inspect.ts +++ b/packages/cli/src/demo-seeded-inspect.ts @@ -1,10 +1,10 @@ import { constants as fsConstants } from 'node:fs'; import { access, readFile, readdir } from 'node:fs/promises'; import { join, resolve } from 'node:path'; -import type { MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { loadPackagedDemoReplay } from './demo-assets.js'; import { DEMO_LATEST_REPLAY_FILE, loadLatestDemoReplay } from './demo-replay-store.js'; -import { KLO_NEXT_STEP_COMMANDS, KLO_NEXT_STEP_COMMAND_WIDTH } from './next-steps.js'; +import { KTX_NEXT_STEP_COMMANDS, KTX_NEXT_STEP_COMMAND_WIDTH } from './next-steps.js'; type SeededInspectReadiness = 'missing' | 'ready' | 'corrupt'; @@ -66,7 +66,7 @@ export interface SeededInspectSummary { } const REQUIRED_SEEDED_PROJECT_PATHS = [ - 'klo.yaml', + 'ktx.yaml', 'demo.db', 'state.sqlite', 'manifest.json', @@ -181,7 +181,7 @@ function sourceBundleFromManifest(manifest: DemoSeededManifest): SeededInspectSu } function nextCommands(): SeededInspectSummary['nextCommands'] { - return [...KLO_NEXT_STEP_COMMANDS]; + return [...KTX_NEXT_STEP_COMMANDS]; } function modeMetadataFromReplay(replay: MemoryFlowReplayInput | null): SeededInspectSummary['modeMetadata'] { @@ -291,9 +291,9 @@ export function formatSeededInspect(summary: SeededInspectSummary): string { ); for (const command of summary.nextCommands) { - lines.push(` $ ${command.command.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} ${command.description}`); + lines.push(` $ ${command.command.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} ${command.description}`); } - lines.push('', `Your KLO project files are at: ${summary.projectDir}`, ''); + lines.push('', `Your KTX project files are at: ${summary.projectDir}`, ''); return lines.join('\n'); } diff --git a/packages/cli/src/demo-seeded.test.ts b/packages/cli/src/demo-seeded.test.ts index a4004bb7..c6065c07 100644 --- a/packages/cli/src/demo-seeded.test.ts +++ b/packages/cli/src/demo-seeded.test.ts @@ -6,7 +6,7 @@ import { ensureSeededDemoProject } from './demo-assets.js'; import { runDemoSeeded } from './demo-seeded.js'; describe('demo seeded mode', () => { - const projectDir = join(tmpdir(), `klo-demo-seeded-${process.pid}`); + const projectDir = join(tmpdir(), `ktx-demo-seeded-${process.pid}`); afterEach(async () => { await rm(projectDir, { recursive: true, force: true }); @@ -17,7 +17,7 @@ describe('demo seeded mode', () => { expect(result.projectDir).toBe(projectDir); await expect(access(join(projectDir, 'demo.db'))).resolves.toBeUndefined(); - await expect(access(join(projectDir, 'klo.yaml'))).resolves.toBeUndefined(); + await expect(access(join(projectDir, 'ktx.yaml'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'manifest.json'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'semantic-layer/orbit_demo/accounts.yaml'))).resolves.toBeUndefined(); await expect(access(join(projectDir, 'knowledge/global/arr-contract-first.md'))).resolves.toBeUndefined(); @@ -35,7 +35,7 @@ describe('demo seeded mode', () => { expect(result.replay.metadata?.timing).toBe('prebuilt'); expect(result.inspect.mode).toBe('seeded'); - const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(config).toContain('api_key: env:ANTHROPIC_API_KEY'); expect(config).not.toContain('sk-ant-'); }); diff --git a/packages/cli/src/demo-seeded.ts b/packages/cli/src/demo-seeded.ts index e14e36f1..58275548 100644 --- a/packages/cli/src/demo-seeded.ts +++ b/packages/cli/src/demo-seeded.ts @@ -1,4 +1,4 @@ -import type { MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { ensureSeededDemoProject, loadPackagedDemoReplay, diff --git a/packages/cli/src/demo.test.ts b/packages/cli/src/demo.test.ts index 012b67ef..0cedba99 100644 --- a/packages/cli/src/demo.test.ts +++ b/packages/cli/src/demo.test.ts @@ -1,14 +1,14 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { IngestReportSnapshot, MemoryFlowReplayInput } from '@klo/context/ingest'; +import type { IngestReportSnapshot, MemoryFlowReplayInput } from '@ktx/context/ingest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloDemo } from './demo.js'; +import { runKtxDemo } from './demo.js'; import { DEMO_FULL_JOB_ID, defaultDemoProjectDir, ensureDemoProject } from './demo-assets.js'; import type { DemoFullResult } from './demo-full.js'; import { createTestDemoPromptAdapter } from './demo-interaction.js'; import type { renderMemoryFlowTui } from './memory-flow-tui.js'; -import { KLO_NEXT_STEP_COMMANDS } from './next-steps.js'; +import { KTX_NEXT_STEP_COMMANDS } from './next-steps.js'; import { resetVizFallbackWarningsForTest } from './viz-fallback.js'; function makeIo(options: { isTTY?: boolean; columns?: number; rawMode?: boolean } = {}) { @@ -108,12 +108,12 @@ function fakeFullResult(projectDir: string): DemoFullResult { }; } -describe('runKloDemo', () => { +describe('runKtxDemo', () => { let tempDir: string; beforeEach(async () => { resetVizFallbackWarningsForTest(); - tempDir = await mkdtemp(join(tmpdir(), 'klo-demo-command-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-demo-command-')); }); afterEach(async () => { @@ -123,7 +123,7 @@ describe('runKloDemo', () => { it('initializes the demo project', async () => { const io = makeIo(); await expect( - runKloDemo({ command: 'init', projectDir: tempDir, force: false, inputMode: 'disabled' }, io.io), + runKtxDemo({ command: 'init', projectDir: tempDir, force: false, inputMode: 'disabled' }, io.io), ).resolves.toBe(0); expect(io.stdout()).toContain(`Demo project: ${tempDir}`); @@ -135,14 +135,14 @@ describe('runKloDemo', () => { it('renders the packaged replay in no-input viz mode', async () => { const io = makeIo({ isTTY: true }); await expect( - runKloDemo( + runKtxDemo( { command: 'replay', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, io.io, { env: { ...process.env, TERM: 'xterm-256color' } }, ), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow Warehouse + dbt + BI + Docs done'); + expect(io.stdout()).toContain('KTX memory flow Warehouse + dbt + BI + Docs done'); expect(io.stdout()).toContain('Saved 16 memories'); expect(io.stderr()).toBe(''); }); @@ -152,7 +152,7 @@ describe('runKloDemo', () => { const renderStoredMemoryFlow = vi.fn(async () => true); await expect( - runKloDemo( + runKtxDemo( { command: 'replay', projectDir: tempDir, outputMode: 'viz' }, io.io, { env: { ...process.env, TERM: 'xterm-256color' }, renderStoredMemoryFlow }, @@ -166,7 +166,7 @@ describe('runKloDemo', () => { adapter: 'live-database', }); expect(renderStoredMemoryFlow.mock.calls[0]?.[2]).toEqual({ speedMultiplier: 0.125 }); - expect(io.stdout()).toContain('KLO finished ingesting your data'); + expect(io.stdout()).toContain('KTX finished ingesting your data'); expect(io.stderr()).toBe(''); }); @@ -175,7 +175,7 @@ describe('runKloDemo', () => { const renderStoredMemoryFlow = vi.fn(async () => true); await expect( - runKloDemo( + runKtxDemo( { command: 'seeded', projectDir: tempDir, outputMode: 'viz' }, io.io, { env: { ...process.env, TERM: 'xterm-256color' }, renderStoredMemoryFlow }, @@ -184,7 +184,7 @@ describe('runKloDemo', () => { expect(renderStoredMemoryFlow).toHaveBeenCalledTimes(1); expect(renderStoredMemoryFlow.mock.calls[0]?.[2]).toEqual({ speedMultiplier: 0.125 }); - expect(io.stdout()).toContain('KLO finished ingesting your data'); + expect(io.stdout()).toContain('KTX finished ingesting your data'); expect(io.stderr()).toBe(''); }); @@ -193,7 +193,7 @@ describe('runKloDemo', () => { const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => true); await expect( - runKloDemo( + runKtxDemo( { command: 'replay', projectDir: tempDir, outputMode: 'viz' }, io.io, { env: { ...process.env, TERM: 'xterm-256color' }, renderStoredMemoryFlow }, @@ -203,10 +203,10 @@ describe('runKloDemo', () => { expect(renderStoredMemoryFlow).not.toHaveBeenCalled(); expect(io.stdout()).toContain('Memory-flow summary: done'); expect(io.stdout()).toContain('Connection: orbit_demo'); - expect(io.stdout()).toContain('klo sl list'); - expect(io.stdout()).toContain('klo wiki list'); - expect(io.stdout()).toContain('klo serve --mcp stdio --user-id local'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).toContain('ktx sl list'); + expect(io.stdout()).toContain('ktx wiki list'); + expect(io.stdout()).toContain('ktx serve --mcp stdio --user-id local'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdin raw mode is unavailable; printing plain output.', ); @@ -216,15 +216,15 @@ describe('runKloDemo', () => { const testIo = makeIo({ isTTY: false }); await expect( - runKloDemo({ command: 'replay', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, testIo.io), + runKtxDemo({ command: 'replay', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, testIo.io), ).resolves.toBe(0); expect(testIo.stdout()).toContain('Memory-flow summary: done'); expect(testIo.stdout()).toContain('Connection: orbit_demo'); - expect(testIo.stdout()).toContain('klo sl list'); - expect(testIo.stdout()).toContain('klo wiki list'); - expect(testIo.stdout()).toContain('klo serve --mcp stdio --user-id local'); - expect(testIo.stdout()).not.toContain('KLO memory flow'); + expect(testIo.stdout()).toContain('ktx sl list'); + expect(testIo.stdout()).toContain('ktx wiki list'); + expect(testIo.stdout()).toContain('ktx serve --mcp stdio --user-id local'); + expect(testIo.stdout()).not.toContain('KTX memory flow'); expect(testIo.stderr()).toContain( 'Visualization requested but stdout is not an interactive terminal; printing plain output.', ); @@ -233,7 +233,7 @@ describe('runKloDemo', () => { it('prints JSON replay output when requested', async () => { const io = makeIo(); await expect( - runKloDemo({ command: 'replay', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, io.io), + runKtxDemo({ command: 'replay', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, io.io), ).resolves.toBe(0); expect(JSON.parse(io.stdout())).toMatchObject({ runId: 'demo-seeded-orbit', connectionId: 'orbit_demo' }); @@ -242,7 +242,7 @@ describe('runKloDemo', () => { it('runs the packaged SQLite demo scan', async () => { const io = makeIo(); - await expect(runKloDemo({ command: 'scan', projectDir: tempDir, inputMode: 'disabled' }, io.io)).resolves.toBe(0); + await expect(runKtxDemo({ command: 'scan', projectDir: tempDir, inputMode: 'disabled' }, io.io)).resolves.toBe(0); expect(io.stdout()).toContain('Demo scan: done'); expect(io.stdout()).toContain('Connection: orbit_demo'); @@ -254,7 +254,7 @@ describe('runKloDemo', () => { it('runs seeded mode with pre-seeded assets and inspect summary', async () => { const io = makeIo({ isTTY: true }); await expect( - runKloDemo( + runKtxDemo( { command: 'seeded', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, io.io, { env: { ...process.env, TERM: 'xterm-256color' } }, @@ -272,7 +272,7 @@ describe('runKloDemo', () => { const io = makeIo(); await expect( - runKloDemo( + runKtxDemo( { command: 'seeded', projectDir: defaultDemoProjectDir(), outputMode: 'plain', inputMode: 'disabled' }, io.io, ), @@ -282,10 +282,10 @@ describe('runKloDemo', () => { expect(io.stdout()).toContain('Source: packaged demo project'); expect(io.stdout()).toContain('Generated context: prebuilt from bundled assets'); expect(io.stdout()).toContain('LLM calls: none'); - expect(io.stdout()).toContain('Your KLO project files are at:'); - expect(io.stdout()).toContain(join(tmpdir(), 'klo-demo-')); - expect(io.stdout()).toContain('klo serve --mcp stdio'); - expect(io.stdout()).not.toContain(['klo', 'mcp'].join(' ')); + expect(io.stdout()).toContain('Your KTX project files are at:'); + expect(io.stdout()).toContain(join(tmpdir(), 'ktx-demo-')); + expect(io.stdout()).toContain('ktx serve --mcp stdio'); + expect(io.stdout()).not.toContain(['ktx', 'mcp'].join(' ')); expect(io.stdout()).not.toContain('deterministic'); }); @@ -293,7 +293,7 @@ describe('runKloDemo', () => { const testIo = makeIo({ isTTY: true, columns: 120 }); await expect( - runKloDemo( + runKtxDemo( { command: 'seeded', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, testIo.io, { env: { ...process.env, TERM: 'dumb' } }, @@ -310,19 +310,19 @@ describe('runKloDemo', () => { it('prints demo inspect as plain text and JSON', async () => { const seededIo = makeIo(); await expect( - runKloDemo({ command: 'seeded', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, seededIo.io), + runKtxDemo({ command: 'seeded', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, seededIo.io), ).resolves.toBe(0); const plainIo = makeIo(); await expect( - runKloDemo({ command: 'inspect', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, plainIo.io), + runKtxDemo({ command: 'inspect', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, plainIo.io), ).resolves.toBe(0); expect(plainIo.stdout()).toContain('Mode: seeded'); expect(plainIo.stdout()).toContain('Semantic-layer sources:'); const jsonIo = makeIo(); await expect( - runKloDemo({ command: 'inspect', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, jsonIo.io), + runKtxDemo({ command: 'inspect', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, jsonIo.io), ).resolves.toBe(0); const parsed = JSON.parse(jsonIo.stdout()); expect(parsed).toMatchObject({ @@ -347,7 +347,7 @@ describe('runKloDemo', () => { generatedContext: 'prebuilt from bundled assets', llmCalls: 'none', }, - nextCommands: KLO_NEXT_STEP_COMMANDS, + nextCommands: KTX_NEXT_STEP_COMMANDS, }); expect(parsed.generatedOutputs.replays.fileCount).toBeGreaterThanOrEqual(3); expect(jsonIo.stderr()).toBe(''); @@ -359,7 +359,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, testIo.io, { + runKtxDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz', inputMode: 'disabled' }, testIo.io, { env: {}, runFullDemo, }), @@ -372,10 +372,10 @@ describe('runKloDemo', () => { onMemoryFlowChange: expect.any(Function), }), ); - expect(testIo.stdout()).toContain('KLO memory flow orbit_demo/live-database done'); + expect(testIo.stdout()).toContain('KTX memory flow orbit_demo/live-database done'); expect(testIo.stdout()).toContain('Full demo ingest: done'); - expect(testIo.stdout()).toContain('Next: klo setup demo inspect'); - expect(testIo.stdout()).toContain('Shows the files, semantic-layer sources, and memory KLO just produced.'); + expect(testIo.stdout()).toContain('Next: ktx setup demo inspect'); + expect(testIo.stdout()).toContain('Shows the files, semantic-layer sources, and memory KTX just produced.'); }); it('streams live memory-flow snapshots for full demo viz and then prints final summary', async () => { @@ -399,7 +399,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz' }, testIo.io, { + runKtxDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz' }, testIo.io, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, // pragma: allowlist secret prompts: createTestDemoPromptAdapter({ choices: ['reuse'] }), runFullDemo, @@ -411,12 +411,12 @@ describe('runKloDemo', () => { expect(liveSession.update).toHaveBeenCalledTimes(1); expect(liveSession.close).toHaveBeenCalledTimes(1); expect(testIo.stdout()).not.toContain('Memory-flow summary: done'); - expect(testIo.stdout()).toContain('KLO finished ingesting your data'); - expect(testIo.stdout()).toContain('klo sl list'); - expect(testIo.stdout()).toContain('klo wiki list'); - expect(testIo.stdout()).toContain('klo serve --mcp stdio --user-id local'); - expect(testIo.stdout()).not.toContain(['klo', 'ask'].join(' ')); - expect(testIo.stdout()).not.toContain(['klo', 'mcp'].join(' ')); + expect(testIo.stdout()).toContain('KTX finished ingesting your data'); + expect(testIo.stdout()).toContain('ktx sl list'); + expect(testIo.stdout()).toContain('ktx wiki list'); + expect(testIo.stdout()).toContain('ktx serve --mcp stdio --user-id local'); + expect(testIo.stdout()).not.toContain(['ktx', 'ask'].join(' ')); + expect(testIo.stdout()).not.toContain(['ktx', 'mcp'].join(' ')); }); it('uses plain progress for full demo viz when stdin raw mode is unavailable', async () => { @@ -440,7 +440,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz' }, testIo.io, { + runKtxDemo({ command: 'full', projectDir: tempDir, outputMode: 'viz' }, testIo.io, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, // pragma: allowlist secret prompts: createTestDemoPromptAdapter({ choices: ['reuse'] }), runFullDemo, @@ -456,7 +456,7 @@ describe('runKloDemo', () => { ); expect(testIo.stdout()).toContain('[connect] Connected live-database - 7 database files (demo_full)'); expect(testIo.stdout()).toContain('Full demo ingest: done'); - expect(testIo.stdout()).not.toContain('KLO memory flow'); + expect(testIo.stdout()).not.toContain('KTX memory flow'); expect(testIo.stderr()).toContain( 'Visualization requested but stdin raw mode is unavailable; printing plain output.', ); @@ -486,7 +486,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, runFullDemo }, // pragma: allowlist secret @@ -510,7 +510,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, testIo.io, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, runFullDemo }, // pragma: allowlist secret @@ -526,7 +526,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'ingest', mode: 'full', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { env: {}, runFullDemo }, @@ -537,12 +537,12 @@ describe('runKloDemo', () => { }); it('saves full-demo replay output for the next demo replay command', async () => { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-demo-full-replay-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-demo-full-replay-')); await ensureDemoProject({ projectDir: tempDir, force: false }); const io = makeIo(); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, io.io, { @@ -554,7 +554,7 @@ describe('runKloDemo', () => { const replayIo = makeIo(); await expect( - runKloDemo({ command: 'replay', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, replayIo.io), + runKtxDemo({ command: 'replay', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, replayIo.io), ).resolves.toBe(0); expect(JSON.parse(replayIo.stdout())).toMatchObject({ runId: 'run-full', @@ -566,7 +566,7 @@ describe('runKloDemo', () => { const testIo = makeIo(); await expect( - runKloDemo( + runKtxDemo( { command: 'ingest', mode: 'seeded', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, ), @@ -581,7 +581,7 @@ describe('runKloDemo', () => { const runDoctor = vi.fn().mockResolvedValue(0); await expect( - runKloDemo( + runKtxDemo( { command: 'doctor', projectDir: tempDir, @@ -610,13 +610,13 @@ describe('runKloDemo', () => { const rejected = makeIo(); await expect( - runKloDemo({ command: 'reset', projectDir: tempDir, force: false, inputMode: 'disabled' }, rejected.io), + runKtxDemo({ command: 'reset', projectDir: tempDir, force: false, inputMode: 'disabled' }, rejected.io), ).resolves.toBe(1); - expect(rejected.stderr()).toContain(`klo setup demo reset is destructive; pass --force to recreate ${tempDir}`); + expect(rejected.stderr()).toContain(`ktx setup demo reset is destructive; pass --force to recreate ${tempDir}`); const accepted = makeIo(); await expect( - runKloDemo({ command: 'reset', projectDir: tempDir, force: true, inputMode: 'disabled' }, accepted.io), + runKtxDemo({ command: 'reset', projectDir: tempDir, force: true, inputMode: 'disabled' }, accepted.io), ).resolves.toBe(0); expect(accepted.stdout()).toContain(`Demo project reset: ${tempDir}`); }); @@ -624,12 +624,12 @@ describe('runKloDemo', () => { it('rehydrates seeded assets after reset --force', async () => { const resetIo = makeIo(); await expect( - runKloDemo({ command: 'reset', projectDir: tempDir, force: true, inputMode: 'disabled' }, resetIo.io), + runKtxDemo({ command: 'reset', projectDir: tempDir, force: true, inputMode: 'disabled' }, resetIo.io), ).resolves.toBe(0); const seededIo = makeIo(); await expect( - runKloDemo( + runKtxDemo( { command: 'seeded', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, seededIo.io, ), @@ -648,11 +648,11 @@ describe('runKloDemo', () => { const testIo = makeIo(); await expect( - runKloDemo({ command: 'replay', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io), + runKtxDemo({ command: 'replay', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io), ).resolves.toBe(1); expect(testIo.stderr()).toContain(`Demo project is not ready at ${tempDir}: missing demo.db`); - expect(testIo.stderr()).toContain(`klo setup demo reset --project-dir ${tempDir} --force --no-input`); + expect(testIo.stderr()).toContain(`ktx setup demo reset --project-dir ${tempDir} --force --no-input`); }); it('uses a process-local Anthropic key from the interactive prompt', async () => { @@ -661,7 +661,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'plain' }, testIo.io, { @@ -682,7 +682,7 @@ describe('runKloDemo', () => { onMemoryFlowChange: expect.any(Function), }), ); - expect(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')).toContain('api_key: env:ANTHROPIC_API_KEY'); + expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).toContain('api_key: env:ANTHROPIC_API_KEY'); }); it('routes an interactive missing-key choice to seeded mode', async () => { @@ -691,7 +691,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'plain' }, testIo.io, { @@ -712,7 +712,7 @@ describe('runKloDemo', () => { const testIo = makeIo({ isTTY: true }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'plain' }, testIo.io, { @@ -733,7 +733,7 @@ describe('runKloDemo', () => { await ensureDemoProject({ projectDir: tempDir, force: false }); await expect( - runKloDemo( + runKtxDemo( { command: 'full', projectDir: tempDir, outputMode: 'viz' }, testIo.io, { @@ -745,7 +745,7 @@ describe('runKloDemo', () => { ).resolves.toBe(0); expect(runFullDemo).not.toHaveBeenCalled(); - expect(testIo.stdout()).toContain('KLO memory flow'); + expect(testIo.stdout()).toContain('KTX memory flow'); expect(testIo.stdout()).toContain('done'); }); }); diff --git a/packages/cli/src/demo.ts b/packages/cli/src/demo.ts index f2096971..ef2b1ba6 100644 --- a/packages/cli/src/demo.ts +++ b/packages/cli/src/demo.ts @@ -3,9 +3,9 @@ import { formatMemoryFlowFinalSummary, renderMemoryFlowReplay, type MemoryFlowReplayInput, -} from '@klo/context/ingest/memory-flow'; -import { resolveKloConfigReference } from '@klo/context/core'; -import { loadKloProject } from '@klo/context/project'; +} from '@ktx/context/ingest/memory-flow'; +import { resolveKtxConfigReference } from '@ktx/context/core'; +import { loadKtxProject } from '@ktx/context/project'; import { DEMO_ADAPTER, DEMO_CONNECTION_ID, @@ -34,11 +34,11 @@ import { resolveFullCredentialDecision, type DemoPromptAdapter, } from './demo-interaction.js'; -import type { KloDoctorArgs } from './doctor.js'; +import type { KtxDoctorArgs } from './doctor.js'; import { renderMemoryFlowTui, startLiveMemoryFlowTui, - type KloMemoryFlowTuiIo, + type KtxMemoryFlowTuiIo, type MemoryFlowTuiLiveSession, } from './memory-flow-tui.js'; import { @@ -51,36 +51,36 @@ import { formatNextStepLines } from './next-steps.js'; profileMark('module:demo'); -export type KloDemoOutputMode = 'plain' | 'json' | 'viz'; -export type KloDemoInputMode = 'auto' | 'disabled'; -export type KloDemoMode = 'full' | 'seeded'; +export type KtxDemoOutputMode = 'plain' | 'json' | 'viz'; +export type KtxDemoInputMode = 'auto' | 'disabled'; +export type KtxDemoMode = 'full' | 'seeded'; -export type KloDemoArgs = - | { command: 'init'; projectDir: string; force: boolean; inputMode?: KloDemoInputMode } - | { command: 'reset'; projectDir: string; force: boolean; inputMode?: KloDemoInputMode } - | { command: 'replay'; projectDir: string; outputMode: KloDemoOutputMode; inputMode?: KloDemoInputMode } - | { command: 'scan'; projectDir: string; inputMode?: KloDemoInputMode } - | { command: 'inspect'; projectDir: string; outputMode: KloDemoOutputMode; inputMode?: KloDemoInputMode } - | { command: 'doctor'; projectDir: string; outputMode: Exclude; inputMode?: KloDemoInputMode } - | { command: 'seeded'; projectDir: string; outputMode: KloDemoOutputMode; inputMode?: KloDemoInputMode } - | { command: 'full'; projectDir: string; outputMode: KloDemoOutputMode; inputMode?: KloDemoInputMode } +export type KtxDemoArgs = + | { command: 'init'; projectDir: string; force: boolean; inputMode?: KtxDemoInputMode } + | { command: 'reset'; projectDir: string; force: boolean; inputMode?: KtxDemoInputMode } + | { command: 'replay'; projectDir: string; outputMode: KtxDemoOutputMode; inputMode?: KtxDemoInputMode } + | { command: 'scan'; projectDir: string; inputMode?: KtxDemoInputMode } + | { command: 'inspect'; projectDir: string; outputMode: KtxDemoOutputMode; inputMode?: KtxDemoInputMode } + | { command: 'doctor'; projectDir: string; outputMode: Exclude; inputMode?: KtxDemoInputMode } + | { command: 'seeded'; projectDir: string; outputMode: KtxDemoOutputMode; inputMode?: KtxDemoInputMode } + | { command: 'full'; projectDir: string; outputMode: KtxDemoOutputMode; inputMode?: KtxDemoInputMode } | { command: 'ingest'; - mode: KloDemoMode; + mode: KtxDemoMode; projectDir: string; - outputMode: KloDemoOutputMode; - inputMode?: KloDemoInputMode; + outputMode: KtxDemoOutputMode; + inputMode?: KtxDemoInputMode; }; -export interface KloDemoIo { - stdin?: KloMemoryFlowTuiIo['stdin']; +export interface KtxDemoIo { + stdin?: KtxMemoryFlowTuiIo['stdin']; stdout: { isTTY?: boolean; columns?: number; write(chunk: string): void }; stderr: { write(chunk: string): void }; } -interface KloDemoDeps { +interface KtxDemoDeps { runFullDemo?: typeof runDemoFull; - runDoctor?: (args: KloDoctorArgs, io: KloDemoIo) => Promise; + runDoctor?: (args: KtxDoctorArgs, io: KtxDemoIo) => Promise; renderStoredMemoryFlow?: typeof renderMemoryFlowTui; startLiveMemoryFlow?: typeof startLiveMemoryFlowTui; env?: NodeJS.ProcessEnv; @@ -127,7 +127,7 @@ function formatReplaySummary(input: MemoryFlowReplayInput): string { } } - const lines: string[] = ['', '★ KLO finished ingesting your data', '']; + const lines: string[] = ['', '★ KTX finished ingesting your data', '']; if (chunkCount > 0) { lines.push(` ✓ Analyzed ${chunkCount} business area${chunkCount === 1 ? '' : 's'}`); @@ -137,7 +137,7 @@ function formatReplaySummary(input: MemoryFlowReplayInput): string { lines.push(''); if (slCount > 0 || wikiCount > 0) { - lines.push(' KLO created:'); + lines.push(' KTX created:'); if (slCount > 0) lines.push(` 📊 ${slCount} query definition${slCount === 1 ? '' : 's'} — so agents can write accurate SQL for your data`); if (wikiCount > 0) lines.push(` 📝 ${wikiCount} knowledge page${wikiCount === 1 ? '' : 's'} — so agents understand your business context`); lines.push(''); @@ -153,7 +153,7 @@ function formatReplaySummary(input: MemoryFlowReplayInput): string { lines.push(...formatNextStepLines()); if (input.sourceDir) { lines.push(''); - lines.push(` Your KLO project files are at: ${input.sourceDir}`); + lines.push(` Your KTX project files are at: ${input.sourceDir}`); } lines.push(''); @@ -164,7 +164,7 @@ function formatPlainReplaySummary(input: MemoryFlowReplayInput): string { return [formatMemoryFlowFinalSummary(input).trimEnd(), '', 'What to do next:', ...formatNextStepLines(), ''].join('\n'); } -function writeReplay(input: MemoryFlowReplayInput, outputMode: KloDemoOutputMode, io: KloDemoIo): void { +function writeReplay(input: MemoryFlowReplayInput, outputMode: KtxDemoOutputMode, io: KtxDemoIo): void { if (outputMode === 'json') { io.stdout.write(`${JSON.stringify(input, null, 2)}\n`); return; @@ -181,10 +181,10 @@ function writeReplay(input: MemoryFlowReplayInput, outputMode: KloDemoOutputMode async function writeStoredReplay( input: MemoryFlowReplayInput, - outputMode: KloDemoOutputMode, - inputMode: KloDemoArgs['inputMode'], - io: KloDemoIo, - deps: KloDemoDeps, + outputMode: KtxDemoOutputMode, + inputMode: KtxDemoArgs['inputMode'], + io: KtxDemoIo, + deps: KtxDemoDeps, env: NodeJS.ProcessEnv, ): Promise { const resolvedOutputMode = effectiveDemoOutputMode(outputMode, io, env, { @@ -211,8 +211,8 @@ async function writeStoredReplay( function writeInspect( summary: Awaited>, - outputMode: KloDemoOutputMode, - io: KloDemoIo, + outputMode: KtxDemoOutputMode, + io: KtxDemoIo, ): void { if (outputMode === 'json') { io.stdout.write(`${JSON.stringify(summary, null, 2)}\n`); @@ -224,8 +224,8 @@ function writeInspect( function writeFullDemo( result: Awaited>, - outputMode: KloDemoOutputMode, - io: KloDemoIo, + outputMode: KtxDemoOutputMode, + io: KtxDemoIo, options: { liveWasRendered?: boolean; projectDir?: string } = {}, ): void { if (outputMode === 'json') { @@ -274,8 +274,8 @@ function replayWithFullMetadata(result: Awaited>) function pickMemoryFlowProgress( liveSession: MemoryFlowTuiLiveSession | null, - outputMode: KloDemoOutputMode, - io: KloDemoIo, + outputMode: KtxDemoOutputMode, + io: KtxDemoIo, ): ((snapshot: MemoryFlowReplayInput) => void) | undefined { if (liveSession) { return (snapshot: MemoryFlowReplayInput) => { @@ -290,7 +290,7 @@ function pickMemoryFlowProgress( return createPlainProgressEmitter(io); } -function isTuiCapableDemoIo(io: KloDemoIo): io is KloDemoIo & KloMemoryFlowTuiIo { +function isTuiCapableDemoIo(io: KtxDemoIo): io is KtxDemoIo & KtxMemoryFlowTuiIo { return ( io.stdin?.isTTY === true && io.stdout.isTTY === true && @@ -304,11 +304,11 @@ interface EffectiveDemoOutputModeOptions { } function effectiveDemoOutputMode( - outputMode: KloDemoOutputMode, - io: KloDemoIo, + outputMode: KtxDemoOutputMode, + io: KtxDemoIo, env: NodeJS.ProcessEnv, options: EffectiveDemoOutputModeOptions = {}, -): KloDemoOutputMode { +): KtxDemoOutputMode { if (outputMode !== 'viz') { return outputMode; } @@ -346,7 +346,7 @@ async function ensureDemoProjectForCommand(projectDir: string): Promise { }); } -async function prepareProjectForDemoCommand(args: KloDemoArgs, io: KloDemoIo, deps: KloDemoDeps): Promise { +async function prepareProjectForDemoCommand(args: KtxDemoArgs, io: KtxDemoIo, deps: KtxDemoDeps): Promise { if (args.command === 'init' || args.command === 'reset' || args.command === 'doctor') { return args.projectDir; } @@ -372,10 +372,10 @@ async function prepareProjectForDemoCommand(args: KloDemoArgs, io: KloDemoIo, de async function runReplayDemo( projectDir: string, - outputMode: KloDemoOutputMode, - inputMode: KloDemoArgs['inputMode'], - io: KloDemoIo, - deps: KloDemoDeps, + outputMode: KtxDemoOutputMode, + inputMode: KtxDemoArgs['inputMode'], + io: KtxDemoIo, + deps: KtxDemoDeps, env: NodeJS.ProcessEnv = process.env, ): Promise { await ensureDemoProjectForCommand(projectDir); @@ -385,10 +385,10 @@ async function runReplayDemo( async function runSeededDemo( projectDir: string, - outputMode: KloDemoOutputMode, - inputMode: KloDemoArgs['inputMode'], - io: KloDemoIo, - deps: KloDemoDeps, + outputMode: KtxDemoOutputMode, + inputMode: KtxDemoArgs['inputMode'], + io: KtxDemoIo, + deps: KtxDemoDeps, env: NodeJS.ProcessEnv = process.env, ): Promise { const result = await runDemoSeeded({ projectDir }); @@ -411,7 +411,7 @@ async function runSeededDemo( return 0; } -export async function runKloDemo(args: KloDemoArgs, io: KloDemoIo = process, deps: KloDemoDeps = {}): Promise { +export async function runKtxDemo(args: KtxDemoArgs, io: KtxDemoIo = process, deps: KtxDemoDeps = {}): Promise { try { if (args.command === 'init') { const result = await ensureDemoProject({ projectDir: args.projectDir, force: args.force }); @@ -419,7 +419,7 @@ export async function runKloDemo(args: KloDemoArgs, io: KloDemoIo = process, dep io.stdout.write(`Config: ${result.configPath}\n`); io.stdout.write(`Database: ${result.databasePath}\n`); io.stdout.write(`Replay: ${result.replayPath}\n`); - io.stdout.write('Next: klo setup demo --no-input\n'); + io.stdout.write('Next: ktx setup demo --no-input\n'); io.stdout.write(' Runs the pre-seeded demo without calling the LLM.\n'); return 0; } @@ -430,7 +430,7 @@ export async function runKloDemo(args: KloDemoArgs, io: KloDemoIo = process, dep io.stdout.write(`Config: ${result.configPath}\n`); io.stdout.write(`Database: ${result.databasePath}\n`); io.stdout.write(`Replay: ${result.replayPath}\n`); - io.stdout.write('Next: klo setup demo --mode full\n'); + io.stdout.write('Next: ktx setup demo --mode full\n'); io.stdout.write(' Runs the full AI-backed pass with your LLM provider.\n'); return 0; } @@ -454,13 +454,13 @@ export async function runKloDemo(args: KloDemoArgs, io: KloDemoIo = process, dep if (args.command === 'full' || (args.command === 'ingest' && args.mode === 'full')) { const executeFullDemo = deps.runFullDemo ?? runDemoFull; await ensureDemoProjectForCommand(preparedProjectDir); - const project = await loadKloProject({ projectDir: preparedProjectDir }); + const project = await loadKtxProject({ projectDir: preparedProjectDir }); const credentialStatus = fullDemoCredentialStatus(project, env); const credentialDecision = await resolveFullCredentialDecision({ needsAnthropicKey: credentialStatus.status === 'missing-anthropic-key' && project.config.llm.provider.backend === 'anthropic' && - !resolveKloConfigReference(project.config.llm.provider.anthropic?.api_key, env), + !resolveKtxConfigReference(project.config.llm.provider.anthropic?.api_key, env), inputMode: args.inputMode, io, env, @@ -523,8 +523,8 @@ export async function runKloDemo(args: KloDemoArgs, io: KloDemoIo = process, dep } if (args.command === 'doctor') { - const { runKloDoctor } = await import('./doctor.js'); - const executeDoctor = deps.runDoctor ?? runKloDoctor; + const { runKtxDoctor } = await import('./doctor.js'); + const executeDoctor = deps.runDoctor ?? runKtxDoctor; return await executeDoctor( { command: 'demo', diff --git a/packages/cli/src/dev.test.ts b/packages/cli/src/dev.test.ts index c1473b3d..639244a6 100644 --- a/packages/cli/src/dev.test.ts +++ b/packages/cli/src/dev.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { runKloCli } from './index.js'; +import { runKtxCli } from './index.js'; function makeIo() { let stdout = ''; @@ -26,9 +26,9 @@ describe('dev Commander tree', () => { it('prints visible dev help with only supported low-level command groups', async () => { const testIo = makeIo(); - await expect(runKloCli(['dev', '--help'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', '--help'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo dev [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx dev [options] [command]'); for (const command of ['init', 'doctor', 'scan', 'ingest', 'mapping']) { expect(testIo.stdout()).toContain(command); } @@ -51,10 +51,10 @@ describe('dev Commander tree', () => { it('keeps dev callable while hiding it from root command rows', async () => { const testIo = makeIo(); - await expect(runKloCli(['--help'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['--help'], testIo.io)).resolves.toBe(0); expect(testIo.stdout()).toContain('Advanced:'); - expect(testIo.stdout()).toContain('klo dev'); + expect(testIo.stdout()).toContain('ktx dev'); expect(testIo.stdout()).not.toContain('dev Low-level diagnostics'); expect(testIo.stderr()).toBe(''); }); @@ -63,15 +63,15 @@ describe('dev Commander tree', () => { const { mkdtemp, readFile, rm } = await import('node:fs/promises'); const { tmpdir } = await import('node:os'); const { join } = await import('node:path'); - const tempDir = await mkdtemp(join(tmpdir(), 'klo-dev-init-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-dev-init-')); const projectDir = join(tempDir, 'warehouse'); const testIo = makeIo(); try { - await expect(runKloCli(['dev', 'init', projectDir, '--name', 'warehouse'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', 'init', projectDir, '--name', 'warehouse'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain(`Initialized KLO project at ${projectDir}`); - await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); + expect(testIo.stdout()).toContain(`Initialized KTX project at ${projectDir}`); + await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); expect(testIo.stderr()).toBe(''); } finally { await rm(tempDir, { recursive: true, force: true }); @@ -82,16 +82,16 @@ describe('dev Commander tree', () => { const { mkdtemp, rm } = await import('node:fs/promises'); const { tmpdir } = await import('node:os'); const { join } = await import('node:path'); - const tempDir = await mkdtemp(join(tmpdir(), 'klo-dev-init-global-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-dev-init-global-')); const projectDir = join(tempDir, 'global-init'); const testIo = makeIo(); try { await expect( - runKloCli(['--project-dir', projectDir, 'dev', 'init', '--name', 'global-init'], testIo.io), + runKtxCli(['--project-dir', projectDir, 'dev', 'init', '--name', 'global-init'], testIo.io), ).resolves.toBe(0); - expect(testIo.stdout()).toContain(`Initialized KLO project at ${projectDir}`); + expect(testIo.stdout()).toContain(`Initialized KTX project at ${projectDir}`); expect(testIo.stderr()).toBe(''); } finally { await rm(tempDir, { recursive: true, force: true }); @@ -106,7 +106,7 @@ describe('dev Commander tree', () => { ]) { const testIo = makeIo(); - await expect(runKloCli(argv, testIo.io)).resolves.toBe(1); + await expect(runKtxCli(argv, testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toMatch(/unknown command|error:/); } @@ -115,12 +115,12 @@ describe('dev Commander tree', () => { it.each([ { argv: ['dev', 'doctor', '--help'], - expected: ['Usage: klo dev doctor', '--json', '--no-input'], + expected: ['Usage: ktx dev doctor', '--json', '--no-input'], }, { argv: ['dev', 'scan', '--help'], expected: [ - 'Usage: klo dev scan', + 'Usage: ktx dev scan', '--mode ', 'structural', 'relationships', @@ -136,12 +136,12 @@ describe('dev Commander tree', () => { }, { argv: ['dev', 'scan', 'report', '--help'], - expected: ['Usage: klo dev scan report [options] ', '', '--json'], + expected: ['Usage: ktx dev scan report [options] ', '', '--json'], }, { argv: ['dev', 'scan', 'relationships', '--help'], expected: [ - 'Usage: klo dev scan relationships [options] ', + 'Usage: ktx dev scan relationships [options] ', '--status ', '--limit ', '--accept ', @@ -154,7 +154,7 @@ describe('dev Commander tree', () => { { argv: ['dev', 'scan', 'relationship-apply', '--help'], expected: [ - 'Usage: klo dev scan relationship-apply [options] ', + 'Usage: ktx dev scan relationship-apply [options] ', '--all-accepted', '--candidate ', '--dry-run', @@ -163,7 +163,7 @@ describe('dev Commander tree', () => { { argv: ['dev', 'scan', 'relationship-thresholds', '--help'], expected: [ - 'Usage: klo dev scan relationship-thresholds [options]', + 'Usage: ktx dev scan relationship-thresholds [options]', '--connection ', '--min-total-labels ', '--min-accepted-labels ', @@ -174,7 +174,7 @@ describe('dev Commander tree', () => { { argv: ['dev', 'scan', 'relationship-feedback', '--help'], expected: [ - 'Usage: klo dev scan relationship-feedback [options]', + 'Usage: ktx dev scan relationship-feedback [options]', '--connection ', '--decision ', '--json', @@ -184,7 +184,7 @@ describe('dev Commander tree', () => { { argv: ['dev', 'scan', 'relationship-calibration', '--help'], expected: [ - 'Usage: klo dev scan relationship-calibration [options]', + 'Usage: ktx dev scan relationship-calibration [options]', '--connection ', '--decision ', '--accept-threshold ', @@ -194,11 +194,11 @@ describe('dev Commander tree', () => { }, { argv: ['dev', 'ingest', 'run', '--help'], - expected: ['Usage: klo dev ingest run [options]', '--connection-id ', '--adapter '], + expected: ['Usage: ktx dev ingest run [options]', '--connection-id ', '--adapter '], }, { argv: ['dev', 'mapping', 'sync-state', 'set', '--help'], - expected: ['Usage: klo dev mapping sync-state set [options] ', '--mode '], + expected: ['Usage: ktx dev mapping sync-state set [options] ', '--mode '], }, ])('prints generated nested help for $argv', async ({ argv, expected }) => { const io = makeIo(); @@ -206,7 +206,7 @@ describe('dev Commander tree', () => { const ingest = vi.fn(async () => 0); const scan = vi.fn(async () => 0); - await expect(runKloCli(argv, io.io, { doctor, ingest, scan })).resolves.toBe(0); + await expect(runKtxCli(argv, io.io, { doctor, ingest, scan })).resolves.toBe(0); for (const text of expected) { expect(io.stdout()).toContain(text); @@ -222,7 +222,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'warehouse', '--project-dir', '/tmp/project', '--dry-run'], scanIo.io, { scan }), + runKtxCli(['dev', 'scan', 'warehouse', '--project-dir', '/tmp/project', '--dry-run'], scanIo.io, { scan }), ).resolves.toBe(0); expect(scan).toHaveBeenCalledWith( @@ -245,7 +245,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'warehouse', '--project-dir', '/tmp/project', '--mode', 'relationships'], io.io, { + runKtxCli(['dev', 'scan', 'warehouse', '--project-dir', '/tmp/project', '--mode', 'relationships'], io.io, { scan, }), ).resolves.toBe(0); @@ -269,7 +269,7 @@ describe('dev Commander tree', () => { const io = makeIo(); const scan = vi.fn(async () => 0); - await expect(runKloCli(['dev', 'scan', 'warehouse', option], io.io, { scan })).resolves.toBe(1); + await expect(runKtxCli(['dev', 'scan', 'warehouse', option], io.io, { scan })).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); expect(io.stderr()).toContain(`unknown option '${option}'`); @@ -279,18 +279,18 @@ describe('dev Commander tree', () => { const io = makeIo(); const scan = vi.fn(async () => 0); - await expect(runKloCli(['dev', 'scan', '--dry-run'], io.io, { scan })).resolves.toBe(1); + await expect(runKtxCli(['dev', 'scan', '--dry-run'], io.io, { scan })).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); - expect(io.stdout()).toContain('Usage: klo dev scan'); - expect(io.stderr()).toContain('klo dev scan requires or a subcommand'); + expect(io.stdout()).toContain('Usage: ktx dev scan'); + expect(io.stderr()).toContain('ktx dev scan requires or a subcommand'); }); it('rejects invalid scan modes before dispatch', async () => { const io = makeIo(); const scan = vi.fn(async () => 0); - await expect(runKloCli(['dev', 'scan', 'warehouse', '--mode', 'deep'], io.io, { scan })).resolves.toBe(1); + await expect(runKtxCli(['dev', 'scan', 'warehouse', '--mode', 'deep'], io.io, { scan })).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); expect(io.stderr()).toContain("argument 'deep' is invalid"); @@ -301,10 +301,10 @@ describe('dev Commander tree', () => { const io = makeIo(); const scan = vi.fn(async () => 0); - await expect(runKloCli(['dev', 'scan', 'report', '--help'], io.io, { scan })).resolves.toBe(0); + await expect(runKtxCli(['dev', 'scan', 'report', '--help'], io.io, { scan })).resolves.toBe(0); - expect(io.stdout()).toContain('--project-dir is inherited from `klo dev scan`'); - expect(io.stdout()).not.toContain('--project-dir is inherited from `klo scan`'); + expect(io.stdout()).toContain('--project-dir is inherited from `ktx dev scan`'); + expect(io.stdout()).not.toContain('--project-dir is inherited from `ktx scan`'); expect(scan).not.toHaveBeenCalled(); }); @@ -314,10 +314,10 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'report', 'scan-run-1', '--project-dir', '/tmp/project'], humanIo.io, { scan }), + runKtxCli(['dev', 'scan', 'report', 'scan-run-1', '--project-dir', '/tmp/project'], humanIo.io, { scan }), ).resolves.toBe(0); await expect( - runKloCli(['dev', 'scan', 'report', 'scan-run-2', '--project-dir', '/tmp/project', '--json'], jsonIo.io, { + runKtxCli(['dev', 'scan', 'report', 'scan-run-2', '--project-dir', '/tmp/project', '--json'], jsonIo.io, { scan, }), ).resolves.toBe(0); @@ -339,7 +339,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -377,7 +377,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -419,7 +419,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'relationships', 'scan-run-review', option, ''], io.io, { scan }), + runKtxCli(['dev', 'scan', 'relationships', 'scan-run-review', option, ''], io.io, { scan }), ).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); @@ -431,7 +431,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'relationship-feedback', '--json', '--jsonl'], io.io, { scan }), + runKtxCli(['dev', 'scan', 'relationship-feedback', '--json', '--jsonl'], io.io, { scan }), ).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); @@ -443,7 +443,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -480,7 +480,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -516,7 +516,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -557,7 +557,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -598,7 +598,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli(['dev', 'scan', 'relationship-calibration', '--accept-threshold', '1.5'], io.io, { scan }), + runKtxCli(['dev', 'scan', 'relationship-calibration', '--accept-threshold', '1.5'], io.io, { scan }), ).resolves.toBe(1); expect(scan).not.toHaveBeenCalled(); @@ -610,7 +610,7 @@ describe('dev Commander tree', () => { const scan = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'scan', @@ -635,7 +635,7 @@ describe('dev Commander tree', () => { const ingest = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'dev', 'ingest', diff --git a/packages/cli/src/dev.ts b/packages/cli/src/dev.ts index 97bb8a5d..b1026499 100644 --- a/packages/cli/src/dev.ts +++ b/packages/cli/src/dev.ts @@ -1,6 +1,6 @@ import { resolve } from 'node:path'; import type { Command } from '@commander-js/extra-typings'; -import { type CommandWithGlobalOptions, type KloCliCommandContext, resolveCommandProjectDir } from './cli-program.js'; +import { type CommandWithGlobalOptions, type KtxCliCommandContext, resolveCommandProjectDir } from './cli-program.js'; import { registerCompletionCommands } from './commands/completion-commands.js'; import { registerConnectionMappingCommands } from './commands/connection-commands.js'; import { registerDoctorCommands } from './commands/doctor-commands.js'; @@ -10,7 +10,7 @@ import { profileMark } from './startup-profile.js'; profileMark('module:dev'); -export function registerDevCommands(program: Command, context: KloCliCommandContext): void { +export function registerDevCommands(program: Command, context: KtxCliCommandContext): void { const dev = program .command('dev', { hidden: true }) .description('Low-level diagnostics, scans, adapter commands, and mapping tools') @@ -27,10 +27,10 @@ export function registerDevCommands(program: Command, context: KloCliCommandCont dev .command('init') - .description('Initialize a Git-backed KLO project directory for maintenance scripts') + .description('Initialize a Git-backed KTX project directory for maintenance scripts') .argument('[directory]', 'Project directory') - .option('--name ', 'Project name written to klo.yaml') - .option('--force', 'Rewrite klo.yaml and scaffold files in an existing project', false) + .option('--name ', 'Project name written to ktx.yaml') + .option('--force', 'Rewrite ktx.yaml and scaffold files in an existing project', false) .action( async ( projectDir: string | undefined, diff --git a/packages/cli/src/doctor.test.ts b/packages/cli/src/doctor.test.ts index e447d7e5..b40af1bd 100644 --- a/packages/cli/src/doctor.test.ts +++ b/packages/cli/src/doctor.test.ts @@ -2,10 +2,10 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { KloEmbeddingConfig, KloEmbeddingHealthCheckOptions, KloEmbeddingHealthCheckResult } from '@klo/llm'; +import type { KtxEmbeddingConfig, KtxEmbeddingHealthCheckOptions, KtxEmbeddingHealthCheckResult } from '@ktx/llm'; import { formatDoctorReport, - runKloDoctor, + runKtxDoctor, runSetupDoctorChecks, type DoctorCheck, } from './doctor.js'; @@ -32,13 +32,13 @@ function makeIo() { } type EmbeddingHealthCheck = ( - config: KloEmbeddingConfig, - options?: KloEmbeddingHealthCheckOptions, -) => Promise; + config: KtxEmbeddingConfig, + options?: KtxEmbeddingHealthCheckOptions, +) => Promise; async function writeProjectConfig(projectDir: string, embeddingLines: string[]): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -69,9 +69,9 @@ describe('formatDoctorReport', () => { }, ]; - expect(formatDoctorReport({ title: 'KLO setup doctor', checks })).toBe( + expect(formatDoctorReport({ title: 'KTX setup doctor', checks })).toBe( [ - 'KLO setup doctor', + 'KTX setup doctor', 'PASS Node 22+: v22.16.0 ABI 127', 'FAIL Native SQLite: Cannot load better-sqlite3', ' Fix: Run: pnpm run native:rebuild', @@ -85,12 +85,12 @@ describe('runSetupDoctorChecks', () => { it('returns pass checks when injected commands and file checks succeed', async () => { const checks = await runSetupDoctorChecks({ env: { PATH: '/bin' }, - workspaceRoot: '/workspace/klo', + workspaceRoot: '/workspace/ktx', execText: async (command, args) => { if (command === 'pnpm' && args[0] === '--version') return '10.28.0'; if (command === 'corepack' && args[0] === '--version') return '0.32.0'; if (command === 'uv' && args[0] === '--version') return 'uv 0.9.5'; - if (command === process.execPath && args.includes('--version')) return '@klo/cli 0.0.0-private'; + if (command === process.execPath && args.includes('--version')) return '@ktx/cli 0.0.0-private'; throw new Error(`${command} ${args.join(' ')}`); }, pathExists: async () => true, @@ -111,7 +111,7 @@ describe('runSetupDoctorChecks', () => { it('returns exact fixes when setup checks fail', async () => { const checks = await runSetupDoctorChecks({ env: {}, - workspaceRoot: '/workspace/klo', + workspaceRoot: '/workspace/ktx', execText: async (command) => { throw new Error(`${command} not found`); }, @@ -140,12 +140,12 @@ describe('runSetupDoctorChecks', () => { it('treats missing corepack as a warning so setup doctor can still pass', async () => { const checks = await runSetupDoctorChecks({ env: { PATH: '/bin' }, - workspaceRoot: '/workspace/klo', + workspaceRoot: '/workspace/ktx', execText: async (command, args) => { if (command === 'pnpm' && args[0] === '--version') return '10.28.0'; if (command === 'corepack' && args[0] === '--version') throw new Error('spawn corepack ENOENT'); if (command === 'uv' && args[0] === '--version') return 'uv 0.9.5'; - if (command === process.execPath && args.includes('--version')) return '@klo/cli 0.0.0-private'; + if (command === process.execPath && args.includes('--version')) return '@ktx/cli 0.0.0-private'; throw new Error(`${command} ${args.join(' ')}`); }, pathExists: async () => true, @@ -154,7 +154,7 @@ describe('runSetupDoctorChecks', () => { const testIo = makeIo(); await expect( - runKloDoctor({ command: 'setup', outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { + runKtxDoctor({ command: 'setup', outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { runSetupChecks: async () => checks, }), ).resolves.toBe(0); @@ -171,11 +171,11 @@ describe('runSetupDoctorChecks', () => { }); }); -describe('runKloDoctor', () => { +describe('runKtxDoctor', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-doctor-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-doctor-')); }); afterEach(async () => { @@ -186,7 +186,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'setup', outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -204,7 +204,7 @@ describe('runKloDoctor', () => { ), ).resolves.toBe(1); - expect(testIo.stdout()).toContain('KLO setup doctor'); + expect(testIo.stdout()).toContain('KTX setup doctor'); expect(testIo.stdout()).toContain('FAIL TypeScript package build: Missing packages/cli/dist/bin.js'); expect(testIo.stdout()).toContain('Fix: Run: pnpm run build'); expect(testIo.stderr()).toBe(''); @@ -214,7 +214,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'setup', outputMode: 'json', inputMode: 'disabled' }, testIo.io, { @@ -226,14 +226,14 @@ describe('runKloDoctor', () => { ).resolves.toBe(0); expect(JSON.parse(testIo.stdout())).toEqual({ - title: 'KLO setup doctor', + title: 'KTX setup doctor', checks: [{ id: 'node', label: 'Node 22+', status: 'pass', detail: 'v22.16.0 ABI 127' }], }); }); - it('runs project checks against a valid klo.yaml', async () => { + it('runs project checks against a valid ktx.yaml', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -250,7 +250,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -261,14 +261,14 @@ describe('runKloDoctor', () => { ), ).resolves.toBe(0); - expect(testIo.stdout()).toContain('KLO project doctor'); + expect(testIo.stdout()).toContain('KTX project doctor'); expect(testIo.stdout()).toContain('PASS Project config: warehouse'); expect(testIo.stdout()).toContain('PASS Connections: 1 configured'); }); it('includes Postgres historic-SQL readiness in project doctor output', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -295,12 +295,12 @@ describe('runKloDoctor', () => { status: 'warn' as const, detail: 'pg_stat_statements ready (PostgreSQL 16.4) with warnings: pg_stat_statements.max is 1000; set it to at least 5000 to reduce query-template eviction churn', - fix: `Update the Postgres parameter group or config, then rerun \`klo dev doctor --project-dir ${tempDir}\``, + fix: `Update the Postgres parameter group or config, then rerun \`ktx dev doctor --project-dir ${tempDir}\``, }, ]); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -322,7 +322,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -338,7 +338,7 @@ describe('runKloDoctor', () => { 'Semantic lane will be skipped; lexical, dictionary, and token lanes remain available.', ); expect(testIo.stdout()).toContain( - `Fix: Run: klo setup --project-dir ${tempDir} --no-input`, + `Fix: Run: ktx setup --project-dir ${tempDir} --no-input`, ); }); @@ -355,7 +355,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -375,7 +375,7 @@ describe('runKloDoctor', () => { dimensions: 384, sentenceTransformers: { baseURL: 'http://127.0.0.1:8765', pathPrefix: '' }, }, - { text: 'KLO semantic search doctor probe', timeoutMs: 1234 }, + { text: 'KTX semantic search doctor probe', timeoutMs: 1234 }, ); expect(testIo.stdout()).toContain( 'PASS Semantic search embeddings: sentence-transformers/all-MiniLM-L6-v2 (384d) probe succeeded', @@ -395,7 +395,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, { @@ -413,7 +413,7 @@ describe('runKloDoctor', () => { model: 'all-MiniLM-L6-v2', dimensions: 384, }), - { text: 'KLO semantic search doctor probe', timeoutMs: 120_000 }, + { text: 'KTX semantic search doctor probe', timeoutMs: 120_000 }, ); }); @@ -433,7 +433,7 @@ describe('runKloDoctor', () => { const testIo = makeIo(); await expect( - runKloDoctor( + runKtxDoctor( { command: 'project', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled' }, testIo.io, { @@ -454,7 +454,7 @@ describe('runKloDoctor', () => { status: 'warn', detail: 'sentence-transformers/all-MiniLM-L6-v2 (384d) probe failed: connect ECONNREFUSED 127.0.0.1:8765. Semantic lane will be skipped; lexical, dictionary, and token lanes remain available.', - fix: `Run: klo setup --project-dir ${tempDir} --no-input`, + fix: `Run: ktx setup --project-dir ${tempDir} --no-input`, }); }); }); diff --git a/packages/cli/src/doctor.ts b/packages/cli/src/doctor.ts index 417f5f43..915c7cef 100644 --- a/packages/cli/src/doctor.ts +++ b/packages/cli/src/doctor.ts @@ -4,15 +4,15 @@ import { access } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; -import type { KloLocalProject, KloProjectEmbeddingConfig } from '@klo/context/project'; -import type { KloEmbeddingConfig, KloEmbeddingHealthCheckOptions, KloEmbeddingHealthCheckResult } from '@klo/llm'; +import type { KtxLocalProject, KtxProjectEmbeddingConfig } from '@ktx/context/project'; +import type { KtxEmbeddingConfig, KtxEmbeddingHealthCheckOptions, KtxEmbeddingHealthCheckResult } from '@ktx/llm'; import type { HistoricSqlDoctorDeps } from './historic-sql-doctor.js'; const execFileAsync = promisify(execFile); type DoctorStatus = 'pass' | 'warn' | 'fail'; -type KloDoctorOutputMode = 'plain' | 'json'; -type KloDoctorInputMode = 'auto' | 'disabled'; +type KtxDoctorOutputMode = 'plain' | 'json'; +type KtxDoctorInputMode = 'auto' | 'disabled'; export interface DoctorCheck { id: string; @@ -27,12 +27,12 @@ interface DoctorReport { checks: DoctorCheck[]; } -export type KloDoctorArgs = - | { command: 'setup'; outputMode: KloDoctorOutputMode; inputMode?: KloDoctorInputMode } - | { command: 'project'; projectDir: string; outputMode: KloDoctorOutputMode; inputMode?: KloDoctorInputMode } - | { command: 'demo'; projectDir: string; outputMode: KloDoctorOutputMode; inputMode?: KloDoctorInputMode }; +export type KtxDoctorArgs = + | { command: 'setup'; outputMode: KtxDoctorOutputMode; inputMode?: KtxDoctorInputMode } + | { command: 'project'; projectDir: string; outputMode: KtxDoctorOutputMode; inputMode?: KtxDoctorInputMode } + | { command: 'demo'; projectDir: string; outputMode: KtxDoctorOutputMode; inputMode?: KtxDoctorInputMode }; -interface KloDoctorIo { +interface KtxDoctorIo { stdout: { write(chunk: string): void }; stderr: { write(chunk: string): void }; } @@ -46,9 +46,9 @@ interface SetupDoctorDeps { } type EmbeddingHealthCheck = ( - config: KloEmbeddingConfig, - options?: KloEmbeddingHealthCheckOptions, -) => Promise; + config: KtxEmbeddingConfig, + options?: KtxEmbeddingHealthCheckOptions, +) => Promise; interface SemanticSearchDoctorDeps { env?: NodeJS.ProcessEnv; @@ -56,9 +56,9 @@ interface SemanticSearchDoctorDeps { embeddingProbeTimeoutMs?: number; } -interface KloDoctorDeps extends SemanticSearchDoctorDeps, HistoricSqlDoctorDeps { +interface KtxDoctorDeps extends SemanticSearchDoctorDeps, HistoricSqlDoctorDeps { runSetupChecks?: () => Promise; - runHistoricSqlDoctorChecks?: (project: KloLocalProject, deps: HistoricSqlDoctorDeps) => Promise; + runHistoricSqlDoctorChecks?: (project: KtxLocalProject, deps: HistoricSqlDoctorDeps) => Promise; } function workspaceRootDir(): string { @@ -119,18 +119,18 @@ function check(status: DoctorStatus, id: string, label: string, detail: string, return fix ? { id, label, status, detail, fix } : { id, label, status, detail }; } -const SEMANTIC_SEARCH_HEALTH_TEXT = 'KLO semantic search doctor probe'; +const SEMANTIC_SEARCH_HEALTH_TEXT = 'KTX semantic search doctor probe'; const SEMANTIC_SEARCH_HEALTH_TIMEOUT_MS = 5_000; const SEMANTIC_SEARCH_LOCAL_HEALTH_TIMEOUT_MS = 120_000; -function semanticEmbeddingSetupFix(projectDir: string, backend: KloProjectEmbeddingConfig['backend']): string { +function semanticEmbeddingSetupFix(projectDir: string, backend: KtxProjectEmbeddingConfig['backend']): string { if (backend === 'openai') { - return `Set OPENAI_API_KEY or rerun: klo setup --project-dir ${projectDir} --embedding-backend openai --no-input`; + return `Set OPENAI_API_KEY or rerun: ktx setup --project-dir ${projectDir} --embedding-backend openai --no-input`; } - return `Run: klo setup --project-dir ${projectDir} --no-input`; + return `Run: ktx setup --project-dir ${projectDir} --no-input`; } -function embeddingConfigLabel(config: KloProjectEmbeddingConfig | KloEmbeddingConfig): string { +function embeddingConfigLabel(config: KtxProjectEmbeddingConfig | KtxEmbeddingConfig): string { const model = config.model?.trim() || 'model not configured'; return `${config.backend}/${model} (${config.dimensions}d)`; } @@ -140,15 +140,15 @@ function semanticLaneFallbackDetail(reason: string): string { } async function defaultEmbeddingHealthCheck( - config: KloEmbeddingConfig, - options?: KloEmbeddingHealthCheckOptions, -): Promise { - const { runKloEmbeddingHealthCheck } = await import('@klo/llm'); - return runKloEmbeddingHealthCheck(config, options); + config: KtxEmbeddingConfig, + options?: KtxEmbeddingHealthCheckOptions, +): Promise { + const { runKtxEmbeddingHealthCheck } = await import('@ktx/llm'); + return runKtxEmbeddingHealthCheck(config, options); } async function runSemanticSearchEmbeddingCheck( - config: KloProjectEmbeddingConfig, + config: KtxProjectEmbeddingConfig, projectDir: string, deps: SemanticSearchDoctorDeps = {}, ): Promise { @@ -163,8 +163,8 @@ async function runSemanticSearchEmbeddingCheck( } try { - const { resolveLocalKloEmbeddingConfig } = await import('@klo/context'); - const resolved = resolveLocalKloEmbeddingConfig(config, deps.env ?? process.env); + const { resolveLocalKtxEmbeddingConfig } = await import('@ktx/context'); + const resolved = resolveLocalKtxEmbeddingConfig(config, deps.env ?? process.env); if (!resolved) { return check( 'warn', @@ -300,7 +300,7 @@ export async function runSetupDoctorChecks(deps: SetupDoctorDeps = {}): Promise< 'workspace-cli', 'Workspace-local CLI', failureMessage(error), - 'Run: pnpm run build && pnpm run klo -- --version', + 'Run: pnpm run build && pnpm run ktx -- --version', ), ); } @@ -308,11 +308,11 @@ export async function runSetupDoctorChecks(deps: SetupDoctorDeps = {}): Promise< return checks; } -async function runProjectChecks(projectDir: string, deps: KloDoctorDeps = {}): Promise { - const { loadKloProject } = await import('@klo/context/project'); +async function runProjectChecks(projectDir: string, deps: KtxDoctorDeps = {}): Promise { + const { loadKtxProject } = await import('@ktx/context/project'); const checks: DoctorCheck[] = []; try { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); checks.push(check('pass', 'project-config', 'Project config', project.config.project)); const connectionCount = Object.keys(project.config.connections).length; checks.push( @@ -323,7 +323,7 @@ async function runProjectChecks(projectDir: string, deps: KloDoctorDeps = {}): P 'connections', 'Connections', '0 configured', - 'Add a connection to klo.yaml or run `klo setup demo init`', + 'Add a connection to ktx.yaml or run `ktx setup demo init`', ), ); checks.push(check('pass', 'storage', 'Storage', `${project.config.storage.state}/${project.config.storage.search}`)); @@ -339,20 +339,20 @@ async function runProjectChecks(projectDir: string, deps: KloDoctorDeps = {}): P 'project-config', 'Project config', failureMessage(error), - `Run: klo init ${projectDir} --name `, + `Run: ktx init ${projectDir} --name `, ), ); } return checks; } -async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {}): Promise { +async function runDemoProjectChecks(projectDir: string, deps: KtxDoctorDeps = {}): Promise { const env = deps.env ?? process.env; const { DEMO_CONNECTION_ID, DEMO_REPLAY_FILE } = await import('./demo-assets.js'); - const { loadKloProject } = await import('@klo/context/project'); + const { loadKtxProject } = await import('@ktx/context/project'); const checks: DoctorCheck[] = []; const requiredPaths = [ - ['demo-config', 'Demo config', 'klo.yaml'], + ['demo-config', 'Demo config', 'ktx.yaml'], ['demo-database', 'Demo dataset', 'demo.db'], ['demo-state', 'Demo state database', 'state.sqlite'], ['demo-replay', 'Demo replay', join('replays', DEMO_REPLAY_FILE)], @@ -371,13 +371,13 @@ async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {} id, label, `Missing ${relativePath}`, - `Run: klo setup demo init --project-dir ${projectDir} --force --no-input`, + `Run: ktx setup demo init --project-dir ${projectDir} --force --no-input`, ), ); } try { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); const connection = project.config.connections[DEMO_CONNECTION_ID]; checks.push( connection?.driver === 'sqlite' @@ -387,7 +387,7 @@ async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {} 'demo-connection', 'Demo connection', `${DEMO_CONNECTION_ID} is missing or is not sqlite`, - `Run: klo setup demo init --project-dir ${projectDir} --force --no-input`, + `Run: ktx setup demo init --project-dir ${projectDir} --force --no-input`, ), ); const provider = project.config.llm.provider.backend; @@ -399,7 +399,7 @@ async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {} 'demo-llm-provider', 'Demo LLM provider', provider, - `Run: klo setup demo init --project-dir ${projectDir} --force --no-input`, + `Run: ktx setup demo init --project-dir ${projectDir} --force --no-input`, ), ); if (provider === 'anthropic' && !env.ANTHROPIC_API_KEY) { @@ -409,7 +409,7 @@ async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {} 'anthropic-credentials', 'Anthropic credentials', 'ANTHROPIC_API_KEY is not set', - 'Export ANTHROPIC_API_KEY to run `klo setup demo --mode full --no-input`', + 'Export ANTHROPIC_API_KEY to run `ktx setup demo --mode full --no-input`', ), ); } else { @@ -426,7 +426,7 @@ async function runDemoProjectChecks(projectDir: string, deps: KloDoctorDeps = {} 'demo-config-parse', 'Demo config parse', failureMessage(error), - `Run: klo setup demo init --project-dir ${projectDir} --force --no-input`, + `Run: ktx setup demo init --project-dir ${projectDir} --force --no-input`, ), ); } @@ -450,7 +450,7 @@ function hasFailures(report: DoctorReport): boolean { return report.checks.some((item) => item.status === 'fail'); } -function writeReport(report: DoctorReport, outputMode: KloDoctorOutputMode, io: KloDoctorIo): void { +function writeReport(report: DoctorReport, outputMode: KtxDoctorOutputMode, io: KtxDoctorIo): void { if (outputMode === 'json') { io.stdout.write(`${JSON.stringify(report, null, 2)}\n`); return; @@ -458,24 +458,24 @@ function writeReport(report: DoctorReport, outputMode: KloDoctorOutputMode, io: io.stdout.write(formatDoctorReport(report)); } -export async function runKloDoctor( - args: KloDoctorArgs, - io: KloDoctorIo = process, - deps: KloDoctorDeps = {}, +export async function runKtxDoctor( + args: KtxDoctorArgs, + io: KtxDoctorIo = process, + deps: KtxDoctorDeps = {}, ): Promise { try { const runSetupChecks = deps.runSetupChecks ?? (() => runSetupDoctorChecks()); const setupChecks = await runSetupChecks(); const report: DoctorReport = args.command === 'setup' - ? { title: 'KLO setup doctor', checks: setupChecks } + ? { title: 'KTX setup doctor', checks: setupChecks } : args.command === 'demo' ? { - title: 'KLO demo doctor', + title: 'KTX demo doctor', checks: [...setupChecks, ...(await runDemoProjectChecks(args.projectDir, deps))], } : { - title: 'KLO project doctor', + title: 'KTX project doctor', checks: [...setupChecks, ...(await runProjectChecks(args.projectDir, deps))], }; diff --git a/packages/cli/src/example-smoke.test.ts b/packages/cli/src/example-smoke.test.ts index 0ea974bb..b20e019d 100644 --- a/packages/cli/src/example-smoke.test.ts +++ b/packages/cli/src/example-smoke.test.ts @@ -70,7 +70,7 @@ describe('standalone local warehouse example', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-example-smoke-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-example-smoke-')); }); afterEach(async () => { @@ -128,14 +128,14 @@ describe('standalone local warehouse example', () => { ]); expect(ingest).toMatchObject({ code: 1, stdout: '' }); expect(ingest.stderr).toContain( - 'klo dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', + 'ktx dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', ); }, 30_000); it('serves local wiki and semantic-layer MCP tools against the copied example project', async () => { const projectDir = await copyExampleProject(tempDir); - const client = new Client({ name: 'klo-example-client', version: '0.0.0' }); + const client = new Client({ name: 'ktx-example-client', version: '0.0.0' }); const transport = new StdioClientTransport({ command: process.execPath, args: [CLI_BIN, 'serve', '--mcp', 'stdio', '--project-dir', projectDir, '--user-id', 'example-user'], diff --git a/packages/cli/src/historic-sql-doctor.test.ts b/packages/cli/src/historic-sql-doctor.test.ts index 1eaf4c59..f4e0ee7f 100644 --- a/packages/cli/src/historic-sql-doctor.test.ts +++ b/packages/cli/src/historic-sql-doctor.test.ts @@ -1,5 +1,5 @@ -import { buildDefaultKloProjectConfig, type KloProjectConnectionConfig } from '@klo/context/project'; -import { HistoricSqlExtensionMissingError } from '@klo/context/ingest'; +import { buildDefaultKtxProjectConfig, type KtxProjectConnectionConfig } from '@ktx/context/project'; +import { HistoricSqlExtensionMissingError } from '@ktx/context/ingest'; import { describe, expect, it, vi } from 'vitest'; import { runPostgresHistoricSqlDoctorChecks, @@ -7,14 +7,14 @@ import { type PostgresHistoricSqlDoctorProbe, } from './historic-sql-doctor.js'; -function projectWithConnections(connections: Record): HistoricSqlDoctorProject { +function projectWithConnections(connections: Record): HistoricSqlDoctorProject { return { - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', config: { - ...buildDefaultKloProjectConfig('warehouse'), + ...buildDefaultKtxProjectConfig('warehouse'), connections, ingest: { - ...buildDefaultKloProjectConfig('warehouse').ingest, + ...buildDefaultKtxProjectConfig('warehouse').ingest, adapters: ['live-database', 'historic-sql'], }, }, @@ -61,7 +61,7 @@ describe('runPostgresHistoricSqlDoctorChecks', () => { ); expect(probe).toHaveBeenCalledWith({ - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', connectionId: 'warehouse', connection: { driver: 'postgres', @@ -108,7 +108,7 @@ describe('runPostgresHistoricSqlDoctorChecks', () => { status: 'warn', detail: 'pg_stat_statements ready (PostgreSQL 16.4) with warnings: pg_stat_statements.max is 1000; set it to at least 5000 to reduce query-template eviction churn', - fix: 'Update the Postgres parameter group or config, then rerun `klo dev doctor --project-dir /tmp/klo-project`', + fix: 'Update the Postgres parameter group or config, then rerun `ktx dev doctor --project-dir /tmp/ktx-project`', }, ]); }); diff --git a/packages/cli/src/historic-sql-doctor.ts b/packages/cli/src/historic-sql-doctor.ts index e734abe5..bb36648e 100644 --- a/packages/cli/src/historic-sql-doctor.ts +++ b/packages/cli/src/historic-sql-doctor.ts @@ -1,15 +1,15 @@ -import type { KloProjectConfig, KloProjectConnectionConfig } from '@klo/context/project'; +import type { KtxProjectConfig, KtxProjectConnectionConfig } from '@ktx/context/project'; import type { DoctorCheck } from './doctor.js'; export interface HistoricSqlDoctorProject { projectDir: string; - config: Pick; + config: Pick; } export interface PostgresHistoricSqlDoctorProbeInput { projectDir: string; connectionId: string; - connection: KloProjectConnectionConfig; + connection: KtxProjectConnectionConfig; env: NodeJS.ProcessEnv; } @@ -31,19 +31,19 @@ function check(status: DoctorCheck['status'], id: string, label: string, detail: return fix ? { id, label, status, detail, fix } : { id, label, status, detail }; } -function historicSqlRecord(connection: KloProjectConnectionConfig): Record | null { +function historicSqlRecord(connection: KtxProjectConnectionConfig): Record | null { const historicSql = connection.historicSql; return historicSql && typeof historicSql === 'object' && !Array.isArray(historicSql) ? (historicSql as Record) : null; } -function isEnabledPostgresHistoricSql(connection: KloProjectConnectionConfig): boolean { +function isEnabledPostgresHistoricSql(connection: KtxProjectConnectionConfig): boolean { const historicSql = historicSqlRecord(connection); return historicSql?.enabled === true && historicSql.dialect === 'postgres'; } -function isPostgresDriver(connection: KloProjectConnectionConfig): boolean { +function isPostgresDriver(connection: KtxProjectConnectionConfig): boolean { const driver = String(connection.driver ?? '').toLowerCase(); return driver === 'postgres' || driver === 'postgresql'; } @@ -62,7 +62,7 @@ function capabilityFailureFix(error: unknown, connectionId: string, projectDir: if (error instanceof Error && error.name === 'HistoricSqlVersionUnsupportedError') { return 'Use PostgreSQL 14 or newer, or disable historicSql for this connection'; } - return `Fix connections.${connectionId} Postgres settings, then rerun \`klo dev doctor --project-dir ${projectDir}\``; + return `Fix connections.${connectionId} Postgres settings, then rerun \`ktx dev doctor --project-dir ${projectDir}\``; } function failureDetail(error: unknown): string { @@ -75,14 +75,14 @@ function failureDetail(error: unknown): string { async function defaultPostgresHistoricSqlProbe( input: PostgresHistoricSqlDoctorProbeInput, ): Promise { - const [{ PostgresPgssQueryHistoryReader }, { KloPostgresHistoricSqlQueryClient, isKloPostgresConnectionConfig }] = - await Promise.all([import('@klo/context/ingest'), import('@klo/connector-postgres')]); + const [{ PostgresPgssQueryHistoryReader }, { KtxPostgresHistoricSqlQueryClient, isKtxPostgresConnectionConfig }] = + await Promise.all([import('@ktx/context/ingest'), import('@ktx/connector-postgres')]); - if (!isKloPostgresConnectionConfig(input.connection)) { + if (!isKtxPostgresConnectionConfig(input.connection)) { throw new Error(`Native PostgreSQL connector cannot run driver "${input.connection.driver ?? 'unknown'}"`); } - const client = new KloPostgresHistoricSqlQueryClient({ + const client = new KtxPostgresHistoricSqlQueryClient({ connectionId: input.connectionId, connection: input.connection, env: input.env, @@ -135,7 +135,7 @@ export async function runPostgresHistoricSqlDoctorChecks( checkId(connectionId), label, `pg_stat_statements ready (${result.pgServerVersion}) with warnings: ${result.warnings.join('; ')}`, - `Update the Postgres parameter group or config, then rerun \`klo dev doctor --project-dir ${project.projectDir}\``, + `Update the Postgres parameter group or config, then rerun \`ktx dev doctor --project-dir ${project.projectDir}\``, ), ); } else { diff --git a/packages/cli/src/index.test.ts b/packages/cli/src/index.test.ts index 6df3811d..aa751925 100644 --- a/packages/cli/src/index.test.ts +++ b/packages/cli/src/index.test.ts @@ -5,11 +5,11 @@ import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - getKloCliPackageInfo, + getKtxCliPackageInfo, rendererUnavailableVizFallback, renderMemoryFlowTui, resolveVizFallback, - runKloCli, + runKtxCli, sanitizeMemoryFlowTuiError, startLiveMemoryFlowTui, warnVizFallbackOnce, @@ -39,20 +39,20 @@ function makeIo(options: { stdoutIsTty?: boolean } = {}) { }; } -describe('getKloCliPackageInfo', () => { +describe('getKtxCliPackageInfo', () => { it('identifies the CLI package and its context dependency', () => { - expect(getKloCliPackageInfo()).toEqual({ - name: '@klo/cli', + expect(getKtxCliPackageInfo()).toEqual({ + name: '@ktx/cli', version: '0.0.0-private', - contextPackageName: '@klo/context', + contextPackageName: '@ktx/context', }); }); it('exports package metadata for package managers and runtime diagnostics', () => { - const packageJson = require('@klo/cli/package.json') as { name: string; version: string }; + const packageJson = require('@ktx/cli/package.json') as { name: string; version: string }; expect(packageJson).toMatchObject({ - name: '@klo/cli', + name: '@ktx/cli', version: '0.0.0-private', }); }); @@ -82,11 +82,11 @@ describe('memory-flow renderer exports', () => { }); }); -describe('runKloCli', () => { +describe('runKtxCli', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-')); }); afterEach(async () => { @@ -96,18 +96,18 @@ describe('runKloCli', () => { it('prints version information', async () => { const testIo = makeIo(); - await expect(runKloCli(['--version'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['--version'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toBe('@klo/cli 0.0.0-private\n'); + expect(testIo.stdout()).toBe('@ktx/cli 0.0.0-private\n'); expect(testIo.stderr()).toBe(''); }); it('prints the May 6 public command surface in root help', async () => { const testIo = makeIo(); - await expect(runKloCli(['--help'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['--help'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx [options] [command]'); for (const command of ['setup', 'connection', 'ingest', 'wiki', 'sl', 'serve', 'status']) { expect(testIo.stdout()).toContain(`${command}`); } @@ -116,22 +116,22 @@ describe('runKloCli', () => { expect(testIo.stdout()).not.toContain(`${removed} `); } expect(testIo.stdout()).toContain('--project-dir '); - expect(testIo.stdout()).toContain('KLO_PROJECT_DIR'); + expect(testIo.stdout()).toContain('KTX_PROJECT_DIR'); expect(testIo.stdout()).toContain('--debug'); expect(testIo.stdout()).not.toContain('--' + 'verbose'); expect(testIo.stdout()).toContain('Advanced:'); - expect(testIo.stdout()).toContain('klo dev'); + expect(testIo.stdout()).toContain('ktx dev'); expect(testIo.stderr()).toBe(''); }); it('exposes demo under setup help instead of root help', async () => { const testIo = makeIo(); - await expect(runKloCli(['setup', '--help'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['setup', '--help'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo setup [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx setup [options] [command]'); expect(testIo.stdout()).toContain('demo'); - expect(testIo.stdout()).toContain('Run the packaged KLO demo from setup'); + expect(testIo.stdout()).toContain('Run the packaged KTX demo from setup'); expect(testIo.stdout()).not.toContain('--skip-llm'); expect(testIo.stdout()).not.toContain('--skip-embeddings'); expect(testIo.stdout()).not.toContain('--embedding-model'); @@ -140,33 +140,33 @@ describe('runKloCli', () => { expect(testIo.stderr()).toBe(''); }); - it('prints help for bare klo outside a TTY', async () => { + it('prints help for bare ktx outside a TTY', async () => { const setup = vi.fn(async () => 0); const testIo = makeIo({ stdoutIsTty: false }); - await expect(runKloCli([], testIo.io, { setup })).resolves.toBe(0); + await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx [options] [command]'); expect(setup).not.toHaveBeenCalled(); expect(testIo.stderr()).toBe(''); }); - it('starts setup for bare klo in a TTY when no project is discoverable', async () => { + it('starts setup for bare ktx in a TTY when no project is discoverable', async () => { const { mkdtemp, realpath, rm } = await import('node:fs/promises'); const { tmpdir } = await import('node:os'); const { join } = await import('node:path'); const originalCwd = process.cwd(); - const tempDir = await mkdtemp(join(tmpdir(), 'klo-bare-setup-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-bare-setup-')); const setup = vi.fn(async () => 0); const testIo = makeIo({ stdoutIsTty: true }); - const previousProjectDir = process.env.KLO_PROJECT_DIR; + const previousProjectDir = process.env.KTX_PROJECT_DIR; const expectedProjectDir = await realpath(tempDir); try { - delete process.env.KLO_PROJECT_DIR; + delete process.env.KTX_PROJECT_DIR; process.chdir(tempDir); - await expect(runKloCli([], testIo.io, { setup })).resolves.toBe(0); + await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0); expect(setup).toHaveBeenCalledWith( { @@ -187,71 +187,71 @@ describe('runKloCli', () => { }, testIo.io, ); - expect(testIo.stdout()).not.toContain('Usage: klo [options] [command]'); + expect(testIo.stdout()).not.toContain('Usage: ktx [options] [command]'); expect(testIo.stderr()).toBe(''); } finally { process.chdir(originalCwd); if (previousProjectDir === undefined) { - delete process.env.KLO_PROJECT_DIR; + delete process.env.KTX_PROJECT_DIR; } else { - process.env.KLO_PROJECT_DIR = previousProjectDir; + process.env.KTX_PROJECT_DIR = previousProjectDir; } await rm(tempDir, { recursive: true, force: true }); } }); - it('prints help without project status for bare klo in a TTY when a project is discoverable', async () => { + it('prints help without project status for bare ktx in a TTY when a project is discoverable', async () => { const { mkdtemp, realpath, rm, writeFile } = await import('node:fs/promises'); const { tmpdir } = await import('node:os'); const { join } = await import('node:path'); const originalCwd = process.cwd(); - const previousProjectDir = process.env.KLO_PROJECT_DIR; - const tempDir = await mkdtemp(join(tmpdir(), 'klo-bare-existing-')); + const previousProjectDir = process.env.KTX_PROJECT_DIR; + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-bare-existing-')); const setup = vi.fn(async () => 0); const testIo = makeIo({ stdoutIsTty: true }); const expectedProjectDir = await realpath(tempDir); try { - delete process.env.KLO_PROJECT_DIR; - await writeFile(join(tempDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + delete process.env.KTX_PROJECT_DIR; + await writeFile(join(tempDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); process.chdir(tempDir); - await expect(runKloCli([], testIo.io, { setup })).resolves.toBe(0); + await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx [options] [command]'); expect(testIo.stdout()).not.toContain(`Project: ${expectedProjectDir}`); expect(setup).not.toHaveBeenCalled(); } finally { process.chdir(originalCwd); if (previousProjectDir === undefined) { - delete process.env.KLO_PROJECT_DIR; + delete process.env.KTX_PROJECT_DIR; } else { - process.env.KLO_PROJECT_DIR = previousProjectDir; + process.env.KTX_PROJECT_DIR = previousProjectDir; } await rm(tempDir, { recursive: true, force: true }); } }); - it('does not invoke status for bare klo in a TTY when status would fail', async () => { + it('does not invoke status for bare ktx in a TTY when status would fail', async () => { const setup = vi.fn(async () => { throw new Error('Unsupported ingest.llm: use top-level llm.provider, llm.models, and ingest.workUnits'); }); const testIo = makeIo({ stdoutIsTty: true }); - const previousProjectDir = process.env.KLO_PROJECT_DIR; + const previousProjectDir = process.env.KTX_PROJECT_DIR; try { - process.env.KLO_PROJECT_DIR = tempDir; + process.env.KTX_PROJECT_DIR = tempDir; - await expect(runKloCli([], testIo.io, { setup })).resolves.toBe(0); + await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx [options] [command]'); expect(setup).not.toHaveBeenCalled(); expect(testIo.stderr()).toBe(''); } finally { if (previousProjectDir === undefined) { - delete process.env.KLO_PROJECT_DIR; + delete process.env.KTX_PROJECT_DIR; } else { - process.env.KLO_PROJECT_DIR = previousProjectDir; + process.env.KTX_PROJECT_DIR = previousProjectDir; } } }); @@ -260,7 +260,7 @@ describe('runKloCli', () => { const testIo = makeIo(); const removedVerboseOption = '--' + 'verbose'; - await expect(runKloCli([removedVerboseOption, 'connection', 'list'], testIo.io)).resolves.toBe(1); + await expect(runKtxCli([removedVerboseOption, 'connection', 'list'], testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toContain(`unknown option '${removedVerboseOption}'`); expect(testIo.stdout()).toBe(''); @@ -270,12 +270,12 @@ describe('runKloCli', () => { const testIo = makeIo(); const zshWords = '$' + '{words[@]}'; - await expect(runKloCli(['dev', 'completion', 'zsh'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', 'completion', 'zsh'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain('#compdef klo'); - expect(testIo.stdout()).toContain('KLO_COMPLETION_COMMAND:-klo'); + expect(testIo.stdout()).toContain('#compdef ktx'); + expect(testIo.stdout()).toContain('KTX_COMPLETION_COMMAND:-ktx'); expect(testIo.stdout()).toContain(`dev __complete --shell zsh --position "$CURRENT" -- "${zshWords}"`); - expect(testIo.stdout()).toContain('compdef _klo klo'); + expect(testIo.stdout()).toContain('compdef _ktx ktx'); expect(testIo.stderr()).toBe(''); }); @@ -283,22 +283,22 @@ describe('runKloCli', () => { const testIo = makeIo(); const previousHome = process.env.HOME; const previousZdotdir = process.env.ZDOTDIR; - const tempHome = await mkdtemp(join(tmpdir(), 'klo-completion-home-')); + const tempHome = await mkdtemp(join(tmpdir(), 'ktx-completion-home-')); try { process.env.HOME = tempHome; delete process.env.ZDOTDIR; - await expect(runKloCli(['dev', 'completion', 'zsh', '--install'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', 'completion', 'zsh', '--install'], testIo.io)).resolves.toBe(0); - const completionFile = await readFile(join(tempHome, '.zfunc', '_klo'), 'utf-8'); + const completionFile = await readFile(join(tempHome, '.zfunc', '_ktx'), 'utf-8'); const zshrc = await readFile(join(tempHome, '.zshrc'), 'utf-8'); - expect(completionFile).toContain('#compdef klo'); - expect(zshrc).toContain('# >>> klo completion >>>'); - expect(zshrc).toContain('_klo_completion_command()'); - expect(zshrc).toContain('"name": "klo-workspace"'); - expect(zshrc).toContain('scripts/run-klo.mjs'); - expect(zshrc).toContain("export KLO_COMPLETION_COMMAND='$(_klo_completion_command)'"); + expect(completionFile).toContain('#compdef ktx'); + expect(zshrc).toContain('# >>> ktx completion >>>'); + expect(zshrc).toContain('_ktx_completion_command()'); + expect(zshrc).toContain('"name": "ktx-workspace"'); + expect(zshrc).toContain('scripts/run-ktx.mjs'); + expect(zshrc).toContain("export KTX_COMPLETION_COMMAND='$(_ktx_completion_command)'"); expect(zshrc).toContain('setopt complete_aliases'); expect(zshrc).toContain('fpath=("$HOME/.zfunc" $fpath)'); expect(zshrc).toContain('autoload -Uz compinit'); @@ -326,20 +326,20 @@ describe('runKloCli', () => { const secondIo = makeIo(); const previousHome = process.env.HOME; const previousZdotdir = process.env.ZDOTDIR; - const tempHome = await mkdtemp(join(tmpdir(), 'klo-completion-home-')); + const tempHome = await mkdtemp(join(tmpdir(), 'ktx-completion-home-')); try { process.env.HOME = tempHome; delete process.env.ZDOTDIR; await writeFile(join(tempHome, '.zshrc'), 'export EDITOR=vim\nautoload -Uz compinit\ncompinit\n', 'utf-8'); - await expect(runKloCli(['dev', 'completion', 'zsh', '--install'], firstIo.io)).resolves.toBe(0); - await expect(runKloCli(['dev', 'completion', 'zsh', '--install'], secondIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', 'completion', 'zsh', '--install'], firstIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev', 'completion', 'zsh', '--install'], secondIo.io)).resolves.toBe(0); const zshrc = await readFile(join(tempHome, '.zshrc'), 'utf-8'); - expect(zshrc.match(/# >>> klo completion >>>/g)).toHaveLength(1); + expect(zshrc.match(/# >>> ktx completion >>>/g)).toHaveLength(1); expect(zshrc.indexOf('fpath=("$HOME/.zfunc" $fpath)')).toBeLessThan(zshrc.indexOf('autoload -Uz compinit')); - expect(zshrc.match(/_klo_completion_command\(\)/g)).toHaveLength(1); + expect(zshrc.match(/_ktx_completion_command\(\)/g)).toHaveLength(1); expect(zshrc.match(/^compinit$/gm)).toHaveLength(1); expect(secondIo.stdout()).toContain('Updated zsh config:'); expect(firstIo.stderr()).toBe(''); @@ -364,11 +364,11 @@ describe('runKloCli', () => { const connectionIo = makeIo(); await expect( - runKloCli(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'klo', 'co'], rootIo.io), + runKtxCli(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', 'co'], rootIo.io), ).resolves.toBe(0); await expect( - runKloCli( - ['dev', '__complete', '--shell', 'zsh', '--position', '3', '--', 'klo', 'connection', 'm'], + runKtxCli( + ['dev', '__complete', '--shell', 'zsh', '--position', '3', '--', 'ktx', 'connection', 'm'], connectionIo.io, ), ).resolves.toBe(0); @@ -386,13 +386,13 @@ describe('runKloCli', () => { const choiceIo = makeIo(); await expect( - runKloCli( - ['dev', '__complete', '--shell', 'zsh', '--position', '4', '--', 'klo', 'connection', 'add', '--cr'], + runKtxCli( + ['dev', '__complete', '--shell', 'zsh', '--position', '4', '--', 'ktx', 'connection', 'add', '--cr'], optionIo.io, ), ).resolves.toBe(0); await expect( - runKloCli( + runKtxCli( [ 'dev', '__complete', @@ -401,7 +401,7 @@ describe('runKloCli', () => { '--position', '7', '--', - 'klo', + 'ktx', 'connection', 'add', 'notion', @@ -425,7 +425,7 @@ describe('runKloCli', () => { const serveStdio = vi.fn().mockResolvedValue(0); await expect( - runKloCli(['--project-dir', tempDir, 'serve', '--mcp', 'stdio', '--user-id', 'agent'], testIo.io, { + runKtxCli(['--project-dir', tempDir, 'serve', '--mcp', 'stdio', '--user-id', 'agent'], testIo.io, { serveStdio, }), ).resolves.toBe(0); @@ -447,7 +447,7 @@ describe('runKloCli', () => { const ingest = vi.fn().mockResolvedValue(0); await expect( - runKloCli(['--project-dir', '/tmp/project', 'ingest', 'warehouse'], testIo.io, { publicIngest: ingest }), + runKtxCli(['--project-dir', '/tmp/project', 'ingest', 'warehouse'], testIo.io, { publicIngest: ingest }), ).resolves.toBe(0); expect(ingest).toHaveBeenCalledWith( @@ -469,10 +469,10 @@ describe('runKloCli', () => { const lowLevelIngest = vi.fn(async () => 0); await expect( - runKloCli(['ingest', 'watch', '--help'], testIo.io, { publicIngest, ingest: lowLevelIngest }), + runKtxCli(['ingest', 'watch', '--help'], testIo.io, { publicIngest, ingest: lowLevelIngest }), ).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo ingest watch [options] [runId]'); + expect(testIo.stdout()).toContain('Usage: ktx ingest watch [options] [runId]'); expect(testIo.stdout()).toContain('[runId]'); expect(testIo.stdout()).toContain('--project-dir '); expect(testIo.stdout()).toContain('--json'); @@ -488,12 +488,12 @@ describe('runKloCli', () => { const publicIngest = vi.fn(async () => 0); await expect( - runKloCli(['--project-dir', tempDir, 'ingest', 'status', 'run-1', '--json', '--no-input'], statusIo.io, { + runKtxCli(['--project-dir', tempDir, 'ingest', 'status', 'run-1', '--json', '--no-input'], statusIo.io, { publicIngest, }), ).resolves.toBe(0); await expect( - runKloCli(['--project-dir', tempDir, 'ingest', 'watch', '--no-input'], watchIo.io, { + runKtxCli(['--project-dir', tempDir, 'ingest', 'watch', '--no-input'], watchIo.io, { publicIngest, }), ).resolves.toBe(0); @@ -527,7 +527,7 @@ describe('runKloCli', () => { const testIo = makeIo(); const demo = vi.fn().mockResolvedValue(0); - await expect(runKloCli(['demo', '--mode', 'replay', '--no-input'], testIo.io, { demo })).resolves.toBe(1); + await expect(runKtxCli(['demo', '--mode', 'replay', '--no-input'], testIo.io, { demo })).resolves.toBe(1); expect(testIo.stderr()).toMatch(/unknown command|error:/i); expect(demo).not.toHaveBeenCalled(); @@ -538,7 +538,7 @@ describe('runKloCli', () => { const demo = vi.fn().mockResolvedValue(0); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'demo', '--mode', 'replay', '--no-input'], testIo.io, { demo }), + runKtxCli(['--project-dir', tempDir, 'setup', 'demo', '--mode', 'replay', '--no-input'], testIo.io, { demo }), ).resolves.toBe(0); expect(demo).toHaveBeenCalledWith( @@ -553,7 +553,7 @@ describe('runKloCli', () => { demo.mockClear(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'demo', '--mode', 'seeded', '--no-input'], testIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', 'demo', '--mode', 'seeded', '--no-input'], testIo.io, { demo, }), ).resolves.toBe(0); @@ -569,7 +569,7 @@ describe('runKloCli', () => { demo.mockClear(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', '--no-input', 'demo', '--mode', 'seeded'], testIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', '--no-input', 'demo', '--mode', 'seeded'], testIo.io, { demo, }), ).resolves.toBe(0); @@ -585,7 +585,7 @@ describe('runKloCli', () => { demo.mockClear(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'demo', 'inspect', '--no-input'], testIo.io, { demo }), + runKtxCli(['--project-dir', tempDir, 'setup', 'demo', 'inspect', '--no-input'], testIo.io, { demo }), ).resolves.toBe(0); expect(demo).toHaveBeenCalledWith( { @@ -603,7 +603,7 @@ describe('runKloCli', () => { const demo = vi.fn().mockResolvedValue(0); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'demo', 'ingest', '--mode', 'full', '--no-input'], testIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', 'demo', 'ingest', '--mode', 'full', '--no-input'], testIo.io, { demo, }), ).resolves.toBe(0); @@ -621,7 +621,7 @@ describe('runKloCli', () => { demo.mockClear(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', '--no-input', 'demo', 'ingest', '--mode', 'seeded'], testIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', '--no-input', 'demo', 'ingest', '--mode', 'seeded'], testIo.io, { demo, }), ).resolves.toBe(0); @@ -639,7 +639,7 @@ describe('runKloCli', () => { demo.mockClear(); await expect( - runKloCli( + runKtxCli( ['--project-dir', tempDir, 'setup', 'demo', 'ingest', '--mode', 'full', '--no-input', '--plain'], testIo.io, { @@ -665,16 +665,16 @@ describe('runKloCli', () => { const publicIngest = vi.fn(); const lowLevelIngest = vi.fn(); - await expect(runKloCli(['ingest', '--help'], testIo.io, { publicIngest, ingest: lowLevelIngest })).resolves.toBe(0); + await expect(runKtxCli(['ingest', '--help'], testIo.io, { publicIngest, ingest: lowLevelIngest })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo ingest [options] [connectionId]'); - expect(testIo.stdout()).toContain('Build and refresh KLO context from configured sources'); + expect(testIo.stdout()).toContain('Usage: ktx ingest [options] [connectionId]'); + expect(testIo.stdout()).toContain('Build and refresh KTX context from configured sources'); expect(testIo.stdout()).toContain('status'); expect(testIo.stdout()).toContain('watch'); - expect(testIo.stdout()).toContain('klo ingest --all [options]'); - expect(testIo.stdout()).toContain('klo ingest status [runId] [options]'); - expect(testIo.stdout()).toContain('klo ingest watch [runId] [options]'); - expect(testIo.stdout()).not.toContain('klo ingest replay [options]'); + expect(testIo.stdout()).toContain('ktx ingest --all [options]'); + expect(testIo.stdout()).toContain('ktx ingest status [runId] [options]'); + expect(testIo.stdout()).toContain('ktx ingest watch [runId] [options]'); + expect(testIo.stdout()).not.toContain('ktx ingest replay [options]'); expect(testIo.stdout()).toContain('--no-input'); expect(testIo.stdout()).not.toContain('--adapter'); expect(testIo.stderr()).toBe(''); @@ -689,20 +689,20 @@ describe('runKloCli', () => { const publicIngest = vi.fn(async () => 0); const lowLevelIngest = vi.fn(async () => 0); - await expect(runKloCli(['ingest', 'run'], publicRunIo.io, { publicIngest, ingest: lowLevelIngest })).resolves.toBe( + await expect(runKtxCli(['ingest', 'run'], publicRunIo.io, { publicIngest, ingest: lowLevelIngest })).resolves.toBe( 1, ); expect(publicRunIo.stderr()).toMatch(/invalid argument|reserved|run/i); expect(publicIngest).not.toHaveBeenCalled(); await expect( - runKloCli(['ingest', 'run', '--help'], publicHelpIo.io, { publicIngest, ingest: lowLevelIngest }), + runKtxCli(['ingest', 'run', '--help'], publicHelpIo.io, { publicIngest, ingest: lowLevelIngest }), ).resolves.toBe(0); - expect(publicHelpIo.stdout()).toContain('Usage: klo ingest [options] [connectionId]'); - expect(publicHelpIo.stdout()).not.toContain('Usage: klo ingest ' + 'run'); + expect(publicHelpIo.stdout()).toContain('Usage: ktx ingest [options] [connectionId]'); + expect(publicHelpIo.stdout()).not.toContain('Usage: ktx ingest ' + 'run'); await expect( - runKloCli(['dev', 'ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], devRunIo.io, { + runKtxCli(['dev', 'ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], devRunIo.io, { publicIngest, ingest: lowLevelIngest, }), @@ -720,11 +720,11 @@ describe('runKloCli', () => { const ingestRunIo = makeIo(); const ingestReplayHelpIo = makeIo(); - await expect(runKloCli(['dev', 'doctor', 'setup', '--json', '--no-input'], doctorIo.io, { doctor })).resolves.toBe( + await expect(runKtxCli(['dev', 'doctor', 'setup', '--json', '--no-input'], doctorIo.io, { doctor })).resolves.toBe( 0, ); await expect( - runKloCli( + runKtxCli( [ 'dev', 'ingest', @@ -746,7 +746,7 @@ describe('runKloCli', () => { { ingest }, ), ).resolves.toBe(0); - await expect(runKloCli(['dev', 'ingest', 'replay', '--help'], ingestReplayHelpIo.io, { ingest })).resolves.toBe(0); + await expect(runKtxCli(['dev', 'ingest', 'replay', '--help'], ingestReplayHelpIo.io, { ingest })).resolves.toBe(0); expect(doctor).toHaveBeenCalledWith({ command: 'setup', outputMode: 'json', inputMode: 'disabled' }, doctorIo.io); expect(ingest).toHaveBeenCalledWith( @@ -763,7 +763,7 @@ describe('runKloCli', () => { }, ingestRunIo.io, ); - expect(ingestReplayHelpIo.stdout()).toContain('Usage: klo dev ingest replay [options] '); + expect(ingestReplayHelpIo.stdout()).toContain('Usage: ktx dev ingest replay [options] '); expect(ingestReplayHelpIo.stdout()).toContain(''); expect(doctorIo.stderr()).toBe(''); expect(ingestRunIo.stderr()).toBe(''); @@ -774,7 +774,7 @@ describe('runKloCli', () => { const testIo = makeIo(); const connection = vi.fn(async () => 0); - await expect(runKloCli(['--project-dir', tempDir, 'connection', 'list'], testIo.io, { connection })).resolves.toBe( + await expect(runKtxCli(['--project-dir', tempDir, 'connection', 'list'], testIo.io, { connection })).resolves.toBe( 0, ); @@ -788,9 +788,9 @@ describe('runKloCli', () => { const statusIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'status', '--json'], setupIo.io, { setup }), + runKtxCli(['--project-dir', tempDir, 'setup', 'status', '--json'], setupIo.io, { setup }), ).resolves.toBe(0); - await expect(runKloCli(['--project-dir', tempDir, 'status', '--json'], statusIo.io, { setup })).resolves.toBe(0); + await expect(runKtxCli(['--project-dir', tempDir, 'status', '--json'], statusIo.io, { setup })).resolves.toBe(0); expect(setup).toHaveBeenNthCalledWith(1, { command: 'status', projectDir: tempDir, json: true }, setupIo.io); expect(setup).toHaveBeenNthCalledWith(2, { command: 'status', projectDir: tempDir, json: true }, statusIo.io); @@ -803,21 +803,21 @@ describe('runKloCli', () => { const statusIo = makeIo(); const stopIo = makeIo(); - await expect(runKloCli(['--project-dir', tempDir, 'setup', 'context', 'build'], buildIo.io, { setup })).resolves.toBe( + await expect(runKtxCli(['--project-dir', tempDir, 'setup', 'context', 'build'], buildIo.io, { setup })).resolves.toBe( 0, ); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'context', 'watch', 'setup-context-local-1'], watchIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', 'context', 'watch', 'setup-context-local-1'], watchIo.io, { setup, }), ).resolves.toBe(0); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'context', 'status', 'setup-context-local-1', '--json'], statusIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', 'context', 'status', 'setup-context-local-1', '--json'], statusIo.io, { setup, }), ).resolves.toBe(0); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'context', 'stop', 'setup-context-local-1'], stopIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', 'context', 'stop', 'setup-context-local-1'], stopIo.io, { setup, }), ).resolves.toBe(0); @@ -849,7 +849,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -883,7 +883,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -907,7 +907,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -943,7 +943,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ 'setup', '--project-dir', @@ -997,7 +997,7 @@ describe('runKloCli', () => { const testIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -1045,7 +1045,7 @@ describe('runKloCli', () => { const removeIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -1064,7 +1064,7 @@ describe('runKloCli', () => { ), ).resolves.toBe(0); await expect( - runKloCli(['--project-dir', tempDir, 'setup', 'remove', '--agents'], removeIo.io, { setup }), + runKtxCli(['--project-dir', tempDir, 'setup', 'remove', '--agents'], removeIo.io, { setup }), ).resolves.toBe(0); expect(setup).toHaveBeenNthCalledWith( @@ -1088,7 +1088,7 @@ describe('runKloCli', () => { const testIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -1115,7 +1115,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'deterministic'], setupIo.io, { setup }), + runKtxCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'deterministic'], setupIo.io, { setup }), ).resolves.toBe(1); expect(setup).not.toHaveBeenCalled(); @@ -1127,7 +1127,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'gateway'], setupIo.io, { setup }), + runKtxCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'gateway'], setupIo.io, { setup }), ).resolves.toBe(1); expect(setup).not.toHaveBeenCalled(); @@ -1139,7 +1139,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -1165,7 +1165,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'setup', '--enable-historic-sql', '--disable-historic-sql'], setupIo.io, { + runKtxCli(['--project-dir', tempDir, 'setup', '--enable-historic-sql', '--disable-historic-sql'], setupIo.io, { setup, }), ).resolves.toBe(1); @@ -1179,12 +1179,12 @@ describe('runKloCli', () => { const toolsIo = makeIo(); const agent = vi.fn(async () => 0); - await expect(runKloCli(['agent', '--help'], helpIo.io, { agent })).resolves.toBe(0); + await expect(runKtxCli(['agent', '--help'], helpIo.io, { agent })).resolves.toBe(0); await expect( - runKloCli(['--project-dir', tempDir, 'agent', 'tools', '--json'], toolsIo.io, { agent }), + runKtxCli(['--project-dir', tempDir, 'agent', 'tools', '--json'], toolsIo.io, { agent }), ).resolves.toBe(0); - expect(helpIo.stdout()).toContain('Usage: klo agent'); + expect(helpIo.stdout()).toContain('Usage: ktx agent'); expect(toolsIo.stderr()).toBe(''); expect(agent).toHaveBeenCalledWith({ command: 'tools', projectDir: tempDir, json: true }, toolsIo.io); }); @@ -1277,13 +1277,13 @@ describe('runKloCli', () => { for (const entry of cases) { const io = makeIo(); - await expect(runKloCli(entry.argv, io.io, { agent })).resolves.toBe(0); + await expect(runKtxCli(entry.argv, io.io, { agent })).resolves.toBe(0); expect(agent).toHaveBeenLastCalledWith(entry.args, io.io); expect(io.stderr()).toBe(''); } const helpIo = makeIo(); - await expect(runKloCli(['--help'], helpIo.io, { agent })).resolves.toBe(0); + await expect(runKtxCli(['--help'], helpIo.io, { agent })).resolves.toBe(0); expect(helpIo.stdout()).not.toContain('agent '); }); @@ -1323,7 +1323,7 @@ describe('runKloCli', () => { const io = makeIo(); await expect( - runKloCli( + runKtxCli( ['--project-dir', tempDir, 'agent', 'sl', 'list', '--json', '--connection-id', 'warehouse', '--query', 'paid'], io.io, { agent }, @@ -1376,7 +1376,7 @@ describe('runKloCli', () => { const io = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'agent', 'wiki', 'search', 'paid order', '--json', '--limit', '5'], io.io, { + runKtxCli(['--project-dir', tempDir, 'agent', 'wiki', 'search', 'paid order', '--json', '--limit', '5'], io.io, { agent, }), ).resolves.toBe(0); @@ -1394,23 +1394,23 @@ describe('runKloCli', () => { }); it('dispatches public connection subcommands through the existing connection implementation', async () => { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-connection-dispatch-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-connection-dispatch-')); const connection = vi.fn(async () => 0); await expect( - runKloCli(['--project-dir', tempDir, 'connection', 'list'], makeIo().io, { connection }), + runKtxCli(['--project-dir', tempDir, 'connection', 'list'], makeIo().io, { connection }), ).resolves.toBe(0); const removeIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'connection', 'remove', 'warehouse', '--force', '--no-input'], removeIo.io, { + runKtxCli(['--project-dir', tempDir, 'connection', 'remove', 'warehouse', '--force', '--no-input'], removeIo.io, { connection, }), ).resolves.toBe(0); const mapIo = makeIo(); await expect( - runKloCli(['--project-dir', tempDir, 'connection', 'map', 'prod-metabase', '--json'], mapIo.io, { + runKtxCli(['--project-dir', tempDir, 'connection', 'map', 'prod-metabase', '--json'], mapIo.io, { connection, }), ).resolves.toBe(0); @@ -1444,9 +1444,9 @@ describe('runKloCli', () => { it('prints help for connection metabase setup', async () => { const helpIo = makeIo(); - await expect(runKloCli(['connection', 'metabase', 'setup', '--help'], helpIo.io)).resolves.toBe(0); + await expect(runKtxCli(['connection', 'metabase', 'setup', '--help'], helpIo.io)).resolves.toBe(0); - expect(helpIo.stdout()).toContain('Usage: klo connection metabase setup'); + expect(helpIo.stdout()).toContain('Usage: ktx connection metabase setup'); for (const option of [ '--id ', '--url ', @@ -1465,10 +1465,10 @@ describe('runKloCli', () => { } expect(helpIo.stdout()).toContain('Guided equivalent of:'); for (const line of [ - 'klo connection mapping refresh --auto-accept', - 'klo connection mapping set databaseMappings =', - 'klo connection mapping set-sync-enabled --enabled true', - 'klo ingest ', + 'ktx connection mapping refresh --auto-accept', + 'ktx connection mapping set databaseMappings =', + 'ktx connection mapping set-sync-enabled --enabled true', + 'ktx ingest ', ]) { expect(helpIo.stdout()).toContain(line); } @@ -1481,7 +1481,7 @@ describe('runKloCli', () => { const setupIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ 'connection', 'metabase', @@ -1598,7 +1598,7 @@ describe('runKloCli', () => { ], ]) { const testIo = makeIo(); - await expect(runKloCli(argv, testIo.io, { connectionMetabaseSetup })).resolves.toBe(1); + await expect(runKtxCli(argv, testIo.io, { connectionMetabaseSetup })).resolves.toBe(1); expect(testIo.stderr()).toMatch(/map|sync|sync-mode|conflict|cannot be used|invalid|integer|choices/i); } @@ -1615,7 +1615,7 @@ describe('runKloCli', () => { ]) { const testIo = makeIo(); - await expect(runKloCli(argv, testIo.io)).resolves.toBe(1); + await expect(runKtxCli(argv, testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toMatch(/unknown command|error:/); } @@ -1626,7 +1626,7 @@ describe('runKloCli', () => { const connection = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'connection', 'add', @@ -1688,9 +1688,9 @@ describe('runKloCli', () => { const testIo = makeIo(); const connectionNotion = vi.fn(async () => 0); - await expect(runKloCli(argv, testIo.io, { connectionNotion })).resolves.toBe(0); + await expect(runKtxCli(argv, testIo.io, { connectionNotion })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo connection notion'); + expect(testIo.stdout()).toContain('Usage: ktx connection notion'); expect(testIo.stdout()).toContain('pick'); expect(testIo.stderr()).toBe(''); expect(connectionNotion).not.toHaveBeenCalled(); @@ -1702,7 +1702,7 @@ describe('runKloCli', () => { const connectionNotion = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ '--project-dir', tempDir, @@ -1739,7 +1739,7 @@ describe('runKloCli', () => { const connectionNotion = vi.fn(async () => 0); await expect( - runKloCli(['connection', 'notion', 'pick', 'notion-main', '--root-page-id', 'not-a-uuid'], testIo.io, { + runKtxCli(['connection', 'notion', 'pick', 'notion-main', '--root-page-id', 'not-a-uuid'], testIo.io, { connectionNotion, }), ).resolves.toBe(0); @@ -1761,7 +1761,7 @@ describe('runKloCli', () => { const connectionNotion = vi.fn(async () => 0); await expect( - runKloCli(['connection', 'notion', 'pick', 'notion-main', '--no-input'], testIo.io, { connectionNotion }), + runKtxCli(['connection', 'notion', 'pick', 'notion-main', '--no-input'], testIo.io, { connectionNotion }), ).resolves.toBe(1); expect(connectionNotion).not.toHaveBeenCalled(); @@ -1773,18 +1773,18 @@ describe('runKloCli', () => { const connection = vi.fn().mockResolvedValue(0); await expect( - runKloCli(['--project-dir', tempDir, '--debug', 'connection', 'list'], testIo.io, { connection }), + runKtxCli(['--project-dir', tempDir, '--debug', 'connection', 'list'], testIo.io, { connection }), ).resolves.toBe(0); expect(testIo.stderr()).toContain(`[debug] projectDir=${tempDir}`); expect(testIo.stderr()).toContain('[debug] dispatch=connection'); }); - it('routes low-level scan through klo dev with top-level project-dir', async () => { + it('routes low-level scan through ktx dev with top-level project-dir', async () => { const testIo = makeIo(); const scan = vi.fn().mockResolvedValue(0); - await expect(runKloCli(['--project-dir', tempDir, 'dev', 'scan', 'warehouse'], testIo.io, { scan })).resolves.toBe( + await expect(runKtxCli(['--project-dir', tempDir, 'dev', 'scan', 'warehouse'], testIo.io, { scan })).resolves.toBe( 0, ); @@ -1807,7 +1807,7 @@ describe('runKloCli', () => { const serveStdio = vi.fn(async () => 0); await expect( - runKloCli( + runKtxCli( [ 'serve', '--mcp', @@ -1843,9 +1843,9 @@ describe('runKloCli', () => { it('prints dev help for bare dev commands', async () => { const testIo = makeIo(); - await expect(runKloCli(['dev'], testIo.io)).resolves.toBe(0); + await expect(runKtxCli(['dev'], testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo dev [options] [command]'); + expect(testIo.stdout()).toContain('Usage: ktx dev [options] [command]'); expect(testIo.stdout()).toContain('Low-level diagnostics'); expect(testIo.stdout()).toContain('scan'); expect(testIo.stdout()).toContain('ingest'); @@ -1857,15 +1857,15 @@ describe('runKloCli', () => { it('prints dev command help without invoking low-level execution', async () => { for (const [command, expected] of [ - ['scan', ['Usage: klo dev scan', '--dry-run', 'status', 'report']], - ['ingest', ['Usage: klo dev ingest', 'run', 'replay']], - ['mapping', ['Usage: klo dev mapping', 'sync-state', 'validate']], + ['scan', ['Usage: ktx dev scan', '--dry-run', 'status', 'report']], + ['ingest', ['Usage: ktx dev ingest', 'run', 'replay']], + ['mapping', ['Usage: ktx dev mapping', 'sync-state', 'validate']], ] as const) { const testIo = makeIo(); const scan = vi.fn().mockResolvedValue(0); const sl = vi.fn().mockResolvedValue(0); - await expect(runKloCli(['dev', command, '--help'], testIo.io, { scan, sl })).resolves.toBe(0); + await expect(runKtxCli(['dev', command, '--help'], testIo.io, { scan, sl })).resolves.toBe(0); for (const text of expected) { expect(testIo.stdout()).toContain(text); @@ -1880,9 +1880,9 @@ describe('runKloCli', () => { const testIo = makeIo(); const scan = vi.fn().mockResolvedValue(0); - await expect(runKloCli(['dev', 'scan', 'report', '--help'], testIo.io, { scan })).resolves.toBe(0); + await expect(runKtxCli(['dev', 'scan', 'report', '--help'], testIo.io, { scan })).resolves.toBe(0); - expect(testIo.stdout()).toContain('Usage: klo dev scan report [options] '); + expect(testIo.stdout()).toContain('Usage: ktx dev scan report [options] '); expect(testIo.stderr()).toBe(''); expect(scan).not.toHaveBeenCalled(); }); @@ -1890,7 +1890,7 @@ describe('runKloCli', () => { it('rejects removed reserved dev subcommands', async () => { const testIo = makeIo(); - await expect(runKloCli(['dev', 'artifacts'], testIo.io)).resolves.toBe(1); + await expect(runKtxCli(['dev', 'artifacts'], testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toMatch(/unknown command|error:/); }); @@ -1906,7 +1906,7 @@ describe('runKloCli', () => { ['setup', 'demo', 'replay', '--json', '--plain'], ]) { const testIo = makeIo(); - await expect(runKloCli(argv, testIo.io, { ingest, demo })).resolves.toBe(1); + await expect(runKtxCli(argv, testIo.io, { ingest, demo })).resolves.toBe(1); expect(testIo.stderr()).toMatch(/conflict|cannot be used/i); } @@ -1920,7 +1920,7 @@ describe('runKloCli', () => { const tokenIo = makeIo(); await expect( - runKloCli( + runKtxCli( [ 'connection', 'add', @@ -1946,14 +1946,14 @@ describe('runKloCli', () => { it('validates connection mapping set syntax before runner domain validation', async () => { const badFieldIo = makeIo(); await expect( - runKloCli(['connection', 'mapping', 'set', 'prod-metabase', 'invalidMappings', '1=warehouse'], badFieldIo.io), + runKtxCli(['connection', 'mapping', 'set', 'prod-metabase', 'invalidMappings', '1=warehouse'], badFieldIo.io), ).resolves.toBe(1); expect(badFieldIo.stderr()).toContain('databaseMappings or connectionMappings'); for (const assignment of ['missing-equals', '=warehouse', '1=']) { const testIo = makeIo(); await expect( - runKloCli(['connection', 'mapping', 'set', 'prod-metabase', 'databaseMappings', assignment], testIo.io), + runKtxCli(['connection', 'mapping', 'set', 'prod-metabase', 'databaseMappings', assignment], testIo.io), ).resolves.toBe(1); expect(testIo.stderr()).toContain('non-empty ='); } @@ -1962,7 +1962,7 @@ describe('runKloCli', () => { it('does not expose root init after setup owns project creation', async () => { const testIo = makeIo(); - await expect(runKloCli(['init'], testIo.io)).resolves.toBe(1); + await expect(runKtxCli(['init'], testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toContain("error: unknown command 'init'"); }); @@ -1970,7 +1970,7 @@ describe('runKloCli', () => { it('returns an error code for unknown commands', async () => { const testIo = makeIo(); - await expect(runKloCli(['unknown'], testIo.io)).resolves.toBe(1); + await expect(runKtxCli(['unknown'], testIo.io)).resolves.toBe(1); expect(testIo.stderr()).toContain("error: unknown command 'unknown'"); }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 03a3afec..6018c28c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,48 +1,48 @@ import { profileMark } from './startup-profile.js'; export { - getKloCliPackageInfo, + getKtxCliPackageInfo, runInitForCommander, - runKloCli, - type KloCliDeps, - type KloCliIo, - type KloCliPackageInfo, + runKtxCli, + type KtxCliDeps, + type KtxCliIo, + type KtxCliPackageInfo, } from './cli-runtime.js'; -export { runKloAgent, type KloAgentArgs } from './agent.js'; +export { runKtxAgent, type KtxAgentArgs } from './agent.js'; export { - KLO_AGENT_MAX_ROWS_CAP, - createKloAgentRuntime, + KTX_AGENT_MAX_ROWS_CAP, + createKtxAgentRuntime, parseAgentMaxRows, readAgentJsonFile, writeAgentJson, writeAgentJsonError, - type KloAgentRuntime, - type KloAgentRuntimeDeps, + type KtxAgentRuntime, + type KtxAgentRuntimeDeps, } from './agent-runtime.js'; -export { runKloSetup, type KloSetupArgs, type KloSetupStatus } from './setup.js'; +export { runKtxSetup, type KtxSetupArgs, type KtxSetupStatus } from './setup.js'; export type { - KloSetupDatabaseDriver, - KloSetupDatabasesArgs, - KloSetupDatabasesDeps, - KloSetupDatabasesResult, + KtxSetupDatabaseDriver, + KtxSetupDatabasesArgs, + KtxSetupDatabasesDeps, + KtxSetupDatabasesResult, } from './setup-databases.js'; -export { runKloSetupDatabasesStep } from './setup-databases.js'; +export { runKtxSetupDatabasesStep } from './setup-databases.js'; export type { - KloSetupEmbeddingBackend, - KloSetupEmbeddingsArgs, - KloSetupEmbeddingsDeps, - KloSetupEmbeddingsResult, + KtxSetupEmbeddingBackend, + KtxSetupEmbeddingsArgs, + KtxSetupEmbeddingsDeps, + KtxSetupEmbeddingsResult, } from './setup-embeddings.js'; -export { runKloSetupEmbeddingsStep } from './setup-embeddings.js'; +export { runKtxSetupEmbeddingsStep } from './setup-embeddings.js'; export type { - KloSetupSourcesArgs, - KloSetupSourcesDeps, - KloSetupSourcesPromptAdapter, - KloSetupSourcesResult, - KloSetupSourceType, + KtxSetupSourcesArgs, + KtxSetupSourcesDeps, + KtxSetupSourcesPromptAdapter, + KtxSetupSourcesResult, + KtxSetupSourceType, } from './setup-sources.js'; -export { runKloSetupSourcesStep } from './setup-sources.js'; -export type { KloMemoryFlowTuiIo, MemoryFlowTuiLiveSession } from './memory-flow-tui.js'; +export { runKtxSetupSourcesStep } from './setup-sources.js'; +export type { KtxMemoryFlowTuiIo, MemoryFlowTuiLiveSession } from './memory-flow-tui.js'; export { renderMemoryFlowTui, sanitizeMemoryFlowTuiError, diff --git a/packages/cli/src/ingest-report-file.test.ts b/packages/cli/src/ingest-report-file.test.ts index 65b4b680..5183764b 100644 --- a/packages/cli/src/ingest-report-file.test.ts +++ b/packages/cli/src/ingest-report-file.test.ts @@ -36,7 +36,7 @@ describe('readIngestReportSnapshotFile', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-report-file-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-report-file-')); }); afterEach(async () => { diff --git a/packages/cli/src/ingest-report-file.ts b/packages/cli/src/ingest-report-file.ts index 50627d56..cd29706b 100644 --- a/packages/cli/src/ingest-report-file.ts +++ b/packages/cli/src/ingest-report-file.ts @@ -1,5 +1,5 @@ import { readFile } from 'node:fs/promises'; -import { parseIngestReportSnapshot, type IngestReportSnapshot } from '@klo/context/ingest'; +import { parseIngestReportSnapshot, type IngestReportSnapshot } from '@ktx/context/ingest'; export async function readIngestReportSnapshotFile(reportFile: string): Promise { const raw = await readFile(reportFile, 'utf-8'); diff --git a/packages/cli/src/ingest.test.ts b/packages/cli/src/ingest.test.ts index ca4f14b1..5c536f0f 100644 --- a/packages/cli/src/ingest.test.ts +++ b/packages/cli/src/ingest.test.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events'; import { access, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { AgentRunnerService, type RunLoopParams } from '@klo/context/agent'; +import { AgentRunnerService, type RunLoopParams } from '@ktx/context/agent'; import { LocalLookerRuntimeStore, LocalMetabaseSourceStateReader, @@ -22,10 +22,10 @@ import { type RunLocalIngestOptions, type SourceAdapter, type SqliteBundleIngestStore, -} from '@klo/context/ingest'; -import { initKloProject, kloLocalStateDbPath, loadKloProject } from '@klo/context/project'; +} from '@ktx/context/ingest'; +import { initKtxProject, ktxLocalStateDbPath, loadKtxProject } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { type KloIngestArgs, runKloIngest } from './ingest.js'; +import { type KtxIngestArgs, runKtxIngest } from './ingest.js'; import { resetVizFallbackWarningsForTest } from './viz-fallback.js'; function makeIo( @@ -104,7 +104,7 @@ function makeIo( async function writeWarehouseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -123,7 +123,7 @@ async function writeWarehouseConfig(projectDir: string): Promise { async function writeMetabaseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -438,9 +438,9 @@ type SyncModeCase = { async function runPublicMetabaseSyncModeCase(tempDir: string, input: SyncModeCase): Promise { const projectDir = join(tempDir, `metabase-sync-mode-${input.name}`); - await initKloProject({ projectDir, projectName: `metabase-sync-mode-${input.name}` }); + await initKtxProject({ projectDir, projectName: `metabase-sync-mode-${input.name}` }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ `project: metabase-sync-mode-${input.name}`, 'connections:', @@ -461,8 +461,8 @@ async function runPublicMetabaseSyncModeCase(tempDir: string, input: SyncModeCas 'utf-8', ); - const project = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) }); + const project = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) }); await store.replaceSourceState({ connectionId: 'prod-metabase', syncMode: input.syncMode, @@ -490,7 +490,7 @@ async function runPublicMetabaseSyncModeCase(tempDir: string, input: SyncModeCas const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -640,10 +640,10 @@ function localFakeBundleReport(jobId: string, overrides: Partial { - const { SqliteBundleIngestStore } = await import('@klo/context/ingest'); - const project = await loadKloProject({ projectDir }); + const { SqliteBundleIngestStore } = await import('@ktx/context/ingest'); + const project = await loadKtxProject({ projectDir }); return new SqliteBundleIngestStore({ - dbPath: kloLocalStateDbPath(project), + dbPath: ktxLocalStateDbPath(project), idFactory: (() => { let index = 0; return () => ids[index++] ?? `generated-${index}`; @@ -696,7 +696,7 @@ function emitLiveLocalMemoryFlow(memoryFlow: MemoryFlowEventSink | undefined): v memoryFlow?.finish('done'); } -describe('runKloIngest', () => { +describe('runKtxIngest', () => { let tempDir: string; let originalTerm: string | undefined; @@ -704,7 +704,7 @@ describe('runKloIngest', () => { resetVizFallbackWarningsForTest(); originalTerm = process.env.TERM; process.env.TERM = 'xterm-256color'; - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-ingest-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-ingest-')); }); afterEach(async () => { @@ -718,7 +718,7 @@ describe('runKloIngest', () => { it('runs local ingest and reads status', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -731,7 +731,7 @@ describe('runKloIngest', () => { const runIo = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -757,7 +757,7 @@ describe('runKloIngest', () => { const statusIo = makeIo(); await expect( - runKloIngest({ command: 'status', projectDir, runId: 'cli-local-run-1', outputMode: 'plain' }, statusIo.io), + runKtxIngest({ command: 'status', projectDir, runId: 'cli-local-run-1', outputMode: 'plain' }, statusIo.io), ).resolves.toBe(0); expect(statusIo.stdout()).toContain('Report: report-live-1'); @@ -770,7 +770,7 @@ describe('runKloIngest', () => { it('routes metabase scheduled pulls to the fan-out runner and prints child summaries', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeMetabaseConfig(projectDir); const io = makeIo(); const report = localFakeBundleReport('metabase-child-1', { @@ -782,7 +782,7 @@ describe('runKloIngest', () => { }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -828,7 +828,7 @@ describe('runKloIngest', () => { it('prints Metabase fan-out progress before the final summary', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeMetabaseConfig(projectDir); const io = makeIo(); const report = localFakeBundleReport('metabase-child-1', { @@ -840,7 +840,7 @@ describe('runKloIngest', () => { }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -908,9 +908,9 @@ describe('runKloIngest', () => { it('runs Metabase scheduled ingest through the public CLI command path with real fan-out', async () => { const projectDir = join(tempDir, 'metabase-cli-project'); - await initKloProject({ projectDir, projectName: 'metabase-cli' }); + await initKtxProject({ projectDir, projectName: 'metabase-cli' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: metabase-cli', 'connections:', @@ -933,12 +933,12 @@ describe('runKloIngest', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir }); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) }); + const project = await loadKtxProject({ projectDir }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) }); await store.replaceSourceState({ connectionId: 'prod-metabase', syncMode: 'ALL', - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], selections: [], mappings: [ { @@ -969,7 +969,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1001,7 +1001,7 @@ describe('runKloIngest', () => { const statusIo = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'status', projectDir, runId: 'metabase-child-1', outputMode: 'plain' }, statusIo.io, ), @@ -1046,12 +1046,12 @@ describe('runKloIngest', () => { it('prints metabase fan-out JSON results', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeMetabaseConfig(projectDir); const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1081,12 +1081,12 @@ describe('runKloIngest', () => { it('rejects source-dir uploads through the metabase fan-out route', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeMetabaseConfig(projectDir); const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1105,13 +1105,13 @@ describe('runKloIngest', () => { ).resolves.toBe(1); expect(io.stderr()).toContain('source-dir uploads are not supported for the Metabase fan-out adapter'); - expect(io.stderr()).not.toContain('klo dev ingest run requires llm.provider.backend'); + expect(io.stderr()).not.toContain('ktx dev ingest run requires llm.provider.backend'); expect(io.stdout()).toBe(''); }); it('prints previous run and diff summary for local ingest results', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1120,7 +1120,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1145,15 +1145,15 @@ describe('runKloIngest', () => { it('passes the debug LLM request file to local ingest runs', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const runLocalIngest = vi.fn(async (input: RunLocalIngestOptions) => completedLocalBundleRun(input, 'job-debug'), ); const io = makeIo(); - const debugFile = join(projectDir, '.klo', 'llm-debug.jsonl'); + const debugFile = join(projectDir, '.ktx', 'llm-debug.jsonl'); - const exitCode = await runKloIngest( + const exitCode = await runKtxIngest( { command: 'run', projectDir, @@ -1172,7 +1172,7 @@ describe('runKloIngest', () => { it('passes daemon database introspection URL to default local ingest adapters', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1187,7 +1187,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1196,7 +1196,7 @@ describe('runKloIngest', () => { sourceDir, databaseIntrospectionUrl: 'http://127.0.0.1:8765', outputMode: 'plain', - } satisfies KloIngestArgs, + } satisfies KtxIngestArgs, io.io, { createAdapters, @@ -1220,9 +1220,9 @@ describe('runKloIngest', () => { it('passes the target connection id when constructing local historic-sql adapters', async () => { const projectDir = join(tempDir, 'historic-sql-project'); - await initKloProject({ projectDir, projectName: 'historic-sql-project' }); + await initKtxProject({ projectDir, projectName: 'historic-sql-project' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: historic-sql-project', 'connections:', @@ -1250,7 +1250,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1281,7 +1281,7 @@ describe('runKloIngest', () => { it('passes local Looker pull-config options and agent runner into scheduled ingest for Looker scheduled ingest', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const pullConfigOptions = { looker: { @@ -1299,14 +1299,14 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, connectionId: 'warehouse', adapter: 'fake', outputMode: 'plain', - } satisfies KloIngestArgs, + } satisfies KtxIngestArgs, io.io, { createAdapters, @@ -1335,9 +1335,9 @@ describe('runKloIngest', () => { it('runs Looker scheduled ingest through the public CLI command path', async () => { const projectDir = join(tempDir, 'looker-project'); - await initKloProject({ projectDir, projectName: 'looker-cli' }); + await initKtxProject({ projectDir, projectName: 'looker-cli' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: looker-cli', 'connections:', @@ -1357,8 +1357,8 @@ describe('runKloIngest', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir }); - const store = new LocalLookerRuntimeStore({ dbPath: kloLocalStateDbPath(project) }); + const project = await loadKtxProject({ projectDir }); + const store = new LocalLookerRuntimeStore({ dbPath: ktxLocalStateDbPath(project) }); await store.setCursors('prod-looker', { dashboardsLastSyncedAt: null, looksLastSyncedAt: null, @@ -1366,7 +1366,7 @@ describe('runKloIngest', () => { await store.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'analytics', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', source: 'cli', }); const runtimeClient = makeCliLookerRuntimeClient(); @@ -1375,7 +1375,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1419,7 +1419,7 @@ describe('runKloIngest', () => { const statusIo = makeIo(); await expect( - runKloIngest( + runKtxIngest( { command: 'status', projectDir, runId: 'cli-looker-job', outputMode: 'plain' }, statusIo.io, ), @@ -1431,7 +1431,7 @@ describe('runKloIngest', () => { it('renders live memory-flow frames for run --viz when stdout is interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1462,7 +1462,7 @@ describe('runKloIngest', () => { const startLiveMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => null); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1483,15 +1483,15 @@ describe('runKloIngest', () => { expect(runLocal).toHaveBeenCalledWith(expect.objectContaining({ memoryFlow: expect.any(Object) })); expect(io.stdout()).toContain('\u001b[2J\u001b[H'); - expect((io.stdout().match(/KLO memory flow/g) ?? []).length).toBeGreaterThan(1); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect((io.stdout().match(/KTX memory flow/g) ?? []).length).toBeGreaterThan(1); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).toContain('fake-orders'); expect(io.stderr()).toBe(''); }); it('uses the TUI live session for run --viz when stdin and stdout are interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1510,7 +1510,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1539,13 +1539,13 @@ describe('runKloIngest', () => { expect(liveSession.update).toHaveBeenCalled(); expect(liveSession.close).toHaveBeenCalledTimes(1); expect(io.stdout()).not.toContain('\u001b[2J\u001b[H'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toBe(''); }); it('prints a final plain summary after live viz completes', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); const liveSession = { @@ -1560,7 +1560,7 @@ describe('runKloIngest', () => { }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1580,7 +1580,7 @@ describe('runKloIngest', () => { it('falls back to text live rendering when the TUI live session is unavailable', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1594,7 +1594,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1614,12 +1614,12 @@ describe('runKloIngest', () => { expect(startLiveMemoryFlow).toHaveBeenCalledTimes(1); expect(io.stdout()).toContain('\u001b[2J\u001b[H'); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); }); it('falls back to text live rendering when TUI startup fails with a redacted warning', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1638,7 +1638,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1657,13 +1657,13 @@ describe('runKloIngest', () => { ).resolves.toBe(0); expect(io.stderr()).toContain('TUI visualization unavailable: Failed [redacted-url] [redacted]'); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).toContain('\u001b[2J\u001b[H'); }); it('does not start live TUI when run --viz disables input', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1680,7 +1680,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1697,12 +1697,12 @@ describe('runKloIngest', () => { expect(startLiveMemoryFlow).not.toHaveBeenCalled(); expect(runLocal).toHaveBeenCalledWith(expect.not.objectContaining({ memoryFlow: expect.anything() })); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); }); it('does not attach a live memory-flow sink for plain run output', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1712,7 +1712,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true }); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1728,12 +1728,12 @@ describe('runKloIngest', () => { expect(runLocal).toHaveBeenCalledWith(expect.not.objectContaining({ memoryFlow: expect.anything() })); expect(io.stdout()).toContain('Job: plain-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); }); it('falls back to plain run output for run --viz when stdout is not interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1742,7 +1742,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: false }); const runLocal = vi.fn(async (input: RunLocalIngestOptions) => completedLocalBundleRun(input, 'non-tty-viz-run')); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1760,7 +1760,7 @@ describe('runKloIngest', () => { ).resolves.toBe(0); expect(io.stdout()).toContain('Job: non-tty-viz-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdout is not an interactive terminal; printing plain output.', ); @@ -1768,7 +1768,7 @@ describe('runKloIngest', () => { it('falls back to plain run output for run --viz when stdin raw mode is unavailable', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1783,7 +1783,7 @@ describe('runKloIngest', () => { })); await expect( - runKloIngest( + runKtxIngest( { command: 'run', projectDir, @@ -1804,7 +1804,7 @@ describe('runKloIngest', () => { expect(startLiveMemoryFlow).not.toHaveBeenCalled(); expect(runLocal).toHaveBeenCalledWith(expect.not.objectContaining({ memoryFlow: expect.anything() })); expect(io.stdout()).toContain('Job: raw-missing-viz-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdin raw mode is unavailable; printing plain output.', ); @@ -1812,11 +1812,11 @@ describe('runKloIngest', () => { it('returns an error code for missing status', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const io = makeIo(); await expect( - runKloIngest({ command: 'status', projectDir, runId: 'missing-run', outputMode: 'plain' }, io.io), + runKtxIngest({ command: 'status', projectDir, runId: 'missing-run', outputMode: 'plain' }, io.io), ).resolves.toBe(1); expect(io.stderr()).toContain('Local ingest run or report "missing-run" was not found'); @@ -1824,13 +1824,13 @@ describe('runKloIngest', () => { it('uses the latest local ingest report when status has no run id', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); await persistLocalBundleReport(projectDir, localFakeBundleReport('older-run')); await persistLocalBundleReport(projectDir, localFakeBundleReport('newer-run')); const io = makeIo(); - await expect(runKloIngest({ command: 'status', projectDir, outputMode: 'plain' }, io.io)).resolves.toBe(0); + await expect(runKtxIngest({ command: 'status', projectDir, outputMode: 'plain' }, io.io)).resolves.toBe(0); expect(io.stdout()).toContain('Run: run-newer-run'); expect(io.stdout()).toContain('Job: newer-run'); @@ -1839,28 +1839,28 @@ describe('runKloIngest', () => { it('renders the latest local ingest report through watch when run id is omitted', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); await persistLocalBundleReport(projectDir, localFakeBundleReport('watch-latest')); const io = makeIo({ isTTY: true }); await expect( - runKloIngest({ command: 'watch', projectDir, outputMode: 'viz', inputMode: 'disabled' }, io.io), + runKtxIngest({ command: 'watch', projectDir, outputMode: 'viz', inputMode: 'disabled' }, io.io), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).toContain('Run: run-watch-latest'); expect(io.stderr()).toBe(''); }); it('renders report-file replay through the memory-flow TUI', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const reportFile = await writeBundleReportFile(tempDir); const io = makeIo({ isTTY: true }); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -1873,7 +1873,7 @@ describe('runKloIngest', () => { ), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow warehouse/metabase done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/metabase done'); expect(io.stdout()).toContain('Saved 2 memories from 2 raw files'); expect(io.stdout()).toContain('Commit: abc12345 Run: run-1 Report: report-1'); expect(io.stdout()).toContain('SOURCE'); @@ -1884,12 +1884,12 @@ describe('runKloIngest', () => { it('prints report-file JSON without looking up local ingest status', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const reportFile = await writeBundleReportFile(tempDir); const io = makeIo(); await expect( - runKloIngest({ command: 'status', projectDir, runId: 'report-1', reportFile, outputMode: 'json' }, io.io), + runKtxIngest({ command: 'status', projectDir, runId: 'report-1', reportFile, outputMode: 'json' }, io.io), ).resolves.toBe(0); const parsed = JSON.parse(io.stdout()); @@ -1905,13 +1905,13 @@ describe('runKloIngest', () => { it('routes interactive report-file replay through the stored TUI renderer', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const reportFile = await writeBundleReportFile(tempDir); const io = makeIo({ isTTY: true, stdinIsTTY: true, columns: 120 }); const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => true); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -1937,12 +1937,12 @@ describe('runKloIngest', () => { it('rejects report-file replay when the requested id does not match the report', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const reportFile = await writeBundleReportFile(tempDir); const io = makeIo(); await expect( - runKloIngest({ command: 'replay', projectDir, runId: 'unrelated-id', reportFile, outputMode: 'plain' }, io.io), + runKtxIngest({ command: 'replay', projectDir, runId: 'unrelated-id', reportFile, outputMode: 'plain' }, io.io), ).resolves.toBe(1); expect(io.stderr()).toContain( @@ -1953,7 +1953,7 @@ describe('runKloIngest', () => { it('renders memory-flow snapshot for status --viz when stdout is interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1963,13 +1963,13 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true }); await expect( - runKloIngest( + runKtxIngest( { command: 'status', projectDir, runId: 'viz-run-1', outputMode: 'viz', inputMode: 'disabled' }, io.io, ), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).toContain('SOURCE'); expect(io.stdout()).toContain('CHUNKS'); expect(io.stdout()).toContain('WORKUNITS'); @@ -1979,7 +1979,7 @@ describe('runKloIngest', () => { it('uses the TUI renderer for stored status --viz when stdin and stdout are interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1991,7 +1991,7 @@ describe('runKloIngest', () => { const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => true); await expect( - runKloIngest( + runKtxIngest( { command: 'status', projectDir, @@ -2015,7 +2015,7 @@ describe('runKloIngest', () => { it('falls back to the text renderer when TUI declines stored status --viz', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2027,7 +2027,7 @@ describe('runKloIngest', () => { const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => false); await expect( - runKloIngest( + runKtxIngest( { command: 'status', projectDir, @@ -2040,12 +2040,12 @@ describe('runKloIngest', () => { ).resolves.toBe(0); expect(renderStoredMemoryFlow).toHaveBeenCalledTimes(1); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); }); it('does not use TUI for stored --viz when input is disabled', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2057,7 +2057,7 @@ describe('runKloIngest', () => { const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => true); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -2071,12 +2071,12 @@ describe('runKloIngest', () => { ).resolves.toBe(0); expect(renderStoredMemoryFlow).not.toHaveBeenCalled(); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); }); it('falls back to plain status for stored --viz when stdin raw mode is unavailable', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2088,7 +2088,7 @@ describe('runKloIngest', () => { const renderStoredMemoryFlow = vi.fn(async (_input: MemoryFlowReplayInput, _io: unknown) => true); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -2103,7 +2103,7 @@ describe('runKloIngest', () => { expect(renderStoredMemoryFlow).not.toHaveBeenCalled(); expect(io.stdout()).toContain('Run: run-raw-missing-stored-viz-run'); expect(io.stdout()).toContain('Job: raw-missing-stored-viz-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdin raw mode is unavailable; printing plain output.', ); @@ -2111,7 +2111,7 @@ describe('runKloIngest', () => { it('keeps stored --viz snapshot-only when input is disabled', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2121,7 +2121,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -2133,14 +2133,14 @@ describe('runKloIngest', () => { ), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).not.toContain('\u001b[2J\u001b[H'); expect(io.stderr()).toBe(''); }); it('keeps disabled-input stored --viz snapshot output even when stdin raw mode is unavailable', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2150,7 +2150,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true, stdinIsTTY: true, rawMode: false, columns: 120 }); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -2162,14 +2162,14 @@ describe('runKloIngest', () => { ), ).resolves.toBe(0); - expect(io.stdout()).toContain('KLO memory flow warehouse/fake done'); + expect(io.stdout()).toContain('KTX memory flow warehouse/fake done'); expect(io.stdout()).not.toContain('\u001b[2J\u001b[H'); expect(io.stderr()).toBe(''); }); it('degrades stored --viz snapshots to plain status when stdout is redirected even when input is disabled', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2179,7 +2179,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: false }); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, @@ -2193,7 +2193,7 @@ describe('runKloIngest', () => { expect(io.stdout()).toContain('Run: run-redirected-no-input-viz-run'); expect(io.stdout()).toContain('Job: redirected-no-input-viz-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdout is not an interactive terminal; printing plain output.', ); @@ -2201,7 +2201,7 @@ describe('runKloIngest', () => { it('degrades ingest replay --viz to plain status when TERM is dumb', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2211,7 +2211,7 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: true }); await expect( - runKloIngest( + runKtxIngest( { command: 'replay', projectDir, runId: 'dumb-terminal-viz-run', outputMode: 'viz' }, io.io, { env: { ...process.env, TERM: 'dumb' } }, @@ -2220,7 +2220,7 @@ describe('runKloIngest', () => { expect(io.stdout()).toContain('Run: run-dumb-terminal-viz-run'); expect(io.stdout()).toContain('Job: dumb-terminal-viz-run'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but TERM=dumb does not support the visual renderer; printing plain output.', ); @@ -2228,7 +2228,7 @@ describe('runKloIngest', () => { it('falls back to plain status for --viz when stdout is not interactive', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2238,12 +2238,12 @@ describe('runKloIngest', () => { const io = makeIo({ isTTY: false }); await expect( - runKloIngest({ command: 'replay', projectDir, runId: 'viz-run-2', outputMode: 'viz' }, io.io), + runKtxIngest({ command: 'replay', projectDir, runId: 'viz-run-2', outputMode: 'viz' }, io.io), ).resolves.toBe(0); expect(io.stdout()).toContain('Run: run-viz-run-2'); expect(io.stdout()).toContain('Job: viz-run-2'); - expect(io.stdout()).not.toContain('KLO memory flow'); + expect(io.stdout()).not.toContain('KTX memory flow'); expect(io.stderr()).toContain( 'Visualization requested but stdout is not an interactive terminal; printing plain output.', ); @@ -2251,7 +2251,7 @@ describe('runKloIngest', () => { it('prints JSON for status --json', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -2261,7 +2261,7 @@ describe('runKloIngest', () => { const io = makeIo(); await expect( - runKloIngest({ command: 'status', projectDir, runId: 'json-run-1', outputMode: 'json' }, io.io), + runKtxIngest({ command: 'status', projectDir, runId: 'json-run-1', outputMode: 'json' }, io.io), ).resolves.toBe(0); expect(JSON.parse(io.stdout())).toMatchObject({ diff --git a/packages/cli/src/ingest.ts b/packages/cli/src/ingest.ts index 15f56e52..d6748991 100644 --- a/packages/cli/src/ingest.ts +++ b/packages/cli/src/ingest.ts @@ -13,13 +13,13 @@ import { renderMemoryFlowReplay, runLocalIngest, runLocalMetabaseIngest, -} from '@klo/context/ingest'; -import { loadKloProject } from '@klo/context/project'; +} from '@ktx/context/ingest'; +import { loadKtxProject } from '@ktx/context/project'; import { readIngestReportSnapshotFile } from './ingest-report-file.js'; -import { createKloCliLocalIngestAdapters } from './local-adapters.js'; -import { type KloMemoryFlowStdin, renderMemoryFlowInteractively } from './memory-flow-interactive.js'; +import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; +import { type KtxMemoryFlowStdin, renderMemoryFlowInteractively } from './memory-flow-interactive.js'; import { - type KloMemoryFlowTuiIo, + type KtxMemoryFlowTuiIo, type MemoryFlowTuiLiveSession, renderMemoryFlowTui, startLiveMemoryFlowTui, @@ -29,10 +29,10 @@ import { profileMark } from './startup-profile.js'; profileMark('module:ingest'); -export type KloIngestOutputMode = 'plain' | 'json' | 'viz'; -type KloIngestInputMode = 'auto' | 'disabled'; +export type KtxIngestOutputMode = 'plain' | 'json' | 'viz'; +type KtxIngestInputMode = 'auto' | 'disabled'; -export type KloIngestArgs = +export type KtxIngestArgs = | { command: 'run'; projectDir: string; @@ -41,28 +41,28 @@ export type KloIngestArgs = sourceDir?: string; databaseIntrospectionUrl?: string; debugLlmRequestFile?: string; - outputMode: KloIngestOutputMode; - inputMode?: KloIngestInputMode; + outputMode: KtxIngestOutputMode; + inputMode?: KtxIngestInputMode; } | { command: 'status' | 'replay' | 'watch'; projectDir: string; runId?: string; reportFile?: string; - outputMode: KloIngestOutputMode; - inputMode?: KloIngestInputMode; + outputMode: KtxIngestOutputMode; + inputMode?: KtxIngestInputMode; }; -interface KloIngestIo { - stdin?: KloMemoryFlowStdin; +interface KtxIngestIo { + stdin?: KtxMemoryFlowStdin; stdout: { isTTY?: boolean; columns?: number; write(chunk: string): void }; stderr: { write(chunk: string): void }; } -interface KloIngestDeps { +interface KtxIngestDeps { jobIdFactory?: () => string; now?: () => Date; - createAdapters?: typeof createKloCliLocalIngestAdapters; + createAdapters?: typeof createKtxCliLocalIngestAdapters; runLocalIngest?: typeof runLocalIngest; runLocalMetabaseIngest?: typeof runLocalMetabaseIngest; readReportFile?: typeof readIngestReportSnapshotFile; @@ -93,7 +93,7 @@ function reportActionCounts(report: IngestReportSnapshot): { wikiCount: number; }; } -function writeReportStatus(report: IngestReportSnapshot, io: KloIngestIo): void { +function writeReportStatus(report: IngestReportSnapshot, io: KtxIngestIo): void { const counts = reportActionCounts(report); io.stdout.write(`Report: ${report.id}\n`); io.stdout.write(`Run: ${report.runId}\n`); @@ -110,7 +110,7 @@ function writeReportStatus(report: IngestReportSnapshot, io: KloIngestIo): void io.stdout.write(`Provenance rows: ${report.body.provenanceRows.length}\n`); } -function writeMetabaseFanoutStatus(result: LocalMetabaseFanoutResult, io: KloIngestIo): void { +function writeMetabaseFanoutStatus(result: LocalMetabaseFanoutResult, io: KtxIngestIo): void { io.stdout.write(`Metabase fan-out: ${result.status}\n`); io.stdout.write(`Source: ${result.metabaseConnectionId}\n`); io.stdout.write(`Children: ${result.children.length}\n`); @@ -132,7 +132,7 @@ function pluralize(count: number, singular: string, plural = `${singular}s`): st function createMetabaseFanoutProgress( connectionId: string, - io: KloIngestIo, + io: KtxIngestIo, ): LocalMetabaseFanoutProgress { io.stdout.write(`Metabase ingest: ${connectionId}\n`); io.stdout.write('Checking mappings and scheduled-pull targets...\n'); @@ -156,7 +156,7 @@ function createMetabaseFanoutProgress( }; } -function writeReportJson(report: IngestReportSnapshot, io: KloIngestIo): void { +function writeReportJson(report: IngestReportSnapshot, io: KtxIngestIo): void { io.stdout.write(`${JSON.stringify(report, null, 2)}\n`); } @@ -172,21 +172,21 @@ function assertReportMatchesReplayId(report: IngestReportSnapshot, requestedId: } async function readStoredIngestReport( - project: Awaited>, + project: Awaited>, runId: string | undefined, ): Promise { return runId ? await getLocalIngestStatus(project, runId) : await getLatestLocalIngestStatus(project); } -function isInteractiveTerminal(io: KloIngestIo): boolean { +function isInteractiveTerminal(io: KtxIngestIo): boolean { return io.stdout.isTTY === true; } -function terminalWidth(io: KloIngestIo): number | undefined { +function terminalWidth(io: KtxIngestIo): number | undefined { return io.stdout.columns ?? process.stdout.columns; } -function isTuiCapableIo(io: KloIngestIo): io is KloIngestIo & KloMemoryFlowTuiIo { +function isTuiCapableIo(io: KtxIngestIo): io is KtxIngestIo & KtxMemoryFlowTuiIo { return ( io.stdin?.isTTY === true && io.stdout.isTTY === true && @@ -201,11 +201,11 @@ interface EffectiveIngestOutputModeOptions { } function effectiveIngestOutputMode( - outputMode: KloIngestOutputMode, - io: KloIngestIo, + outputMode: KtxIngestOutputMode, + io: KtxIngestIo, env: NodeJS.ProcessEnv, options: EffectiveIngestOutputModeOptions = {}, -): KloIngestOutputMode { +): KtxIngestOutputMode { if (outputMode !== 'viz') { return outputMode; } @@ -219,7 +219,7 @@ function effectiveIngestOutputMode( return 'plain'; } -function writeMemoryFlowInput(input: MemoryFlowReplayInput, io: KloIngestIo, options: { clear?: boolean } = {}): void { +function writeMemoryFlowInput(input: MemoryFlowReplayInput, io: KtxIngestIo, options: { clear?: boolean } = {}): void { if (options.clear) { io.stdout.write('\u001b[2J\u001b[H'); } @@ -228,7 +228,7 @@ function writeMemoryFlowInput(input: MemoryFlowReplayInput, io: KloIngestIo, opt } function initialRunMemoryFlowInput( - args: Extract, + args: Extract, runId: string, ): MemoryFlowReplayInput { return { @@ -247,8 +247,8 @@ function initialRunMemoryFlowInput( async function writeReportRecord( report: IngestReportSnapshot, - outputMode: KloIngestOutputMode, - io: KloIngestIo, + outputMode: KtxIngestOutputMode, + io: KtxIngestIo, options: { interactive?: boolean; renderStoredMemoryFlow?: typeof renderMemoryFlowTui; @@ -288,16 +288,16 @@ async function writeReportRecord( writeReportStatus(report, io); } -export async function runKloIngest( - args: KloIngestArgs, - io: KloIngestIo = process, - deps: KloIngestDeps = {}, +export async function runKtxIngest( + args: KtxIngestArgs, + io: KtxIngestIo = process, + deps: KtxIngestDeps = {}, ): Promise { try { - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); const env = deps.env ?? process.env; if (args.command === 'run') { - const createAdapters = deps.createAdapters ?? createKloCliLocalIngestAdapters; + const createAdapters = deps.createAdapters ?? createKtxCliLocalIngestAdapters; const executeLocalIngest = deps.runLocalIngest ?? runLocalIngest; const localIngestOptions = deps.localIngestOptions ?? {}; const adapterOptions = { @@ -409,7 +409,7 @@ export async function runKloIngest( throw new Error( args.runId ? `Local ingest run or report "${args.runId}" was not found` - : 'No local ingest reports were found. Run `klo ingest --all` first.', + : 'No local ingest reports were found. Run `ktx ingest --all` first.', ); } await writeReportRecord(report, args.outputMode, io, { diff --git a/packages/cli/src/io/mode.test.ts b/packages/cli/src/io/mode.test.ts index 06bb5d63..cfc9a9fc 100644 --- a/packages/cli/src/io/mode.test.ts +++ b/packages/cli/src/io/mode.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import type { KloCliIo } from '../cli-runtime.js'; +import type { KtxCliIo } from '../cli-runtime.js'; import { resolveOutputMode } from './mode.js'; -function ioWith(isTTY: boolean | undefined): KloCliIo { +function ioWith(isTTY: boolean | undefined): KtxCliIo { return { stdout: { isTTY, write: () => {} }, stderr: { write: () => {} }, @@ -24,14 +24,14 @@ describe('resolveOutputMode', () => { expect(() => resolveOutputMode({ explicit: 'fancy', io: ioWith(true), env: {} })).toThrow(/Invalid --output/); }); - it('honors KLO_OUTPUT env var when no explicit value', () => { - expect(resolveOutputMode({ io: ioWith(true), env: { KLO_OUTPUT: 'plain' } })).toBe('plain'); - expect(resolveOutputMode({ io: ioWith(false), env: { KLO_OUTPUT: 'pretty' } })).toBe('pretty'); - expect(resolveOutputMode({ io: ioWith(false), env: { KLO_OUTPUT: 'json' } })).toBe('json'); + it('honors KTX_OUTPUT env var when no explicit value', () => { + expect(resolveOutputMode({ io: ioWith(true), env: { KTX_OUTPUT: 'plain' } })).toBe('plain'); + expect(resolveOutputMode({ io: ioWith(false), env: { KTX_OUTPUT: 'pretty' } })).toBe('pretty'); + expect(resolveOutputMode({ io: ioWith(false), env: { KTX_OUTPUT: 'json' } })).toBe('json'); }); - it('throws on unknown KLO_OUTPUT', () => { - expect(() => resolveOutputMode({ io: ioWith(true), env: { KLO_OUTPUT: 'fancy' } })).toThrow(/Invalid KLO_OUTPUT/); + it('throws on unknown KTX_OUTPUT', () => { + expect(() => resolveOutputMode({ io: ioWith(true), env: { KTX_OUTPUT: 'fancy' } })).toThrow(/Invalid KTX_OUTPUT/); }); it('returns plain when CI is set to a truthy value', () => { @@ -54,7 +54,7 @@ describe('resolveOutputMode', () => { expect(resolveOutputMode({ io: ioWith(undefined), env: {} })).toBe('plain'); }); - it('explicit value beats KLO_OUTPUT env var', () => { - expect(resolveOutputMode({ explicit: 'json', io: ioWith(true), env: { KLO_OUTPUT: 'plain' } })).toBe('json'); + it('explicit value beats KTX_OUTPUT env var', () => { + expect(resolveOutputMode({ explicit: 'json', io: ioWith(true), env: { KTX_OUTPUT: 'plain' } })).toBe('json'); }); }); diff --git a/packages/cli/src/io/mode.ts b/packages/cli/src/io/mode.ts index e25e32a0..8bce9fce 100644 --- a/packages/cli/src/io/mode.ts +++ b/packages/cli/src/io/mode.ts @@ -1,17 +1,17 @@ -import type { KloCliIo } from '../cli-runtime.js'; +import type { KtxCliIo } from '../cli-runtime.js'; -export type KloOutputMode = 'pretty' | 'plain' | 'json'; +export type KtxOutputMode = 'pretty' | 'plain' | 'json'; const MODES: ReadonlySet = new Set(['pretty', 'plain', 'json']); export interface ResolveOutputModeArgs { explicit?: string; json?: boolean; - io: KloCliIo; + io: KtxCliIo; env?: NodeJS.ProcessEnv; } -export function resolveOutputMode(args: ResolveOutputModeArgs): KloOutputMode { +export function resolveOutputMode(args: ResolveOutputModeArgs): KtxOutputMode { if (args.json === true) { return 'json'; } @@ -19,15 +19,15 @@ export function resolveOutputMode(args: ResolveOutputModeArgs): KloOutputMode { if (!MODES.has(args.explicit)) { throw new Error(`Invalid --output value: ${args.explicit}. Expected one of pretty, plain, json.`); } - return args.explicit as KloOutputMode; + return args.explicit as KtxOutputMode; } const env = args.env ?? process.env; - const envMode = env.KLO_OUTPUT; + const envMode = env.KTX_OUTPUT; if (envMode !== undefined && envMode !== '') { if (!MODES.has(envMode)) { - throw new Error(`Invalid KLO_OUTPUT value: ${envMode}. Expected one of pretty, plain, json.`); + throw new Error(`Invalid KTX_OUTPUT value: ${envMode}. Expected one of pretty, plain, json.`); } - return envMode as KloOutputMode; + return envMode as KtxOutputMode; } const ci = env.CI; if (ci !== undefined && ci !== '' && ci !== '0' && ci !== 'false') { diff --git a/packages/cli/src/io/print-list.test.ts b/packages/cli/src/io/print-list.test.ts index 8d42d32a..4f413667 100644 --- a/packages/cli/src/io/print-list.test.ts +++ b/packages/cli/src/io/print-list.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import type { KloCliIo } from '../cli-runtime.js'; +import type { KtxCliIo } from '../cli-runtime.js'; import { printList, type PrintListColumn } from './print-list.js'; import { SYMBOLS } from './symbols.js'; -function recorder(): { io: KloCliIo; out: () => string; err: () => string } { +function recorder(): { io: KtxCliIo; out: () => string; err: () => string } { let stdout = ''; let stderr = ''; return { diff --git a/packages/cli/src/io/print-list.ts b/packages/cli/src/io/print-list.ts index f0b10f48..b66fa7ad 100644 --- a/packages/cli/src/io/print-list.ts +++ b/packages/cli/src/io/print-list.ts @@ -1,5 +1,5 @@ -import type { KloCliIo } from '../cli-runtime.js'; -import type { KloOutputMode } from './mode.js'; +import type { KtxCliIo } from '../cli-runtime.js'; +import type { KtxOutputMode } from './mode.js'; import { bold, dim, SYMBOLS } from './symbols.js'; export interface PrintListColumn { @@ -24,8 +24,8 @@ export interface PrintListArgs { groupBy?: keyof Row & string; emptyMessage: string; command: string; - mode: KloOutputMode; - io: KloCliIo; + mode: KtxOutputMode; + io: KtxCliIo; } export function printList(args: PrintListArgs): void { diff --git a/packages/cli/src/knowledge.test.ts b/packages/cli/src/knowledge.test.ts index ed4ac587..d3db8465 100644 --- a/packages/cli/src/knowledge.test.ts +++ b/packages/cli/src/knowledge.test.ts @@ -1,9 +1,9 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject } from '@klo/context/project'; +import { initKtxProject } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { runKloKnowledge } from './knowledge.js'; +import { runKtxKnowledge } from './knowledge.js'; function makeIo() { let stdout = ''; @@ -26,11 +26,11 @@ function makeIo() { }; } -describe('runKloKnowledge', () => { +describe('runKtxKnowledge', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-knowledge-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-knowledge-')); }); afterEach(async () => { @@ -39,11 +39,11 @@ describe('runKloKnowledge', () => { it('writes, reads, lists, and searches knowledge pages', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const writeIo = makeIo(); await expect( - runKloKnowledge( + runKtxKnowledge( { command: 'write', projectDir, @@ -63,33 +63,33 @@ describe('runKloKnowledge', () => { const readIo = makeIo(); await expect( - runKloKnowledge({ command: 'read', projectDir, key: 'metrics/revenue', userId: 'local' }, readIo.io), + runKtxKnowledge({ command: 'read', projectDir, key: 'metrics/revenue', userId: 'local' }, readIo.io), ).resolves.toBe(0); expect(readIo.stdout()).toContain('# metrics/revenue'); expect(readIo.stdout()).toContain('Revenue is paid order value.'); const listIo = makeIo(); - await expect(runKloKnowledge({ command: 'list', projectDir, userId: 'local' }, listIo.io)).resolves.toBe(0); + await expect(runKtxKnowledge({ command: 'list', projectDir, userId: 'local' }, listIo.io)).resolves.toBe(0); expect(listIo.stdout()).toContain('GLOBAL\tmetrics/revenue\tRevenue'); const searchIo = makeIo(); await expect( - runKloKnowledge({ command: 'search', projectDir, query: 'paid order', userId: 'local' }, searchIo.io), + runKtxKnowledge({ command: 'search', projectDir, query: 'paid order', userId: 'local' }, searchIo.io), ).resolves.toBe(0); expect(searchIo.stdout()).toContain('metrics/revenue'); }); it('explains empty search results for a project without wiki pages', async () => { const projectDir = join(tempDir, 'empty-project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const searchIo = makeIo(); await expect( - runKloKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local' }, searchIo.io), + runKtxKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local' }, searchIo.io), ).resolves.toBe(0); expect(searchIo.stdout()).toBe(''); expect(searchIo.stderr()).toContain('No local wiki pages found'); - expect(searchIo.stderr()).toContain('klo wiki write'); + expect(searchIo.stderr()).toContain('ktx wiki write'); }); }); diff --git a/packages/cli/src/knowledge.ts b/packages/cli/src/knowledge.ts index ccbb09da..89afda8e 100644 --- a/packages/cli/src/knowledge.ts +++ b/packages/cli/src/knowledge.ts @@ -1,13 +1,13 @@ -import { loadKloProject } from '@klo/context/project'; +import { loadKtxProject } from '@ktx/context/project'; import { type LocalKnowledgeScope, listLocalKnowledgePages, readLocalKnowledgePage, searchLocalKnowledgePages, writeLocalKnowledgePage, -} from '@klo/context/wiki'; +} from '@ktx/context/wiki'; -export type KloKnowledgeArgs = +export type KtxKnowledgeArgs = | { command: 'list'; projectDir: string; userId: string } | { command: 'read'; projectDir: string; key: string; userId: string } | { command: 'search'; projectDir: string; query: string; userId: string } @@ -24,14 +24,14 @@ export type KloKnowledgeArgs = slRefs: string[]; }; -interface KloKnowledgeIo { +interface KtxKnowledgeIo { stdout: { write(chunk: string): void }; stderr: { write(chunk: string): void }; } -export async function runKloKnowledge(args: KloKnowledgeArgs, io: KloKnowledgeIo = process): Promise { +export async function runKtxKnowledge(args: KtxKnowledgeArgs, io: KtxKnowledgeIo = process): Promise { try { - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if (args.command === 'list') { const pages = await listLocalKnowledgePages(project, { userId: args.userId }); for (const page of pages) { @@ -56,11 +56,11 @@ export async function runKloKnowledge(args: KloKnowledgeArgs, io: KloKnowledgeIo const pages = await listLocalKnowledgePages(project, { userId: args.userId }); if (pages.length === 0) { io.stderr.write( - `No local wiki pages found in ${project.projectDir}. Create one with \`klo wiki write --summary --content \` or run ingest.\n`, + `No local wiki pages found in ${project.projectDir}. Create one with \`ktx wiki write --summary --content \` or run ingest.\n`, ); } else { io.stderr.write( - `No local wiki pages matched "${args.query}". Run \`klo wiki list\` to inspect available pages.\n`, + `No local wiki pages matched "${args.query}". Run \`ktx wiki list\` to inspect available pages.\n`, ); } return 0; diff --git a/packages/cli/src/local-adapters.ts b/packages/cli/src/local-adapters.ts index 3f6b08ec..9c7669e8 100644 --- a/packages/cli/src/local-adapters.ts +++ b/packages/cli/src/local-adapters.ts @@ -1,15 +1,15 @@ import { join } from 'node:path'; -import { createBigQueryLiveDatabaseIntrospection, isKloBigQueryConnectionConfig } from '@klo/connector-bigquery'; -import { createClickHouseLiveDatabaseIntrospection, isKloClickHouseConnectionConfig } from '@klo/connector-clickhouse'; -import { createMysqlLiveDatabaseIntrospection, isKloMysqlConnectionConfig } from '@klo/connector-mysql'; +import { createBigQueryLiveDatabaseIntrospection, isKtxBigQueryConnectionConfig } from '@ktx/connector-bigquery'; +import { createClickHouseLiveDatabaseIntrospection, isKtxClickHouseConnectionConfig } from '@ktx/connector-clickhouse'; +import { createMysqlLiveDatabaseIntrospection, isKtxMysqlConnectionConfig } from '@ktx/connector-mysql'; import { createPostgresLiveDatabaseIntrospection, - isKloPostgresConnectionConfig, - type KloPostgresConnectionConfig, - KloPostgresHistoricSqlQueryClient, -} from '@klo/connector-postgres'; -import { createSqliteLiveDatabaseIntrospection, isKloSqliteConnectionConfig } from '@klo/connector-sqlite'; -import { createSqlServerLiveDatabaseIntrospection, isKloSqlServerConnectionConfig } from '@klo/connector-sqlserver'; + isKtxPostgresConnectionConfig, + type KtxPostgresConnectionConfig, + KtxPostgresHistoricSqlQueryClient, +} from '@ktx/connector-postgres'; +import { createSqliteLiveDatabaseIntrospection, isKtxSqliteConnectionConfig } from '@ktx/connector-sqlite'; +import { createSqlServerLiveDatabaseIntrospection, isKtxSqlServerConnectionConfig } from '@ktx/connector-sqlserver'; import { createDaemonLiveDatabaseIntrospection, createDefaultLocalIngestAdapters, @@ -17,9 +17,9 @@ import { type LiveDatabaseIntrospectionPort, LiveDatabaseSourceAdapter, type SourceAdapter, -} from '@klo/context/ingest'; -import type { KloLocalProject } from '@klo/context/project'; -import { createHttpSqlAnalysisPort } from '@klo/context/sql-analysis'; +} from '@ktx/context/ingest'; +import type { KtxLocalProject } from '@ktx/context/project'; +import { createHttpSqlAnalysisPort } from '@ktx/context/sql-analysis'; function hasSnowflakeDriver(connection: unknown): boolean { return ( @@ -29,8 +29,8 @@ function hasSnowflakeDriver(connection: unknown): boolean { ); } -function createKloCliLiveDatabaseIntrospection( - project: KloLocalProject, +function createKtxCliLiveDatabaseIntrospection( + project: KtxLocalProject, options: DefaultLocalIngestAdaptersOptions = {}, ): LiveDatabaseIntrospectionPort { const daemon = createDaemonLiveDatabaseIntrospection({ @@ -60,29 +60,29 @@ function createKloCliLiveDatabaseIntrospection( return { async extractSchema(connectionId: string) { const connection = project.config.connections[connectionId]; - if (isKloPostgresConnectionConfig(connection)) { + if (isKtxPostgresConnectionConfig(connection)) { return postgres.extractSchema(connectionId); } - if (isKloSqliteConnectionConfig(connection)) { + if (isKtxSqliteConnectionConfig(connection)) { return sqlite.extractSchema(connectionId); } - if (isKloMysqlConnectionConfig(connection)) { + if (isKtxMysqlConnectionConfig(connection)) { return mysql.extractSchema(connectionId); } - if (isKloClickHouseConnectionConfig(connection)) { + if (isKtxClickHouseConnectionConfig(connection)) { return clickhouse.extractSchema(connectionId); } - if (isKloSqlServerConnectionConfig(connection)) { + if (isKtxSqlServerConnectionConfig(connection)) { return sqlserver.extractSchema(connectionId); } - if (isKloBigQueryConnectionConfig(connection)) { + if (isKtxBigQueryConnectionConfig(connection)) { return bigquery.extractSchema(connectionId); } if (hasSnowflakeDriver(connection)) { - const { createSnowflakeLiveDatabaseIntrospection, isKloSnowflakeConnectionConfig } = await import( - '@klo/connector-snowflake' + const { createSnowflakeLiveDatabaseIntrospection, isKtxSnowflakeConnectionConfig } = await import( + '@ktx/connector-snowflake' ); - if (!isKloSnowflakeConnectionConfig(connection)) { + if (!isKtxSnowflakeConnectionConfig(connection)) { return daemon.extractSchema(connectionId); } const snowflake = createSnowflakeLiveDatabaseIntrospection({ @@ -95,13 +95,13 @@ function createKloCliLiveDatabaseIntrospection( }; } -interface KloCliLocalIngestAdaptersOptions extends DefaultLocalIngestAdaptersOptions { +interface KtxCliLocalIngestAdaptersOptions extends DefaultLocalIngestAdaptersOptions { historicSqlConnectionId?: string; sqlAnalysisUrl?: string; } -function isEnabledPostgresHistoricSqlConnection(connection: KloPostgresConnectionConfig | undefined): boolean { - if (!connection || !isKloPostgresConnectionConfig(connection)) { +function isEnabledPostgresHistoricSqlConnection(connection: KtxPostgresConnectionConfig | undefined): boolean { + if (!connection || !isKtxPostgresConnectionConfig(connection)) { return false; } const historicSql = @@ -113,16 +113,16 @@ function isEnabledPostgresHistoricSqlConnection(connection: KloPostgresConnectio return historicSql?.enabled === true && historicSql.dialect === 'postgres'; } -function createEphemeralPostgresHistoricSqlClient(project: KloLocalProject, connectionId: string) { - const connection = project.config.connections[connectionId] as KloPostgresConnectionConfig | undefined; - if (!isKloPostgresConnectionConfig(connection)) { +function createEphemeralPostgresHistoricSqlClient(project: KtxLocalProject, connectionId: string) { + const connection = project.config.connections[connectionId] as KtxPostgresConnectionConfig | undefined; + if (!isKtxPostgresConnectionConfig(connection)) { throw new Error( `Historic SQL local ingest requires a Postgres connection, got ${String(connection?.driver ?? 'unknown')}`, ); } return { async executeQuery(sql: string, params?: unknown[]) { - const client = new KloPostgresHistoricSqlQueryClient({ + const client = new KtxPostgresHistoricSqlQueryClient({ connectionId, connection, }); @@ -135,12 +135,12 @@ function createEphemeralPostgresHistoricSqlClient(project: KloLocalProject, conn }; } -function historicSqlOptionsForLocalRun(project: KloLocalProject, options: KloCliLocalIngestAdaptersOptions) { +function historicSqlOptionsForLocalRun(project: KtxLocalProject, options: KtxCliLocalIngestAdaptersOptions) { const connectionId = options.historicSqlConnectionId; if (!connectionId) { return undefined; } - const connection = project.config.connections[connectionId] as KloPostgresConnectionConfig | undefined; + const connection = project.config.connections[connectionId] as KtxPostgresConnectionConfig | undefined; if (!isEnabledPostgresHistoricSqlConnection(connection)) { return undefined; } @@ -148,18 +148,18 @@ function historicSqlOptionsForLocalRun(project: KloLocalProject, options: KloCli sqlAnalysis: createHttpSqlAnalysisPort({ baseUrl: options.sqlAnalysisUrl ?? - process.env.KLO_SQL_ANALYSIS_URL ?? - process.env.KLO_DAEMON_URL ?? + process.env.KTX_SQL_ANALYSIS_URL ?? + process.env.KTX_DAEMON_URL ?? 'http://127.0.0.1:8765', }), postgresQueryClient: createEphemeralPostgresHistoricSqlClient(project, connectionId), - postgresBaselineRootDir: join(project.projectDir, '.klo/cache/historic-sql'), + postgresBaselineRootDir: join(project.projectDir, '.ktx/cache/historic-sql'), }; } -export function createKloCliLocalIngestAdapters( - project: KloLocalProject, - options: KloCliLocalIngestAdaptersOptions = {}, +export function createKtxCliLocalIngestAdapters( + project: KtxLocalProject, + options: KtxCliLocalIngestAdaptersOptions = {}, ): SourceAdapter[] { const historicSql = historicSqlOptionsForLocalRun(project, options); const base = createDefaultLocalIngestAdapters(project, { @@ -167,7 +167,7 @@ export function createKloCliLocalIngestAdapters( ...(historicSql ? { historicSql } : {}), }); const liveDatabase = new LiveDatabaseSourceAdapter({ - introspection: createKloCliLiveDatabaseIntrospection(project, options), + introspection: createKtxCliLiveDatabaseIntrospection(project, options), }); return base.map((adapter) => (adapter.source === 'live-database' ? liveDatabase : adapter)); } diff --git a/packages/cli/src/local-scan-connectors.test.ts b/packages/cli/src/local-scan-connectors.test.ts index 638ad3e9..13d19c18 100644 --- a/packages/cli/src/local-scan-connectors.test.ts +++ b/packages/cli/src/local-scan-connectors.test.ts @@ -1,9 +1,9 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject, loadKloProject } from '@klo/context/project'; +import { initKtxProject, loadKtxProject } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { createKloCliScanConnector } from './local-scan-connectors.js'; +import { createKtxCliScanConnector } from './local-scan-connectors.js'; const bigQueryMock = vi.hoisted(() => ({ constructorInputs: [] as Array<{ @@ -13,10 +13,10 @@ const bigQueryMock = vi.hoisted(() => ({ }>, })); -vi.mock('@klo/connector-bigquery', () => ({ - isKloBigQueryConnectionConfig: (connection: { driver?: unknown } | undefined) => +vi.mock('@ktx/connector-bigquery', () => ({ + isKtxBigQueryConnectionConfig: (connection: { driver?: unknown } | undefined) => String(connection?.driver ?? '').toLowerCase() === 'bigquery', - KloBigQueryScanConnector: class { + KtxBigQueryScanConnector: class { readonly id: string; readonly driver = 'bigquery'; @@ -27,12 +27,12 @@ vi.mock('@klo/connector-bigquery', () => ({ }, })); -describe('createKloCliScanConnector', () => { +describe('createKtxCliScanConnector', () => { let tempDir: string; beforeEach(async () => { bigQueryMock.constructorInputs.length = 0; - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-scan-connector-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-scan-connector-')); }); afterEach(async () => { @@ -40,9 +40,9 @@ describe('createKloCliScanConnector', () => { }); it('creates a native sqlite connector from standalone config', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -54,9 +54,9 @@ describe('createKloCliScanConnector', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir: tempDir }); + const project = await loadKtxProject({ projectDir: tempDir }); - const connector = await createKloCliScanConnector(project, 'warehouse'); + const connector = await createKtxCliScanConnector(project, 'warehouse'); expect(connector.id).toBe('sqlite:warehouse'); expect(connector.driver).toBe('sqlite'); @@ -66,9 +66,9 @@ describe('createKloCliScanConnector', () => { ['maxBytesBilled', ' maxBytesBilled: 123456789', 123456789], ['max_bytes_billed', ' max_bytes_billed: "987654321"', '987654321'], ])('passes BigQuery %s from standalone config', async (_label, byteCapLine, expectedMaxBytesBilled) => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -81,9 +81,9 @@ describe('createKloCliScanConnector', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir: tempDir }); + const project = await loadKtxProject({ projectDir: tempDir }); - const connector = await createKloCliScanConnector(project, 'warehouse'); + const connector = await createKtxCliScanConnector(project, 'warehouse'); expect(connector.id).toBe('bigquery:warehouse'); expect(connector.driver).toBe('bigquery'); @@ -96,9 +96,9 @@ describe('createKloCliScanConnector', () => { }); it('does not create a standalone PostHog scan connector', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -111,17 +111,17 @@ describe('createKloCliScanConnector', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir: tempDir }); + const project = await loadKtxProject({ projectDir: tempDir }); - await expect(createKloCliScanConnector(project, 'product')).rejects.toThrow( - 'Connection "product" uses driver "posthog", which has no native standalone KLO scan connector', + await expect(createKtxCliScanConnector(project, 'product')).rejects.toThrow( + 'Connection "product" uses driver "posthog", which has no native standalone KTX scan connector', ); }); it('throws for structural daemon-only fallback configs', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -132,17 +132,17 @@ describe('createKloCliScanConnector', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir: tempDir }); + const project = await loadKtxProject({ projectDir: tempDir }); - await expect(createKloCliScanConnector(project, 'warehouse')).rejects.toThrow( - 'Connection "warehouse" uses driver "duckdb", which has no native standalone KLO scan connector', + await expect(createKtxCliScanConnector(project, 'warehouse')).rejects.toThrow( + 'Connection "warehouse" uses driver "duckdb", which has no native standalone KTX scan connector', ); }); it('throws a clear error when the connection block has no driver field', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -154,10 +154,10 @@ describe('createKloCliScanConnector', () => { ].join('\n'), 'utf-8', ); - const project = await loadKloProject({ projectDir: tempDir }); + const project = await loadKtxProject({ projectDir: tempDir }); - await expect(createKloCliScanConnector(project, 'warehouse')).rejects.toThrow( - 'Connection "warehouse" has no `driver` field in klo.yaml', + await expect(createKtxCliScanConnector(project, 'warehouse')).rejects.toThrow( + 'Connection "warehouse" has no `driver` field in ktx.yaml', ); }); }); diff --git a/packages/cli/src/local-scan-connectors.ts b/packages/cli/src/local-scan-connectors.ts index ca9c37f7..d3377b0c 100644 --- a/packages/cli/src/local-scan-connectors.ts +++ b/packages/cli/src/local-scan-connectors.ts @@ -1,10 +1,10 @@ -import type { KloLocalProject } from '@klo/context/project'; -import type { KloScanConnector } from '@klo/context/scan'; +import type { KtxLocalProject } from '@ktx/context/project'; +import type { KtxScanConnector } from '@ktx/context/scan'; const SUPPORTED_DRIVERS = 'sqlite, postgres, mysql, clickhouse, sqlserver, bigquery, snowflake'; function bigQueryMaxBytesBilled( - connection: KloLocalProject['config']['connections'][string], + connection: KtxLocalProject['config']['connections'][string], ): number | string | undefined { const raw = connection.maxBytesBilled ?? connection.max_bytes_billed; if (typeof raw === 'number') { @@ -17,55 +17,55 @@ function bigQueryMaxBytesBilled( return undefined; } -export async function createKloCliScanConnector( - project: KloLocalProject, +export async function createKtxCliScanConnector( + project: KtxLocalProject, connectionId: string, -): Promise { +): Promise { const connection = project.config.connections[connectionId]; if (!connection) { - throw new Error(`Connection "${connectionId}" is not configured in klo.yaml`); + throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`); } const driver = String(connection.driver ?? '').toLowerCase(); if (!driver) { throw new Error( - `Connection "${connectionId}" has no \`driver\` field in klo.yaml. Supported drivers: ${SUPPORTED_DRIVERS}.`, + `Connection "${connectionId}" has no \`driver\` field in ktx.yaml. Supported drivers: ${SUPPORTED_DRIVERS}.`, ); } if (driver === 'sqlite' || driver === 'sqlite3') { - const { KloSqliteScanConnector, isKloSqliteConnectionConfig } = await import('@klo/connector-sqlite'); - if (isKloSqliteConnectionConfig(connection)) { - return new KloSqliteScanConnector({ connectionId, connection, projectDir: project.projectDir }); + const { KtxSqliteScanConnector, isKtxSqliteConnectionConfig } = await import('@ktx/connector-sqlite'); + if (isKtxSqliteConnectionConfig(connection)) { + return new KtxSqliteScanConnector({ connectionId, connection, projectDir: project.projectDir }); } } if (driver === 'postgres' || driver === 'postgresql') { - const { KloPostgresScanConnector, isKloPostgresConnectionConfig } = await import('@klo/connector-postgres'); - if (isKloPostgresConnectionConfig(connection)) { - return new KloPostgresScanConnector({ connectionId, connection }); + const { KtxPostgresScanConnector, isKtxPostgresConnectionConfig } = await import('@ktx/connector-postgres'); + if (isKtxPostgresConnectionConfig(connection)) { + return new KtxPostgresScanConnector({ connectionId, connection }); } } if (driver === 'mysql') { - const { KloMysqlScanConnector, isKloMysqlConnectionConfig } = await import('@klo/connector-mysql'); - if (isKloMysqlConnectionConfig(connection)) { - return new KloMysqlScanConnector({ connectionId, connection }); + const { KtxMysqlScanConnector, isKtxMysqlConnectionConfig } = await import('@ktx/connector-mysql'); + if (isKtxMysqlConnectionConfig(connection)) { + return new KtxMysqlScanConnector({ connectionId, connection }); } } if (driver === 'clickhouse') { - const { KloClickHouseScanConnector, isKloClickHouseConnectionConfig } = await import('@klo/connector-clickhouse'); - if (isKloClickHouseConnectionConfig(connection)) { - return new KloClickHouseScanConnector({ connectionId, connection }); + const { KtxClickHouseScanConnector, isKtxClickHouseConnectionConfig } = await import('@ktx/connector-clickhouse'); + if (isKtxClickHouseConnectionConfig(connection)) { + return new KtxClickHouseScanConnector({ connectionId, connection }); } } if (driver === 'sqlserver') { - const { KloSqlServerScanConnector, isKloSqlServerConnectionConfig } = await import('@klo/connector-sqlserver'); - if (isKloSqlServerConnectionConfig(connection)) { - return new KloSqlServerScanConnector({ connectionId, connection }); + const { KtxSqlServerScanConnector, isKtxSqlServerConnectionConfig } = await import('@ktx/connector-sqlserver'); + if (isKtxSqlServerConnectionConfig(connection)) { + return new KtxSqlServerScanConnector({ connectionId, connection }); } } if (driver === 'bigquery') { - const { KloBigQueryScanConnector, isKloBigQueryConnectionConfig } = await import('@klo/connector-bigquery'); - if (isKloBigQueryConnectionConfig(connection)) { + const { KtxBigQueryScanConnector, isKtxBigQueryConnectionConfig } = await import('@ktx/connector-bigquery'); + if (isKtxBigQueryConnectionConfig(connection)) { const maxBytesBilled = bigQueryMaxBytesBilled(connection); - return new KloBigQueryScanConnector({ + return new KtxBigQueryScanConnector({ connectionId, connection, ...(maxBytesBilled !== undefined ? { maxBytesBilled } : {}), @@ -73,12 +73,12 @@ export async function createKloCliScanConnector( } } if (driver === 'snowflake') { - const { KloSnowflakeScanConnector, isKloSnowflakeConnectionConfig } = await import('@klo/connector-snowflake'); - if (isKloSnowflakeConnectionConfig(connection)) { - return new KloSnowflakeScanConnector({ connectionId, connection }); + const { KtxSnowflakeScanConnector, isKtxSnowflakeConnectionConfig } = await import('@ktx/connector-snowflake'); + if (isKtxSnowflakeConnectionConfig(connection)) { + return new KtxSnowflakeScanConnector({ connectionId, connection }); } } throw new Error( - `Connection "${connectionId}" uses driver "${driver}", which has no native standalone KLO scan connector. Supported drivers: ${SUPPORTED_DRIVERS}.`, + `Connection "${connectionId}" uses driver "${driver}", which has no native standalone KTX scan connector. Supported drivers: ${SUPPORTED_DRIVERS}.`, ); } diff --git a/packages/cli/src/memory-flow-hud.tsx b/packages/cli/src/memory-flow-hud.tsx index 29b29170..68f50bae 100644 --- a/packages/cli/src/memory-flow-hud.tsx +++ b/packages/cli/src/memory-flow-hud.tsx @@ -1,5 +1,5 @@ /* @jsxImportSource react */ -import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@klo/context/ingest/memory-flow'; +import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow'; import { Box, Text } from 'ink'; import React, { type ReactNode } from 'react'; import { buildDemoMetrics, formatCost, formatDuration } from './demo-metrics.js'; @@ -315,7 +315,7 @@ function pad(str: string, width: number): string { return str.length >= width ? str : str + ' '.repeat(width - str.length); } -const KLO_LOGO_SMALL = [ +const KTX_LOGO_SMALL = [ '██╗ ██╗██╗ ██████╗ ', '██║ ██╔╝██║ ██╔═══██╗', '█████╔╝ ██║ ██║ ██║', @@ -328,7 +328,7 @@ export function Logo(props: { theme: HudTheme; done: boolean }): ReactNode { const color = props.done ? props.theme.complete : props.theme.active; return ( - {KLO_LOGO_SMALL.map((line, idx) => ( + {KTX_LOGO_SMALL.map((line, idx) => ( {line} @@ -492,7 +492,7 @@ export function ActivityFeed(props: { )} - {/* Results — what KLO has created */} + {/* Results — what KTX has created */} {insights.length > 0 && ( Created so far: @@ -524,7 +524,7 @@ export function ActivityFeed(props: { {spinner(props.frame)} Saving to context layer... )} {savedEvent && ( - ✓ Saved — your agents can now use the KLO context layer + ✓ Saved — your agents can now use the KTX context layer )} {/* Phase 7: Completion */} @@ -559,12 +559,12 @@ function CompletionSummary(props: { <> {'─'.repeat(60)} - ★ KLO finished ingesting your data + ★ KTX finished ingesting your data {(sl > 0 || wiki > 0) && ( <> - KLO created: + KTX created: {sl > 0 && ( {' '}📊 {sl} query definition{sl === 1 ? '' : 's'} — so agents can write accurate SQL for your data diff --git a/packages/cli/src/memory-flow-interactive.test.ts b/packages/cli/src/memory-flow-interactive.test.ts index 1ac30316..456a3110 100644 --- a/packages/cli/src/memory-flow-interactive.test.ts +++ b/packages/cli/src/memory-flow-interactive.test.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'node:events'; -import type { MemoryFlowReplayInput } from '@klo/context/ingest'; +import type { MemoryFlowReplayInput } from '@ktx/context/ingest'; import { describe, expect, it, vi } from 'vitest'; import { memoryFlowCommandForKey, renderMemoryFlowInteractively } from './memory-flow-interactive.js'; diff --git a/packages/cli/src/memory-flow-interactive.ts b/packages/cli/src/memory-flow-interactive.ts index f356e725..7998a978 100644 --- a/packages/cli/src/memory-flow-interactive.ts +++ b/packages/cli/src/memory-flow-interactive.ts @@ -7,26 +7,26 @@ import { type MemoryFlowInteractionCommand, type MemoryFlowInteractionState, type MemoryFlowReplayInput, -} from '@klo/context/ingest'; +} from '@ktx/context/ingest'; -interface KloMemoryFlowKey { +interface KtxMemoryFlowKey { name?: string; ctrl?: boolean; } -export interface KloMemoryFlowStdin { +export interface KtxMemoryFlowStdin { isTTY?: boolean; isRaw?: boolean; setRawMode?(value: boolean): void; resume?(): void; pause?(): void; - on(event: 'keypress', listener: (chunk: string, key: KloMemoryFlowKey) => void): this; - off?(event: 'keypress', listener: (chunk: string, key: KloMemoryFlowKey) => void): this; - removeListener?(event: 'keypress', listener: (chunk: string, key: KloMemoryFlowKey) => void): this; + on(event: 'keypress', listener: (chunk: string, key: KtxMemoryFlowKey) => void): this; + off?(event: 'keypress', listener: (chunk: string, key: KtxMemoryFlowKey) => void): this; + removeListener?(event: 'keypress', listener: (chunk: string, key: KtxMemoryFlowKey) => void): this; } -interface KloMemoryFlowInteractiveIo { - stdin?: KloMemoryFlowStdin; +interface KtxMemoryFlowInteractiveIo { + stdin?: KtxMemoryFlowStdin; stdout: { isTTY?: boolean; columns?: number; @@ -35,17 +35,17 @@ interface KloMemoryFlowInteractiveIo { } interface RenderMemoryFlowInteractiveOptions { - prepareKeypressEvents?(stdin: KloMemoryFlowStdin): void; + prepareKeypressEvents?(stdin: KtxMemoryFlowStdin): void; } -function defaultPrepareKeypressEvents(stdin: KloMemoryFlowStdin): void { +function defaultPrepareKeypressEvents(stdin: KtxMemoryFlowStdin): void { emitKeypressEvents(stdin as Parameters[0]); } export function memoryFlowCommandForKey( chunk: string, search: MemoryFlowInteractionState['search'], - key: KloMemoryFlowKey, + key: KtxMemoryFlowKey, ): MemoryFlowInteractionCommand | null { if (search.editing) { if (key.name === 'escape') return 'search-clear'; @@ -76,8 +76,8 @@ export function memoryFlowCommandForKey( } function removeKeypressListener( - stdin: KloMemoryFlowStdin, - handler: (chunk: string, key: KloMemoryFlowKey) => void, + stdin: KtxMemoryFlowStdin, + handler: (chunk: string, key: KtxMemoryFlowKey) => void, ): void { if (stdin.off) { stdin.off('keypress', handler); @@ -86,7 +86,7 @@ function removeKeypressListener( stdin.removeListener?.('keypress', handler); } -function repaint(input: MemoryFlowReplayInput, state: MemoryFlowInteractionState, io: KloMemoryFlowInteractiveIo): void { +function repaint(input: MemoryFlowReplayInput, state: MemoryFlowInteractionState, io: KtxMemoryFlowInteractiveIo): void { const view = buildMemoryFlowViewModel(input); io.stdout.write('\u001b[2J\u001b[H'); io.stdout.write(renderMemoryFlowInteractive(view, state, { terminalWidth: io.stdout.columns })); @@ -94,7 +94,7 @@ function repaint(input: MemoryFlowReplayInput, state: MemoryFlowInteractionState export async function renderMemoryFlowInteractively( input: MemoryFlowReplayInput, - io: KloMemoryFlowInteractiveIo, + io: KtxMemoryFlowInteractiveIo, options: RenderMemoryFlowInteractiveOptions = {}, ): Promise { const stdin = io.stdin; @@ -119,7 +119,7 @@ export async function renderMemoryFlowInteractively( stdin.pause?.(); }; - const handleKeypress = (_chunk: string, key: KloMemoryFlowKey): void => { + const handleKeypress = (_chunk: string, key: KtxMemoryFlowKey): void => { const command = memoryFlowCommandForKey(_chunk, state.search, key); if (!command) { return; diff --git a/packages/cli/src/memory-flow-tui.test.tsx b/packages/cli/src/memory-flow-tui.test.tsx index 565d8c24..9bf5425e 100644 --- a/packages/cli/src/memory-flow-tui.test.tsx +++ b/packages/cli/src/memory-flow-tui.test.tsx @@ -1,5 +1,5 @@ /* @jsxImportSource react */ -import type { MemoryFlowReplayInput } from '@klo/context/ingest'; +import type { MemoryFlowReplayInput } from '@ktx/context/ingest'; import { render as renderInkTest } from 'ink-testing-library'; import React, { type ReactNode } from 'react'; import { describe, expect, it, vi } from 'vitest'; @@ -9,7 +9,7 @@ import { renderMemoryFlowTui, sanitizeMemoryFlowTuiError, startLiveMemoryFlowTui, - type KloMemoryFlowTuiIo, + type KtxMemoryFlowTuiIo, type MemoryFlowInkInstance, type MemoryFlowInkRenderOptions, } from './memory-flow-tui.js'; @@ -72,7 +72,7 @@ function packagedReplayInput(overrides: Partial = {}): Me }; } -function makeIo(): { io: KloMemoryFlowTuiIo; stderr: () => string } { +function makeIo(): { io: KtxMemoryFlowTuiIo; stderr: () => string } { let stderr = ''; return { io: { stdin: { isTTY: true, setRawMode: vi.fn() }, stdout: { isTTY: true, columns: 120, write: vi.fn() }, stderr: { write(chunk: string) { stderr += chunk; } } }, stderr: () => stderr }; } @@ -103,7 +103,7 @@ describe('sanitizeMemoryFlowTuiError', () => { }); describe('MemoryFlowTuiApp', () => { - it('always shows the KLO logo', () => { + it('always shows the KTX logo', () => { const { lastFrame } = renderInkTest(); expect(lastFrame()).toContain('█████╔╝'); }); @@ -198,12 +198,12 @@ describe('MemoryFlowTuiApp', () => { expect(frame).toContain('Created so far:'); expect(frame).toContain('order lifecycle'); expect(frame).toContain('customer metrics'); - expect(frame).toContain('KLO finished ingesting your data'); - expect(frame).toContain('klo sl list'); - expect(frame).toContain('klo wiki list'); - expect(frame).toContain('klo serve --mcp stdio --user-id local'); - expect(frame).not.toContain(['klo', 'ask'].join(' ')); - expect(frame).not.toContain(['klo', 'mcp'].join(' ')); + expect(frame).toContain('KTX finished ingesting your data'); + expect(frame).toContain('ktx sl list'); + expect(frame).toContain('ktx wiki list'); + expect(frame).toContain('ktx serve --mcp stdio --user-id local'); + expect(frame).not.toContain(['ktx', 'ask'].join(' ')); + expect(frame).not.toContain(['ktx', 'mcp'].join(' ')); }); it('handles quit while running', async () => { @@ -254,7 +254,7 @@ describe('MemoryFlowTuiApp', () => { it('hides completion while running', () => { const { lastFrame } = renderInkTest(); - expect(lastFrame()).not.toContain('KLO finished ingesting'); + expect(lastFrame()).not.toContain('KTX finished ingesting'); }); }); diff --git a/packages/cli/src/memory-flow-tui.tsx b/packages/cli/src/memory-flow-tui.tsx index dd0186db..31a3aea4 100644 --- a/packages/cli/src/memory-flow-tui.tsx +++ b/packages/cli/src/memory-flow-tui.tsx @@ -12,7 +12,7 @@ import { reduceMemoryFlowInteractionState, selectedMemoryFlowColumn, selectedMemoryFlowDetails, -} from '@klo/context/ingest'; +} from '@ktx/context/ingest'; import { Box, Text, render as renderInkRuntime, useApp, useInput } from 'ink'; import React, { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { buildDemoMetrics } from './demo-metrics.js'; @@ -56,7 +56,7 @@ const STAGE_LABELS = { saved: 'MEMORY', } satisfies Record; -export interface KloMemoryFlowTuiIo { +export interface KtxMemoryFlowTuiIo { stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void }; stdout: { isTTY?: boolean; columns?: number; write(chunk: string): void }; stderr: { write(chunk: string): void }; @@ -76,9 +76,9 @@ export interface MemoryFlowInkInstance { } export interface MemoryFlowInkRenderOptions { - stdin?: KloMemoryFlowTuiIo['stdin']; - stdout: KloMemoryFlowTuiIo['stdout']; - stderr: KloMemoryFlowTuiIo['stderr']; + stdin?: KtxMemoryFlowTuiIo['stdin']; + stdout: KtxMemoryFlowTuiIo['stdout']; + stderr: KtxMemoryFlowTuiIo['stderr']; exitOnCtrlC: boolean; patchConsole: boolean; maxFps: number; @@ -429,7 +429,7 @@ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode { function renderTree( input: MemoryFlowReplayInput, - io: KloMemoryFlowTuiIo, + io: KtxMemoryFlowTuiIo, onExit: () => void, options: RenderTreeOptions = {}, ): ReactNode { @@ -459,7 +459,7 @@ function renderInk(tree: ReactNode, options: MemoryFlowInkRenderOptions): Memory }) as MemoryFlowInkInstance; } -function renderOptions(io: KloMemoryFlowTuiIo): MemoryFlowInkRenderOptions { +function renderOptions(io: KtxMemoryFlowTuiIo): MemoryFlowInkRenderOptions { return { stdin: io.stdin, stdout: io.stdout, @@ -491,7 +491,7 @@ function resolveTiming(options: RenderMemoryFlowTuiOptions): MemoryFlowTuiTiming export async function renderMemoryFlowTui( input: MemoryFlowReplayInput, - io: KloMemoryFlowTuiIo, + io: KtxMemoryFlowTuiIo, options: RenderMemoryFlowTuiOptions = {}, ): Promise { let instance: MemoryFlowInkInstance | null = null; @@ -516,7 +516,7 @@ export async function renderMemoryFlowTui( export async function startLiveMemoryFlowTui( input: MemoryFlowReplayInput, - io: KloMemoryFlowTuiIo, + io: KtxMemoryFlowTuiIo, options: StartLiveMemoryFlowTuiOptions = {}, ): Promise { let instance: MemoryFlowInkInstance | null = null; diff --git a/packages/cli/src/next-steps.test.ts b/packages/cli/src/next-steps.test.ts index cb638277..39b0e22d 100644 --- a/packages/cli/src/next-steps.test.ts +++ b/packages/cli/src/next-steps.test.ts @@ -1,96 +1,96 @@ import { describe, expect, it } from 'vitest'; import { - KLO_CONTEXT_BUILD_COMMANDS, - KLO_NEXT_STEP_COMMANDS, + KTX_CONTEXT_BUILD_COMMANDS, + KTX_NEXT_STEP_COMMANDS, formatNextStepLines, formatSetupNextStepLines, } from './next-steps.js'; const command = (...parts: string[]) => parts.join(' '); -describe('KLO demo next steps', () => { +describe('KTX demo next steps', () => { it('uses supported context-build commands before agent usage', () => { - expect(KLO_CONTEXT_BUILD_COMMANDS).toEqual([ + expect(KTX_CONTEXT_BUILD_COMMANDS).toEqual([ { - command: 'klo setup context build', + command: 'ktx setup context build', description: 'Build agent-ready context from configured primary and context sources', }, { - command: 'klo status', + command: 'ktx status', description: 'Check setup and context readiness', }, { - command: 'klo setup context status', + command: 'ktx setup context status', description: 'Check the setup-managed context build state', }, ]); }); it('uses supported final public commands', () => { - expect(KLO_NEXT_STEP_COMMANDS).toEqual([ + expect(KTX_NEXT_STEP_COMMANDS).toEqual([ { - command: 'klo agent context --json', + command: 'ktx agent context --json', description: 'Verify the project context your agent can read', }, { - command: 'klo agent tools --json', + command: 'ktx agent tools --json', description: 'List direct CLI tools available to agents', }, { - command: 'klo sl list', + command: 'ktx sl list', description: 'Inspect generated semantic-layer sources', }, { - command: 'klo wiki list', + command: 'ktx wiki list', description: 'Inspect generated wiki pages', }, { - command: 'klo serve --mcp stdio --user-id local', + command: 'ktx serve --mcp stdio --user-id local', description: 'Optional MCP server route for clients that require MCP', }, ]); }); it('prefers the direct CLI route before MCP serving', () => { - const commands = KLO_NEXT_STEP_COMMANDS.map((step) => step.command); + const commands = KTX_NEXT_STEP_COMMANDS.map((step) => step.command); - expect(commands.indexOf('klo agent context --json')).toBeLessThan( - commands.indexOf('klo serve --mcp stdio --user-id local'), + expect(commands.indexOf('ktx agent context --json')).toBeLessThan( + commands.indexOf('ktx serve --mcp stdio --user-id local'), ); - expect(commands.indexOf('klo agent tools --json')).toBeLessThan( - commands.indexOf('klo serve --mcp stdio --user-id local'), + expect(commands.indexOf('ktx agent tools --json')).toBeLessThan( + commands.indexOf('ktx serve --mcp stdio --user-id local'), ); }); it('explains what the next-step commands are for', () => { const rendered = formatNextStepLines().join('\n'); - expect(rendered).toContain('KLO context is ready for agents.'); + expect(rendered).toContain('KTX context is ready for agents.'); expect(rendered).toContain('Preferred route: CLI + Skills'); expect(rendered).toContain('no MCP server is required'); expect(rendered).toContain('Direct CLI checks:'); expect(rendered).toContain('Optional MCP:'); - expect(rendered).not.toContain('Ask your agent to use KLO'); + expect(rendered).not.toContain('Ask your agent to use KTX'); }); it('does not advertise removed Commander migration commands', () => { const rendered = formatNextStepLines().join('\n'); - expect(rendered).toContain('klo agent tools --json'); - expect(rendered).toContain('klo agent context --json'); - expect(rendered).toContain('klo sl list'); - expect(rendered).toContain('klo wiki list'); - expect(rendered).toContain('klo serve --mcp stdio --user-id local'); + expect(rendered).toContain('ktx agent tools --json'); + expect(rendered).toContain('ktx agent context --json'); + expect(rendered).toContain('ktx sl list'); + expect(rendered).toContain('ktx wiki list'); + expect(rendered).toContain('ktx serve --mcp stdio --user-id local'); for (const removed of [ - command('klo', 'ask'), - command('klo', 'mcp'), - command('klo', 'connect'), - command('klo', 'knowledge'), + command('ktx', 'ask'), + command('ktx', 'mcp'), + command('ktx', 'connect'), + command('ktx', 'knowledge'), command('dev', 'model'), command('dev', 'knowledge'), - command('klo', 'ingest', 'run'), - command('klo', 'ingest', 'replay'), + command('ktx', 'ingest', 'run'), + command('ktx', 'ingest', 'replay'), ]) { expect(rendered).not.toContain(removed); } @@ -104,13 +104,13 @@ describe('KLO demo next steps', () => { agentIntegrationReady: true, }).join('\n'); - expect(rendered).toContain('Build KLO context next.'); + expect(rendered).toContain('Build KTX context next.'); expect(rendered).toContain('primary-source scans and context-source ingests'); - expect(rendered).toContain('klo setup context build'); - expect(rendered).toContain('klo status'); - expect(rendered).toContain('klo setup context status'); - expect(rendered).not.toContain('klo agent context --json'); - expect(rendered).not.toContain('klo serve --mcp'); + expect(rendered).toContain('ktx setup context build'); + expect(rendered).toContain('ktx status'); + expect(rendered).toContain('ktx setup context status'); + expect(rendered).not.toContain('ktx agent context --json'); + expect(rendered).not.toContain('ktx serve --mcp'); }); it('shows agent commands only after setup and context build are ready', () => { @@ -121,9 +121,9 @@ describe('KLO demo next steps', () => { agentIntegrationReady: true, }).join('\n'); - expect(rendered).toContain('KLO context is ready for agents.'); - expect(rendered).toContain('klo agent context --json'); - expect(rendered).toContain('klo serve --mcp stdio --user-id local'); - expect(rendered).not.toContain('Build KLO context next.'); + expect(rendered).toContain('KTX context is ready for agents.'); + expect(rendered).toContain('ktx agent context --json'); + expect(rendered).toContain('ktx serve --mcp stdio --user-id local'); + expect(rendered).not.toContain('Build KTX context next.'); }); }); diff --git a/packages/cli/src/next-steps.ts b/packages/cli/src/next-steps.ts index e8ca05c7..d9c41dd3 100644 --- a/packages/cli/src/next-steps.ts +++ b/packages/cli/src/next-steps.ts @@ -1,51 +1,51 @@ -export const KLO_CONTEXT_BUILD_COMMANDS = [ +export const KTX_CONTEXT_BUILD_COMMANDS = [ { - command: 'klo setup context build', + command: 'ktx setup context build', description: 'Build agent-ready context from configured primary and context sources', }, { - command: 'klo status', + command: 'ktx status', description: 'Check setup and context readiness', }, { - command: 'klo setup context status', + command: 'ktx setup context status', description: 'Check the setup-managed context build state', }, ] as const; -export const KLO_NEXT_STEP_DIRECT_COMMANDS = [ +export const KTX_NEXT_STEP_DIRECT_COMMANDS = [ { - command: 'klo agent context --json', + command: 'ktx agent context --json', description: 'Verify the project context your agent can read', }, { - command: 'klo agent tools --json', + command: 'ktx agent tools --json', description: 'List direct CLI tools available to agents', }, { - command: 'klo sl list', + command: 'ktx sl list', description: 'Inspect generated semantic-layer sources', }, { - command: 'klo wiki list', + command: 'ktx wiki list', description: 'Inspect generated wiki pages', }, ] as const; -export const KLO_NEXT_STEP_MCP_COMMANDS = [ +export const KTX_NEXT_STEP_MCP_COMMANDS = [ { - command: 'klo serve --mcp stdio --user-id local', + command: 'ktx serve --mcp stdio --user-id local', description: 'Optional MCP server route for clients that require MCP', }, ] as const; -export const KLO_NEXT_STEP_COMMANDS = [...KLO_NEXT_STEP_DIRECT_COMMANDS, ...KLO_NEXT_STEP_MCP_COMMANDS] as const; +export const KTX_NEXT_STEP_COMMANDS = [...KTX_NEXT_STEP_DIRECT_COMMANDS, ...KTX_NEXT_STEP_MCP_COMMANDS] as const; -export const KLO_NEXT_STEP_COMMAND_WIDTH = Math.max( - ...[...KLO_CONTEXT_BUILD_COMMANDS, ...KLO_NEXT_STEP_COMMANDS].map((step) => step.command.length), +export const KTX_NEXT_STEP_COMMAND_WIDTH = Math.max( + ...[...KTX_CONTEXT_BUILD_COMMANDS, ...KTX_NEXT_STEP_COMMANDS].map((step) => step.command.length), ); -export interface KloSetupNextStepState { +export interface KtxSetupNextStepState { setupReady: boolean; hasContextTargets: boolean; contextReady: boolean; @@ -53,50 +53,50 @@ export interface KloSetupNextStepState { } function commandLines(commands: ReadonlyArray<{ command: string; description: string }>, indent: string): string[] { - return commands.map((step) => `${indent}$ ${step.command.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} ${step.description}`); + return commands.map((step) => `${indent}$ ${step.command.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} ${step.description}`); } export function formatNextStepLines(indent = ' '): string[] { return [ - `${indent}KLO context is ready for agents.`, - `${indent}Preferred route: CLI + Skills; installed rules call \`klo agent ...\` directly, so no MCP server is required.`, + `${indent}KTX context is ready for agents.`, + `${indent}Preferred route: CLI + Skills; installed rules call \`ktx agent ...\` directly, so no MCP server is required.`, `${indent}Direct CLI checks:`, - ...commandLines(KLO_NEXT_STEP_DIRECT_COMMANDS, indent), + ...commandLines(KTX_NEXT_STEP_DIRECT_COMMANDS, indent), `${indent}Optional MCP:`, - ...commandLines(KLO_NEXT_STEP_MCP_COMMANDS, indent), + ...commandLines(KTX_NEXT_STEP_MCP_COMMANDS, indent), ]; } -export function formatSetupNextStepLines(state: KloSetupNextStepState, indent = ' '): string[] { +export function formatSetupNextStepLines(state: KtxSetupNextStepState, indent = ' '): string[] { if (!state.setupReady) { return [ `${indent}Finish setup first.`, - `${indent}$ ${'klo setup'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Resume configuration and validation`, - `${indent}$ ${'klo status'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Check which setup steps still need attention`, + `${indent}$ ${'ktx setup'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Resume configuration and validation`, + `${indent}$ ${'ktx status'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Check which setup steps still need attention`, ]; } if (!state.hasContextTargets) { return [ `${indent}Connect data, then build context.`, - `${indent}$ ${'klo setup'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Add primary or context sources`, - `${indent}$ ${'klo status'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Check setup and context readiness`, + `${indent}$ ${'ktx setup'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Add primary or context sources`, + `${indent}$ ${'ktx status'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Check setup and context readiness`, ]; } if (!state.contextReady) { return [ - `${indent}Build KLO context next.`, + `${indent}Build KTX context next.`, `${indent}Preferred route: run the CLI build; it covers primary-source scans and context-source ingests.`, - ...commandLines(KLO_CONTEXT_BUILD_COMMANDS, indent), + ...commandLines(KTX_CONTEXT_BUILD_COMMANDS, indent), ]; } if (!state.agentIntegrationReady) { return [ - `${indent}KLO context is built. Install agent rules when you want your coding agent to use it.`, - `${indent}$ ${'klo setup --agents'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Install CLI-based agent rules`, - `${indent}$ ${'klo status'.padEnd(KLO_NEXT_STEP_COMMAND_WIDTH)} Check setup and context readiness`, + `${indent}KTX context is built. Install agent rules when you want your coding agent to use it.`, + `${indent}$ ${'ktx setup --agents'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Install CLI-based agent rules`, + `${indent}$ ${'ktx status'.padEnd(KTX_NEXT_STEP_COMMAND_WIDTH)} Check setup and context readiness`, ]; } diff --git a/packages/cli/src/project-dir.test.ts b/packages/cli/src/project-dir.test.ts index 120221d5..02f6dfde 100644 --- a/packages/cli/src/project-dir.test.ts +++ b/packages/cli/src/project-dir.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { runKloCli, type KloCliDeps } from './index.js'; +import { runKtxCli, type KtxCliDeps } from './index.js'; function makeIo() { let stdout = ''; @@ -24,11 +24,11 @@ function makeIo() { describe('project directory defaults', () => { afterEach(() => { - delete process.env.KLO_PROJECT_DIR; + delete process.env.KTX_PROJECT_DIR; }); - it('uses KLO_PROJECT_DIR when Commander-dispatched commands omit --project-dir', async () => { - process.env.KLO_PROJECT_DIR = '/tmp/klo-env-project'; + it('uses KTX_PROJECT_DIR when Commander-dispatched commands omit --project-dir', async () => { + process.env.KTX_PROJECT_DIR = '/tmp/ktx-env-project'; const connection = vi.fn(async () => 0); const demo = vi.fn(async () => 0); @@ -39,7 +39,7 @@ describe('project directory defaults', () => { const serveStdio = vi.fn(async () => 0); const setup = vi.fn(async () => 0); const agent = vi.fn(async () => 0); - const deps: KloCliDeps = { agent, connection, demo, doctor, ingest, publicIngest, scan, serveStdio, setup }; + const deps: KtxCliDeps = { agent, connection, demo, doctor, ingest, publicIngest, scan, serveStdio, setup }; const cases: Array<{ argv: string[]; @@ -50,56 +50,56 @@ describe('project directory defaults', () => { { argv: ['connection', 'list'], spy: connection, - expected: { command: 'list', projectDir: '/tmp/klo-env-project' }, + expected: { command: 'list', projectDir: '/tmp/ktx-env-project' }, runnerType: 'cli', }, { argv: ['setup', 'demo', 'scan', '--no-input'], spy: demo, - expected: { command: 'scan', projectDir: '/tmp/klo-env-project' }, + expected: { command: 'scan', projectDir: '/tmp/ktx-env-project' }, runnerType: 'cli', }, { argv: ['dev', 'doctor', '--no-input'], spy: doctor, - expected: { command: 'project', projectDir: '/tmp/klo-env-project' }, + expected: { command: 'project', projectDir: '/tmp/ktx-env-project' }, runnerType: 'cli', }, { argv: ['ingest', 'status', 'run-1'], spy: publicIngest, - expected: { command: 'status', projectDir: '/tmp/klo-env-project', runId: 'run-1' }, + expected: { command: 'status', projectDir: '/tmp/ktx-env-project', runId: 'run-1' }, runnerType: 'cli', }, { argv: ['setup', 'status'], spy: setup, - expected: { command: 'status', projectDir: '/tmp/klo-env-project' }, + expected: { command: 'status', projectDir: '/tmp/ktx-env-project' }, runnerType: 'cli', }, { argv: ['dev', 'scan', 'warehouse'], spy: scan, - expected: { command: 'run', projectDir: '/tmp/klo-env-project', connectionId: 'warehouse' }, + expected: { command: 'run', projectDir: '/tmp/ktx-env-project', connectionId: 'warehouse' }, runnerType: 'cli', }, { argv: ['serve', '--mcp', 'stdio'], spy: serveStdio, - expected: { mcp: 'stdio', projectDir: '/tmp/klo-env-project' }, + expected: { mcp: 'stdio', projectDir: '/tmp/ktx-env-project' }, runnerType: 'serve', }, { argv: ['agent', 'tools', '--json'], spy: agent, - expected: { command: 'tools', projectDir: '/tmp/klo-env-project' }, + expected: { command: 'tools', projectDir: '/tmp/ktx-env-project' }, runnerType: 'cli', }, ]; for (const item of cases) { const testIo = makeIo(); - await expect(runKloCli(item.argv, testIo.io, deps)).resolves.toBe(0); + await expect(runKtxCli(item.argv, testIo.io, deps)).resolves.toBe(0); if (item.runnerType === 'serve') { expect(item.spy).toHaveBeenLastCalledWith(expect.objectContaining(item.expected)); } else { @@ -109,8 +109,8 @@ describe('project directory defaults', () => { } }); - it('lets explicit global --project-dir override KLO_PROJECT_DIR before and after nested commands', async () => { - process.env.KLO_PROJECT_DIR = '/tmp/klo-env-project'; + it('lets explicit global --project-dir override KTX_PROJECT_DIR before and after nested commands', async () => { + process.env.KTX_PROJECT_DIR = '/tmp/ktx-env-project'; const scan = vi.fn(async () => 0); const publicIngest = vi.fn(async () => 0); @@ -118,38 +118,38 @@ describe('project directory defaults', () => { const ingestIo = makeIo(); await expect( - runKloCli(['--project-dir', '/tmp/klo-explicit-project', 'dev', 'scan', 'warehouse'], scanIo.io, { scan }), + runKtxCli(['--project-dir', '/tmp/ktx-explicit-project', 'dev', 'scan', 'warehouse'], scanIo.io, { scan }), ).resolves.toBe(0); await expect( - runKloCli(['ingest', 'status', 'run-1', '--project-dir=/tmp/klo-explicit-project'], ingestIo.io, { + runKtxCli(['ingest', 'status', 'run-1', '--project-dir=/tmp/ktx-explicit-project'], ingestIo.io, { publicIngest, }), ).resolves.toBe(0); expect(scan).toHaveBeenCalledWith( - expect.objectContaining({ command: 'run', projectDir: '/tmp/klo-explicit-project' }), + expect.objectContaining({ command: 'run', projectDir: '/tmp/ktx-explicit-project' }), scanIo.io, ); expect(publicIngest).toHaveBeenCalledWith( - expect.objectContaining({ command: 'status', projectDir: '/tmp/klo-explicit-project' }), + expect.objectContaining({ command: 'status', projectDir: '/tmp/ktx-explicit-project' }), ingestIo.io, ); expect(scanIo.stderr()).toBe(''); expect(ingestIo.stderr()).toBe(''); }); - it('uses nearest ancestor containing klo.yaml when no explicit or environment project-dir exists', async () => { + it('uses nearest ancestor containing ktx.yaml when no explicit or environment project-dir exists', async () => { const { mkdir, realpath, writeFile } = await import('node:fs/promises'); const { mkdtemp, rm } = await import('node:fs/promises'); const { tmpdir } = await import('node:os'); const { join } = await import('node:path'); const originalCwd = process.cwd(); - const root = await mkdtemp(join(tmpdir(), 'klo-cli-nearest-project-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-cli-nearest-project-')); const projectDir = join(root, 'warehouse'); const nestedDir = join(projectDir, 'nested', 'deeper'); await mkdir(nestedDir, { recursive: true }); - await writeFile(join(projectDir, 'klo.yaml'), 'project: warehouse\n', 'utf-8'); + await writeFile(join(projectDir, 'ktx.yaml'), 'project: warehouse\n', 'utf-8'); const expectedProjectDir = await realpath(projectDir); const scan = vi.fn(async () => 0); @@ -157,7 +157,7 @@ describe('project directory defaults', () => { try { process.chdir(nestedDir); - await expect(runKloCli(['dev', 'scan', 'warehouse'], testIo.io, { scan })).resolves.toBe(0); + await expect(runKtxCli(['dev', 'scan', 'warehouse'], testIo.io, { scan })).resolves.toBe(0); } finally { process.chdir(originalCwd); await rm(root, { recursive: true, force: true }); diff --git a/packages/cli/src/project-resolver.test.ts b/packages/cli/src/project-resolver.test.ts index d3412048..74ef02bc 100644 --- a/packages/cli/src/project-resolver.test.ts +++ b/packages/cli/src/project-resolver.test.ts @@ -3,13 +3,13 @@ import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { findNearestKloProjectDir, resolveKloProjectDir } from './project-resolver.js'; +import { findNearestKtxProjectDir, resolveKtxProjectDir } from './project-resolver.js'; -describe('resolveKloProjectDir', () => { +describe('resolveKtxProjectDir', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-project-resolver-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-project-resolver-')); }); afterEach(async () => { @@ -23,48 +23,48 @@ describe('resolveKloProjectDir', () => { await mkdir(envProject, { recursive: true }); expect( - resolveKloProjectDir({ + resolveKtxProjectDir({ explicitProjectDir: explicit, - env: { KLO_PROJECT_DIR: envProject }, + env: { KTX_PROJECT_DIR: envProject }, cwd: tempDir, }), ).toBe(resolve(explicit)); }); - it('uses KLO_PROJECT_DIR when no explicit project directory is set', async () => { + it('uses KTX_PROJECT_DIR when no explicit project directory is set', async () => { const envProject = join(tempDir, 'env-project'); await mkdir(envProject, { recursive: true }); - expect(resolveKloProjectDir({ env: { KLO_PROJECT_DIR: envProject }, cwd: tempDir })).toBe(resolve(envProject)); + expect(resolveKtxProjectDir({ env: { KTX_PROJECT_DIR: envProject }, cwd: tempDir })).toBe(resolve(envProject)); }); - it('resolves a relative KLO_PROJECT_DIR value from cwd', () => { - expect(resolveKloProjectDir({ env: { KLO_PROJECT_DIR: 'env-project' }, cwd: tempDir })).toBe( + it('resolves a relative KTX_PROJECT_DIR value from cwd', () => { + expect(resolveKtxProjectDir({ env: { KTX_PROJECT_DIR: 'env-project' }, cwd: tempDir })).toBe( resolve(tempDir, 'env-project'), ); }); - it('uses the nearest ancestor containing klo.yaml', async () => { + it('uses the nearest ancestor containing ktx.yaml', async () => { const project = join(tempDir, 'warehouse'); const nested = join(project, 'nested', 'deeper'); await mkdir(nested, { recursive: true }); - await writeFile(join(project, 'klo.yaml'), 'project: warehouse\n', 'utf-8'); + await writeFile(join(project, 'ktx.yaml'), 'project: warehouse\n', 'utf-8'); - expect(resolveKloProjectDir({ env: {}, cwd: nested })).toBe(resolve(project)); - expect(findNearestKloProjectDir(nested)).toBe(resolve(project)); + expect(resolveKtxProjectDir({ env: {}, cwd: nested })).toBe(resolve(project)); + expect(findNearestKtxProjectDir(nested)).toBe(resolve(project)); }); it('falls back to the current directory when no project marker exists', () => { - expect(resolveKloProjectDir({ env: {}, cwd: tempDir })).toBe(resolve(tempDir)); - expect(findNearestKloProjectDir(tempDir)).toBeUndefined(); + expect(resolveKtxProjectDir({ env: {}, cwd: tempDir })).toBe(resolve(tempDir)); + expect(findNearestKtxProjectDir(tempDir)).toBeUndefined(); }); it('rejects empty explicit and environment project directory values', () => { - expect(() => resolveKloProjectDir({ explicitProjectDir: ' ', cwd: tempDir })).toThrow( + expect(() => resolveKtxProjectDir({ explicitProjectDir: ' ', cwd: tempDir })).toThrow( '--project-dir requires a value', ); - expect(() => resolveKloProjectDir({ env: { KLO_PROJECT_DIR: ' ' }, cwd: tempDir })).toThrow( - 'KLO_PROJECT_DIR must not be empty', + expect(() => resolveKtxProjectDir({ env: { KTX_PROJECT_DIR: ' ' }, cwd: tempDir })).toThrow( + 'KTX_PROJECT_DIR must not be empty', ); }); }); diff --git a/packages/cli/src/project-resolver.ts b/packages/cli/src/project-resolver.ts index 8d948e65..e6295089 100644 --- a/packages/cli/src/project-resolver.ts +++ b/packages/cli/src/project-resolver.ts @@ -1,9 +1,9 @@ import { existsSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; -export interface KloProjectResolverOptions { +export interface KtxProjectResolverOptions { explicitProjectDir?: string; - env?: Partial>; + env?: Partial>; cwd?: string; } @@ -15,11 +15,11 @@ function nonEmptyValue(value: string | undefined): string | undefined { return trimmed.length > 0 ? value : undefined; } -export function findNearestKloProjectDir(startDir = process.cwd()): string | undefined { +export function findNearestKtxProjectDir(startDir = process.cwd()): string | undefined { let current = resolve(startDir); while (true) { - if (existsSync(join(current, 'klo.yaml'))) { + if (existsSync(join(current, 'ktx.yaml'))) { return current; } @@ -31,7 +31,7 @@ export function findNearestKloProjectDir(startDir = process.cwd()): string | und } } -export function resolveKloProjectDir(options: KloProjectResolverOptions = {}): string { +export function resolveKtxProjectDir(options: KtxProjectResolverOptions = {}): string { const cwd = options.cwd ?? process.cwd(); if (options.explicitProjectDir !== undefined) { @@ -42,15 +42,15 @@ export function resolveKloProjectDir(options: KloProjectResolverOptions = {}): s return resolve(cwd, explicit); } - const rawEnvProjectDir = options.env ? options.env.KLO_PROJECT_DIR : process.env.KLO_PROJECT_DIR; + const rawEnvProjectDir = options.env ? options.env.KTX_PROJECT_DIR : process.env.KTX_PROJECT_DIR; const envProjectDir = nonEmptyValue(rawEnvProjectDir); if (rawEnvProjectDir !== undefined && envProjectDir === undefined) { - throw new Error('KLO_PROJECT_DIR must not be empty'); + throw new Error('KTX_PROJECT_DIR must not be empty'); } if (envProjectDir !== undefined) { return resolve(cwd, envProjectDir); } const resolvedCwd = resolve(cwd); - return findNearestKloProjectDir(resolvedCwd) ?? resolvedCwd; + return findNearestKtxProjectDir(resolvedCwd) ?? resolvedCwd; } diff --git a/packages/cli/src/prompt-navigation.test.ts b/packages/cli/src/prompt-navigation.test.ts index 8fbfc7fe..4dd428df 100644 --- a/packages/cli/src/prompt-navigation.test.ts +++ b/packages/cli/src/prompt-navigation.test.ts @@ -7,8 +7,8 @@ describe('prompt navigation helpers', () => { }); it('adds a blank separator between multiline menu copy and the option list', () => { - expect(withMenuOptionSpacing('Which embedding option should KLO use?\n\nKLO uses embeddings for search.')).toBe( - 'Which embedding option should KLO use?\n\nKLO uses embeddings for search.\n', + expect(withMenuOptionSpacing('Which embedding option should KTX use?\n\nKTX uses embeddings for search.')).toBe( + 'Which embedding option should KTX use?\n\nKTX uses embeddings for search.\n', ); }); @@ -25,10 +25,10 @@ describe('prompt navigation helpers', () => { it('adds a blank separator between text input helper copy and the editable value', () => { expect( withTextInputNavigation( - 'Name this PostgreSQL connection\nKLO will use this short name in commands and config. You can rename it now.', + 'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.', ), ).toBe( - 'Name this PostgreSQL connection\n\nKLO will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n', + 'Name this PostgreSQL connection\n\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n', ); }); @@ -39,10 +39,10 @@ describe('prompt navigation helpers', () => { it('normalizes already hinted text input prompts without duplicating the hint', () => { expect( withTextInputNavigation( - 'Name this PostgreSQL connection\nKLO will use this short name in commands and config. You can rename it now.\nPress Escape to go back.', + 'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.', ), ).toBe( - 'Name this PostgreSQL connection\n\nKLO will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n', + 'Name this PostgreSQL connection\n\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n', ); }); }); diff --git a/packages/cli/src/public-ingest.test.ts b/packages/cli/src/public-ingest.test.ts index 09aa4ad7..e00b11da 100644 --- a/packages/cli/src/public-ingest.test.ts +++ b/packages/cli/src/public-ingest.test.ts @@ -1,6 +1,6 @@ -import { buildDefaultKloProjectConfig, type KloProjectConfig } from '@klo/context/project'; +import { buildDefaultKtxProjectConfig, type KtxProjectConfig } from '@ktx/context/project'; import { describe, expect, it, vi } from 'vitest'; -import { buildPublicIngestPlan, type KloPublicIngestProject, runKloPublicIngest } from './public-ingest.js'; +import { buildPublicIngestPlan, type KtxPublicIngestProject, runKtxPublicIngest } from './public-ingest.js'; function makeIo(options: { isTTY?: boolean } = {}) { let stdout = ''; @@ -24,11 +24,11 @@ function makeIo(options: { isTTY?: boolean } = {}) { }; } -function projectWithConnections(connections: KloProjectConfig['connections']): KloPublicIngestProject { +function projectWithConnections(connections: KtxProjectConfig['connections']): KtxPublicIngestProject { return { projectDir: '/tmp/project', config: { - ...buildDefaultKloProjectConfig('warehouse'), + ...buildDefaultKtxProjectConfig('warehouse'), connections, }, }; @@ -49,7 +49,7 @@ describe('buildPublicIngestPlan', () => { connectionId: 'warehouse', driver: 'postgres', operation: 'scan', - debugCommand: 'klo scan warehouse --debug', + debugCommand: 'ktx scan warehouse --debug', steps: ['scan'], }, { @@ -57,7 +57,7 @@ describe('buildPublicIngestPlan', () => { driver: 'notion', operation: 'source-ingest', adapter: 'notion', - debugCommand: 'klo dev ingest run --connection-id docs --adapter notion --debug', + debugCommand: 'ktx dev ingest run --connection-id docs --adapter notion --debug', steps: ['source-ingest', 'memory-update'], }, { @@ -65,7 +65,7 @@ describe('buildPublicIngestPlan', () => { driver: 'metabase', operation: 'source-ingest', adapter: 'metabase', - debugCommand: 'klo dev ingest run --connection-id prod_metabase --adapter metabase --debug', + debugCommand: 'ktx dev ingest run --connection-id prod_metabase --adapter metabase --debug', steps: ['source-ingest', 'memory-update'], }, ], @@ -76,7 +76,7 @@ describe('buildPublicIngestPlan', () => { const project = projectWithConnections({ warehouse: { driver: 'postgres' } }); expect(() => buildPublicIngestPlan(project, { projectDir: '/tmp/project', all: false })).toThrow( - 'klo ingest requires or --all in this release', + 'ktx ingest requires or --all in this release', ); }); @@ -89,7 +89,7 @@ describe('buildPublicIngestPlan', () => { }); }); -describe('runKloPublicIngest', () => { +describe('runKtxPublicIngest', () => { it('runs all independent targets and reports partial failures', async () => { const io = makeIo(); const project = projectWithConnections({ @@ -100,7 +100,7 @@ describe('runKloPublicIngest', () => { const runIngest = vi.fn(async () => 0); await expect( - runKloPublicIngest( + runKtxPublicIngest( { command: 'run', projectDir: '/tmp/project', all: true, json: false, inputMode: 'disabled' }, io.io, { @@ -135,7 +135,7 @@ describe('runKloPublicIngest', () => { ); expect(io.stdout()).toContain('Ingest finished with partial failures'); expect(io.stdout()).toContain('warehouse failed at scan.'); - expect(io.stdout()).toContain('Debug: klo scan warehouse --debug'); + expect(io.stdout()).toContain('Debug: ktx scan warehouse --debug'); }); it('can request enriched relationship scans for setup-managed context builds', async () => { @@ -144,7 +144,7 @@ describe('runKloPublicIngest', () => { const runScan = vi.fn(async () => 0); await expect( - runKloPublicIngest( + runKtxPublicIngest( { command: 'run', projectDir: '/tmp/project', @@ -180,7 +180,7 @@ describe('runKloPublicIngest', () => { const project = projectWithConnections({ warehouse: { driver: 'postgres' } }); await expect( - runKloPublicIngest( + runKtxPublicIngest( { command: 'run', projectDir: '/tmp/project', @@ -203,15 +203,15 @@ describe('runKloPublicIngest', () => { }); }); - it('passes dbt source_dir from connection config to runKloIngest', async () => { + it('passes dbt source_dir from connection config to runKtxIngest', async () => { const runIngest = vi.fn(async () => 0); const io = makeIo(); await expect( - runKloPublicIngest( + runKtxPublicIngest( { command: 'run', - projectDir: '/tmp/klo', + projectDir: '/tmp/ktx', targetConnectionId: 'analytics_dbt', all: false, json: false, @@ -221,7 +221,7 @@ describe('runKloPublicIngest', () => { { loadProject: async () => ({ - projectDir: '/tmp/klo', + projectDir: '/tmp/ktx', config: { connections: { analytics_dbt: { @@ -253,15 +253,15 @@ describe('runKloPublicIngest', () => { const watchIo = makeIo(); await expect( - runKloPublicIngest( - { command: 'status', projectDir: '/tmp/klo', json: false, inputMode: 'disabled' }, + runKtxPublicIngest( + { command: 'status', projectDir: '/tmp/ktx', json: false, inputMode: 'disabled' }, statusIo.io, { runIngest }, ), ).resolves.toBe(0); await expect( - runKloPublicIngest( - { command: 'watch', projectDir: '/tmp/klo', runId: 'run-1', json: false, inputMode: 'auto' }, + runKtxPublicIngest( + { command: 'watch', projectDir: '/tmp/ktx', runId: 'run-1', json: false, inputMode: 'auto' }, watchIo.io, { runIngest }, ), @@ -271,7 +271,7 @@ describe('runKloPublicIngest', () => { 1, { command: 'status', - projectDir: '/tmp/klo', + projectDir: '/tmp/ktx', outputMode: 'plain', inputMode: 'disabled', }, @@ -281,7 +281,7 @@ describe('runKloPublicIngest', () => { 2, { command: 'watch', - projectDir: '/tmp/klo', + projectDir: '/tmp/ktx', runId: 'run-1', outputMode: 'viz', inputMode: 'auto', diff --git a/packages/cli/src/public-ingest.ts b/packages/cli/src/public-ingest.ts index d3aa6bc3..3c0ede75 100644 --- a/packages/cli/src/public-ingest.ts +++ b/packages/cli/src/public-ingest.ts @@ -1,24 +1,24 @@ -import { type KloLocalProject, type KloProjectConnectionConfig, loadKloProject } from '@klo/context/project'; -import type { KloCliIo } from './index.js'; -import type { KloIngestArgs } from './ingest.js'; -import type { KloScanArgs } from './scan.js'; +import { type KtxLocalProject, type KtxProjectConnectionConfig, loadKtxProject } from '@ktx/context/project'; +import type { KtxCliIo } from './index.js'; +import type { KtxIngestArgs } from './ingest.js'; +import type { KtxScanArgs } from './scan.js'; import { profileMark } from './startup-profile.js'; profileMark('module:public-ingest'); -export type KloPublicIngestStepName = 'scan' | 'source-ingest' | 'enrich' | 'memory-update'; -export type KloPublicIngestStepStatus = 'done' | 'skipped' | 'failed' | 'not-run'; -export type KloPublicIngestInputMode = 'auto' | 'disabled'; +export type KtxPublicIngestStepName = 'scan' | 'source-ingest' | 'enrich' | 'memory-update'; +export type KtxPublicIngestStepStatus = 'done' | 'skipped' | 'failed' | 'not-run'; +export type KtxPublicIngestInputMode = 'auto' | 'disabled'; -export type KloPublicIngestArgs = +export type KtxPublicIngestArgs = | { command: 'run'; projectDir: string; targetConnectionId?: string; all: boolean; json: boolean; - inputMode: KloPublicIngestInputMode; - scanMode?: Extract['mode']; + inputMode: KtxPublicIngestInputMode; + scanMode?: Extract['mode']; detectRelationships?: boolean; } | { @@ -26,41 +26,41 @@ export type KloPublicIngestArgs = projectDir: string; runId?: string; json: boolean; - inputMode: KloPublicIngestInputMode; + inputMode: KtxPublicIngestInputMode; }; -export interface KloPublicIngestPlanTarget { +export interface KtxPublicIngestPlanTarget { connectionId: string; driver: string; operation: 'scan' | 'source-ingest'; adapter?: string; sourceDir?: string; debugCommand: string; - steps: KloPublicIngestStepName[]; + steps: KtxPublicIngestStepName[]; } -export interface KloPublicIngestPlan { +export interface KtxPublicIngestPlan { projectDir: string; - targets: KloPublicIngestPlanTarget[]; + targets: KtxPublicIngestPlanTarget[]; } -export interface KloPublicIngestTargetResult { +export interface KtxPublicIngestTargetResult { connectionId: string; driver: string; steps: Array<{ - operation: KloPublicIngestStepName; - status: KloPublicIngestStepStatus; + operation: KtxPublicIngestStepName; + status: KtxPublicIngestStepStatus; detail?: string; debugCommand?: string; }>; } -export type KloPublicIngestProject = Pick; +export type KtxPublicIngestProject = Pick; -export interface KloPublicIngestDeps { - loadProject?: (options: Parameters[0]) => Promise; - runScan?: (args: KloScanArgs, io: KloCliIo) => Promise; - runIngest?: (args: KloIngestArgs, io: KloCliIo) => Promise; +export interface KtxPublicIngestDeps { + loadProject?: (options: Parameters[0]) => Promise; + runScan?: (args: KtxScanArgs, io: KtxCliIo) => Promise; + runIngest?: (args: KtxIngestArgs, io: KtxCliIo) => Promise; } const sourceAdapterByDriver = new Map([ @@ -85,18 +85,18 @@ const warehouseDrivers = new Set([ 'snowflake', ]); -function normalizedDriver(connection: KloProjectConnectionConfig): string { +function normalizedDriver(connection: KtxProjectConnectionConfig): string { return String(connection.driver ?? '') .trim() .toLowerCase(); } -function sourceDirForConnection(connection: KloProjectConnectionConfig): string | undefined { +function sourceDirForConnection(connection: KtxProjectConnectionConfig): string | undefined { const value = connection.source_dir ?? connection.sourceDir; return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined; } -function targetForConnection(connectionId: string, connection: KloProjectConnectionConfig): KloPublicIngestPlanTarget { +function targetForConnection(connectionId: string, connection: KtxProjectConnectionConfig): KtxPublicIngestPlanTarget { const driver = normalizedDriver(connection); const adapter = sourceAdapterByDriver.get(driver); const sourceDir = sourceDirForConnection(connection); @@ -107,7 +107,7 @@ function targetForConnection(connectionId: string, connection: KloProjectConnect operation: 'source-ingest', adapter, ...(sourceDir ? { sourceDir } : {}), - debugCommand: `klo dev ingest run --connection-id ${connectionId} --adapter ${adapter} --debug`, + debugCommand: `ktx dev ingest run --connection-id ${connectionId} --adapter ${adapter} --debug`, steps: ['source-ingest', 'memory-update'], }; } @@ -117,7 +117,7 @@ function targetForConnection(connectionId: string, connection: KloProjectConnect connectionId, driver, operation: 'scan', - debugCommand: `klo scan ${connectionId} --debug`, + debugCommand: `ktx scan ${connectionId} --debug`, steps: ['scan'], }; } @@ -126,18 +126,18 @@ function targetForConnection(connectionId: string, connection: KloProjectConnect } export function buildPublicIngestPlan( - project: KloPublicIngestProject, + project: KtxPublicIngestProject, args: { projectDir: string; targetConnectionId?: string; all: boolean }, -): KloPublicIngestPlan { +): KtxPublicIngestPlan { if (!args.all && !args.targetConnectionId) { - throw new Error('klo ingest requires or --all in this release'); + throw new Error('ktx ingest requires or --all in this release'); } const entries = Object.entries(project.config.connections).sort(([a], [b]) => a.localeCompare(b)); const selected = args.all ? entries : entries.filter(([connectionId]) => connectionId === args.targetConnectionId); if (!args.all && selected.length === 0) { - throw new Error(`Connection "${args.targetConnectionId}" is not configured in klo.yaml`); + throw new Error(`Connection "${args.targetConnectionId}" is not configured in ktx.yaml`); } if (selected.length === 0) { throw new Error('No configured connections are eligible for ingest'); @@ -150,7 +150,7 @@ export function buildPublicIngestPlan( }; } -function defaultSteps(target: KloPublicIngestPlanTarget): KloPublicIngestTargetResult['steps'] { +function defaultSteps(target: KtxPublicIngestPlanTarget): KtxPublicIngestTargetResult['steps'] { return [ { operation: 'scan', @@ -171,7 +171,7 @@ function defaultSteps(target: KloPublicIngestPlanTarget): KloPublicIngestTargetR ]; } -function markTargetResult(target: KloPublicIngestPlanTarget, status: 'done' | 'failed'): KloPublicIngestTargetResult { +function markTargetResult(target: KtxPublicIngestPlanTarget, status: 'done' | 'failed'): KtxPublicIngestTargetResult { const failedOperation = target.operation === 'scan' ? 'scan' : 'source-ingest'; return { connectionId: target.connectionId, @@ -191,15 +191,15 @@ function markTargetResult(target: KloPublicIngestPlanTarget, status: 'done' | 'f }; } -function resultFailed(result: KloPublicIngestTargetResult): boolean { +function resultFailed(result: KtxPublicIngestTargetResult): boolean { return result.steps.some((step) => step.status === 'failed'); } -function stepStatus(result: KloPublicIngestTargetResult, operation: KloPublicIngestStepName): string { +function stepStatus(result: KtxPublicIngestTargetResult, operation: KtxPublicIngestStepName): string { return result.steps.find((step) => step.operation === operation)?.status ?? 'not-run'; } -function renderPlainResults(results: KloPublicIngestTargetResult[], io: KloCliIo): void { +function renderPlainResults(results: KtxPublicIngestTargetResult[], io: KtxCliIo): void { const failures = results.filter(resultFailed); io.stdout.write(failures.length > 0 ? 'Ingest finished with partial failures\n' : 'Ingest finished\n'); io.stdout.write('\n'); @@ -230,24 +230,24 @@ function renderPlainResults(results: KloPublicIngestTargetResult[], io: KloCliIo } } -function hasInteractiveInput(io: KloCliIo): boolean { +function hasInteractiveInput(io: KtxCliIo): boolean { const stdin = (io as { stdin?: { isTTY?: boolean; setRawMode?: (value: boolean) => void } }).stdin; return stdin?.isTTY === true && typeof stdin.setRawMode === 'function'; } -function sourceIngestOutputMode(args: Extract, io: KloCliIo): 'plain' | 'viz' { +function sourceIngestOutputMode(args: Extract, io: KtxCliIo): 'plain' | 'viz' { return args.inputMode === 'auto' && io.stdout.isTTY === true && hasInteractiveInput(io) ? 'viz' : 'plain'; } export async function executePublicIngestTarget( - target: KloPublicIngestPlanTarget, - args: Extract, - io: KloCliIo, - deps: KloPublicIngestDeps, -): Promise { + target: KtxPublicIngestPlanTarget, + args: Extract, + io: KtxCliIo, + deps: KtxPublicIngestDeps, +): Promise { if (target.operation === 'scan') { - const { runKloScan } = await import('./scan.js'); - const exitCode = await (deps.runScan ?? runKloScan)( + const { runKtxScan } = await import('./scan.js'); + const exitCode = await (deps.runScan ?? runKtxScan)( { command: 'run', projectDir: args.projectDir, @@ -261,8 +261,8 @@ export async function executePublicIngestTarget( return markTargetResult(target, exitCode === 0 ? 'done' : 'failed'); } - const { runKloIngest } = await import('./ingest.js'); - const exitCode = await (deps.runIngest ?? runKloIngest)( + const { runKtxIngest } = await import('./ingest.js'); + const exitCode = await (deps.runIngest ?? runKtxIngest)( { command: 'run', projectDir: args.projectDir, @@ -277,14 +277,14 @@ export async function executePublicIngestTarget( return markTargetResult(target, exitCode === 0 ? 'done' : 'failed'); } -export async function runKloPublicIngest( - args: KloPublicIngestArgs, - io: KloCliIo, - deps: KloPublicIngestDeps = {}, +export async function runKtxPublicIngest( + args: KtxPublicIngestArgs, + io: KtxCliIo, + deps: KtxPublicIngestDeps = {}, ): Promise { if (args.command !== 'run') { - const { runKloIngest } = await import('./ingest.js'); - return await (deps.runIngest ?? runKloIngest)( + const { runKtxIngest } = await import('./ingest.js'); + return await (deps.runIngest ?? runKtxIngest)( { command: args.command, projectDir: args.projectDir, @@ -296,10 +296,10 @@ export async function runKloPublicIngest( ); } - const loadProject = deps.loadProject ?? loadKloProject; + const loadProject = deps.loadProject ?? loadKtxProject; const project = await loadProject({ projectDir: args.projectDir }); const plan = buildPublicIngestPlan(project, args); - const results: KloPublicIngestTargetResult[] = []; + const results: KtxPublicIngestTargetResult[] = []; for (const target of plan.targets) { results.push(await executePublicIngestTarget(target, args, io, deps)); diff --git a/packages/cli/src/scan.test.ts b/packages/cli/src/scan.test.ts index 256f98ed..603a0091 100644 --- a/packages/cli/src/scan.test.ts +++ b/packages/cli/src/scan.test.ts @@ -1,21 +1,21 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject } from '@klo/context/project'; +import { initKtxProject } from '@ktx/context/project'; import type { ApplyLocalScanRelationshipReviewDecisionsResult, ExportLocalRelationshipFeedbackLabelsResult, - KloRelationshipFeedbackCalibrationReport, - KloRelationshipThresholdAdviceReport, - KloScanReport, + KtxRelationshipFeedbackCalibrationReport, + KtxRelationshipThresholdAdviceReport, + KtxScanReport, LocalScanRunResult, LocalScanStatusResponse, ReadLocalScanRelationshipArtifactsResult, RunLocalScanOptions, WriteLocalScanRelationshipReviewDecisionResult, -} from '@klo/context/scan'; +} from '@ktx/context/scan'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { createCliScanProgress, runKloScan } from './scan.js'; +import { createCliScanProgress, runKtxScan } from './scan.js'; const sqlServerExtractSchema = vi.hoisted(() => vi.fn(async (connectionId: string) => ({ @@ -36,10 +36,10 @@ const sqlServerExtractSchema = vi.hoisted(() => const createSqlServerLiveDatabaseIntrospection = vi.hoisted(() => vi.fn(() => ({ extractSchema: sqlServerExtractSchema })), ); -const isKloSqlServerConnectionConfig = vi.hoisted(() => +const isKtxSqlServerConnectionConfig = vi.hoisted(() => vi.fn((connection: { driver?: string } | undefined) => connection?.driver === 'sqlserver'), ); -const KloSqlServerScanConnector = vi.hoisted( +const KtxSqlServerScanConnector = vi.hoisted( () => class { readonly id: string; @@ -69,10 +69,10 @@ const bigQueryExtractSchema = vi.hoisted(() => const createBigQueryLiveDatabaseIntrospection = vi.hoisted(() => vi.fn(() => ({ extractSchema: bigQueryExtractSchema })), ); -const isKloBigQueryConnectionConfig = vi.hoisted(() => +const isKtxBigQueryConnectionConfig = vi.hoisted(() => vi.fn((connection: { driver?: string } | undefined) => connection?.driver === 'bigquery'), ); -const KloBigQueryScanConnector = vi.hoisted( +const KtxBigQueryScanConnector = vi.hoisted( () => class { readonly id: string; @@ -102,10 +102,10 @@ const snowflakeExtractSchema = vi.hoisted(() => const createSnowflakeLiveDatabaseIntrospection = vi.hoisted(() => vi.fn(() => ({ extractSchema: snowflakeExtractSchema })), ); -const isKloSnowflakeConnectionConfig = vi.hoisted(() => +const isKtxSnowflakeConnectionConfig = vi.hoisted(() => vi.fn((connection: { driver?: string } | undefined) => connection?.driver === 'snowflake'), ); -const KloSnowflakeScanConnector = vi.hoisted( +const KtxSnowflakeScanConnector = vi.hoisted( () => class { readonly id: string; @@ -127,12 +127,12 @@ const postgresExtractSchema = vi.hoisted(() => const createPostgresLiveDatabaseIntrospection = vi.hoisted(() => vi.fn(() => ({ extractSchema: postgresExtractSchema })), ); -const isKloPostgresConnectionConfig = vi.hoisted(() => +const isKtxPostgresConnectionConfig = vi.hoisted(() => vi.fn((connection: { driver?: string } | undefined) => ['postgres', 'postgresql'].includes(String(connection?.driver ?? '').toLowerCase()), ), ); -const KloPostgresScanConnector = vi.hoisted( +const KtxPostgresScanConnector = vi.hoisted( () => class { readonly id: string; @@ -144,28 +144,28 @@ const KloPostgresScanConnector = vi.hoisted( }, ); -vi.mock('@klo/connector-sqlserver', () => ({ +vi.mock('@ktx/connector-sqlserver', () => ({ createSqlServerLiveDatabaseIntrospection, - isKloSqlServerConnectionConfig, - KloSqlServerScanConnector, + isKtxSqlServerConnectionConfig, + KtxSqlServerScanConnector, })); -vi.mock('@klo/connector-bigquery', () => ({ +vi.mock('@ktx/connector-bigquery', () => ({ createBigQueryLiveDatabaseIntrospection, - isKloBigQueryConnectionConfig, - KloBigQueryScanConnector, + isKtxBigQueryConnectionConfig, + KtxBigQueryScanConnector, })); -vi.mock('@klo/connector-snowflake', () => ({ +vi.mock('@ktx/connector-snowflake', () => ({ createSnowflakeLiveDatabaseIntrospection, - isKloSnowflakeConnectionConfig, - KloSnowflakeScanConnector, + isKtxSnowflakeConnectionConfig, + KtxSnowflakeScanConnector, })); -vi.mock('@klo/connector-postgres', () => ({ +vi.mock('@ktx/connector-postgres', () => ({ createPostgresLiveDatabaseIntrospection, - isKloPostgresConnectionConfig, - KloPostgresScanConnector, + isKtxPostgresConnectionConfig, + KtxPostgresScanConnector, })); function makeIo(options: { isTTY?: boolean } = {}) { @@ -190,7 +190,7 @@ function makeIo(options: { isTTY?: boolean } = {}) { }; } -const report: KloScanReport = { +const report: KtxScanReport = { connectionId: 'warehouse', driver: 'postgres', syncId: 'sync-1', @@ -242,7 +242,7 @@ const report: KloScanReport = { createdAt: '2026-04-29T09:00:00.000Z', }; -const reportWithAttention: KloScanReport = { +const reportWithAttention: KtxScanReport = { ...report, mode: 'relationships', diffSummary: { @@ -258,7 +258,7 @@ const reportWithAttention: KloScanReport = { warnings: [ { code: 'connector_capability_missing', - message: 'KLO scan connector is missing optional capability: columnStats', + message: 'KTX scan connector is missing optional capability: columnStats', recoverable: true, metadata: { capability: 'columnStats' }, }, @@ -283,11 +283,11 @@ const reportWithAttention: KloScanReport = { }, }; -describe('runKloScan', () => { +describe('runKtxScan', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-scan-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-scan-')); }); afterEach(async () => { @@ -295,7 +295,7 @@ describe('runKloScan', () => { }); it('runs structural scans and prints a dev-friendly plain summary', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn( async (_input: RunLocalScanOptions): Promise => ({ runId: 'scan-run-1', @@ -311,7 +311,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -334,7 +334,7 @@ describe('runKloScan', () => { connector: undefined, }), ); - expect(io.stdout()).toContain('KLO scan completed\n'); + expect(io.stdout()).toContain('KTX scan completed\n'); expect(io.stdout()).toContain('Run: scan-run-1'); expect(io.stdout()).toContain('Mode: structural'); expect(io.stdout()).toContain('What changed\n'); @@ -346,9 +346,9 @@ describe('runKloScan', () => { expect(io.stdout()).toContain('Artifacts\n'); expect(io.stdout()).toContain('Report: raw-sources/warehouse/live-database/sync-1/scan-report.json'); expect(io.stdout()).toContain('Next:\n'); - expect(io.stdout()).toContain('klo dev scan status --project-dir '); + expect(io.stdout()).toContain('ktx dev scan status --project-dir '); expect(io.stdout()).toContain(' scan-run-1\n'); - expect(io.stdout()).toContain('klo dev scan report --project-dir '); + expect(io.stdout()).toContain('ktx dev scan report --project-dir '); expect(io.stdout()).toContain(' scan-run-1\n'); expect(io.stdout()).not.toContain('\u001b['); expect(io.stdout()).not.toContain('✓'); @@ -357,7 +357,7 @@ describe('runKloScan', () => { }); it('explains warnings, capability gaps, and relationships in human scan summaries', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn( async (_input: RunLocalScanOptions): Promise => ({ runId: 'scan-run-1', @@ -373,7 +373,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -408,14 +408,14 @@ describe('runKloScan', () => { }); it('prints review-only relationship summaries and validation capability warnings', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const reviewOnlyReport: KloScanReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const reviewOnlyReport: KtxScanReport = { ...reportWithAttention, capabilityGaps: [], warnings: [ { code: 'connector_capability_missing', - message: 'KLO scan connector cannot run read-only SQL relationship validation', + message: 'KTX scan connector cannot run read-only SQL relationship validation', recoverable: true, metadata: { capability: 'readOnlySql' }, }, @@ -437,7 +437,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -456,12 +456,12 @@ describe('runKloScan', () => { expect(io.stdout()).toContain('Review: 12'); expect(io.stdout()).toContain('Rejected: 44'); expect(io.stdout()).toContain( - 'connector_capability_missing: KLO scan connector cannot run read-only SQL relationship validation', + 'connector_capability_missing: KTX scan connector cannot run read-only SQL relationship validation', ); }); it('passes a scan progress port and prints TTY progress messages', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn(async (input: RunLocalScanOptions): Promise => { await input.progress?.update(0.15, 'Inspecting database schema'); await input.progress?.update(0.55, 'Semantic layer comparison found 5 changes across 18 tables'); @@ -481,7 +481,7 @@ describe('runKloScan', () => { delete process.env.CI; try { - const exitCode = await runKloScan( + const exitCode = await runKtxScan( { command: 'run', projectDir: tempDir, @@ -531,7 +531,7 @@ describe('runKloScan', () => { }); it('flushes transient TTY progress messages before printing scan failures', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn(async (input: RunLocalScanOptions): Promise => { await input.progress?.update(0.42, 'Generating descriptions 3/35 tables', { transient: true }); throw new Error('scan failed'); @@ -542,7 +542,7 @@ describe('runKloScan', () => { try { await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -568,7 +568,7 @@ describe('runKloScan', () => { }); it('does not print live progress messages for non-TTY output', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn(async (input: RunLocalScanOptions): Promise => { await input.progress?.update(0.15, 'Inspecting database schema'); return { @@ -585,7 +585,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -604,7 +604,7 @@ describe('runKloScan', () => { }); it('uses terminal-aware visual styling only for TTY output', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn( async (_input: RunLocalScanOptions): Promise => ({ runId: 'scan-run-1', @@ -627,7 +627,7 @@ describe('runKloScan', () => { try { await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -659,12 +659,12 @@ describe('runKloScan', () => { } expect(io.stdout()).toContain('✓'); - expect(io.stdout()).toContain('KLO scan completed'); + expect(io.stdout()).toContain('KTX scan completed'); expect(io.stdout()).toContain('\u001b['); }); it('honors NO_COLOR for TTY scan summaries', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const runLocalScan = vi.fn( async (_input: RunLocalScanOptions): Promise => ({ runId: 'scan-run-1', @@ -683,7 +683,7 @@ describe('runKloScan', () => { try { await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempDir, @@ -704,12 +704,12 @@ describe('runKloScan', () => { } } - expect(io.stdout()).toContain('KLO scan completed'); + expect(io.stdout()).toContain('KTX scan completed'); expect(io.stdout()).not.toContain('\u001b['); }); it('prints status and human report output by default', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const status: LocalScanStatusResponse = { runId: 'scan-run-1', status: 'done', @@ -727,7 +727,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan({ command: 'status', projectDir: tempDir, runId: 'scan-run-1' }, io.io, { + runKtxScan({ command: 'status', projectDir: tempDir, runId: 'scan-run-1' }, io.io, { getLocalScanStatus: vi.fn().mockResolvedValue(status), }), ).resolves.toBe(0); @@ -736,22 +736,22 @@ describe('runKloScan', () => { const reportIo = makeIo(); await expect( - runKloScan({ command: 'report', projectDir: tempDir, runId: 'scan-run-1', json: false }, reportIo.io, { + runKtxScan({ command: 'report', projectDir: tempDir, runId: 'scan-run-1', json: false }, reportIo.io, { getLocalScanReport: vi.fn().mockResolvedValue(report), }), ).resolves.toBe(0); - expect(reportIo.stdout()).toContain('KLO scan report\n'); + expect(reportIo.stdout()).toContain('KTX scan report\n'); expect(reportIo.stdout()).toContain('Run: scan-run-1'); expect(reportIo.stdout()).toContain('What changed\n'); expect(() => JSON.parse(reportIo.stdout())).toThrow(); }); it('prints raw report JSON when requested', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const reportIo = makeIo(); await expect( - runKloScan({ command: 'report', projectDir: tempDir, runId: 'scan-run-1', json: true }, reportIo.io, { + runKtxScan({ command: 'report', projectDir: tempDir, runId: 'scan-run-1', json: true }, reportIo.io, { getLocalScanReport: vi.fn().mockResolvedValue(report), }), ).resolves.toBe(0); @@ -760,8 +760,8 @@ describe('runKloScan', () => { }); it('prints review relationship artifacts in human form', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const reviewReport: KloScanReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const reviewReport: KtxScanReport = { ...reportWithAttention, runId: 'scan-run-review', syncId: 'sync-review', @@ -866,7 +866,7 @@ describe('runKloScan', () => { tables: [], columns: {}, queryCount: 0, - warnings: ['KLO scan connector cannot run read-only SQL relationship validation'], + warnings: ['KTX scan connector cannot run read-only SQL relationship validation'], }, paths: { relationships: 'raw-sources/warehouse/live-database/sync-review/enrichment/relationships.json', @@ -878,7 +878,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationships', projectDir: tempDir, @@ -897,7 +897,7 @@ describe('runKloScan', () => { 'scan-run-review', ); - expect(io.stdout()).toContain('KLO relationship artifacts'); + expect(io.stdout()).toContain('KTX relationship artifacts'); expect(io.stdout()).toContain('Run: scan-run-review'); expect(io.stdout()).toContain('Summary: accepted=0 review=1 rejected=1 skipped=0'); expect(io.stdout()).toContain('Reason: relationship candidates require review before manifest writes'); @@ -911,8 +911,8 @@ describe('runKloScan', () => { }); it('prints filtered relationship artifacts as JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const jsonReport: KloScanReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const jsonReport: KtxScanReport = { ...reportWithAttention, runId: 'scan-run-json', syncId: 'sync-json', @@ -946,7 +946,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationships', projectDir: tempDir, @@ -974,7 +974,7 @@ describe('runKloScan', () => { }); it('records an accepted relationship review decision in human form', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const decisionResult: WriteLocalScanRelationshipReviewDecisionResult = { path: 'raw-sources/warehouse/live-database/sync-review/enrichment/relationship-review-decisions.json', decision: { @@ -1019,7 +1019,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipDecision', projectDir: tempDir, @@ -1055,7 +1055,7 @@ describe('runKloScan', () => { }); it('records a rejected relationship review decision as JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const decisionResult: WriteLocalScanRelationshipReviewDecisionResult = { path: 'raw-sources/warehouse/live-database/sync-review/enrichment/relationship-review-decisions.json', decision: { @@ -1100,14 +1100,14 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipDecision', projectDir: tempDir, runId: 'scan-run-review', candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'rejected', - reviewer: 'klo', + reviewer: 'ktx', note: null, json: true, }, @@ -1127,19 +1127,19 @@ describe('runKloScan', () => { }); it('reports missing scan runs when recording relationship decisions', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const writeLocalScanRelationshipReviewDecision = vi.fn(async () => null); const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipDecision', projectDir: tempDir, runId: 'missing-run', candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'accepted', - reviewer: 'klo', + reviewer: 'ktx', note: null, json: false, }, @@ -1152,7 +1152,7 @@ describe('runKloScan', () => { }); it('applies accepted relationship review decisions with human output', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const applyLocalScanRelationshipReviewDecisions = vi.fn( async (): Promise => ({ runId: 'scan-run-a', @@ -1190,7 +1190,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipApply', projectDir: tempDir, @@ -1222,7 +1222,7 @@ describe('runKloScan', () => { }); it('prints relationship review apply JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const applyResult: ApplyLocalScanRelationshipReviewDecisionsResult = { runId: 'scan-run-a', connectionId: 'warehouse', @@ -1239,7 +1239,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipApply', projectDir: tempDir, @@ -1267,7 +1267,7 @@ describe('runKloScan', () => { }); it('prints relationship feedback export summary in human form', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const feedback: ExportLocalRelationshipFeedbackLabelsResult = { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: null, decision: 'all' }, @@ -1328,7 +1328,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipFeedback', projectDir: tempDir, @@ -1349,7 +1349,7 @@ describe('runKloScan', () => { decision: 'all', }, ); - expect(io.stdout()).toContain('KLO relationship feedback labels'); + expect(io.stdout()).toContain('KTX relationship feedback labels'); expect(io.stdout()).toContain('Total: 2'); expect(io.stdout()).toContain('Accepted: 1'); expect(io.stdout()).toContain('Rejected: 1'); @@ -1358,7 +1358,7 @@ describe('runKloScan', () => { }); it('prints relationship feedback labels as JSONL', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const feedback: ExportLocalRelationshipFeedbackLabelsResult = { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: 'warehouse', decision: 'accepted' }, @@ -1373,7 +1373,7 @@ describe('runKloScan', () => { runId: 'scan-run-review', syncId: 'sync-review', decidedAt: '2026-05-07T12:00:00.000Z', - reviewer: 'klo', + reviewer: 'ktx', note: null, relationshipType: 'many_to_one', source: 'deterministic_name', @@ -1392,13 +1392,13 @@ describe('runKloScan', () => { warnings: [], }; const exportLocalRelationshipFeedbackLabels = vi.fn(async () => feedback); - const formatKloRelationshipFeedbackLabelsJsonl = vi.fn( + const formatKtxRelationshipFeedbackLabelsJsonl = vi.fn( () => '{"candidateId":"orders:orders.customer_id->customers:customers.id"}\n', ); const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipFeedback', projectDir: tempDir, @@ -1408,7 +1408,7 @@ describe('runKloScan', () => { jsonl: true, }, io.io, - { exportLocalRelationshipFeedbackLabels, formatKloRelationshipFeedbackLabelsJsonl }, + { exportLocalRelationshipFeedbackLabels, formatKtxRelationshipFeedbackLabelsJsonl }, ), ).resolves.toBe(0); @@ -1419,12 +1419,12 @@ describe('runKloScan', () => { decision: 'accepted', }, ); - expect(formatKloRelationshipFeedbackLabelsJsonl).toHaveBeenCalledWith(feedback); + expect(formatKtxRelationshipFeedbackLabelsJsonl).toHaveBeenCalledWith(feedback); expect(JSON.parse(io.stdout())).toEqual({ candidateId: 'orders:orders.customer_id->customers:customers.id' }); }); it('prints relationship feedback export as JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const feedback: ExportLocalRelationshipFeedbackLabelsResult = { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: null, decision: 'rejected' }, @@ -1436,7 +1436,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipFeedback', projectDir: tempDir, @@ -1458,8 +1458,8 @@ describe('runKloScan', () => { }); it('prints relationship feedback calibration as human output', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const calibration: KloRelationshipFeedbackCalibrationReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const calibration: KtxRelationshipFeedbackCalibrationReport = { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: null, decision: 'all' }, thresholds: { accept: 0.85, review: 0.55 }, @@ -1520,13 +1520,13 @@ describe('runKloScan', () => { warnings: [], }; const calibrateLocalRelationshipFeedbackLabels = vi.fn(async () => calibration); - const formatKloRelationshipFeedbackCalibrationMarkdown = vi.fn( - () => 'KLO relationship feedback calibration\nTotal labels: 2\n', + const formatKtxRelationshipFeedbackCalibrationMarkdown = vi.fn( + () => 'KTX relationship feedback calibration\nTotal labels: 2\n', ); const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipCalibration', projectDir: tempDir, @@ -1537,7 +1537,7 @@ describe('runKloScan', () => { json: false, }, io.io, - { calibrateLocalRelationshipFeedbackLabels, formatKloRelationshipFeedbackCalibrationMarkdown }, + { calibrateLocalRelationshipFeedbackLabels, formatKtxRelationshipFeedbackCalibrationMarkdown }, ), ).resolves.toBe(0); @@ -1550,13 +1550,13 @@ describe('runKloScan', () => { reviewThreshold: 0.55, }, ); - expect(formatKloRelationshipFeedbackCalibrationMarkdown).toHaveBeenCalledWith(calibration); - expect(io.stdout()).toBe('KLO relationship feedback calibration\nTotal labels: 2\n'); + expect(formatKtxRelationshipFeedbackCalibrationMarkdown).toHaveBeenCalledWith(calibration); + expect(io.stdout()).toBe('KTX relationship feedback calibration\nTotal labels: 2\n'); }); it('prints relationship feedback calibration as JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const calibration: KloRelationshipFeedbackCalibrationReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const calibration: KtxRelationshipFeedbackCalibrationReport = { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: 'warehouse', decision: 'rejected' }, thresholds: { accept: 0.9, review: 0.5 }, @@ -1583,7 +1583,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipCalibration', projectDir: tempDir, @@ -1606,8 +1606,8 @@ describe('runKloScan', () => { }); it('prints relationship threshold advice as human output', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const advice: KloRelationshipThresholdAdviceReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const advice: KtxRelationshipThresholdAdviceReport = { generatedAt: '2026-05-07T14:00:00.000Z', filters: { connectionId: null, decision: 'all' }, status: 'ready', @@ -1648,13 +1648,13 @@ describe('runKloScan', () => { warnings: [], }; const adviseLocalRelationshipFeedbackThresholds = vi.fn(async () => advice); - const formatKloRelationshipThresholdAdviceMarkdown = vi.fn( - () => 'KLO relationship threshold advice\nRecommended: accept=0.90 review=0.55\n', + const formatKtxRelationshipThresholdAdviceMarkdown = vi.fn( + () => 'KTX relationship threshold advice\nRecommended: accept=0.90 review=0.55\n', ); const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipThresholds', projectDir: tempDir, @@ -1665,7 +1665,7 @@ describe('runKloScan', () => { json: false, }, io.io, - { adviseLocalRelationshipFeedbackThresholds, formatKloRelationshipThresholdAdviceMarkdown }, + { adviseLocalRelationshipFeedbackThresholds, formatKtxRelationshipThresholdAdviceMarkdown }, ), ).resolves.toBe(0); @@ -1678,13 +1678,13 @@ describe('runKloScan', () => { minRejectedLabels: 2, }, ); - expect(formatKloRelationshipThresholdAdviceMarkdown).toHaveBeenCalledWith(advice); - expect(io.stdout()).toBe('KLO relationship threshold advice\nRecommended: accept=0.90 review=0.55\n'); + expect(formatKtxRelationshipThresholdAdviceMarkdown).toHaveBeenCalledWith(advice); + expect(io.stdout()).toBe('KTX relationship threshold advice\nRecommended: accept=0.90 review=0.55\n'); }); it('prints relationship threshold advice as JSON', async () => { - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); - const advice: KloRelationshipThresholdAdviceReport = { + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); + const advice: KtxRelationshipThresholdAdviceReport = { generatedAt: '2026-05-07T14:00:00.000Z', filters: { connectionId: 'warehouse', decision: 'all' }, status: 'insufficient_labels', @@ -1714,7 +1714,7 @@ describe('runKloScan', () => { const io = makeIo(); await expect( - runKloScan( + runKtxScan( { command: 'relationshipThresholds', projectDir: tempDir, @@ -1737,10 +1737,10 @@ describe('runKloScan', () => { }); it('passes native CLI adapters into local scan runs for mysql configs', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1767,7 +1767,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -1786,10 +1786,10 @@ describe('runKloScan', () => { }); it('creates a native connector for standalone relationship scans', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-relationships-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-relationships-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1816,7 +1816,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -1841,10 +1841,10 @@ describe('runKloScan', () => { }); it('routes standalone postgres scans through the native connector before daemon fallback', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-postgres-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-postgres-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1874,7 +1874,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -1905,10 +1905,10 @@ describe('runKloScan', () => { }); it('passes native CLI adapters into local scan runs for clickhouse configs', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-clickhouse-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-clickhouse-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1938,7 +1938,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -1957,10 +1957,10 @@ describe('runKloScan', () => { }); it('passes native CLI adapters into local scan runs for sqlserver configs', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-sqlserver-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-sqlserver-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1990,7 +1990,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -2021,10 +2021,10 @@ describe('runKloScan', () => { }); it('passes native CLI adapters into local scan runs for bigquery configs', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-bigquery-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-bigquery-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -2053,7 +2053,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, @@ -2084,10 +2084,10 @@ describe('runKloScan', () => { }); it('passes native CLI adapters into local scan runs for snowflake configs', async () => { - const tempProject = await mkdtemp(join(tmpdir(), 'klo-scan-cli-native-snowflake-')); - await initKloProject({ projectDir: tempProject, projectName: 'warehouse' }); + const tempProject = await mkdtemp(join(tmpdir(), 'ktx-scan-cli-native-snowflake-')); + await initKtxProject({ projectDir: tempProject, projectName: 'warehouse' }); await writeFile( - join(tempProject, 'klo.yaml'), + join(tempProject, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -2119,7 +2119,7 @@ describe('runKloScan', () => { ); await expect( - runKloScan( + runKtxScan( { command: 'run', projectDir: tempProject, diff --git a/packages/cli/src/scan.ts b/packages/cli/src/scan.ts index 8008b72e..accccf57 100644 --- a/packages/cli/src/scan.ts +++ b/packages/cli/src/scan.ts @@ -1,4 +1,4 @@ -import { loadKloProject } from '@klo/context/project'; +import { loadKtxProject } from '@ktx/context/project'; import { type ApplyLocalScanRelationshipReviewDecisionsResult, adviseLocalRelationshipFeedbackThresholds, @@ -6,43 +6,43 @@ import { calibrateLocalRelationshipFeedbackLabels, type ExportLocalRelationshipFeedbackLabelsResult, exportLocalRelationshipFeedbackLabels, - formatKloRelationshipFeedbackCalibrationMarkdown, - formatKloRelationshipFeedbackLabelsJsonl, - formatKloRelationshipThresholdAdviceMarkdown, + formatKtxRelationshipFeedbackCalibrationMarkdown, + formatKtxRelationshipFeedbackLabelsJsonl, + formatKtxRelationshipThresholdAdviceMarkdown, getLocalScanReport, getLocalScanStatus, - type KloProgressPort, - type KloRelationshipArtifact, - type KloRelationshipArtifactEdge, - type KloRelationshipArtifactStatus, - type KloRelationshipDiagnosticsArtifact, - type KloRelationshipFeedbackCalibrationReport, - type KloRelationshipFeedbackDecisionFilter, - type KloRelationshipFeedbackLabel, - type KloRelationshipReviewDecisionValue, - type KloRelationshipThresholdAdviceReport, - type KloScanMode, - type KloScanReport, - type KloScanWarning, + type KtxProgressPort, + type KtxRelationshipArtifact, + type KtxRelationshipArtifactEdge, + type KtxRelationshipArtifactStatus, + type KtxRelationshipDiagnosticsArtifact, + type KtxRelationshipFeedbackCalibrationReport, + type KtxRelationshipFeedbackDecisionFilter, + type KtxRelationshipFeedbackLabel, + type KtxRelationshipReviewDecisionValue, + type KtxRelationshipThresholdAdviceReport, + type KtxScanMode, + type KtxScanReport, + type KtxScanWarning, type LocalScanStatusResponse, readLocalScanRelationshipArtifacts, runLocalScan, type WriteLocalScanRelationshipReviewDecisionResult, writeLocalScanRelationshipReviewDecision, -} from '@klo/context/scan'; -import type { KloCliIo } from './index.js'; -import { createKloCliLocalIngestAdapters } from './local-adapters.js'; -import { createKloCliScanConnector } from './local-scan-connectors.js'; +} from '@ktx/context/scan'; +import type { KtxCliIo } from './index.js'; +import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; +import { createKtxCliScanConnector } from './local-scan-connectors.js'; import { profileMark } from './startup-profile.js'; profileMark('module:scan'); -export type KloScanArgs = +export type KtxScanArgs = | { command: 'run'; projectDir: string; connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; detectRelationships: boolean; dryRun: boolean; databaseIntrospectionUrl?: string; @@ -53,7 +53,7 @@ export type KloScanArgs = command: 'relationships'; projectDir: string; runId: string; - status: KloRelationshipArtifactStatus; + status: KtxRelationshipArtifactStatus; json: boolean; limit: number; } @@ -62,7 +62,7 @@ export type KloScanArgs = projectDir: string; runId: string; candidateId: string; - decision: KloRelationshipReviewDecisionValue; + decision: KtxRelationshipReviewDecisionValue; reviewer: string; note: string | null; json: boolean; @@ -80,7 +80,7 @@ export type KloScanArgs = command: 'relationshipFeedback'; projectDir: string; connectionId: string | null; - decision: KloRelationshipFeedbackDecisionFilter; + decision: KtxRelationshipFeedbackDecisionFilter; json: boolean; jsonl: boolean; } @@ -88,7 +88,7 @@ export type KloScanArgs = command: 'relationshipCalibration'; projectDir: string; connectionId: string | null; - decision: KloRelationshipFeedbackDecisionFilter; + decision: KtxRelationshipFeedbackDecisionFilter; acceptThreshold: number; reviewThreshold: number; json: boolean; @@ -103,23 +103,23 @@ export type KloScanArgs = json: boolean; }; -interface KloScanDeps { +interface KtxScanDeps { runLocalScan?: typeof runLocalScan; - createLocalIngestAdapters?: typeof createKloCliLocalIngestAdapters; + createLocalIngestAdapters?: typeof createKtxCliLocalIngestAdapters; getLocalScanStatus?: typeof getLocalScanStatus; getLocalScanReport?: typeof getLocalScanReport; readLocalScanRelationshipArtifacts?: typeof readLocalScanRelationshipArtifacts; writeLocalScanRelationshipReviewDecision?: typeof writeLocalScanRelationshipReviewDecision; applyLocalScanRelationshipReviewDecisions?: typeof applyLocalScanRelationshipReviewDecisions; exportLocalRelationshipFeedbackLabels?: typeof exportLocalRelationshipFeedbackLabels; - formatKloRelationshipFeedbackLabelsJsonl?: typeof formatKloRelationshipFeedbackLabelsJsonl; + formatKtxRelationshipFeedbackLabelsJsonl?: typeof formatKtxRelationshipFeedbackLabelsJsonl; calibrateLocalRelationshipFeedbackLabels?: typeof calibrateLocalRelationshipFeedbackLabels; - formatKloRelationshipFeedbackCalibrationMarkdown?: typeof formatKloRelationshipFeedbackCalibrationMarkdown; + formatKtxRelationshipFeedbackCalibrationMarkdown?: typeof formatKtxRelationshipFeedbackCalibrationMarkdown; adviseLocalRelationshipFeedbackThresholds?: typeof adviseLocalRelationshipFeedbackThresholds; - formatKloRelationshipThresholdAdviceMarkdown?: typeof formatKloRelationshipThresholdAdviceMarkdown; + formatKtxRelationshipThresholdAdviceMarkdown?: typeof formatKtxRelationshipThresholdAdviceMarkdown; } -function shouldUseStyledOutput(io: KloCliIo): boolean { +function shouldUseStyledOutput(io: KtxCliIo): boolean { return io.stdout.isTTY === true && !process.env.NO_COLOR && process.env.TERM !== 'dumb' && !process.env.CI; } @@ -142,15 +142,15 @@ function plural(count: number, singular: string, pluralValue = `${singular}s`): return count === 1 ? singular : pluralValue; } -function tableChangeCount(report: KloScanReport): number { +function tableChangeCount(report: KtxScanReport): number { return report.diffSummary.tablesAdded + report.diffSummary.tablesModified + report.diffSummary.tablesDeleted; } -function totalTableCount(report: KloScanReport): number { +function totalTableCount(report: KtxScanReport): number { return tableChangeCount(report) + report.diffSummary.tablesUnchanged; } -function writeScanIdentity(report: KloScanReport, io: KloCliIo): void { +function writeScanIdentity(report: KtxScanReport, io: KtxCliIo): void { io.stdout.write(`Run: ${report.runId}\n`); io.stdout.write(`Connection: ${report.connectionId}\n`); io.stdout.write(`Mode: ${report.mode}\n`); @@ -158,7 +158,7 @@ function writeScanIdentity(report: KloScanReport, io: KloCliIo): void { io.stdout.write(`Dry run: ${report.dryRun ? 'yes' : 'no'}\n`); } -function writeWhatChanged(report: KloScanReport, io: KloCliIo): void { +function writeWhatChanged(report: KtxScanReport, io: KtxCliIo): void { const changedTables = tableChangeCount(report); const totalTables = totalTableCount(report); io.stdout.write('\nWhat changed\n'); @@ -182,7 +182,7 @@ function writeWhatChanged(report: KloScanReport, io: KloCliIo): void { } } -function hasRelationshipResults(report: KloScanReport): boolean { +function hasRelationshipResults(report: KtxScanReport): boolean { return ( report.relationships.accepted > 0 || report.relationships.review > 0 || @@ -191,7 +191,7 @@ function hasRelationshipResults(report: KloScanReport): boolean { ); } -function writeRelationships(report: KloScanReport, io: KloCliIo): void { +function writeRelationships(report: KtxScanReport, io: KtxCliIo): void { if (!hasRelationshipResults(report)) { return; } @@ -215,12 +215,12 @@ function capabilityGapMessage(gap: string): string { return `${gap} is unavailable; scan results may be less complete.`; } -function warningLine(warning: KloScanWarning): string { +function warningLine(warning: KtxScanWarning): string { const location = warning.table ? `${warning.table}${warning.column ? `.${warning.column}` : ''}: ` : ''; return `${warning.code}: ${location}${warning.message}`; } -function writeNeedsAttention(report: KloScanReport, io: KloCliIo): void { +function writeNeedsAttention(report: KtxScanReport, io: KtxCliIo): void { io.stdout.write('\nNeeds attention\n'); if (report.warnings.length === 0 && report.capabilityGaps.length === 0) { io.stdout.write(' None\n'); @@ -243,7 +243,7 @@ function writeNeedsAttention(report: KloScanReport, io: KloCliIo): void { } } -function writeArtifacts(report: KloScanReport, io: KloCliIo): void { +function writeArtifacts(report: KtxScanReport, io: KtxCliIo): void { io.stdout.write('\nArtifacts\n'); io.stdout.write(` Report: ${report.artifactPaths.reportPath ?? 'none'}\n`); io.stdout.write(` Raw sources: ${report.artifactPaths.rawSourcesDir ?? 'none'}\n`); @@ -255,7 +255,7 @@ function writeArtifacts(report: KloScanReport, io: KloCliIo): void { } } -function writeHumanReportBody(report: KloScanReport, io: KloCliIo): void { +function writeHumanReportBody(report: KtxScanReport, io: KtxCliIo): void { writeScanIdentity(report, io); writeWhatChanged(report, io); writeRelationships(report, io); @@ -263,25 +263,25 @@ function writeHumanReportBody(report: KloScanReport, io: KloCliIo): void { writeArtifacts(report, io); } -function writeRunSummary(report: KloScanReport, projectDir: string, io: KloCliIo): void { +function writeRunSummary(report: KtxScanReport, projectDir: string, io: KtxCliIo): void { const styled = shouldUseStyledOutput(io); - io.stdout.write(`${styled ? green('✓') : ''}${styled ? ' ' : ''}KLO scan completed\n`); + io.stdout.write(`${styled ? green('✓') : ''}${styled ? ' ' : ''}KTX scan completed\n`); io.stdout.write('Status: done\n'); writeHumanReportBody(report, io); const projectDirArg = quoteCliArg(projectDir); io.stdout.write('\nNext:\n'); - const statusCommand = styled ? dim('klo dev scan status') : 'klo dev scan status'; - const reportCommand = styled ? dim('klo dev scan report') : 'klo dev scan report'; + const statusCommand = styled ? dim('ktx dev scan status') : 'ktx dev scan status'; + const reportCommand = styled ? dim('ktx dev scan report') : 'ktx dev scan report'; io.stdout.write(` ${statusCommand} --project-dir ${projectDirArg} ${report.runId}\n`); io.stdout.write(` ${reportCommand} --project-dir ${projectDirArg} ${report.runId}\n`); } -function writeReport(report: KloScanReport, io: KloCliIo): void { - io.stdout.write('KLO scan report\n'); +function writeReport(report: KtxScanReport, io: KtxCliIo): void { + io.stdout.write('KTX scan report\n'); writeHumanReportBody(report, io); } -function formatRelationshipEndpoint(edge: KloRelationshipArtifactEdge, side: 'from' | 'to'): string { +function formatRelationshipEndpoint(edge: KtxRelationshipArtifactEdge, side: 'from' | 'to'): string { const endpoint = edge[side]; if (endpoint.columns.length === 1) { return `${endpoint.table.name}.${endpoint.columns[0]}`; @@ -293,7 +293,7 @@ function formatRelationshipScore(value: number | null): string { return value === null ? 'n/a' : value.toFixed(2); } -function relationshipStatusTitle(status: Exclude): string { +function relationshipStatusTitle(status: Exclude): string { if (status === 'accepted') { return 'Accepted relationships'; } @@ -307,9 +307,9 @@ function relationshipStatusTitle(status: Exclude ${formatRelationshipEndpoint(edge, 'to')}\n`, ); @@ -333,10 +333,10 @@ function writeRelationshipEdge(edge: KloRelationshipArtifactEdge, index: number, } function writeRelationshipGroup( - status: Exclude, - relationships: KloRelationshipArtifact, + status: Exclude, + relationships: KtxRelationshipArtifact, limit: number, - io: KloCliIo, + io: KtxCliIo, ): void { if (status === 'skipped') { io.stdout.write(`\n${relationshipStatusTitle(status)} (${relationships.skipped.length})\n`); @@ -366,15 +366,15 @@ function writeRelationshipArtifactSummary(input: { runId: string; connectionId: string; syncId: string; - status: KloRelationshipArtifactStatus; + status: KtxRelationshipArtifactStatus; limit: number; - summary: KloRelationshipArtifact; - relationships: KloRelationshipArtifact; - diagnostics: KloRelationshipDiagnosticsArtifact | null; + summary: KtxRelationshipArtifact; + relationships: KtxRelationshipArtifact; + diagnostics: KtxRelationshipDiagnosticsArtifact | null; relationshipsPath: string; - io: KloCliIo; + io: KtxCliIo; }): void { - input.io.stdout.write('KLO relationship artifacts\n'); + input.io.stdout.write('KTX relationship artifacts\n'); input.io.stdout.write(`Run: ${input.runId}\n`); input.io.stdout.write(`Connection: ${input.connectionId}\n`); input.io.stdout.write(`Sync: ${input.syncId}\n`); @@ -386,14 +386,14 @@ function writeRelationshipArtifactSummary(input: { } input.io.stdout.write(`Artifacts: ${input.relationshipsPath}\n`); - const statuses: Array> = + const statuses: Array> = input.status === 'all' ? ['accepted', 'review', 'rejected', 'skipped'] : [input.status]; for (const status of statuses) { writeRelationshipGroup(status, input.relationships, input.limit, input.io); } } -function writeRelationshipDecisionResult(result: WriteLocalScanRelationshipReviewDecisionResult, io: KloCliIo): void { +function writeRelationshipDecisionResult(result: WriteLocalScanRelationshipReviewDecisionResult, io: KtxCliIo): void { io.stdout.write('Recorded relationship decision\n'); io.stdout.write(`Decision: ${result.decision.decision}\n`); io.stdout.write(`Candidate: ${result.decision.candidateId}\n`); @@ -405,7 +405,7 @@ function writeRelationshipDecisionResult(result: WriteLocalScanRelationshipRevie io.stdout.write(`Path: ${result.path}\n`); } -function writeRelationshipApplyResult(result: ApplyLocalScanRelationshipReviewDecisionsResult, io: KloCliIo): void { +function writeRelationshipApplyResult(result: ApplyLocalScanRelationshipReviewDecisionsResult, io: KtxCliIo): void { io.stdout.write('Relationship review apply\n'); io.stdout.write(`Run: ${result.runId}\n`); io.stdout.write(`Connection: ${result.connectionId}\n`); @@ -433,15 +433,15 @@ function feedbackTableShortName(value: string): string { return value.split('.').at(-1) ?? value; } -function feedbackEndpoint(label: KloRelationshipFeedbackLabel, side: 'from' | 'to'): string { +function feedbackEndpoint(label: KtxRelationshipFeedbackLabel, side: 'from' | 'to'): string { if (side === 'from') { return `${feedbackTableShortName(label.fromTable)}.${formatFeedbackColumns(label.fromColumns)}`; } return `${feedbackTableShortName(label.toTable)}.${formatFeedbackColumns(label.toColumns)}`; } -function writeRelationshipFeedbackSummary(result: ExportLocalRelationshipFeedbackLabelsResult, io: KloCliIo): void { - io.stdout.write('KLO relationship feedback labels\n'); +function writeRelationshipFeedbackSummary(result: ExportLocalRelationshipFeedbackLabelsResult, io: KtxCliIo): void { + io.stdout.write('KTX relationship feedback labels\n'); io.stdout.write(`Generated: ${result.generatedAt}\n`); io.stdout.write(`Filter connection: ${result.filters.connectionId ?? 'all'}\n`); io.stdout.write(`Filter decision: ${result.filters.decision}\n`); @@ -474,29 +474,29 @@ function writeRelationshipFeedbackSummary(result: ExportLocalRelationshipFeedbac } } -interface KloCliScanProgressState { +interface KtxCliScanProgressState { progress: number; hasPendingTransient: boolean; } -interface KloCliScanProgressUpdateOptions { +interface KtxCliScanProgressUpdateOptions { transient?: boolean; } -interface KloCliScanProgress extends Omit { - update(progress: number, message?: string, options?: KloCliScanProgressUpdateOptions): Promise; +interface KtxCliScanProgress extends Omit { + update(progress: number, message?: string, options?: KtxCliScanProgressUpdateOptions): Promise; flush(): void; } export function createCliScanProgress( - io: KloCliIo, - state: KloCliScanProgressState = { progress: 0, hasPendingTransient: false }, + io: KtxCliIo, + state: KtxCliScanProgressState = { progress: 0, hasPendingTransient: false }, start = 0, weight = 1, -): KloCliScanProgress { +): KtxCliScanProgress { const shouldWrite = io.stdout.isTTY === true && !process.env.CI; - const progress: KloCliScanProgress = { - async update(value: number, message?: string, options?: KloCliScanProgressUpdateOptions) { + const progress: KtxCliScanProgress = { + async update(value: number, message?: string, options?: KtxCliScanProgressUpdateOptions) { const absoluteValue = start + Math.max(0, Math.min(1, value)) * weight; state.progress = Math.max(state.progress, Math.min(1, absoluteValue)); if (!shouldWrite || !message) { @@ -526,7 +526,7 @@ export function createCliScanProgress( return progress; } -function writeStatus(status: LocalScanStatusResponse, io: KloCliIo): void { +function writeStatus(status: LocalScanStatusResponse, io: KtxCliIo): void { io.stdout.write(`Run: ${status.runId}\n`); io.stdout.write(`Status: ${status.status}\n`); io.stdout.write(`Connection: ${status.connectionId}\n`); @@ -536,9 +536,9 @@ function writeStatus(status: LocalScanStatusResponse, io: KloCliIo): void { io.stdout.write(`Report: ${status.reportPath ?? 'none'}\n`); } -export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps: KloScanDeps = {}): Promise { +export async function runKtxScan(args: KtxScanArgs, io: KtxCliIo = process, deps: KtxScanDeps = {}): Promise { try { - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if (args.command === 'status') { const status = await (deps.getLocalScanStatus ?? getLocalScanStatus)(project, args.runId); if (!status) { @@ -655,7 +655,7 @@ export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps ); if (args.jsonl) { io.stdout.write( - (deps.formatKloRelationshipFeedbackLabelsJsonl ?? formatKloRelationshipFeedbackLabelsJsonl)(result), + (deps.formatKtxRelationshipFeedbackLabelsJsonl ?? formatKtxRelationshipFeedbackLabelsJsonl)(result), ); } else if (args.json) { io.stdout.write(`${JSON.stringify(result, null, 2)}\n`); @@ -675,10 +675,10 @@ export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps }, ); if (args.json) { - io.stdout.write(`${JSON.stringify(result satisfies KloRelationshipFeedbackCalibrationReport, null, 2)}\n`); + io.stdout.write(`${JSON.stringify(result satisfies KtxRelationshipFeedbackCalibrationReport, null, 2)}\n`); } else { io.stdout.write( - (deps.formatKloRelationshipFeedbackCalibrationMarkdown ?? formatKloRelationshipFeedbackCalibrationMarkdown)( + (deps.formatKtxRelationshipFeedbackCalibrationMarkdown ?? formatKtxRelationshipFeedbackCalibrationMarkdown)( result, ), ); @@ -695,10 +695,10 @@ export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps minRejectedLabels: args.minRejectedLabels, }); if (args.json) { - io.stdout.write(`${JSON.stringify(result satisfies KloRelationshipThresholdAdviceReport, null, 2)}\n`); + io.stdout.write(`${JSON.stringify(result satisfies KtxRelationshipThresholdAdviceReport, null, 2)}\n`); } else { io.stdout.write( - (deps.formatKloRelationshipThresholdAdviceMarkdown ?? formatKloRelationshipThresholdAdviceMarkdown)(result), + (deps.formatKtxRelationshipThresholdAdviceMarkdown ?? formatKtxRelationshipThresholdAdviceMarkdown)(result), ); } return 0; @@ -706,7 +706,7 @@ export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps const connector = args.mode !== 'structural' || args.detectRelationships - ? await createKloCliScanConnector(project, args.connectionId) + ? await createKtxCliScanConnector(project, args.connectionId) : undefined; const progress = createCliScanProgress(io); try { @@ -719,7 +719,7 @@ export async function runKloScan(args: KloScanArgs, io: KloCliIo = process, deps trigger: 'cli', databaseIntrospectionUrl: args.databaseIntrospectionUrl, connector, - adapters: (deps.createLocalIngestAdapters ?? createKloCliLocalIngestAdapters)(project, { + adapters: (deps.createLocalIngestAdapters ?? createKtxCliLocalIngestAdapters)(project, { databaseIntrospectionUrl: args.databaseIntrospectionUrl, }), progress, diff --git a/packages/cli/src/serve.test.ts b/packages/cli/src/serve.test.ts index 53637db5..ca328d07 100644 --- a/packages/cli/src/serve.test.ts +++ b/packages/cli/src/serve.test.ts @@ -1,16 +1,16 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { SourceAdapter } from '@klo/context/ingest'; -import { initKloProject } from '@klo/context/project'; +import type { SourceAdapter } from '@ktx/context/ingest'; +import { initKtxProject } from '@ktx/context/project'; import { describe, expect, it, vi } from 'vitest'; -import { runKloServeStdio } from './serve.js'; +import { runKtxServeStdio } from './serve.js'; -describe('runKloServeStdio', () => { +describe('runKtxServeStdio', () => { it('loads the project, creates local ports, and connects the server to stdio', async () => { const connect = vi.fn().mockResolvedValue(undefined); const project = { - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', config: { connections: {}, llm: { @@ -27,10 +27,10 @@ describe('runKloServeStdio', () => { let stderr = ''; await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: false, semanticComputeUrl: undefined, @@ -49,7 +49,7 @@ describe('runKloServeStdio', () => { ), ).resolves.toBe(0); - expect(loadProject).toHaveBeenCalledWith({ projectDir: '/tmp/klo-project' }); + expect(loadProject).toHaveBeenCalledWith({ projectDir: '/tmp/ktx-project' }); expect(createContextTools).toHaveBeenCalledWith( project, expect.objectContaining({ @@ -62,26 +62,26 @@ describe('runKloServeStdio', () => { }), ); expect(createServer).toHaveBeenCalledWith({ - name: 'klo', + name: 'ktx', version: '0.0.0-private', userContext: { userId: 'agent' }, contextTools, memoryCapture: undefined, }); expect(connect).toHaveBeenCalledWith({ kind: 'stdio' }); - expect(stderr).toContain('klo MCP server running on stdio for /tmp/klo-project'); + expect(stderr).toContain('ktx MCP server running on stdio for /tmp/ktx-project'); }); it('enables local ingest ports by default when serving stdio', async () => { - const project = { projectDir: '/tmp/klo-project', config: { connections: {} } } as never; + const project = { projectDir: '/tmp/ktx-project', config: { connections: {} } } as never; const connect = vi.fn().mockResolvedValue(undefined); const createContextTools = vi.fn(() => ({ connections: { list: async () => [] } })); await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: false, semanticComputeUrl: undefined, @@ -114,17 +114,17 @@ describe('runKloServeStdio', () => { }); it('passes daemon database introspection URL to MCP local ingest adapters', async () => { - const project = { projectDir: '/tmp/klo-project', config: { connections: {} } } as never; + const project = { projectDir: '/tmp/ktx-project', config: { connections: {} } } as never; const connect = vi.fn().mockResolvedValue(undefined); const createContextTools = vi.fn(() => ({ connections: { list: async () => [] } })); const createdAdapters: SourceAdapter[] = []; const createIngestAdapters = vi.fn(() => createdAdapters); await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: false, semanticComputeUrl: undefined, @@ -162,13 +162,13 @@ describe('runKloServeStdio', () => { }); it('uses CLI-native local ingest adapters for standalone scan tools', async () => { - const project = { projectDir: '/tmp/klo-project', config: { connections: {} } } as never; + const project = { projectDir: '/tmp/ktx-project', config: { connections: {} } } as never; const createContextTools = vi.fn(() => ({}) as never); - await runKloServeStdio( + await runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'local', semanticCompute: false, executeQueries: false, @@ -193,14 +193,14 @@ describe('runKloServeStdio', () => { }); it('passes semantic compute to local project ports when enabled', async () => { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-serve-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-serve-')); try { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const createContextTools = vi.fn(() => ({ connections: { list: async () => [] } })); const semanticLayerCompute = { query: vi.fn(), validateSources: vi.fn(), generateSources: vi.fn() }; await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', projectDir: project.projectDir, @@ -242,16 +242,16 @@ describe('runKloServeStdio', () => { }); it('uses the HTTP semantic compute port when a daemon URL is provided', async () => { - const project = { projectDir: '/tmp/klo-project', config: { connections: {} } } as never; + const project = { projectDir: '/tmp/ktx-project', config: { connections: {} } } as never; const semanticLayerCompute = { query: vi.fn(), validateSources: vi.fn(), generateSources: vi.fn() }; const createHttpSemanticLayerCompute = vi.fn(() => semanticLayerCompute); const createContextTools = vi.fn(() => ({ connections: { list: async () => [] } })); await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: true, semanticComputeUrl: 'http://127.0.0.1:8765', @@ -281,17 +281,17 @@ describe('runKloServeStdio', () => { }); it('passes a query executor to local project ports only when query execution is enabled', async () => { - const project = { projectDir: '/tmp/klo-project', config: { connections: {} } } as never; + const project = { projectDir: '/tmp/ktx-project', config: { connections: {} } } as never; const connect = vi.fn().mockResolvedValue(undefined); const createContextTools = vi.fn(() => ({ connections: { list: async () => [] } })); const semanticLayerCompute = { query: vi.fn(), validateSources: vi.fn(), generateSources: vi.fn() }; const queryExecutor = { execute: vi.fn() }; await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: true, semanticComputeUrl: undefined, @@ -331,7 +331,7 @@ describe('runKloServeStdio', () => { it('creates a local memory capture port when memory capture is enabled', async () => { const project = { - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', config: { connections: {}, llm: { @@ -348,10 +348,10 @@ describe('runKloServeStdio', () => { const createServer = vi.fn().mockReturnValue({ connect }); await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: false, semanticComputeUrl: undefined, @@ -376,7 +376,7 @@ describe('runKloServeStdio', () => { semanticLayerCompute: undefined, }); expect(createServer).toHaveBeenCalledWith({ - name: 'klo', + name: 'ktx', version: '0.0.0-private', userContext: { userId: 'agent' }, contextTools, @@ -386,7 +386,7 @@ describe('runKloServeStdio', () => { it('reuses semantic compute for local memory capture when enabled', async () => { const project = { - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', config: { connections: {}, llm: { @@ -399,10 +399,10 @@ describe('runKloServeStdio', () => { const createMemoryCapture = vi.fn().mockReturnValue({ capture: vi.fn(), status: vi.fn() }); await expect( - runKloServeStdio( + runKtxServeStdio( { mcp: 'stdio', - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', userId: 'agent', semanticCompute: true, semanticComputeUrl: undefined, diff --git a/packages/cli/src/serve.ts b/packages/cli/src/serve.ts index 9e491598..7857e7d6 100644 --- a/packages/cli/src/serve.ts +++ b/packages/cli/src/serve.ts @@ -1,27 +1,27 @@ -import { createLocalKloLlmProviderFromConfig } from '@klo/context'; -import { createDefaultLocalQueryExecutor, type KloSqlQueryExecutorPort } from '@klo/context/connections'; +import { createLocalKtxLlmProviderFromConfig } from '@ktx/context'; +import { createDefaultLocalQueryExecutor, type KtxSqlQueryExecutorPort } from '@ktx/context/connections'; import { createHttpSemanticLayerComputePort, createPythonSemanticLayerComputePort, - type KloSemanticLayerComputePort, -} from '@klo/context/daemon'; -import { createDefaultLocalIngestAdapters, type LocalIngestMcpOptions } from '@klo/context/ingest'; + type KtxSemanticLayerComputePort, +} from '@ktx/context/daemon'; +import { createDefaultLocalIngestAdapters, type LocalIngestMcpOptions } from '@ktx/context/ingest'; import { - createDefaultKloMcpServer, + createDefaultKtxMcpServer, createLocalProjectMcpContextPorts, - type KloMcpContextPorts, -} from '@klo/context/mcp'; -import { createLocalProjectMemoryCapture, type MemoryCaptureService } from '@klo/context/memory'; -import { type KloLocalProject, loadKloProject } from '@klo/context/project'; -import type { LocalScanMcpOptions } from '@klo/context/scan'; + type KtxMcpContextPorts, +} from '@ktx/context/mcp'; +import { createLocalProjectMemoryCapture, type MemoryCaptureService } from '@ktx/context/memory'; +import { type KtxLocalProject, loadKtxProject } from '@ktx/context/project'; +import type { LocalScanMcpOptions } from '@ktx/context/scan'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { createKloCliLocalIngestAdapters } from './local-adapters.js'; -import { createKloCliScanConnector } from './local-scan-connectors.js'; +import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; +import { createKtxCliScanConnector } from './local-scan-connectors.js'; import { profileMark } from './startup-profile.js'; profileMark('module:serve'); -export interface KloServeArgs { +export interface KtxServeArgs { mcp: 'stdio'; projectDir: string; userId: string; @@ -33,34 +33,34 @@ export interface KloServeArgs { memoryModel?: string; } -interface KloServeIo { +interface KtxServeIo { stderr: { write(chunk: string): void }; } interface LocalProjectContextToolOptions { - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: KloSqlQueryExecutorPort; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: KtxSqlQueryExecutorPort; localIngest?: LocalIngestMcpOptions; localScan?: LocalScanMcpOptions; } -interface KloServeDeps { - loadProject?: typeof loadKloProject; - createContextTools?: (project: KloLocalProject, options?: LocalProjectContextToolOptions) => KloMcpContextPorts; - createSemanticLayerCompute?: () => KloSemanticLayerComputePort; - createHttpSemanticLayerCompute?: (baseUrl: string) => KloSemanticLayerComputePort; +interface KtxServeDeps { + loadProject?: typeof loadKtxProject; + createContextTools?: (project: KtxLocalProject, options?: LocalProjectContextToolOptions) => KtxMcpContextPorts; + createSemanticLayerCompute?: () => KtxSemanticLayerComputePort; + createHttpSemanticLayerCompute?: (baseUrl: string) => KtxSemanticLayerComputePort; createIngestAdapters?: typeof createDefaultLocalIngestAdapters; - createQueryExecutor?: () => KloSqlQueryExecutorPort; + createQueryExecutor?: () => KtxSqlQueryExecutorPort; createMemoryCapture?: typeof createLocalProjectMemoryCapture; - createServer?: typeof createDefaultKloMcpServer; + createServer?: typeof createDefaultKtxMcpServer; createTransport?: () => StdioServerTransport; - stderr?: KloServeIo['stderr']; + stderr?: KtxServeIo['stderr']; } -export async function runKloServeStdio(args: KloServeArgs, deps: KloServeDeps = {}): Promise { - const loadProjectFn = deps.loadProject ?? loadKloProject; +export async function runKtxServeStdio(args: KtxServeArgs, deps: KtxServeDeps = {}): Promise { + const loadProjectFn = deps.loadProject ?? loadKtxProject; const createContextToolsFn = deps.createContextTools ?? createLocalProjectMcpContextPorts; - const createServerFn = deps.createServer ?? createDefaultKloMcpServer; + const createServerFn = deps.createServer ?? createDefaultKtxMcpServer; const createTransportFn = deps.createTransport ?? (() => new StdioServerTransport()); const stderr = deps.stderr ?? process.stderr; @@ -75,12 +75,12 @@ export async function runKloServeStdio(args: KloServeArgs, deps: KloServeDeps = const queryExecutor = args.executeQueries ? (deps.createQueryExecutor ?? createDefaultLocalQueryExecutor)() : undefined; - const createIngestAdapters = deps.createIngestAdapters ?? createKloCliLocalIngestAdapters; + const createIngestAdapters = deps.createIngestAdapters ?? createKtxCliLocalIngestAdapters; const localAdapters = createIngestAdapters(project, { databaseIntrospectionUrl: args.databaseIntrospectionUrl, }); const llmProvider = args.memoryCapture - ? (createLocalKloLlmProviderFromConfig(project.config.llm) ?? undefined) + ? (createLocalKtxLlmProviderFromConfig(project.config.llm) ?? undefined) : undefined; const memoryCapture: MemoryCaptureService | undefined = args.memoryCapture ? (deps.createMemoryCapture ?? createLocalProjectMemoryCapture)(project, { @@ -96,7 +96,7 @@ export async function runKloServeStdio(args: KloServeArgs, deps: KloServeDeps = const localScan: LocalScanMcpOptions = { adapters: localAdapters, databaseIntrospectionUrl: args.databaseIntrospectionUrl, - createConnector: (connectionId) => createKloCliScanConnector(project, connectionId), + createConnector: (connectionId) => createKtxCliScanConnector(project, connectionId), }; const contextToolOptions: LocalProjectContextToolOptions = { localIngest, @@ -106,7 +106,7 @@ export async function runKloServeStdio(args: KloServeArgs, deps: KloServeDeps = }; const contextTools = createContextToolsFn(project, contextToolOptions); const server = createServerFn({ - name: 'klo', + name: 'ktx', version: '0.0.0-private', userContext: { userId: args.userId }, contextTools, @@ -114,6 +114,6 @@ export async function runKloServeStdio(args: KloServeArgs, deps: KloServeDeps = }); const transport = createTransportFn(); await server.connect(transport); - stderr.write(`klo MCP server running on stdio for ${project.projectDir}\n`); + stderr.write(`ktx MCP server running on stdio for ${project.projectDir}\n`); return 0; } diff --git a/packages/cli/src/setup-agents.test.ts b/packages/cli/src/setup-agents.test.ts index b20e1b07..739c9912 100644 --- a/packages/cli/src/setup-agents.test.ts +++ b/packages/cli/src/setup-agents.test.ts @@ -3,10 +3,10 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - plannedKloAgentFiles, - readKloAgentInstallManifest, - removeKloAgentInstall, - runKloSetupAgentsStep, + plannedKtxAgentFiles, + readKtxAgentInstallManifest, + removeKtxAgentInstall, + runKtxSetupAgentsStep, } from './setup-agents.js'; function makeIo() { @@ -26,9 +26,9 @@ describe('setup agents', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-agents-')); - await mkdir(join(tempDir, '.klo', 'agents'), { recursive: true }); - await writeFile(join(tempDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-')); + await mkdir(join(tempDir, '.ktx', 'agents'), { recursive: true }); + await writeFile(join(tempDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); }); afterEach(async () => { @@ -36,22 +36,22 @@ describe('setup agents', () => { }); it('plans project-scoped CLI and MCP files for every target', () => { - expect(plannedKloAgentFiles({ projectDir: tempDir, target: 'claude-code', scope: 'project', mode: 'both' })).toEqual([ - { kind: 'file', path: join(tempDir, '.claude/skills/klo/SKILL.md') }, - { kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'klo'] }, + expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'claude-code', scope: 'project', mode: 'both' })).toEqual([ + { kind: 'file', path: join(tempDir, '.claude/skills/ktx/SKILL.md') }, + { kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] }, ]); - expect(plannedKloAgentFiles({ projectDir: tempDir, target: 'codex', scope: 'project', mode: 'cli' })).toEqual([ - { kind: 'file', path: join(tempDir, '.agents/skills/klo/SKILL.md') }, + expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'codex', scope: 'project', mode: 'cli' })).toEqual([ + { kind: 'file', path: join(tempDir, '.agents/skills/ktx/SKILL.md') }, ]); - expect(plannedKloAgentFiles({ projectDir: tempDir, target: 'cursor', scope: 'project', mode: 'mcp' })).toEqual([ - { kind: 'json-key', path: join(tempDir, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'klo'] }, + expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'cursor', scope: 'project', mode: 'mcp' })).toEqual([ + { kind: 'json-key', path: join(tempDir, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'ktx'] }, ]); - expect(plannedKloAgentFiles({ projectDir: tempDir, target: 'opencode', scope: 'project', mode: 'cli' })).toEqual([ - { kind: 'file', path: join(tempDir, '.opencode/commands/klo.md') }, + expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'opencode', scope: 'project', mode: 'cli' })).toEqual([ + { kind: 'file', path: join(tempDir, '.opencode/commands/ktx.md') }, ]); - expect(plannedKloAgentFiles({ projectDir: tempDir, target: 'universal', scope: 'project', mode: 'both' })).toEqual([ - { kind: 'file', path: join(tempDir, '.agents/skills/klo/SKILL.md') }, - { kind: 'json-key', path: join(tempDir, '.agents/mcp/klo.json'), jsonPath: ['mcpServers', 'klo'] }, + expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'universal', scope: 'project', mode: 'both' })).toEqual([ + { kind: 'file', path: join(tempDir, '.agents/skills/ktx/SKILL.md') }, + { kind: 'json-key', path: join(tempDir, '.agents/mcp/ktx.json'), jsonPath: ['mcpServers', 'ktx'] }, ]); }); @@ -59,7 +59,7 @@ describe('setup agents', () => { const io = makeIo(); await expect( - runKloSetupAgentsStep( + runKtxSetupAgentsStep( { projectDir: tempDir, inputMode: 'disabled', @@ -78,24 +78,24 @@ describe('setup agents', () => { installs: [{ target: 'universal', scope: 'project', mode: 'both' }], }); - await expect(stat(join(tempDir, '.agents/skills/klo/SKILL.md'))).resolves.toBeDefined(); - await expect(stat(join(tempDir, '.agents/mcp/klo.json'))).resolves.toBeDefined(); - const skill = await readFile(join(tempDir, '.agents/skills/klo/SKILL.md'), 'utf-8'); + await expect(stat(join(tempDir, '.agents/skills/ktx/SKILL.md'))).resolves.toBeDefined(); + await expect(stat(join(tempDir, '.agents/mcp/ktx.json'))).resolves.toBeDefined(); + const skill = await readFile(join(tempDir, '.agents/skills/ktx/SKILL.md'), 'utf-8'); expect(skill).toContain(`--project-dir ${tempDir}`); expect(skill).toContain('must not print secrets'); - expect(skill).toContain('klo agent sql execute'); - expect(await readKloAgentInstallManifest(tempDir)).toMatchObject({ + expect(skill).toContain('ktx agent sql execute'); + expect(await readKtxAgentInstallManifest(tempDir)).toMatchObject({ version: 1, projectDir: tempDir, installs: [{ target: 'universal', scope: 'project', mode: 'both' }], }); - expect(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')).toContain('agents'); + expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).toContain('agents'); expect(io.stderr()).toBe(''); }); it('removes only manifest-listed files and JSON keys', async () => { const io = makeIo(); - await runKloSetupAgentsStep( + await runKtxSetupAgentsStep( { projectDir: tempDir, inputMode: 'disabled', @@ -108,13 +108,13 @@ describe('setup agents', () => { }, io.io, ); - await writeFile(join(tempDir, '.claude/skills/klo/keep.txt'), 'user file', 'utf-8'); + await writeFile(join(tempDir, '.claude/skills/ktx/keep.txt'), 'user file', 'utf-8'); - await expect(removeKloAgentInstall(tempDir, io.io)).resolves.toBe(0); + await expect(removeKtxAgentInstall(tempDir, io.io)).resolves.toBe(0); - await expect(stat(join(tempDir, '.claude/skills/klo/SKILL.md'))).rejects.toThrow(); - await expect(stat(join(tempDir, '.claude/skills/klo/keep.txt'))).resolves.toBeDefined(); - await expect(readKloAgentInstallManifest(tempDir)).resolves.toEqual(null); + await expect(stat(join(tempDir, '.claude/skills/ktx/SKILL.md'))).rejects.toThrow(); + await expect(stat(join(tempDir, '.claude/skills/ktx/keep.txt'))).resolves.toBeDefined(); + await expect(readKtxAgentInstallManifest(tempDir)).resolves.toEqual(null); }); it('uses prompts in interactive mode and supports Back', async () => { @@ -126,7 +126,7 @@ describe('setup agents', () => { }; await expect( - runKloSetupAgentsStep( + runKtxSetupAgentsStep( { projectDir: tempDir, inputMode: 'auto', @@ -151,7 +151,7 @@ describe('setup agents', () => { }; await expect( - runKloSetupAgentsStep( + runKtxSetupAgentsStep( { projectDir: tempDir, inputMode: 'auto', @@ -169,7 +169,7 @@ describe('setup agents', () => { expect(prompts.multiselect).toHaveBeenCalledWith( expect.objectContaining({ message: - 'Which agent targets should KLO install?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', + 'Which agent targets should KTX install?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', }), ); }); diff --git a/packages/cli/src/setup-agents.ts b/packages/cli/src/setup-agents.ts index 83c18bf9..303f5844 100644 --- a/packages/cli/src/setup-agents.ts +++ b/packages/cli/src/setup-agents.ts @@ -1,83 +1,83 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { cancel, isCancel, multiselect, select } from '@clack/prompts'; -import { loadKloProject, markKloSetupStepComplete, serializeKloProjectConfig } from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; +import { loadKtxProject, markKtxSetupStepComplete, serializeKtxProjectConfig } from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; import { withMenuOptionsSpacing, withMultiselectNavigation } from './prompt-navigation.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; -export type KloAgentTarget = 'claude-code' | 'codex' | 'cursor' | 'opencode' | 'universal'; -export type KloAgentScope = 'project' | 'global'; -export type KloAgentInstallMode = 'cli' | 'mcp' | 'both'; +export type KtxAgentTarget = 'claude-code' | 'codex' | 'cursor' | 'opencode' | 'universal'; +export type KtxAgentScope = 'project' | 'global'; +export type KtxAgentInstallMode = 'cli' | 'mcp' | 'both'; -export interface KloSetupAgentsArgs { +export interface KtxSetupAgentsArgs { projectDir: string; inputMode: 'auto' | 'disabled'; yes: boolean; agents: boolean; - target?: KloAgentTarget; - scope: KloAgentScope; - mode: KloAgentInstallMode; + target?: KtxAgentTarget; + scope: KtxAgentScope; + mode: KtxAgentInstallMode; skipAgents: boolean; } -export type KloSetupAgentsResult = +export type KtxSetupAgentsResult = | { status: 'ready'; projectDir: string; - installs: Array<{ target: KloAgentTarget; scope: KloAgentScope; mode: KloAgentInstallMode }>; + installs: Array<{ target: KtxAgentTarget; scope: KtxAgentScope; mode: KtxAgentInstallMode }>; } | { status: 'skipped'; projectDir: string } | { status: 'back'; projectDir: string } | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; -export interface KloAgentInstallManifest { +export interface KtxAgentInstallManifest { version: 1; projectDir: string; installedAt: string; - installs: Array<{ target: KloAgentTarget; scope: KloAgentScope; mode: KloAgentInstallMode }>; + installs: Array<{ target: KtxAgentTarget; scope: KtxAgentScope; mode: KtxAgentInstallMode }>; entries: Array<{ kind: 'file'; path: string } | { kind: 'json-key'; path: string; jsonPath: string[] }>; } -type InstallEntry = KloAgentInstallManifest['entries'][number]; +type InstallEntry = KtxAgentInstallManifest['entries'][number]; export function agentInstallManifestPath(projectDir: string): string { - return join(resolve(projectDir), '.klo/agents/install-manifest.json'); + return join(resolve(projectDir), '.ktx/agents/install-manifest.json'); } -export function plannedKloAgentFiles(input: { +export function plannedKtxAgentFiles(input: { projectDir: string; - target: KloAgentTarget; - scope: KloAgentScope; - mode: KloAgentInstallMode; + target: KtxAgentTarget; + scope: KtxAgentScope; + mode: KtxAgentInstallMode; }): InstallEntry[] { if (input.scope === 'global') { if (input.target === 'claude-code') { - return [{ kind: 'file', path: join(process.env.HOME ?? '', '.claude/skills/klo/SKILL.md') }]; + return [{ kind: 'file', path: join(process.env.HOME ?? '', '.claude/skills/ktx/SKILL.md') }]; } if (input.target === 'codex') { return [ - { kind: 'file', path: join(process.env.CODEX_HOME ?? join(process.env.HOME ?? '', '.codex'), 'skills/klo/SKILL.md') }, + { kind: 'file', path: join(process.env.CODEX_HOME ?? join(process.env.HOME ?? '', '.codex'), 'skills/ktx/SKILL.md') }, ]; } throw new Error(`Global ${input.target} installation is not supported; use --project.`); } const root = resolve(input.projectDir); - const cliEntries: Partial> = { - 'claude-code': { kind: 'file', path: join(root, '.claude/skills/klo/SKILL.md') }, - codex: { kind: 'file', path: join(root, '.agents/skills/klo/SKILL.md') }, - cursor: { kind: 'file', path: join(root, '.cursor/rules/klo.mdc') }, - opencode: { kind: 'file', path: join(root, '.opencode/commands/klo.md') }, - universal: { kind: 'file', path: join(root, '.agents/skills/klo/SKILL.md') }, + const cliEntries: Partial> = { + 'claude-code': { kind: 'file', path: join(root, '.claude/skills/ktx/SKILL.md') }, + codex: { kind: 'file', path: join(root, '.agents/skills/ktx/SKILL.md') }, + cursor: { kind: 'file', path: join(root, '.cursor/rules/ktx.mdc') }, + opencode: { kind: 'file', path: join(root, '.opencode/commands/ktx.md') }, + universal: { kind: 'file', path: join(root, '.agents/skills/ktx/SKILL.md') }, }; - const mcpEntries: Record = { - 'claude-code': { kind: 'json-key', path: join(root, '.mcp.json'), jsonPath: ['mcpServers', 'klo'] }, - codex: { kind: 'json-key', path: join(root, '.agents/mcp/klo.json'), jsonPath: ['mcpServers', 'klo'] }, - cursor: { kind: 'json-key', path: join(root, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'klo'] }, - opencode: { kind: 'json-key', path: join(root, '.opencode/mcp.json'), jsonPath: ['mcpServers', 'klo'] }, - universal: { kind: 'json-key', path: join(root, '.agents/mcp/klo.json'), jsonPath: ['mcpServers', 'klo'] }, + const mcpEntries: Record = { + 'claude-code': { kind: 'json-key', path: join(root, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] }, + codex: { kind: 'json-key', path: join(root, '.agents/mcp/ktx.json'), jsonPath: ['mcpServers', 'ktx'] }, + cursor: { kind: 'json-key', path: join(root, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'ktx'] }, + opencode: { kind: 'json-key', path: join(root, '.opencode/mcp.json'), jsonPath: ['mcpServers', 'ktx'] }, + universal: { kind: 'json-key', path: join(root, '.agents/mcp/ktx.json'), jsonPath: ['mcpServers', 'ktx'] }, }; return [ ...(input.mode === 'cli' || input.mode === 'both' ? [cliEntries[input.target]] : []), @@ -85,28 +85,28 @@ export function plannedKloAgentFiles(input: { ].filter((entry): entry is InstallEntry => entry !== undefined); } -function cliInstructionContent(input: { projectDir: string; target: KloAgentTarget }): string { +function cliInstructionContent(input: { projectDir: string; target: KtxAgentTarget }): string { return [ '---', - 'name: klo', - 'description: Use local KLO semantic context, wiki knowledge, and safe SQL execution for this project.', + 'name: ktx', + 'description: Use local KTX semantic context, wiki knowledge, and safe SQL execution for this project.', '---', '', - '# KLO Local Context', + '# KTX Local Context', '', `Use this project with \`--project-dir ${input.projectDir}\`.`, '', - 'Agents must not print secrets, credential references, environment variable values, or file contents from `.klo/secrets`.', + 'Agents must not print secrets, credential references, environment variable values, or file contents from `.ktx/secrets`.', '', 'Available commands:', '', - `- \`klo agent context --json --project-dir ${input.projectDir}\``, - `- \`klo agent sl list --json --project-dir ${input.projectDir}\``, - `- \`klo agent sl read --json --project-dir ${input.projectDir}\``, - `- \`klo agent sl query --json --project-dir ${input.projectDir} --connection-id --query-file --execute --max-rows 100\``, - `- \`klo agent wiki search --json --project-dir ${input.projectDir}\``, - `- \`klo agent wiki read --json --project-dir ${input.projectDir}\``, - `- \`klo agent sql execute --json --project-dir ${input.projectDir} --connection-id --sql-file --max-rows 100\``, + `- \`ktx agent context --json --project-dir ${input.projectDir}\``, + `- \`ktx agent sl list --json --project-dir ${input.projectDir}\``, + `- \`ktx agent sl read --json --project-dir ${input.projectDir}\``, + `- \`ktx agent sl query --json --project-dir ${input.projectDir} --connection-id --query-file --execute --max-rows 100\``, + `- \`ktx agent wiki search --json --project-dir ${input.projectDir}\``, + `- \`ktx agent wiki read --json --project-dir ${input.projectDir}\``, + `- \`ktx agent sql execute --json --project-dir ${input.projectDir} --connection-id --sql-file --max-rows 100\``, '', 'SQL execution is read-only, requires an explicit row limit, and should use the smallest useful limit.', '', @@ -115,7 +115,7 @@ function cliInstructionContent(input: { projectDir: string; target: KloAgentTarg function mcpConfig(projectDir: string): Record { return { - command: 'klo', + command: 'ktx', args: ['--project-dir', projectDir, 'serve', '--mcp', 'stdio', '--semantic-compute', '--execute-queries'], env: {}, }; @@ -151,15 +151,15 @@ async function removeJsonKey(path: string, jsonPath: string[]): Promise { await writeFile(path, `${JSON.stringify(root, null, 2)}\n`, 'utf-8'); } -export async function readKloAgentInstallManifest(projectDir: string): Promise { +export async function readKtxAgentInstallManifest(projectDir: string): Promise { try { - return JSON.parse(await readFile(agentInstallManifestPath(projectDir), 'utf-8')) as KloAgentInstallManifest; + return JSON.parse(await readFile(agentInstallManifestPath(projectDir), 'utf-8')) as KtxAgentInstallManifest; } catch { return null; } } -async function writeManifest(projectDir: string, manifest: KloAgentInstallManifest): Promise { +async function writeManifest(projectDir: string, manifest: KtxAgentInstallManifest): Promise { const path = agentInstallManifestPath(projectDir); await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8'); @@ -171,11 +171,11 @@ function entryKey(entry: InstallEntry): string { function mergeManifest( projectDir: string, - existing: KloAgentInstallManifest | null, - installs: KloAgentInstallManifest['installs'], + existing: KtxAgentInstallManifest | null, + installs: KtxAgentInstallManifest['installs'], entries: InstallEntry[], -): KloAgentInstallManifest { - const installMap = new Map(); +): KtxAgentInstallManifest { + const installMap = new Map(); for (const install of [...(existing?.installs ?? []), ...installs]) { installMap.set(`${install.target}:${install.scope}:${install.mode}`, install); } @@ -192,10 +192,10 @@ function mergeManifest( }; } -export async function removeKloAgentInstall(projectDir: string, io: KloCliIo): Promise { - const manifest = await readKloAgentInstallManifest(projectDir); +export async function removeKtxAgentInstall(projectDir: string, io: KtxCliIo): Promise { + const manifest = await readKtxAgentInstallManifest(projectDir); if (!manifest) { - io.stdout.write('No KLO agent installation manifest found.\n'); + io.stdout.write('No KTX agent installation manifest found.\n'); return 0; } for (const entry of manifest.entries) { @@ -203,11 +203,11 @@ export async function removeKloAgentInstall(projectDir: string, io: KloCliIo): P if (entry.kind === 'json-key') await removeJsonKey(entry.path, entry.jsonPath).catch(() => undefined); } await rm(agentInstallManifestPath(projectDir), { force: true }); - io.stdout.write('Removed KLO agent integration files from manifest.\n'); + io.stdout.write('Removed KTX agent integration files from manifest.\n'); return 0; } -export interface KloSetupAgentsPromptAdapter { +export interface KtxSetupAgentsPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; multiselect(options: { message: string; @@ -217,11 +217,11 @@ export interface KloSetupAgentsPromptAdapter { cancel(message: string): void; } -export interface KloSetupAgentsDeps { - prompts?: KloSetupAgentsPromptAdapter; +export interface KtxSetupAgentsDeps { + prompts?: KtxSetupAgentsPromptAdapter; } -function createPromptAdapter(): KloSetupAgentsPromptAdapter { +function createPromptAdapter(): KtxSetupAgentsPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -247,11 +247,11 @@ function createPromptAdapter(): KloSetupAgentsPromptAdapter { async function installTarget(input: { projectDir: string; - target: KloAgentTarget; - scope: KloAgentScope; - mode: KloAgentInstallMode; + target: KtxAgentTarget; + scope: KtxAgentScope; + mode: KtxAgentInstallMode; }): Promise { - const entries = plannedKloAgentFiles(input); + const entries = plannedKtxAgentFiles(input); for (const entry of entries) { if (entry.kind === 'file') { await mkdir(dirname(entry.path), { recursive: true }); @@ -264,15 +264,15 @@ async function installTarget(input: { } async function markAgentsComplete(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); - await writeFile(project.configPath, serializeKloProjectConfig(markKloSetupStepComplete(project.config, 'agents')), 'utf-8'); + const project = await loadKtxProject({ projectDir }); + await writeFile(project.configPath, serializeKtxProjectConfig(markKtxSetupStepComplete(project.config, 'agents')), 'utf-8'); } -export async function runKloSetupAgentsStep( - args: KloSetupAgentsArgs, - io: KloCliIo, - deps: KloSetupAgentsDeps = {}, -): Promise { +export async function runKtxSetupAgentsStep( + args: KtxSetupAgentsArgs, + io: KtxCliIo, + deps: KtxSetupAgentsDeps = {}, +): Promise { if (args.skipAgents) { io.stdout.write('Agent integration skipped.\n'); return { status: 'skipped', projectDir: args.projectDir }; @@ -286,7 +286,7 @@ export async function runKloSetupAgentsStep( args.inputMode === 'disabled' ? args.mode : ((await prompts.select({ - message: 'How should agents use this KLO project?', + message: 'How should agents use this KTX project?', options: [ { value: 'cli', label: 'CLI tools and skills' }, { value: 'mcp', label: 'MCP server config' }, @@ -294,7 +294,7 @@ export async function runKloSetupAgentsStep( { value: 'skip', label: 'Skip' }, { value: 'back', label: 'Back' }, ], - })) as KloAgentInstallMode | 'skip' | 'back'); + })) as KtxAgentInstallMode | 'skip' | 'back'); if (mode === 'back') return { status: 'back', projectDir: args.projectDir }; if (mode === 'skip') return { status: 'skipped', projectDir: args.projectDir }; @@ -304,7 +304,7 @@ export async function runKloSetupAgentsStep( : args.inputMode === 'disabled' ? [] : ((await prompts.multiselect({ - message: withMultiselectNavigation('Which agent targets should KLO install?'), + message: withMultiselectNavigation('Which agent targets should KTX install?'), options: [ { value: 'claude-code', label: 'Claude Code' }, { value: 'codex', label: 'Codex' }, @@ -314,8 +314,8 @@ export async function runKloSetupAgentsStep( { value: 'back', label: 'Back' }, ], required: true, - })) as KloAgentTarget[]); - if (targets.includes('back' as KloAgentTarget)) return { status: 'back', projectDir: args.projectDir }; + })) as KtxAgentTarget[]); + if (targets.includes('back' as KtxAgentTarget)) return { status: 'back', projectDir: args.projectDir }; if (targets.length === 0) { io.stderr.write('Missing agent target: pass --target or use interactive setup.\n'); return { status: 'missing-input', projectDir: args.projectDir }; @@ -325,7 +325,7 @@ export async function runKloSetupAgentsStep( const entries: InstallEntry[] = []; try { for (const install of installs) entries.push(...(await installTarget({ projectDir: args.projectDir, ...install }))); - await writeManifest(args.projectDir, mergeManifest(args.projectDir, await readKloAgentInstallManifest(args.projectDir), installs, entries)); + await writeManifest(args.projectDir, mergeManifest(args.projectDir, await readKtxAgentInstallManifest(args.projectDir), installs, entries)); await markAgentsComplete(args.projectDir); io.stdout.write(`Agent integration installed for ${installs.map((install) => install.target).join(', ')}.\n`); return { status: 'ready', projectDir: args.projectDir, installs }; diff --git a/packages/cli/src/setup-context.test.ts b/packages/cli/src/setup-context.test.ts index 7bcf27ce..2cebff8d 100644 --- a/packages/cli/src/setup-context.test.ts +++ b/packages/cli/src/setup-context.test.ts @@ -5,10 +5,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { contextBuildCommands, - readKloSetupContextState, - runKloSetupContextCommand, - runKloSetupContextStep, - writeKloSetupContextState, + readKtxSetupContextState, + runKtxSetupContextCommand, + runKtxSetupContextStep, + writeKtxSetupContextState, } from './setup-context.js'; function makeIo() { @@ -34,7 +34,7 @@ function makeIo() { async function writeReadyProject(projectDir: string) { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -124,7 +124,7 @@ describe('setup context build state', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-context-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-context-')); }); afterEach(async () => { @@ -132,9 +132,9 @@ describe('setup context build state', () => { }); it('reads missing state as not started and writes durable command metadata without secrets', async () => { - await expect(readKloSetupContextState(tempDir)).resolves.toMatchObject({ status: 'not_started' }); + await expect(readKtxSetupContextState(tempDir)).resolves.toMatchObject({ status: 'not_started' }); - await writeKloSetupContextState(tempDir, { + await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-abc123', status: 'running', startedAt: '2026-05-09T10:00:00.000Z', @@ -147,16 +147,16 @@ describe('setup context build state', () => { commands: contextBuildCommands(tempDir, 'setup-context-local-abc123'), }); - const state = await readKloSetupContextState(tempDir); + const state = await readKtxSetupContextState(tempDir); expect(state).toMatchObject({ runId: 'setup-context-local-abc123', status: 'running', primarySourceConnectionIds: ['warehouse'], contextSourceConnectionIds: ['docs'], commands: { - watch: `klo setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, - status: `klo setup context status setup-context-local-abc123 --project-dir ${tempDir}`, - resume: `klo setup --project-dir ${tempDir}`, + watch: `ktx setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, + status: `ktx setup context status setup-context-local-abc123 --project-dir ${tempDir}`, + resume: `ktx setup --project-dir ${tempDir}`, }, }); expect(JSON.stringify(state)).not.toContain('DATABASE_URL'); @@ -175,7 +175,7 @@ describe('setup context build state', () => { })); await expect( - runKloSetupContextStep( + runKtxSetupContextStep( { projectDir: tempDir, inputMode: 'disabled' }, io.io, { @@ -199,13 +199,13 @@ describe('setup context build state', () => { expect.objectContaining({ onDetach: expect.any(Function) }), ); expect(verifyContextReady).toHaveBeenCalledWith(tempDir); - expect(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')).toContain(' - context'); - await expect(readKloSetupContextState(tempDir)).resolves.toMatchObject({ + expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).toContain(' - context'); + await expect(readKtxSetupContextState(tempDir)).resolves.toMatchObject({ runId: 'setup-context-local-abc123', status: 'completed', completedAt: '2026-05-09T10:00:00.000Z', }); - expect(io.stdout()).toContain('KLO context is ready for agents.'); + expect(io.stdout()).toContain('KTX context is ready for agents.'); }); it('marks context complete without prompting when initial source ingest already made agent context', async () => { @@ -219,7 +219,7 @@ describe('setup context build state', () => { const runContextBuildMock = vi.fn(async () => ({ exitCode: 0, detached: false })); await expect( - runKloSetupContextStep( + runKtxSetupContextStep( { projectDir: tempDir, inputMode: 'auto' }, io.io, { @@ -237,14 +237,14 @@ describe('setup context build state', () => { ).resolves.toEqual({ status: 'ready', projectDir: tempDir, runId: 'setup-context-local-existing' }); expect(runContextBuildMock).not.toHaveBeenCalled(); - expect(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')).toContain(' - context'); - await expect(readKloSetupContextState(tempDir)).resolves.toMatchObject({ + expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).toContain(' - context'); + await expect(readKtxSetupContextState(tempDir)).resolves.toMatchObject({ runId: 'setup-context-local-existing', status: 'completed', completedAt: '2026-05-09T10:00:00.000Z', contextSourceConnectionIds: ['docs'], }); - expect(io.stdout()).toContain('KLO context is ready for agents.'); + expect(io.stdout()).toContain('KTX context is ready for agents.'); }); it('does not mark context ready until primary scans have completed description enrichment', async () => { @@ -264,7 +264,7 @@ describe('setup context build state', () => { }); await expect( - runKloSetupContextStep( + runKtxSetupContextStep( { projectDir: tempDir, inputMode: 'disabled' }, io.io, { @@ -292,7 +292,7 @@ describe('setup context build state', () => { }); await expect( - runKloSetupContextStep( + runKtxSetupContextStep( { projectDir: tempDir, inputMode: 'disabled' }, io.io, { @@ -309,7 +309,7 @@ describe('setup context build state', () => { it('refuses empty setup context builds', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'connections: {}', @@ -330,19 +330,19 @@ describe('setup context build state', () => { const io = makeIo(); await expect( - runKloSetupContextStep( + runKtxSetupContextStep( { projectDir: tempDir, inputMode: 'disabled' }, io.io, { runIdFactory: () => 'setup-context-local-empty' }, ), ).resolves.toEqual({ status: 'failed', projectDir: tempDir }); - expect(io.stderr()).toContain('No primary or context sources are configured for a KLO context build.'); + expect(io.stderr()).toContain('No primary or context sources are configured for a KTX context build.'); }); it('prints JSON setup context command status with watch and resume commands', async () => { - await mkdir(join(tempDir, '.klo', 'setup'), { recursive: true }); - await writeKloSetupContextState(tempDir, { + await mkdir(join(tempDir, '.ktx', 'setup'), { recursive: true }); + await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-abc123', status: 'detached', startedAt: '2026-05-09T10:00:00.000Z', @@ -357,7 +357,7 @@ describe('setup context build state', () => { const io = makeIo(); await expect( - runKloSetupContextCommand( + runKtxSetupContextCommand( { command: 'status', projectDir: tempDir, runId: 'setup-context-local-abc123', json: true }, io.io, ), @@ -367,8 +367,8 @@ describe('setup context build state', () => { ready: false, status: 'detached', runId: 'setup-context-local-abc123', - watchCommand: `klo setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, - statusCommand: `klo setup context status setup-context-local-abc123 --project-dir ${tempDir}`, + watchCommand: `ktx setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, + statusCommand: `ktx setup context status setup-context-local-abc123 --project-dir ${tempDir}`, }); }); @@ -378,7 +378,7 @@ describe('setup context build state', () => { const runContextBuildMock = vi.fn(async () => ({ exitCode: 0, detached: false })); await expect( - runKloSetupContextCommand( + runKtxSetupContextCommand( { command: 'build', projectDir: tempDir, inputMode: 'auto' }, io.io, { diff --git a/packages/cli/src/setup-context.ts b/packages/cli/src/setup-context.ts index da354f34..042c5b1e 100644 --- a/packages/cli/src/setup-context.ts +++ b/packages/cli/src/setup-context.ts @@ -3,18 +3,18 @@ import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { cancel, isCancel, select } from '@clack/prompts'; import { - type KloLocalProject, - loadKloProject, - markKloSetupStepComplete, - serializeKloProjectConfig, -} from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; + type KtxLocalProject, + loadKtxProject, + markKtxSetupStepComplete, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; import { buildPublicIngestPlan } from './public-ingest.js'; import { runContextBuild } from './context-build-view.js'; import { withMenuOptionsSpacing } from './prompt-navigation.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; -export type KloSetupContextBuildStatus = +export type KtxSetupContextBuildStatus = | 'not_started' | 'running' | 'detached' @@ -24,7 +24,7 @@ export type KloSetupContextBuildStatus = | 'interrupted' | 'stale'; -export interface KloSetupContextCommands { +export interface KtxSetupContextCommands { build: string; watch: string; status: string; @@ -32,9 +32,9 @@ export interface KloSetupContextCommands { resume: string; } -export interface KloSetupContextState { +export interface KtxSetupContextState { runId?: string; - status: KloSetupContextBuildStatus; + status: KtxSetupContextBuildStatus; startedAt?: string; updatedAt?: string; completedAt?: string; @@ -43,13 +43,13 @@ export interface KloSetupContextState { reportIds: string[]; artifactPaths: string[]; retryableFailedTargets: string[]; - commands: KloSetupContextCommands; + commands: KtxSetupContextCommands; failureReason?: string; } -export interface KloSetupContextStatusSummary { +export interface KtxSetupContextStatusSummary { ready: boolean; - status: KloSetupContextBuildStatus; + status: KtxSetupContextBuildStatus; runId?: string; watchCommand?: string; statusCommand?: string; @@ -57,7 +57,7 @@ export interface KloSetupContextStatusSummary { detail?: string; } -export interface KloSetupContextReadiness { +export interface KtxSetupContextReadiness { ready: boolean; agentContextReady: boolean; semanticSearchReady: boolean; @@ -65,7 +65,7 @@ export interface KloSetupContextReadiness { failedTargets?: string[]; } -export type KloSetupContextResult = +export type KtxSetupContextResult = | { status: 'ready'; projectDir: string; runId: string } | { status: 'skipped'; projectDir: string } | { status: 'detached'; projectDir: string; runId: string } @@ -74,7 +74,7 @@ export type KloSetupContextResult = | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; -export interface KloSetupContextStepArgs { +export interface KtxSetupContextStepArgs { projectDir: string; inputMode: 'auto' | 'disabled'; forcePrompt?: boolean; @@ -82,35 +82,35 @@ export interface KloSetupContextStepArgs { prompt?: boolean; } -export type KloSetupContextCommandArgs = +export type KtxSetupContextCommandArgs = | { command: 'build'; projectDir: string; inputMode: 'auto' | 'disabled' } | { command: 'watch'; projectDir: string; runId?: string; inputMode: 'auto' | 'disabled' } | { command: 'status'; projectDir: string; runId?: string; json: boolean } | { command: 'stop'; projectDir: string; runId?: string }; -export interface KloSetupContextPromptAdapter { +export interface KtxSetupContextPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; cancel(message: string): void; } -export interface KloSetupContextDeps { - prompts?: KloSetupContextPromptAdapter; +export interface KtxSetupContextDeps { + prompts?: KtxSetupContextPromptAdapter; runIdFactory?: () => string; now?: () => Date; runContextBuild?: typeof runContextBuild; - verifyContextReady?: (projectDir: string) => Promise; + verifyContextReady?: (projectDir: string) => Promise; } -interface KloSetupContextTargets { +interface KtxSetupContextTargets { primarySourceConnectionIds: string[]; contextSourceConnectionIds: string[]; } -const SETUP_CONTEXT_STATE_PATH = ['.klo', 'setup', 'context-build.json'] as const; +const SETUP_CONTEXT_STATE_PATH = ['.ktx', 'setup', 'context-build.json'] as const; const LIVE_DATABASE_ADAPTER = 'live-database'; const SCAN_REPORT_FILE = 'scan-report.json'; -function createPromptAdapter(): KloSetupContextPromptAdapter { +function createPromptAdapter(): KtxSetupContextPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -139,19 +139,19 @@ async function pathExists(path: string): Promise { } } -export function contextBuildCommands(projectDir: string, runId?: string): KloSetupContextCommands { +export function contextBuildCommands(projectDir: string, runId?: string): KtxSetupContextCommands { const resolvedProjectDir = resolve(projectDir); const runIdArg = runId ? ` ${runId}` : ''; return { - build: `klo setup context build --project-dir ${resolvedProjectDir}`, - watch: `klo setup context watch${runIdArg} --project-dir ${resolvedProjectDir}`, - status: `klo setup context status${runIdArg} --project-dir ${resolvedProjectDir}`, - stop: `klo setup context stop${runIdArg} --project-dir ${resolvedProjectDir}`, - resume: `klo setup --project-dir ${resolvedProjectDir}`, + build: `ktx setup context build --project-dir ${resolvedProjectDir}`, + watch: `ktx setup context watch${runIdArg} --project-dir ${resolvedProjectDir}`, + status: `ktx setup context status${runIdArg} --project-dir ${resolvedProjectDir}`, + stop: `ktx setup context stop${runIdArg} --project-dir ${resolvedProjectDir}`, + resume: `ktx setup --project-dir ${resolvedProjectDir}`, }; } -function notStartedState(projectDir: string): KloSetupContextState { +function notStartedState(projectDir: string): KtxSetupContextState { return { status: 'not_started', primarySourceConnectionIds: [], @@ -163,11 +163,11 @@ function notStartedState(projectDir: string): KloSetupContextState { }; } -function normalizeState(projectDir: string, value: unknown): KloSetupContextState { +function normalizeState(projectDir: string, value: unknown): KtxSetupContextState { if (typeof value !== 'object' || value === null || Array.isArray(value)) { return notStartedState(projectDir); } - const record = value as Partial; + const record = value as Partial; const status = record.status ?? 'not_started'; const runId = typeof record.runId === 'string' && record.runId.length > 0 ? record.runId : undefined; return { @@ -196,7 +196,7 @@ function normalizeState(projectDir: string, value: unknown): KloSetupContextStat }; } -export async function readKloSetupContextState(projectDir: string): Promise { +export async function readKtxSetupContextState(projectDir: string): Promise { const filePath = statePath(projectDir); if (!(await pathExists(filePath))) { return notStartedState(projectDir); @@ -204,9 +204,9 @@ export async function readKloSetupContextState(projectDir: string): Promise { +export async function writeKtxSetupContextState(projectDir: string, state: KtxSetupContextState): Promise { const resolvedProjectDir = resolve(projectDir); - await mkdir(join(resolvedProjectDir, '.klo', 'setup'), { recursive: true }); + await mkdir(join(resolvedProjectDir, '.ktx', 'setup'), { recursive: true }); const normalized = normalizeState(resolvedProjectDir, { ...state, commands: contextBuildCommands(resolvedProjectDir, state.runId), @@ -215,9 +215,9 @@ export async function writeKloSetupContextState(projectDir: string, state: KloSe } export function setupContextStatusFromState( - state: KloSetupContextState, + state: KtxSetupContextState, options: { completedStep: boolean } = { completedStep: false }, -): KloSetupContextStatusSummary { +): KtxSetupContextStatusSummary { const status = options.completedStep && state.status === 'not_started' ? 'completed' : state.status; const ready = options.completedStep && status === 'completed'; return { @@ -234,7 +234,7 @@ function runIdFactory(): string { return `setup-context-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; } -function listContextTargets(project: KloLocalProject): KloSetupContextTargets { +function listContextTargets(project: KtxLocalProject): KtxSetupContextTargets { if (Object.keys(project.config.connections).length === 0) { return { primarySourceConnectionIds: [], contextSourceConnectionIds: [] }; } @@ -249,7 +249,7 @@ function listContextTargets(project: KloLocalProject): KloSetupContextTargets { }; } -function missingCapabilities(project: KloLocalProject): string[] { +function missingCapabilities(project: KtxLocalProject): string[] { const missing: string[] = []; const llm = project.config.llm; if (llm.provider.backend === 'none' || !llm.models.default) { @@ -374,8 +374,8 @@ async function verifyPrimarySourceScans( return { ready: details.length === 0, details }; } -async function defaultVerifyContextReady(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); +async function defaultVerifyContextReady(projectDir: string): Promise { + const project = await loadKtxProject({ projectDir }); const targets = listContextTargets(project); const primarySourceScans = await verifyPrimarySourceScans(projectDir, targets.primarySourceConnectionIds); const semanticLayerContextReady = await hasFileWithExtension( @@ -411,17 +411,17 @@ async function defaultVerifyContextReady(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await writeFile( project.configPath, - serializeKloProjectConfig(markKloSetupStepComplete(project.config, 'context')), + serializeKtxProjectConfig(markKtxSetupStepComplete(project.config, 'context')), 'utf-8', ); } -function writeBuildHeader(projectDir: string, runId: string, io: KloCliIo): void { +function writeBuildHeader(projectDir: string, runId: string, io: KtxCliIo): void { const commands = contextBuildCommands(projectDir, runId); - io.stdout.write('\nKLO context build\n'); + io.stdout.write('\nKTX context build\n'); io.stdout.write(`Run: ${runId}\n`); io.stdout.write(`Project: ${resolve(projectDir)}\n\n`); io.stdout.write('Detach: press d to leave this running.\n'); @@ -429,8 +429,8 @@ function writeBuildHeader(projectDir: string, runId: string, io: KloCliIo): void io.stdout.write(`Status: ${commands.status}\n\n`); } -function writeMissingCapabilities(missing: string[], io: KloCliIo): void { - io.stderr.write('KLO cannot build agent-ready context yet.\n\n'); +function writeMissingCapabilities(missing: string[], io: KtxCliIo): void { + io.stderr.write('KTX cannot build agent-ready context yet.\n\n'); io.stderr.write('Missing:\n'); for (const item of missing) { io.stderr.write(` ${item}\n`); @@ -438,16 +438,16 @@ function writeMissingCapabilities(missing: string[], io: KloCliIo): void { io.stderr.write('\nFix this in setup before building context.\n'); } -function writeSkippedContext(projectDir: string, io: KloCliIo): void { - io.stdout.write('\nKLO is configured, but context has not been built yet.\n\n'); - io.stdout.write('Agents were not connected because KLO has not prepared searchable context for them.\n\n'); - io.stdout.write(`Resume setup:\n klo setup --project-dir ${resolve(projectDir)}\n\n`); - io.stdout.write(`Build context directly:\n klo setup context build --project-dir ${resolve(projectDir)}\n\n`); - io.stdout.write(`Check status:\n klo status --project-dir ${resolve(projectDir)}\n`); +function writeSkippedContext(projectDir: string, io: KtxCliIo): void { + io.stdout.write('\nKTX is configured, but context has not been built yet.\n\n'); + io.stdout.write('Agents were not connected because KTX has not prepared searchable context for them.\n\n'); + io.stdout.write(`Resume setup:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`); + io.stdout.write(`Build context directly:\n ktx setup context build --project-dir ${resolve(projectDir)}\n\n`); + io.stdout.write(`Check status:\n ktx status --project-dir ${resolve(projectDir)}\n`); } -function writeSuccess(readiness: KloSetupContextReadiness, targets: KloSetupContextTargets, io: KloCliIo): void { - io.stdout.write('\nKLO context is ready for agents.\n\n'); +function writeSuccess(readiness: KtxSetupContextReadiness, targets: KtxSetupContextTargets, io: KtxCliIo): void { + io.stdout.write('\nKTX context is ready for agents.\n\n'); io.stdout.write('Primary sources:\n'); if (targets.primarySourceConnectionIds.length === 0) { io.stdout.write(' none\n'); @@ -469,19 +469,19 @@ function writeSuccess(readiness: KloSetupContextReadiness, targets: KloSetupCont io.stdout.write(` Semantic search: ${readiness.semanticSearchReady ? 'ready' : 'not ready'}\n`); } -function writeExistingContextSuccess(readiness: KloSetupContextReadiness, io: KloCliIo): void { - io.stdout.write('\nKLO context is ready for agents.\n\n'); +function writeExistingContextSuccess(readiness: KtxSetupContextReadiness, io: KtxCliIo): void { + io.stdout.write('\nKTX context is ready for agents.\n\n'); io.stdout.write('Existing context artifacts were found from setup ingest.\n\n'); io.stdout.write('Verification:\n'); io.stdout.write(` Agent context: ${readiness.agentContextReady ? 'ready' : 'not ready'}\n`); io.stdout.write(` Semantic search: ${readiness.semanticSearchReady ? 'ready' : 'not ready'}\n`); } -async function promptForBuild(prompts: KloSetupContextPromptAdapter): Promise<'build' | 'skip' | 'back'> { +async function promptForBuild(prompts: KtxSetupContextPromptAdapter): Promise<'build' | 'skip' | 'back'> { return (await prompts.select({ message: - 'Build KLO context for agents?\n\n' + - 'KLO is fully configured and ready to build context. This may take a few minutes to a few hours.', + 'Build KTX context for agents?\n\n' + + 'KTX is fully configured and ready to build context. This may take a few minutes to a few hours.', options: [ { value: 'build', label: 'Build context now (recommended)' }, { value: 'skip', label: 'Leave context unbuilt and exit setup' }, @@ -491,16 +491,16 @@ async function promptForBuild(prompts: KloSetupContextPromptAdapter): Promise<'b } async function runBuild( - args: KloSetupContextStepArgs, - io: KloCliIo, - deps: KloSetupContextDeps, - project: KloLocalProject, - targets: KloSetupContextTargets, -): Promise { + args: KtxSetupContextStepArgs, + io: KtxCliIo, + deps: KtxSetupContextDeps, + project: KtxLocalProject, + targets: KtxSetupContextTargets, +): Promise { const now = deps.now ?? (() => new Date()); const runId = deps.runIdFactory?.() ?? runIdFactory(); const startedAt = now().toISOString(); - const runningState: KloSetupContextState = { + const runningState: KtxSetupContextState = { runId, status: 'running', startedAt, @@ -512,7 +512,7 @@ async function runBuild( retryableFailedTargets: [], commands: contextBuildCommands(args.projectDir, runId), }; - await writeKloSetupContextState(args.projectDir, runningState); + await writeKtxSetupContextState(args.projectDir, runningState); const contextBuild = deps.runContextBuild ?? runContextBuild; const buildResult = await contextBuild( @@ -527,7 +527,7 @@ async function runBuild( { onDetach: () => { const resolvedDir = resolve(args.projectDir); - mkdirSync(join(resolvedDir, '.klo', 'setup'), { recursive: true }); + mkdirSync(join(resolvedDir, '.ktx', 'setup'), { recursive: true }); const detachedState = normalizeState(resolvedDir, { ...runningState, status: 'detached', @@ -539,12 +539,12 @@ async function runBuild( ); if (buildResult.detached) { const updatedAt = now().toISOString(); - await writeKloSetupContextState(args.projectDir, { ...runningState, status: 'detached', updatedAt }); + await writeKtxSetupContextState(args.projectDir, { ...runningState, status: 'detached', updatedAt }); return { status: 'detached', projectDir: args.projectDir, runId }; } if (buildResult.exitCode !== 0) { const updatedAt = now().toISOString(); - await writeKloSetupContextState(args.projectDir, { + await writeKtxSetupContextState(args.projectDir, { ...runningState, status: 'failed', updatedAt, @@ -557,14 +557,14 @@ async function runBuild( const readiness = await (deps.verifyContextReady ?? defaultVerifyContextReady)(args.projectDir); if (!readiness.ready) { const updatedAt = now().toISOString(); - await writeKloSetupContextState(args.projectDir, { + await writeKtxSetupContextState(args.projectDir, { ...runningState, status: 'failed', updatedAt, retryableFailedTargets: readiness.failedTargets ?? [], failureReason: readiness.details.join(' '), }); - io.stderr.write('KLO context build did not pass agent-readiness verification.\n'); + io.stderr.write('KTX context build did not pass agent-readiness verification.\n'); for (const detail of readiness.details) { io.stderr.write(` ${detail}\n`); } @@ -573,7 +573,7 @@ async function runBuild( await markContextComplete(project.projectDir); const completedAt = now().toISOString(); - await writeKloSetupContextState(args.projectDir, { + await writeKtxSetupContextState(args.projectDir, { ...runningState, status: 'completed', updatedAt: completedAt, @@ -585,11 +585,11 @@ async function runBuild( } async function completeExistingContext( - args: KloSetupContextStepArgs, - io: KloCliIo, - deps: KloSetupContextDeps, - targets: KloSetupContextTargets, -): Promise { + args: KtxSetupContextStepArgs, + io: KtxCliIo, + deps: KtxSetupContextDeps, + targets: KtxSetupContextTargets, +): Promise { const readiness = await (deps.verifyContextReady ?? defaultVerifyContextReady)(args.projectDir); if (!readiness.ready) { return null; @@ -599,7 +599,7 @@ async function completeExistingContext( const completedAt = now().toISOString(); const runId = deps.runIdFactory?.() ?? runIdFactory(); await markContextComplete(args.projectDir); - await writeKloSetupContextState(args.projectDir, { + await writeKtxSetupContextState(args.projectDir, { runId, status: 'completed', startedAt: completedAt, @@ -616,14 +616,14 @@ async function completeExistingContext( return { status: 'ready', projectDir: args.projectDir, runId }; } -export async function runKloSetupContextStep( - args: KloSetupContextStepArgs, - io: KloCliIo, - deps: KloSetupContextDeps = {}, -): Promise { +export async function runKtxSetupContextStep( + args: KtxSetupContextStepArgs, + io: KtxCliIo, + deps: KtxSetupContextDeps = {}, +): Promise { try { - const project = await loadKloProject({ projectDir: args.projectDir }); - const existingState = await readKloSetupContextState(args.projectDir); + const project = await loadKtxProject({ projectDir: args.projectDir }); + const existingState = await readKtxSetupContextState(args.projectDir); if (project.config.setup?.completed_steps.includes('context') === true && existingState.status === 'completed') { return { status: 'ready', projectDir: args.projectDir, runId: existingState.runId ?? 'setup-context-completed' }; } @@ -646,7 +646,7 @@ export async function runKloSetupContextStep( if (choice === 'status') { const commands = contextBuildCommands(args.projectDir, existingState.runId); io.stdout.write(`\nRun: ${commands.status}\n`); - io.stdout.write(`Log: ${join(resolve(args.projectDir), '.klo', 'setup', 'context-build.log')}\n`); + io.stdout.write(`Log: ${join(resolve(args.projectDir), '.ktx', 'setup', 'context-build.log')}\n`); return { status: 'detached', projectDir: args.projectDir, runId: existingState.runId ?? '' }; } if (choice === 'back') { @@ -659,7 +659,7 @@ export async function runKloSetupContextStep( if (args.allowEmpty === true) { return { status: 'skipped', projectDir: args.projectDir }; } - io.stderr.write('No primary or context sources are configured for a KLO context build.\n'); + io.stderr.write('No primary or context sources are configured for a KTX context build.\n'); return { status: 'failed', projectDir: args.projectDir }; } @@ -694,16 +694,16 @@ export async function runKloSetupContextStep( } } -function stateMatchesRunId(state: KloSetupContextState, runId: string | undefined): boolean { +function stateMatchesRunId(state: KtxSetupContextState, runId: string | undefined): boolean { return !runId || state.runId === runId; } -function statusPayload(state: KloSetupContextState): KloSetupContextStatusSummary { +function statusPayload(state: KtxSetupContextState): KtxSetupContextStatusSummary { return setupContextStatusFromState(state, { completedStep: state.status === 'completed' }); } -function writeContextStatus(state: KloSetupContextState, io: KloCliIo): void { - io.stdout.write(`KLO context built: ${state.status === 'completed' ? 'yes' : state.status.replaceAll('_', ' ')}\n`); +function writeContextStatus(state: KtxSetupContextState, io: KtxCliIo): void { + io.stdout.write(`KTX context built: ${state.status === 'completed' ? 'yes' : state.status.replaceAll('_', ' ')}\n`); if (state.runId) { io.stdout.write(`Run: ${state.runId}\n`); io.stdout.write(`Watch: ${state.commands.watch}\n`); @@ -714,13 +714,13 @@ function writeContextStatus(state: KloSetupContextState, io: KloCliIo): void { } } -export async function runKloSetupContextCommand( - args: KloSetupContextCommandArgs, - io: KloCliIo, - deps: KloSetupContextDeps = {}, +export async function runKtxSetupContextCommand( + args: KtxSetupContextCommandArgs, + io: KtxCliIo, + deps: KtxSetupContextDeps = {}, ): Promise { if (args.command === 'build') { - const result = await runKloSetupContextStep( + const result = await runKtxSetupContextStep( { projectDir: args.projectDir, inputMode: args.inputMode, prompt: false }, io, deps, @@ -728,9 +728,9 @@ export async function runKloSetupContextCommand( return result.status === 'ready' || result.status === 'skipped' ? 0 : 1; } - const state = await readKloSetupContextState(args.projectDir); + const state = await readKtxSetupContextState(args.projectDir); if (!stateMatchesRunId(state, args.runId)) { - io.stderr.write(`KLO setup context run "${args.runId}" was not found.\n`); + io.stderr.write(`KTX setup context run "${args.runId}" was not found.\n`); return 1; } @@ -744,22 +744,22 @@ export async function runKloSetupContextCommand( } if (args.command === 'watch') { - io.stdout.write('KLO context build\n'); + io.stdout.write('KTX context build\n'); writeContextStatus(state, io); return 0; } const updatedAt = new Date().toISOString(); - const nextState: KloSetupContextState = { + const nextState: KtxSetupContextState = { ...state, status: state.status === 'completed' ? 'completed' : 'paused', updatedAt, }; - await writeKloSetupContextState(args.projectDir, nextState); + await writeKtxSetupContextState(args.projectDir, nextState); io.stdout.write( state.status === 'completed' - ? 'KLO context build already completed.\n' - : 'KLO context build pause requested. Resume with setup when ready.\n', + ? 'KTX context build already completed.\n' + : 'KTX context build pause requested. Resume with setup when ready.\n', ); return 0; } diff --git a/packages/cli/src/setup-databases.test.ts b/packages/cli/src/setup-databases.test.ts index ba893bce..41b12f95 100644 --- a/packages/cli/src/setup-databases.test.ts +++ b/packages/cli/src/setup-databases.test.ts @@ -1,14 +1,14 @@ import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; -import { initKloProject, parseKloProjectConfig } from '@klo/context/project'; +import { initKtxProject, parseKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - type KloSetupDatabaseDriver, - type KloSetupDatabasesPromptAdapter, - runKloSetupDatabasesStep, + type KtxSetupDatabaseDriver, + type KtxSetupDatabasesPromptAdapter, + runKtxSetupDatabasesStep, } from './setup-databases.js'; -import type { KloCliIo } from './cli-runtime.js'; +import type { KtxCliIo } from './cli-runtime.js'; function makeIo() { let stdout = ''; @@ -37,7 +37,7 @@ function makePromptAdapter(options: { selectValues?: string[]; textValues?: (string | undefined)[]; passwordValues?: (string | undefined)[]; -}): KloSetupDatabasesPromptAdapter { +}): KtxSetupDatabasesPromptAdapter { const multiselectValues = [...(options.multiselectValues ?? [])]; const selectValues = [...(options.selectValues ?? [])]; const textValues = [...(options.textValues ?? [])]; @@ -52,7 +52,7 @@ function makePromptAdapter(options: { } function connectionNamePrompt(label: string): string { - return `Name this ${label} connection\nKLO will use this short name in commands and config. You can rename it now.`; + return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`; } function textInputPrompt(message: string): string { @@ -68,8 +68,8 @@ describe('setup databases step', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-databases-')); - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-databases-')); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); }); afterEach(async () => { @@ -79,7 +79,7 @@ describe('setup databases step', () => { it('shows every supported primary source in the interactive checklist', async () => { const prompts = makePromptAdapter({ multiselectValues: [['back']] }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts }, @@ -88,7 +88,7 @@ describe('setup databases step', () => { expect(result.status).toBe('back'); expect(prompts.multiselect).toHaveBeenCalledWith({ message: - 'Which primary sources should KLO connect to?\n' + + 'Which primary sources should KTX connect to?\n' + 'Use Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', options: [ { value: 'sqlite', label: 'SQLite' }, @@ -110,7 +110,7 @@ describe('setup databases step', () => { selectValues: ['choose'], }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, io.io, { prompts }, @@ -119,7 +119,7 @@ describe('setup databases step', () => { expect(result.status).toBe('back'); expect(prompts.select).not.toHaveBeenCalled(); expect(io.stdout()).toContain( - 'KLO cannot work without at least one primary source. Select a source or press Escape to go back.', + 'KTX cannot work without at least one primary source. Select a source or press Escape to go back.', ); expect(prompts.multiselect).toHaveBeenCalledTimes(2); }); @@ -130,7 +130,7 @@ describe('setup databases step', () => { selectValues: ['back'], }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts }, @@ -147,7 +147,7 @@ describe('setup databases step', () => { }); expect(prompts.multiselect).toHaveBeenCalledTimes(2); expect(vi.mocked(prompts.multiselect).mock.calls[1]?.[0].message).toBe( - 'Which primary sources should KLO connect to?\n' + + 'Which primary sources should KTX connect to?\n' + 'Use Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', ); }); @@ -155,7 +155,7 @@ describe('setup databases step', () => { it('lets Back leave database setup when the driver came from flags', async () => { const prompts = makePromptAdapter({ selectValues: ['back'] }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -174,7 +174,7 @@ describe('setup databases step', () => { it('labels existing database connections with the database type', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -188,7 +188,7 @@ describe('setup databases step', () => { ); const prompts = makePromptAdapter({ selectValues: ['back'] }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -220,7 +220,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -240,7 +240,7 @@ describe('setup databases step', () => { }); expect(testConnection).toHaveBeenCalledWith(tempDir, 'postgres-warehouse', expect.anything()); expect(scanConnection).toHaveBeenCalledWith(tempDir, 'postgres-warehouse', expect.anything()); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections['postgres-warehouse']).toEqual({ driver: 'postgres', url: 'env:DATABASE_URL', @@ -254,7 +254,7 @@ describe('setup databases step', () => { textValues: ['', 'env:DATABASE_URL'], }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -283,7 +283,7 @@ describe('setup databases step', () => { it('uses clear setup prompts for every new database connection type', async () => { const cases: Array<{ - driver: KloSetupDatabaseDriver; + driver: KtxSetupDatabaseDriver; selectValues?: string[]; textValues: string[]; passwordValues?: string[]; @@ -433,7 +433,7 @@ describe('setup databases step', () => { textValues: testCase.textValues, passwordValues: testCase.passwordValues, }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -476,7 +476,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts, testConnection, scanConnection }, @@ -498,7 +498,7 @@ describe('setup databases step', () => { it('shows a configured primary source menu instead of the type checklist when a primary source exists', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -519,7 +519,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts, testConnection, scanConnection }, @@ -541,7 +541,7 @@ describe('setup databases step', () => { it('preserves existing primary source ids when adding another source from the configured menu', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -566,7 +566,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts, testConnection, scanConnection }, @@ -588,7 +588,7 @@ describe('setup databases step', () => { }); expect(testConnection).toHaveBeenCalledTimes(1); expect(testConnection).toHaveBeenCalledWith(tempDir, 'mysql-warehouse', expect.anything()); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.database_connection_ids).toEqual(['warehouse', 'mysql-warehouse']); }); @@ -601,7 +601,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { prompts, testConnection, scanConnection }, @@ -621,7 +621,7 @@ describe('setup databases step', () => { { value: 'back', label: 'Back' }, ], }); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.database_connection_ids).toEqual(['postgres-warehouse', 'mysql-warehouse']); }); @@ -635,7 +635,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, io.io, { prompts, testConnection, scanConnection }, @@ -647,7 +647,7 @@ describe('setup databases step', () => { connectionIds: ['postgres-warehouse'], }); expect(prompts.multiselect).toHaveBeenCalledTimes(2); - expect(io.stdout()).not.toContain('KLO cannot work without at least one primary source'); + expect(io.stdout()).not.toContain('KTX cannot work without at least one primary source'); expect(prompts.select).toHaveBeenNthCalledWith(2, { message: 'Primary sources already configured: postgres-warehouse\nWhat would you like to do?', options: [ @@ -660,7 +660,7 @@ describe('setup databases step', () => { it('returns to configured primary menu when submitting empty driver selection with pre-existing source', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -683,14 +683,14 @@ describe('setup databases step', () => { selectValues: ['add', 'continue'], }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, io.io, { prompts }, ); expect(result).toEqual({ status: 'ready', projectDir: tempDir, connectionIds: ['warehouse'] }); - expect(io.stdout()).not.toContain('KLO cannot work without at least one primary source'); + expect(io.stdout()).not.toContain('KTX cannot work without at least one primary source'); expect(prompts.select).toHaveBeenNthCalledWith(2, { message: 'Primary sources already configured: warehouse\nWhat would you like to do?', options: [ @@ -709,7 +709,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -735,7 +735,7 @@ describe('setup databases step', () => { textValues: ['', 'db.example.com', '5432', ''], }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', skipDatabases: false, databaseSchemas: [] }, makeIo().io, { @@ -765,7 +765,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -783,7 +783,7 @@ describe('setup databases step', () => { expect(scanConnection).not.toHaveBeenCalled(); }); - it('builds a Postgres connection from individual fields and stores password in .klo/secrets', async () => { + it('builds a Postgres connection from individual fields and stores password in .ktx/secrets', async () => { const io = makeIo(); const prompts = makePromptAdapter({ selectValues: ['fields'], @@ -793,7 +793,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -806,7 +806,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); const connection = config.connections['postgres-warehouse']; expect(connection).toMatchObject({ driver: 'postgres', @@ -817,14 +817,14 @@ describe('setup databases step', () => { readonly: true, }); expect(connection.password).toMatch(/^file:/); - const secretPath = join(tempDir, '.klo/secrets/postgres-warehouse-password'); + const secretPath = join(tempDir, '.ktx/secrets/postgres-warehouse-password'); await expect(readFile(secretPath, 'utf-8')).resolves.toBe('s3cret\n'); if (process.platform !== 'win32') { expect((await stat(secretPath)).mode & 0o777).toBe(0o600); } }); - it('stores credential-bearing pasted URLs in .klo/secrets automatically', async () => { + it('stores credential-bearing pasted URLs in .ktx/secrets automatically', async () => { const io = makeIo(); const prompts = makePromptAdapter({ selectValues: ['url'], @@ -833,7 +833,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -846,11 +846,11 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); const connection = config.connections['postgres-warehouse']; - expect(connection.url).toBe(`file:${resolve(tempDir, '.klo/secrets/postgres-warehouse-url')}`); + expect(connection.url).toBe(`file:${resolve(tempDir, '.ktx/secrets/postgres-warehouse-url')}`); expect(connection.driver).toBe('postgres'); - const secretContent = await readFile(join(tempDir, '.klo/secrets/postgres-warehouse-url'), 'utf-8'); + const secretContent = await readFile(join(tempDir, '.ktx/secrets/postgres-warehouse-url'), 'utf-8'); expect(secretContent).toBe('postgresql://myuser:s3cret@db.example.com:5432/analytics\n'); // pragma: allowlist secret }); @@ -860,20 +860,20 @@ describe('setup databases step', () => { selectValues: ['url'], textValues: ['', 'env:DATABASE_URL'], }); - const testConnection = vi.fn(async (_projectDir: string, _connectionId: string, commandIo: KloCliIo) => { + const testConnection = vi.fn(async (_projectDir: string, _connectionId: string, commandIo: KtxCliIo) => { commandIo.stdout.write('Connection test passed: postgres-warehouse\n'); commandIo.stdout.write('Driver: postgres\n'); commandIo.stdout.write('Tables: 2\n'); return 0; }); - const scanConnection = vi.fn(async (_projectDir: string, _connectionId: string, commandIo: KloCliIo) => { + const scanConnection = vi.fn(async (_projectDir: string, _connectionId: string, commandIo: KtxCliIo) => { commandIo.stdout.write('Scanning postgres-warehouse for context. Large primary sources can take a while.\n'); commandIo.stdout.write('[5%] Preparing scan\n'); commandIo.stdout.write('[15%] Inspecting database schema\n'); commandIo.stdout.write('[55%] Semantic layer comparison found 2 changes across 2 tables\n'); commandIo.stdout.write('[70%] Writing schema artifacts\n'); commandIo.stdout.write('[100%] Scan completed\n'); - commandIo.stdout.write('✓ KLO scan completed\n'); + commandIo.stdout.write('✓ KTX scan completed\n'); commandIo.stdout.write('Status: done\n'); commandIo.stdout.write('Run: local-moywh3ky\n'); commandIo.stdout.write('Connection: postgres-warehouse\n'); @@ -895,11 +895,11 @@ describe('setup databases step', () => { commandIo.stdout.write(' Raw sources: raw-sources/postgres-warehouse/live-database/2026-05-09-221301-local-moywh3ky\n'); commandIo.stdout.write(' Schema shards: 1\n\n'); commandIo.stdout.write('Next:\n'); - commandIo.stdout.write(` klo dev scan status --project-dir ${tempDir} local-moywh3ky\n`); + commandIo.stdout.write(` ktx dev scan status --project-dir ${tempDir} local-moywh3ky\n`); return 0; }); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -941,7 +941,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -954,7 +954,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections['postgres-warehouse']).toMatchObject({ driver: 'postgres', url: 'env:DATABASE_URL', @@ -967,7 +967,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'auto', @@ -984,7 +984,7 @@ describe('setup databases step', () => { expect(result.status).toBe('ready'); expect(testConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); expect(scanConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.warehouse).toEqual({ driver: 'postgres', url: 'env:DATABASE_URL', @@ -1005,7 +1005,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1023,7 +1023,7 @@ describe('setup databases step', () => { expect(prompts.text).not.toHaveBeenCalled(); expect(testConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); expect(scanConnection).toHaveBeenCalledWith(tempDir, 'warehouse', expect.anything()); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.warehouse).toEqual({ driver: 'sqlite', path: './warehouse.sqlite', @@ -1037,7 +1037,7 @@ describe('setup databases step', () => { it('selects multiple existing connections and validates each before recording setup ids', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1063,7 +1063,7 @@ describe('setup databases step', () => { const testConnection = vi.fn(async () => 0); const scanConnection = vi.fn(async () => 0); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1078,14 +1078,14 @@ describe('setup databases step', () => { expect(result.status).toBe('ready'); expect(testConnection).toHaveBeenCalledTimes(2); expect(scanConnection).toHaveBeenCalledTimes(2); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.database_connection_ids).toEqual(['warehouse', 'analytics']); expect(config.setup?.completed_steps).toContain('databases'); }); it('keeps the connection config but does not mark databases complete when scanning fails', async () => { const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1103,7 +1103,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('failed'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.warehouse).toMatchObject({ driver: 'postgres', url: 'env:DATABASE_URL' }); expect(config.setup?.completed_steps ?? []).not.toContain('databases'); expect(io.stderr()).toContain('Structural scan failed for warehouse.'); @@ -1111,7 +1111,7 @@ describe('setup databases step', () => { it('writes Historic SQL config for supported Snowflake databases after validation succeeds', async () => { const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1136,7 +1136,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.snowflake).toMatchObject({ driver: 'snowflake', authMethod: 'password', @@ -1153,7 +1153,7 @@ describe('setup databases step', () => { it('writes Postgres Historic SQL config with minCalls and ignores window/redaction output', async () => { const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1177,7 +1177,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.warehouse).toMatchObject({ driver: 'postgres', url: 'env:DATABASE_URL', @@ -1199,7 +1199,7 @@ describe('setup databases step', () => { it('writes Historic SQL config for supported existing database connections', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1214,7 +1214,7 @@ describe('setup databases step', () => { ); const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1232,7 +1232,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.analytics).toMatchObject({ historicSql: { enabled: true, @@ -1247,7 +1247,7 @@ describe('setup databases step', () => { it('enables Historic SQL on an existing Postgres connection', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1261,7 +1261,7 @@ describe('setup databases step', () => { ); const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1280,7 +1280,7 @@ describe('setup databases step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.connections.warehouse).toMatchObject({ historicSql: { enabled: true, @@ -1303,7 +1303,7 @@ describe('setup databases step', () => { ], })); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1339,7 +1339,7 @@ describe('setup databases step', () => { const io = makeIo(); const historicSqlProbe = vi.fn(async () => ({ ok: true, lines: [] })); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1365,7 +1365,7 @@ describe('setup databases step', () => { it('returns missing input when non-interactive database flags are incomplete', async () => { const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', @@ -1383,14 +1383,14 @@ describe('setup databases step', () => { it('leaves setup incomplete when primary sources are skipped', async () => { const io = makeIo(); - const result = await runKloSetupDatabasesStep( + const result = await runKtxSetupDatabasesStep( { projectDir: tempDir, inputMode: 'disabled', databaseSchemas: [], skipDatabases: true }, io.io, ); expect(result.status).toBe('skipped'); - expect(io.stdout()).toContain('KLO cannot work until you add a primary source.'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + expect(io.stdout()).toContain('KTX cannot work until you add a primary source.'); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps ?? []).not.toContain('databases'); }); }); diff --git a/packages/cli/src/setup-databases.ts b/packages/cli/src/setup-databases.ts index 03dab1b3..e22f4741 100644 --- a/packages/cli/src/setup-databases.ts +++ b/packages/cli/src/setup-databases.ts @@ -1,20 +1,20 @@ import { writeFile } from 'node:fs/promises'; import { cancel, isCancel, multiselect, password, select, text } from '@clack/prompts'; -import type { HistoricSqlDialect } from '@klo/context/ingest'; +import type { HistoricSqlDialect } from '@ktx/context/ingest'; import { - type KloProjectConnectionConfig, - loadKloProject, - serializeKloProjectConfig, - setKloSetupDatabaseConnectionIds, -} from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; -import { runKloConnection } from './connection.js'; + type KtxProjectConnectionConfig, + loadKtxProject, + serializeKtxProjectConfig, + setKtxSetupDatabaseConnectionIds, +} from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; +import { runKtxConnection } from './connection.js'; import { withMenuOptionsSpacing, withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js'; -import { runKloScan } from './scan.js'; +import { runKtxScan } from './scan.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; import { writeProjectLocalSecretReference } from './setup-secrets.js'; -export type KloSetupDatabaseDriver = +export type KtxSetupDatabaseDriver = | 'sqlite' | 'postgres' | 'mysql' @@ -23,10 +23,10 @@ export type KloSetupDatabaseDriver = | 'bigquery' | 'snowflake'; -export interface KloSetupDatabasesArgs { +export interface KtxSetupDatabasesArgs { projectDir: string; inputMode: 'auto' | 'disabled'; - databaseDrivers?: KloSetupDatabaseDriver[]; + databaseDrivers?: KtxSetupDatabaseDriver[]; databaseConnectionIds?: string[]; databaseConnectionId?: string; databaseUrl?: string; @@ -40,14 +40,14 @@ export interface KloSetupDatabasesArgs { skipDatabases: boolean; } -export type KloSetupDatabasesResult = +export type KtxSetupDatabasesResult = | { status: 'ready'; projectDir: string; connectionIds: string[] } | { status: 'skipped'; projectDir: string } | { status: 'back'; projectDir: string } | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; -export interface KloSetupDatabasesPromptAdapter { +export interface KtxSetupDatabasesPromptAdapter { multiselect(options: { message: string; options: Array<{ value: string; label: string }>; @@ -59,27 +59,27 @@ export interface KloSetupDatabasesPromptAdapter { cancel(message: string): void; } -interface KloSetupHistoricSqlProbeInput { +interface KtxSetupHistoricSqlProbeInput { projectDir: string; connectionId: string; dialect: HistoricSqlDialect; } -interface KloSetupHistoricSqlProbeResult { +interface KtxSetupHistoricSqlProbeResult { ok: boolean; lines: string[]; } -type KloSetupHistoricSqlProbe = (input: KloSetupHistoricSqlProbeInput) => Promise; +type KtxSetupHistoricSqlProbe = (input: KtxSetupHistoricSqlProbeInput) => Promise; -export interface KloSetupDatabasesDeps { - prompts?: KloSetupDatabasesPromptAdapter; - testConnection?: (projectDir: string, connectionId: string, io: KloCliIo) => Promise; - scanConnection?: (projectDir: string, connectionId: string, io: KloCliIo) => Promise; - historicSqlProbe?: KloSetupHistoricSqlProbe; +export interface KtxSetupDatabasesDeps { + prompts?: KtxSetupDatabasesPromptAdapter; + testConnection?: (projectDir: string, connectionId: string, io: KtxCliIo) => Promise; + scanConnection?: (projectDir: string, connectionId: string, io: KtxCliIo) => Promise; + historicSqlProbe?: KtxSetupHistoricSqlProbe; } -const DRIVER_OPTIONS: Array<{ value: KloSetupDatabaseDriver; label: string }> = [ +const DRIVER_OPTIONS: Array<{ value: KtxSetupDatabaseDriver; label: string }> = [ { value: 'sqlite', label: 'SQLite' }, { value: 'postgres', label: 'PostgreSQL' }, { value: 'mysql', label: 'MySQL' }, @@ -90,17 +90,17 @@ const DRIVER_OPTIONS: Array<{ value: KloSetupDatabaseDriver; label: string }> = ]; const DRIVER_LABELS = Object.fromEntries(DRIVER_OPTIONS.map((option) => [option.value, option.label])) as Record< - KloSetupDatabaseDriver, + KtxSetupDatabaseDriver, string >; -const HISTORIC_SQL_DIALECT_BY_DRIVER: Partial> = { +const HISTORIC_SQL_DIALECT_BY_DRIVER: Partial> = { snowflake: 'snowflake', bigquery: 'bigquery', postgres: 'postgres', }; -const DEFAULT_CONNECTION_IDS: Record = { +const DEFAULT_CONNECTION_IDS: Record = { sqlite: 'sqlite-local', postgres: 'postgres-warehouse', mysql: 'mysql-warehouse', @@ -110,7 +110,7 @@ const DEFAULT_CONNECTION_IDS: Record = { snowflake: 'snowflake-warehouse', }; -type UrlDriverType = Extract; +type UrlDriverType = Extract; const DRIVER_CONNECTION_DEFAULTS: Record = { postgres: { port: '5432' }, @@ -119,12 +119,12 @@ const DRIVER_CONNECTION_DEFAULTS: Record = { sqlserver: { port: '1433' }, }; -function driverLabel(driver: KloSetupDatabaseDriver): string { +function driverLabel(driver: KtxSetupDatabaseDriver): string { return DRIVER_LABELS[driver]; } function connectionNamePrompt(label: string): string { - return `Name this ${label} connection\nKLO will use this short name in commands and config. You can rename it now.`; + return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`; } function missingConnectionDetailsPrompt( @@ -143,7 +143,7 @@ function missingConnectionDetailsPrompt( }; } -function createPromptAdapter(): KloSetupDatabasesPromptAdapter { +function createPromptAdapter(): KtxSetupDatabasesPromptAdapter { return { async multiselect(options) { const value = await withSetupInterruptConfirmation(() => multiselect(withMenuOptionsSpacing(options))); @@ -179,18 +179,18 @@ function createPromptAdapter(): KloSetupDatabasesPromptAdapter { }; } -function normalizeDriver(driver: string | undefined): KloSetupDatabaseDriver | null { +function normalizeDriver(driver: string | undefined): KtxSetupDatabaseDriver | null { const normalized = String(driver ?? '').toLowerCase(); if (normalized === 'postgresql') return 'postgres'; if (normalized === 'sqlite3') return 'sqlite'; - return DRIVER_OPTIONS.some((option) => option.value === normalized) ? (normalized as KloSetupDatabaseDriver) : null; + return DRIVER_OPTIONS.some((option) => option.value === normalized) ? (normalized as KtxSetupDatabaseDriver) : null; } function unique(values: string[]): string[] { return [...new Set(values.filter((value) => value.trim().length > 0))]; } -function historicSqlConfigRecord(connection: KloProjectConnectionConfig | undefined): Record | null { +function historicSqlConfigRecord(connection: KtxProjectConnectionConfig | undefined): Record | null { const historicSql = connection?.historicSql; return historicSql && typeof historicSql === 'object' && !Array.isArray(historicSql) ? (historicSql as Record) @@ -217,25 +217,25 @@ function historicSqlProbeFailureLines(error: unknown): string[] { return [` FAIL Historic SQL probe failed: ${error instanceof Error ? error.message : String(error)}`]; } -async function defaultHistoricSqlProbe(input: KloSetupHistoricSqlProbeInput): Promise { +async function defaultHistoricSqlProbe(input: KtxSetupHistoricSqlProbeInput): Promise { if (input.dialect !== 'postgres') { return { ok: true, lines: [] }; } - const project = await loadKloProject({ projectDir: input.projectDir }); + const project = await loadKtxProject({ projectDir: input.projectDir }); const connection = project.config.connections[input.connectionId]; - const [{ PostgresPgssQueryHistoryReader }, { KloPostgresHistoricSqlQueryClient, isKloPostgresConnectionConfig }] = - await Promise.all([import('@klo/context/ingest'), import('@klo/connector-postgres')]); + const [{ PostgresPgssQueryHistoryReader }, { KtxPostgresHistoricSqlQueryClient, isKtxPostgresConnectionConfig }] = + await Promise.all([import('@ktx/context/ingest'), import('@ktx/connector-postgres')]); - const postgresConnection = connection as Parameters[0]; - if (!isKloPostgresConnectionConfig(postgresConnection)) { + const postgresConnection = connection as Parameters[0]; + if (!isKtxPostgresConnectionConfig(postgresConnection)) { return { ok: false, lines: [` FAIL Connection ${input.connectionId} is not a native Postgres connection.`], }; } - const client = new KloPostgresHistoricSqlQueryClient({ + const client = new KtxPostgresHistoricSqlQueryClient({ connectionId: input.connectionId, connection: postgresConnection, }); @@ -256,8 +256,8 @@ async function defaultHistoricSqlProbe(input: KloSetupHistoricSqlProbeInput): Pr } function existingConnectionIdsByDriver( - connections: Record, - driver: KloSetupDatabaseDriver, + connections: Record, + driver: KtxSetupDatabaseDriver, ): string[] { return Object.entries(connections) .filter(([, connection]) => normalizeDriver(connection.driver) === driver) @@ -266,7 +266,7 @@ function existingConnectionIdsByDriver( } function configuredPrimaryConnectionIds( - connections: Record, + connections: Record, setupConnectionIds: string[] | undefined, ): string[] { const configuredIds = @@ -303,8 +303,8 @@ function pushUniqueConnectionId(connectionIds: string[], connectionId: string): } function defaultConnectionIdForDriver( - connections: Record, - driver: KloSetupDatabaseDriver, + connections: Record, + driver: KtxSetupDatabaseDriver, ): string { const base = DEFAULT_CONNECTION_IDS[driver]; if (!connections[base]) { @@ -318,7 +318,7 @@ function defaultConnectionIdForDriver( } async function promptText( - prompts: KloSetupDatabasesPromptAdapter, + prompts: KtxSetupDatabasesPromptAdapter, message: string, fallback?: string, ): Promise { @@ -352,7 +352,7 @@ function normalizeFileReference(value: string): string { } async function promptCredential(input: { - prompts: KloSetupDatabasesPromptAdapter; + prompts: KtxSetupDatabasesPromptAdapter; message: string; projectDir: string; connectionId: string; @@ -379,9 +379,9 @@ async function promptCredential(input: { async function buildFieldsConnectionConfig(input: { driver: UrlDriverType; connectionId: string; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; -}): Promise { + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; +}): Promise { const label = driverLabel(input.driver); const defaults = DRIVER_CONNECTION_DEFAULTS[input.driver]; @@ -429,9 +429,9 @@ async function buildFieldsConnectionConfig(input: { async function buildPastedUrlConnectionConfig(input: { driver: UrlDriverType; connectionId: string; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; -}): Promise { + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; +}): Promise { const label = driverLabel(input.driver); const rawUrl = await promptText(input.prompts, `${label} connection URL`); if (rawUrl === undefined) return 'back'; @@ -473,9 +473,9 @@ async function buildPastedUrlConnectionConfig(input: { async function buildUrlConnectionConfig(input: { driver: UrlDriverType; connectionId: string; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; -}): Promise { + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; +}): Promise { if (input.args.inputMode === 'disabled' && !input.args.databaseUrl) return null; if (input.args.databaseUrl) { @@ -520,11 +520,11 @@ async function buildUrlConnectionConfig(input: { } async function buildConnectionConfig(input: { - driver: KloSetupDatabaseDriver; + driver: KtxSetupDatabaseDriver; connectionId: string; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; -}): Promise { + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; +}): Promise { const { driver, args, prompts } = input; if (driver === 'sqlite') { if (args.inputMode === 'disabled' && !args.databaseUrl) return null; @@ -603,11 +603,11 @@ async function buildConnectionConfig(input: { } async function maybeApplyHistoricSqlConfig(input: { - connection: KloProjectConnectionConfig; - driver: KloSetupDatabaseDriver; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; -}): Promise { + connection: KtxProjectConnectionConfig; + driver: KtxSetupDatabaseDriver; + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; +}): Promise { const dialect = HISTORIC_SQL_DIALECT_BY_DRIVER[input.driver]; if (!dialect) { if (input.args.enableHistoricSql === true) { @@ -675,12 +675,12 @@ async function maybeApplyHistoricSqlConfig(input: { }; } -async function defaultTestConnection(projectDir: string, connectionId: string, io: KloCliIo): Promise { - return await runKloConnection({ command: 'test', projectDir, connectionId }, io); +async function defaultTestConnection(projectDir: string, connectionId: string, io: KtxCliIo): Promise { + return await runKtxConnection({ command: 'test', projectDir, connectionId }, io); } -async function defaultScanConnection(projectDir: string, connectionId: string, io: KloCliIo): Promise { - return await runKloScan( +async function defaultScanConnection(projectDir: string, connectionId: string, io: KtxCliIo): Promise { + return await runKtxScan( { command: 'run', projectDir, @@ -693,7 +693,7 @@ async function defaultScanConnection(projectDir: string, connectionId: string, i ); } -interface BufferedCommandIo extends KloCliIo { +interface BufferedCommandIo extends KtxCliIo { stdoutText(): string; stderrText(): string; } @@ -722,7 +722,7 @@ function createBufferedCommandIo(): BufferedCommandIo { }; } -function flushBufferedCommandOutput(io: KloCliIo, bufferedIo: BufferedCommandIo): void { +function flushBufferedCommandOutput(io: KtxCliIo, bufferedIo: BufferedCommandIo): void { const stdout = bufferedIo.stdoutText(); const stderr = bufferedIo.stderrText(); if (stdout.length > 0) { @@ -780,7 +780,7 @@ function shortenScanReportPath(path: string): string { return `${normalized.slice(0, markerIndex + liveDatabaseMarker.length)}.../${filename}`; } -function writeSetupSection(io: KloCliIo, title: string, lines: string[]): void { +function writeSetupSection(io: KtxCliIo, title: string, lines: string[]): void { io.stdout.write(`◇ ${title}\n`); for (const line of lines) { io.stdout.write(`│ ${line}\n`); @@ -791,9 +791,9 @@ function writeSetupSection(io: KloCliIo, title: string, lines: string[]): void { async function writeConnectionConfig(input: { projectDir: string; connectionId: string; - connection: KloProjectConnectionConfig; + connection: KtxProjectConnectionConfig; }): Promise { - const project = await loadKloProject({ projectDir: input.projectDir }); + const project = await loadKtxProject({ projectDir: input.projectDir }); const config = { ...project.config, connections: { @@ -801,7 +801,7 @@ async function writeConnectionConfig(input: { [input.connectionId]: input.connection, }, }; - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); const historicSql = typeof input.connection.historicSql === 'object' && @@ -815,13 +815,13 @@ async function writeConnectionConfig(input: { } async function ensureHistoricSqlAdapterEnabled(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); if (project.config.ingest.adapters.includes('historic-sql')) { return; } await writeFile( project.configPath, - serializeKloProjectConfig({ + serializeKtxProjectConfig({ ...project.config, ingest: { ...project.config.ingest, @@ -833,18 +833,18 @@ async function ensureHistoricSqlAdapterEnabled(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); - const config = setKloSetupDatabaseConnectionIds(project.config, unique(connectionIds), { complete: true }); - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); + const project = await loadKtxProject({ projectDir }); + const config = setKtxSetupDatabaseConnectionIds(project.config, unique(connectionIds), { complete: true }); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); } async function maybeRunHistoricSqlSetupProbe(input: { projectDir: string; connectionId: string; - io: KloCliIo; - deps: KloSetupDatabasesDeps; + io: KtxCliIo; + deps: KtxSetupDatabasesDeps; }): Promise { - const project = await loadKloProject({ projectDir: input.projectDir }); + const project = await loadKtxProject({ projectDir: input.projectDir }); const connection = project.config.connections[input.connectionId]; const historicSql = historicSqlConfigRecord(connection); if (historicSql?.enabled !== true || historicSql.dialect !== 'postgres') { @@ -869,14 +869,14 @@ async function maybeRunHistoricSqlSetupProbe(input: { async function applyHistoricSqlConfigToExistingConnection(input: { projectDir: string; connectionId: string; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; }): Promise<'back' | void> { if (input.args.enableHistoricSql !== true && input.args.disableHistoricSql !== true) { return; } - const project = await loadKloProject({ projectDir: input.projectDir }); + const project = await loadKtxProject({ projectDir: input.projectDir }); const existing = project.config.connections[input.connectionId]; const driver = normalizeDriver(existing?.driver); if (!existing || !driver) { @@ -900,12 +900,12 @@ async function applyHistoricSqlConfigToExistingConnection(input: { async function validateAndScanConnection(input: { projectDir: string; connectionId: string; - io: KloCliIo; - deps: KloSetupDatabasesDeps; + io: KtxCliIo; + deps: KtxSetupDatabasesDeps; }): Promise { const testConnection = input.deps.testConnection ?? defaultTestConnection; const scanConnection = input.deps.scanConnection ?? defaultScanConnection; - const project = await loadKloProject({ projectDir: input.projectDir }); + const project = await loadKtxProject({ projectDir: input.projectDir }); const configuredDriver = normalizeDriver(project.config.connections[input.connectionId]?.driver); const configuredDriverLabel = configuredDriver ? driverLabel(configuredDriver) : undefined; const testIo = createBufferedCommandIo(); @@ -934,7 +934,7 @@ async function validateAndScanConnection(input: { if (scanCode !== 0) { flushBufferedCommandOutput(input.io, scanIo); input.io.stderr.write(`Structural scan failed for ${input.connectionId}.\n`); - input.io.stderr.write(`Debug command: klo dev scan --project-dir ${input.projectDir} ${input.connectionId}\n`); + input.io.stderr.write(`Debug command: ktx dev scan --project-dir ${input.projectDir} ${input.connectionId}\n`); return false; } const scanOutput = scanIo.stdoutText(); @@ -955,11 +955,11 @@ async function validateAndScanConnection(input: { } async function chooseDrivers( - args: KloSetupDatabasesArgs, - io: KloCliIo, - prompts: KloSetupDatabasesPromptAdapter, + args: KtxSetupDatabasesArgs, + io: KtxCliIo, + prompts: KtxSetupDatabasesPromptAdapter, options?: { hasPrimarySources?: boolean }, -): Promise { +): Promise { if (args.databaseDrivers && args.databaseDrivers.length > 0) { return [...new Set(args.databaseDrivers)]; } @@ -968,13 +968,13 @@ async function chooseDrivers( } if (args.inputMode === 'disabled') { io.stderr.write( - 'KLO cannot work without a primary source. Pass --database or --database-connection-id, or pass --skip-databases to leave setup incomplete.\n', + 'KTX cannot work without a primary source. Pass --database or --database-connection-id, or pass --skip-databases to leave setup incomplete.\n', ); return 'missing-input'; } while (true) { const choices = await prompts.multiselect({ - message: withMultiselectNavigation('Which primary sources should KLO connect to?'), + message: withMultiselectNavigation('Which primary sources should KTX connect to?'), options: [...DRIVER_OPTIONS], required: false, }); @@ -982,22 +982,22 @@ async function chooseDrivers( return 'back'; } if (choices.length > 0) { - return choices as KloSetupDatabaseDriver[]; + return choices as KtxSetupDatabaseDriver[]; } if (options?.hasPrimarySources) { return 'back'; } - io.stdout.write('KLO cannot work without at least one primary source. Select a source or press Escape to go back.\n'); + io.stdout.write('KTX cannot work without at least one primary source. Select a source or press Escape to go back.\n'); } } async function chooseConnectionIdForDriver(input: { - driver: KloSetupDatabaseDriver; - connections: Record; - args: KloSetupDatabasesArgs; - prompts: KloSetupDatabasesPromptAdapter; + driver: KtxSetupDatabaseDriver; + connections: Record; + args: KtxSetupDatabasesArgs; + prompts: KtxSetupDatabasesPromptAdapter; }): Promise<{ kind: 'existing' | 'new'; connectionId: string } | 'back' | 'missing-input'> { if (input.args.databaseConnectionId) { return { kind: 'new', connectionId: input.args.databaseConnectionId }; @@ -1047,13 +1047,13 @@ async function chooseConnectionIdForDriver(input: { } } -export async function runKloSetupDatabasesStep( - args: KloSetupDatabasesArgs, - io: KloCliIo, - deps: KloSetupDatabasesDeps = {}, -): Promise { +export async function runKtxSetupDatabasesStep( + args: KtxSetupDatabasesArgs, + io: KtxCliIo, + deps: KtxSetupDatabasesDeps = {}, +): Promise { if (args.skipDatabases) { - io.stdout.write('Primary source setup skipped. KLO cannot work until you add a primary source.\n'); + io.stdout.write('Primary source setup skipped. KTX cannot work until you add a primary source.\n'); return { status: 'skipped', projectDir: args.projectDir }; } @@ -1079,7 +1079,7 @@ export async function runKloSetupDatabasesStep( } const canReturnToDriverSelection = args.databaseDrivers === undefined || args.databaseDrivers.length === 0; - const initialProject = await loadKloProject({ projectDir: args.projectDir }); + const initialProject = await loadKtxProject({ projectDir: args.projectDir }); const selectedConnectionIds = args.inputMode !== 'disabled' && canReturnToDriverSelection ? configuredPrimaryConnectionIds(initialProject.config.connections, initialProject.config.setup?.database_connection_ids) @@ -1110,14 +1110,14 @@ export async function runKloSetupDatabasesStep( if (drivers === 'missing-input') return { status: 'missing-input', projectDir: args.projectDir }; if (drivers.length === 0) { await markDatabasesComplete(args.projectDir, []); - io.stdout.write('KLO cannot work without a primary source.\n'); + io.stdout.write('KTX cannot work without a primary source.\n'); return { status: 'skipped', projectDir: args.projectDir }; } let returnToDriverSelection = false; for (const driver of drivers) { - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); const connectionChoice = await chooseConnectionIdForDriver({ driver, connections: project.config.connections, diff --git a/packages/cli/src/setup-embeddings.test.ts b/packages/cli/src/setup-embeddings.test.ts index 4cf6bdef..a4a7b4c3 100644 --- a/packages/cli/src/setup-embeddings.test.ts +++ b/packages/cli/src/setup-embeddings.test.ts @@ -1,14 +1,14 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject, parseKloProjectConfig } from '@klo/context/project'; +import { initKtxProject, parseKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { type KloSetupEmbeddingsPromptAdapter, runKloSetupEmbeddingsStep } from './setup-embeddings.js'; +import { type KtxSetupEmbeddingsPromptAdapter, runKtxSetupEmbeddingsStep } from './setup-embeddings.js'; const EMBEDDING_OPTION_PROMPT_MESSAGE = [ - 'Which embedding option should KLO use?', + 'Which embedding option should KTX use?', '', - 'KLO uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' + + 'KTX uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' + 'and relationship evidence.', ].join('\n'); @@ -37,7 +37,7 @@ function makeIo() { function makePromptAdapter(options: { selectValues?: string[]; passwordValue?: string; -}): KloSetupEmbeddingsPromptAdapter { +}): KtxSetupEmbeddingsPromptAdapter { const selectValues = [...(options.selectValues ?? [])]; return { select: vi.fn(async () => selectValues.shift() ?? 'retry'), @@ -50,8 +50,8 @@ describe('setup embeddings step', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-embeddings-')); - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-embeddings-')); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); }); afterEach(async () => { @@ -63,7 +63,7 @@ describe('setup embeddings step', () => { const healthCheck = vi.fn(async () => ({ ok: true as const })); const prompts = makePromptAdapter({ selectValues: ['back'] }); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', @@ -90,7 +90,7 @@ describe('setup embeddings step', () => { const healthCheck = vi.fn(async () => ({ ok: true as const })); const prompts = makePromptAdapter({ selectValues: ['openai', 'back', 'sentence-transformers'] }); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', @@ -110,7 +110,7 @@ describe('setup embeddings step', () => { }); expect(vi.mocked(prompts.select).mock.calls.map((call) => call[0].message)).toEqual([ EMBEDDING_OPTION_PROMPT_MESSAGE, - 'How should KLO find your OpenAI embedding API key?', + 'How should KTX find your OpenAI embedding API key?', EMBEDDING_OPTION_PROMPT_MESSAGE, ]); }); @@ -120,7 +120,7 @@ describe('setup embeddings step', () => { const healthCheck = vi.fn(async () => ({ ok: true as const })); const prompts = makePromptAdapter({ selectValues: ['sentence-transformers'] }); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', @@ -137,7 +137,7 @@ describe('setup embeddings step', () => { dimensions: 384, sentenceTransformers: { baseURL: 'http://127.0.0.1:8765', pathPrefix: '' }, }); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.ingest.embeddings).toMatchObject({ backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', @@ -163,7 +163,7 @@ describe('setup embeddings step', () => { }), ); - const result = runKloSetupEmbeddingsStep( + const result = runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', @@ -188,7 +188,7 @@ describe('setup embeddings step', () => { const io = makeIo(); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'disabled', @@ -205,7 +205,7 @@ describe('setup embeddings step', () => { dimensions: 384, sentenceTransformers: { baseURL: 'http://127.0.0.1:8765', pathPrefix: '' }, }); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.ingest.embeddings).toMatchObject({ backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', @@ -218,7 +218,7 @@ describe('setup embeddings step', () => { it('does not persist embedding completion when the health check fails', async () => { const io = makeIo(); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'disabled', @@ -232,11 +232,11 @@ describe('setup embeddings step', () => { ); expect(result.status).toBe('failed'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps ?? []).not.toContain('embeddings'); expect(config.ingest.embeddings.backend).toBe('deterministic'); expect(io.stderr()).toContain('Local embedding health check failed: 401 invalid api key [redacted]'); - expect(io.stderr()).toContain('klo-daemon serve-http --host 127.0.0.1 --port 8765'); + expect(io.stderr()).toContain('ktx-daemon serve-http --host 127.0.0.1 --port 8765'); expect(io.stderr()).not.toContain('skip for now'); }); @@ -244,7 +244,7 @@ describe('setup embeddings step', () => { const io = makeIo(); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'disabled', @@ -266,7 +266,7 @@ describe('setup embeddings step', () => { dimensions: 1536, openai: { apiKey: 'sk-openai-test' }, }); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.ingest.embeddings).toMatchObject({ backend: 'openai', model: 'text-embedding-3-small', @@ -284,7 +284,7 @@ describe('setup embeddings step', () => { .mockResolvedValueOnce({ ok: false as const, message: 'fetch failed' }) .mockResolvedValueOnce({ ok: true as const }); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', skipEmbeddings: false }, io.io, { prompts, env: { OPENAI_API_KEY: 'sk-openai-test' }, healthCheck }, @@ -305,7 +305,7 @@ describe('setup embeddings step', () => { }); expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Local embeddings are not reachable. Start the local KLO daemon, then retry.', + message: 'Local embeddings are not reachable. Start the local KTX daemon, then retry.', options: expect.arrayContaining([expect.objectContaining({ value: 'openai' })]), }), ); @@ -314,40 +314,40 @@ describe('setup embeddings step', () => { { value: 'openai', label: 'Use OpenAI embeddings' }, { value: 'back', label: 'Back' }, ]); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.ingest.embeddings.backend).toBe('openai'); }); it('leaves setup incomplete when skipped', async () => { - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'disabled', skipEmbeddings: true }, makeIo().io, ); expect(result.status).toBe('skipped'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps ?? []).not.toContain('embeddings'); expect(config.ingest.embeddings.backend).toBe('deterministic'); }); it('returns back without writing config when the local health check fails and Back is selected', async () => { const prompts = makePromptAdapter({ selectValues: ['sentence-transformers', 'back'] }); - const result = await runKloSetupEmbeddingsStep( + const result = await runKtxSetupEmbeddingsStep( { projectDir: tempDir, inputMode: 'auto', skipEmbeddings: false }, makeIo().io, { prompts, env: {}, healthCheck: vi.fn(async () => ({ ok: false as const, message: 'daemon unavailable' })) }, ); expect(result.status).toBe('back'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.ingest.embeddings.backend).toBe('deterministic'); }); it('preserves already completed embeddings setup when no embedding args request changes', async () => { - await mkdir(join(tempDir, '.klo'), { recursive: true }); - await initKloProject({ projectDir: tempDir, projectName: 'warehouse', force: true }); + await mkdir(join(tempDir, '.ktx'), { recursive: true }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse', force: true }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'setup:', @@ -371,7 +371,7 @@ describe('setup embeddings step', () => { const healthCheck = vi.fn(async () => ({ ok: true as const })); await expect( - runKloSetupEmbeddingsStep({ projectDir: tempDir, inputMode: 'disabled', skipEmbeddings: false }, makeIo().io, { + runKtxSetupEmbeddingsStep({ projectDir: tempDir, inputMode: 'disabled', skipEmbeddings: false }, makeIo().io, { env: { OPENAI_API_KEY: 'sk-openai-test' }, healthCheck, }), diff --git a/packages/cli/src/setup-embeddings.ts b/packages/cli/src/setup-embeddings.ts index d6d380dc..61cdc68d 100644 --- a/packages/cli/src/setup-embeddings.ts +++ b/packages/cli/src/setup-embeddings.ts @@ -1,25 +1,25 @@ import { writeFile } from 'node:fs/promises'; import { cancel, isCancel, password, select } from '@clack/prompts'; -import { resolveKloConfigReference } from '@klo/context/core'; +import { resolveKtxConfigReference } from '@ktx/context/core'; import { - type KloProjectConfig, - type KloProjectEmbeddingConfig, - loadKloProject, - markKloSetupStepComplete, - serializeKloProjectConfig, -} from '@klo/context/project'; -import { type KloEmbeddingConfig, type KloEmbeddingHealthCheckResult, runKloEmbeddingHealthCheck } from '@klo/llm'; -import type { KloCliIo } from './cli-runtime.js'; + type KtxProjectConfig, + type KtxProjectEmbeddingConfig, + loadKtxProject, + markKtxSetupStepComplete, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import { type KtxEmbeddingConfig, type KtxEmbeddingHealthCheckResult, runKtxEmbeddingHealthCheck } from '@ktx/llm'; +import type { KtxCliIo } from './cli-runtime.js'; import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; import { envCredentialReference, writeProjectLocalSecretReference } from './setup-secrets.js'; -export type KloSetupEmbeddingBackend = 'openai' | 'sentence-transformers'; +export type KtxSetupEmbeddingBackend = 'openai' | 'sentence-transformers'; -export interface KloSetupEmbeddingsArgs { +export interface KtxSetupEmbeddingsArgs { projectDir: string; inputMode: 'auto' | 'disabled'; - embeddingBackend?: KloSetupEmbeddingBackend; + embeddingBackend?: KtxSetupEmbeddingBackend; embeddingApiKeyEnv?: string; embeddingApiKeyFile?: string; forcePrompt?: boolean; @@ -27,29 +27,29 @@ export interface KloSetupEmbeddingsArgs { skipEmbeddings: boolean; } -export type KloSetupEmbeddingsResult = +export type KtxSetupEmbeddingsResult = | { status: 'ready'; projectDir: string } | { status: 'skipped'; projectDir: string } | { status: 'back'; projectDir: string } | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; -export interface KloSetupEmbeddingsPromptAdapter { +export interface KtxSetupEmbeddingsPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; password(options: { message: string }): Promise; cancel(message: string): void; } -export interface KloSetupEmbeddingsDeps { +export interface KtxSetupEmbeddingsDeps { env?: NodeJS.ProcessEnv; - prompts?: KloSetupEmbeddingsPromptAdapter; - healthCheck?: (config: KloEmbeddingConfig) => Promise; + prompts?: KtxSetupEmbeddingsPromptAdapter; + healthCheck?: (config: KtxEmbeddingConfig) => Promise; } -type BackendChoice = KloSetupEmbeddingBackend | 'back'; +type BackendChoice = KtxSetupEmbeddingBackend | 'back'; const DEFAULTS: Record< - KloSetupEmbeddingBackend, + KtxSetupEmbeddingBackend, { model: string; dimensions: number; envName?: string; baseUrl?: string; pathPrefix?: string } > = { openai: { model: 'text-embedding-3-small', dimensions: 1536, envName: 'OPENAI_API_KEY' }, @@ -61,12 +61,12 @@ const DEFAULTS: Record< }, }; -const LOCAL_EMBEDDING_BACKEND: KloSetupEmbeddingBackend = 'sentence-transformers'; -const LOCAL_EMBEDDING_DAEMON_COMMAND = 'klo-daemon serve-http --host 127.0.0.1 --port 8765'; +const LOCAL_EMBEDDING_BACKEND: KtxSetupEmbeddingBackend = 'sentence-transformers'; +const LOCAL_EMBEDDING_DAEMON_COMMAND = 'ktx-daemon serve-http --host 127.0.0.1 --port 8765'; const LOCAL_EMBEDDING_DAEMON_DEV_COMMAND = - 'cd klo && source .venv/bin/activate && uv run klo-daemon serve-http --host 127.0.0.1 --port 8765'; + 'cd ktx && source .venv/bin/activate && uv run ktx-daemon serve-http --host 127.0.0.1 --port 8765'; const EMBEDDING_OPTION_PROMPT_CONTEXT = - 'KLO uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' + + 'KTX uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' + 'and relationship evidence.'; const LOCAL_EMBEDDING_HEALTH_TIMEOUT_MS = 120_000; const HEALTH_CHECK_SPINNER_FRAMES = ['-', '\\', '|', '/'] as const; @@ -78,7 +78,7 @@ interface HealthCheckProgress { fail(message: string): void; } -function createPromptAdapter(): KloSetupEmbeddingsPromptAdapter { +function createPromptAdapter(): KtxSetupEmbeddingsPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -100,7 +100,7 @@ function createPromptAdapter(): KloSetupEmbeddingsPromptAdapter { }; } -function hasCompletedEmbeddings(config: KloProjectConfig): boolean { +function hasCompletedEmbeddings(config: KtxProjectConfig): boolean { return ( config.setup?.completed_steps.includes('embeddings') === true && config.ingest.embeddings.backend !== 'none' && @@ -112,11 +112,11 @@ function hasCompletedEmbeddings(config: KloProjectConfig): boolean { } function buildProjectEmbeddingConfig(input: { - backend: KloSetupEmbeddingBackend; + backend: KtxSetupEmbeddingBackend; model: string; dimensions: number; credentialRef?: string; -}): KloProjectEmbeddingConfig { +}): KtxProjectEmbeddingConfig { if (input.backend === 'openai') { return { backend: 'openai', @@ -140,11 +140,11 @@ function buildProjectEmbeddingConfig(input: { } function buildHealthConfig(input: { - backend: KloSetupEmbeddingBackend; + backend: KtxSetupEmbeddingBackend; model: string; dimensions: number; credentialValue?: string; -}): KloEmbeddingConfig { +}): KtxEmbeddingConfig { if (input.backend === 'openai') { return { backend: 'openai', @@ -167,16 +167,16 @@ function buildHealthConfig(input: { }; } -function embeddingBackendDisplayName(backend: KloSetupEmbeddingBackend): string { +function embeddingBackendDisplayName(backend: KtxSetupEmbeddingBackend): string { if (backend === 'openai') { return 'OpenAI'; } return 'sentence-transformers'; } -async function persistEmbeddingConfig(projectDir: string, embeddings: KloProjectEmbeddingConfig): Promise { - const project = await loadKloProject({ projectDir }); - const config = markKloSetupStepComplete( +async function persistEmbeddingConfig(projectDir: string, embeddings: KtxProjectEmbeddingConfig): Promise { + const project = await loadKtxProject({ projectDir }); + const config = markKtxSetupStepComplete( { ...project.config, ingest: { @@ -193,19 +193,19 @@ async function persistEmbeddingConfig(projectDir: string, embeddings: KloProject }, 'embeddings', ); - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); } async function chooseCredentialRef( - backend: Extract, - args: KloSetupEmbeddingsArgs, - io: KloCliIo, - deps: KloSetupEmbeddingsDeps, + backend: Extract, + args: KtxSetupEmbeddingsArgs, + io: KtxCliIo, + deps: KtxSetupEmbeddingsDeps, ): Promise<{ status: 'ready'; ref: string; value: string } | { status: 'back' | 'missing-input' }> { const env = deps.env ?? process.env; if (args.embeddingApiKeyEnv) { const ref = envCredentialReference(args.embeddingApiKeyEnv); - const value = resolveKloConfigReference(ref, env); + const value = resolveKtxConfigReference(ref, env); if (!value) { io.stderr.write(`Missing embedding API key: ${args.embeddingApiKeyEnv} is not set.\n`); return { status: 'missing-input' }; @@ -216,7 +216,7 @@ async function chooseCredentialRef( const ref = `file:${args.embeddingApiKeyFile}`; let value: string | undefined; try { - value = resolveKloConfigReference(ref, env); + value = resolveKtxConfigReference(ref, env); } catch { value = undefined; } @@ -234,7 +234,7 @@ async function chooseCredentialRef( const defaultEnv = DEFAULTS[backend].envName ?? 'EMBEDDING_API_KEY'; const prompts = deps.prompts ?? createPromptAdapter(); const choice = await prompts.select({ - message: `How should KLO find your ${embeddingBackendDisplayName(backend)} embedding API key?`, + message: `How should KTX find your ${embeddingBackendDisplayName(backend)} embedding API key?`, options: [ { value: 'env', label: `Use ${defaultEnv} from the environment` }, { value: 'paste', label: 'Paste a key and save it as a local secret file' }, @@ -247,8 +247,8 @@ async function chooseCredentialRef( if (choice === 'paste') { io.stdout.write( `${[ - `KLO will save the key in .klo/secrets/${backend}-api-key with local file permissions,`, - 'then write a file: reference in klo.yaml.', + `KTX will save the key in .ktx/secrets/${backend}-api-key with local file permissions,`, + 'then write a file: reference in ktx.yaml.', ].join(' ')}\n`, ); const value = await prompts.password({ message: withTextInputNavigation(`${backend} embedding API key`) }); @@ -267,7 +267,7 @@ async function chooseCredentialRef( } const ref = envCredentialReference(defaultEnv); - const value = resolveKloConfigReference(ref, env); + const value = resolveKtxConfigReference(ref, env); if (!value) { io.stderr.write(`Missing embedding API key: ${defaultEnv} is not set.\n`); return { status: 'missing-input' }; @@ -276,8 +276,8 @@ async function chooseCredentialRef( } async function chooseEmbeddingBackend( - args: KloSetupEmbeddingsArgs, - deps: KloSetupEmbeddingsDeps, + args: KtxSetupEmbeddingsArgs, + deps: KtxSetupEmbeddingsDeps, ): Promise { if (args.embeddingBackend) { return args.embeddingBackend; @@ -286,7 +286,7 @@ async function chooseEmbeddingBackend( return LOCAL_EMBEDDING_BACKEND; } const choice = await (deps.prompts ?? createPromptAdapter()).select({ - message: `Which embedding option should KLO use?\n\n${EMBEDDING_OPTION_PROMPT_CONTEXT}`, + message: `Which embedding option should KTX use?\n\n${EMBEDDING_OPTION_PROMPT_CONTEXT}`, options: [ { value: 'sentence-transformers', label: 'Local sentence-transformers embeddings' }, { value: 'openai', label: 'OpenAI embeddings (recommended)' }, @@ -302,18 +302,18 @@ async function chooseEmbeddingBackend( function localEmbeddingSetupMessage(message: string): string { return [ `Local embedding health check failed: ${message}`, - 'Local embeddings use the KLO Python daemon. KLO can call klo-daemon automatically when it is on PATH.', + 'Local embeddings use the KTX Python daemon. KTX can call ktx-daemon automatically when it is on PATH.', `For repeated inference, start the HTTP daemon in another terminal with: ${LOCAL_EMBEDDING_DAEMON_COMMAND}`, - `From the KLO repo, use: ${LOCAL_EMBEDDING_DAEMON_DEV_COMMAND}`, + `From the KTX repo, use: ${LOCAL_EMBEDDING_DAEMON_DEV_COMMAND}`, 'The first run may download the all-MiniLM-L6-v2 model, so it can take a minute.', ].join('\n'); } async function promptAfterLocalEmbeddingFailure( - deps: KloSetupEmbeddingsDeps, -): Promise<'retry' | Extract | 'back'> { + deps: KtxSetupEmbeddingsDeps, +): Promise<'retry' | Extract | 'back'> { const choice = await (deps.prompts ?? createPromptAdapter()).select({ - message: 'Local embeddings are not reachable. Start the local KLO daemon, then retry.', + message: 'Local embeddings are not reachable. Start the local KTX daemon, then retry.', options: [ { value: 'retry', label: 'Retry' }, { value: 'openai', label: 'Use OpenAI embeddings' }, @@ -326,7 +326,7 @@ async function promptAfterLocalEmbeddingFailure( return 'retry'; } -function healthCheckStartText(backend: KloSetupEmbeddingBackend, model: string, dimensions: number): string { +function healthCheckStartText(backend: KtxSetupEmbeddingBackend, model: string, dimensions: number): string { if (backend === LOCAL_EMBEDDING_BACKEND) { return [ `Testing local sentence-transformers embeddings (${model}, ${dimensions} dimensions).`, @@ -336,7 +336,7 @@ function healthCheckStartText(backend: KloSetupEmbeddingBackend, model: string, return `Checking ${backend} embeddings (${model}, ${dimensions} dimensions).`; } -function startHealthCheckProgress(io: KloCliIo, message: string): HealthCheckProgress { +function startHealthCheckProgress(io: KtxCliIo, message: string): HealthCheckProgress { if (io.stdout.isTTY !== true) { io.stdout.write(`${message}\n`); const noop = () => undefined; @@ -376,17 +376,17 @@ function startHealthCheckProgress(io: KloCliIo, message: string): HealthCheckPro }; } -export async function runKloSetupEmbeddingsStep( - args: KloSetupEmbeddingsArgs, - io: KloCliIo, - deps: KloSetupEmbeddingsDeps = {}, -): Promise { +export async function runKtxSetupEmbeddingsStep( + args: KtxSetupEmbeddingsArgs, + io: KtxCliIo, + deps: KtxSetupEmbeddingsDeps = {}, +): Promise { if (args.skipEmbeddings) { io.stdout.write('Embeddings setup skipped.\n'); return { status: 'skipped', projectDir: args.projectDir }; } - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if ( args.forcePrompt !== true && hasCompletedEmbeddings(project.config) && @@ -400,9 +400,9 @@ export async function runKloSetupEmbeddingsStep( const healthCheck = deps.healthCheck ?? - ((config: KloEmbeddingConfig) => - runKloEmbeddingHealthCheck(config, { timeoutMs: LOCAL_EMBEDDING_HEALTH_TIMEOUT_MS })); - let selectedBackend: KloSetupEmbeddingBackend | undefined; + ((config: KtxEmbeddingConfig) => + runKtxEmbeddingHealthCheck(config, { timeoutMs: LOCAL_EMBEDDING_HEALTH_TIMEOUT_MS })); + let selectedBackend: KtxSetupEmbeddingBackend | undefined; while (true) { if (!selectedBackend) { @@ -439,7 +439,7 @@ export async function runKloSetupEmbeddingsStep( credentialValue, }); const progress = startHealthCheckProgress(io, healthCheckStartText(selectedBackend, model, dimensions)); - let health: KloEmbeddingHealthCheckResult; + let health: KtxEmbeddingHealthCheckResult; try { health = await healthCheck(healthConfig); } catch (error) { diff --git a/packages/cli/src/setup-interrupt.test.ts b/packages/cli/src/setup-interrupt.test.ts index 5bc2fb90..d8e6350a 100644 --- a/packages/cli/src/setup-interrupt.test.ts +++ b/packages/cli/src/setup-interrupt.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { - KloSetupExitError, + KtxSetupExitError, withSetupInterruptConfirmation, type SetupInterruptTracker, } from './setup-interrupt.js'; @@ -58,7 +58,7 @@ describe('setup interrupt confirmation', () => { isCancel: (value): value is symbol => value === CANCEL, tracker: makeTracker([true]), }), - ).rejects.toBeInstanceOf(KloSetupExitError); + ).rejects.toBeInstanceOf(KtxSetupExitError); }); it('keeps non-Ctrl+C cancellation available for Back and Escape flows', async () => { @@ -85,6 +85,6 @@ describe('setup interrupt confirmation', () => { isCancel: (value): value is symbol => value === CANCEL, tracker: makeTracker([true]), }), - ).rejects.toBeInstanceOf(KloSetupExitError); + ).rejects.toBeInstanceOf(KtxSetupExitError); }); }); diff --git a/packages/cli/src/setup-interrupt.ts b/packages/cli/src/setup-interrupt.ts index 715f2e07..5773c336 100644 --- a/packages/cli/src/setup-interrupt.ts +++ b/packages/cli/src/setup-interrupt.ts @@ -2,10 +2,10 @@ import { stdin } from 'node:process'; import type { Key } from 'node:readline'; import { cancel, confirm, isCancel as isClackCancel } from '@clack/prompts'; -export class KloSetupExitError extends Error { +export class KtxSetupExitError extends Error { constructor() { - super('KLO setup exit requested'); - this.name = 'KloSetupExitError'; + super('KTX setup exit requested'); + this.name = 'KtxSetupExitError'; } } @@ -56,8 +56,8 @@ async function defaultConfirmExit(): Promise { }); } -export function isKloSetupExitError(error: unknown): error is KloSetupExitError { - return error instanceof KloSetupExitError; +export function isKtxSetupExitError(error: unknown): error is KtxSetupExitError { + return error instanceof KtxSetupExitError; } export async function withSetupInterruptConfirmation( @@ -84,7 +84,7 @@ export async function withSetupInterruptConfirmation( const shouldExit = await confirmExit(); if (isCancel(shouldExit) || shouldExit === true) { cancel('Setup cancelled.'); - throw new KloSetupExitError(); + throw new KtxSetupExitError(); } } } diff --git a/packages/cli/src/setup-models.test.ts b/packages/cli/src/setup-models.test.ts index 34396966..81c8b361 100644 --- a/packages/cli/src/setup-models.test.ts +++ b/packages/cli/src/setup-models.test.ts @@ -1,13 +1,13 @@ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject, parseKloProjectConfig } from '@klo/context/project'; +import { initKtxProject, parseKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { BUNDLED_ANTHROPIC_MODELS, fetchAnthropicModels, - type KloSetupModelPromptAdapter, - runKloSetupAnthropicModelStep, + type KtxSetupModelPromptAdapter, + runKtxSetupAnthropicModelStep, } from './setup-models.js'; function makeIo() { @@ -39,7 +39,7 @@ function makePromptAdapter(options: { textValues?: string[]; passwordValue?: string; passwordValues?: Array; -}): KloSetupModelPromptAdapter { +}): KtxSetupModelPromptAdapter { const selectValues = [...(options.selectValues ?? [])]; const textValues = [...(options.textValues ?? [])]; const passwordValues = [...(options.passwordValues ?? [])]; @@ -64,8 +64,8 @@ describe('setup Anthropic model step', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-models-')); - await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-models-')); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); }); afterEach(async () => { @@ -102,7 +102,7 @@ describe('setup Anthropic model step', () => { it('filters Claude Sonnet 4 and Claude Opus 4 from Anthropic model prompt choices', async () => { const prompts = makePromptAdapter({ selectValues: ['env', 'back', 'back'] }); - await runKloSetupAnthropicModelStep( + await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { @@ -120,7 +120,7 @@ describe('setup Anthropic model step', () => { expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: expect.stringContaining('Which Anthropic model should KLO use?'), + message: expect.stringContaining('Which Anthropic model should KTX use?'), options: [ { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (recommended)' }, { value: 'claude-opus-4-6', label: 'Claude Opus 4.6' }, @@ -134,7 +134,7 @@ describe('setup Anthropic model step', () => { it('configures env credentials, selected model, prompt caching, and llm completion state', async () => { const io = makeIo(); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', @@ -150,7 +150,7 @@ describe('setup Anthropic model step', () => { ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm).toMatchObject({ provider: { backend: 'anthropic', @@ -171,7 +171,7 @@ describe('setup Anthropic model step', () => { await writeFile(secretPath, 'sk-ant-file', 'utf-8'); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', @@ -190,7 +190,7 @@ describe('setup Anthropic model step', () => { modelSlots: { default: 'claude-sonnet-4-6' }, }), ); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm).toMatchObject({ provider: { backend: 'anthropic', @@ -207,7 +207,7 @@ describe('setup Anthropic model step', () => { const missingSecretPath = join(tempDir, 'missing-anthropic-api-key'); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', @@ -227,7 +227,7 @@ describe('setup Anthropic model step', () => { it('does not recommend skipping when non-interactive setup is missing an Anthropic credential source', async () => { const io = makeIo(); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', skipLlm: false }, io.io, ); @@ -243,7 +243,7 @@ describe('setup Anthropic model step', () => { const io = makeIo(); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', @@ -260,7 +260,7 @@ describe('setup Anthropic model step', () => { expect(io.stderr()).not.toContain('--skip-llm'); }); - it('writes pasted keys to .klo/secrets and never prints the key', async () => { + it('writes pasted keys to .ktx/secrets and never prints the key', async () => { const io = makeIo(); const prompts = makePromptAdapter({ credentialChoice: 'paste', @@ -268,7 +268,7 @@ describe('setup Anthropic model step', () => { passwordValue: 'sk-ant-pasted', }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { @@ -280,11 +280,11 @@ describe('setup Anthropic model step', () => { ); expect(result.status).toBe('ready'); - await expect(readFile(join(tempDir, '.klo/secrets/anthropic-api-key'), 'utf-8')).resolves.toBe('sk-ant-pasted\n'); + await expect(readFile(join(tempDir, '.ktx/secrets/anthropic-api-key'), 'utf-8')).resolves.toBe('sk-ant-pasted\n'); if (process.platform !== 'win32') { - expect((await stat(join(tempDir, '.klo/secrets/anthropic-api-key'))).mode & 0o777).toBe(0o600); + expect((await stat(join(tempDir, '.ktx/secrets/anthropic-api-key'))).mode & 0o777).toBe(0o600); } - const yaml = await readFile(join(tempDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(tempDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('api_key: file:'); expect(yaml).not.toContain('sk-ant-pasted'); expect(io.stdout()).not.toContain('sk-ant-pasted'); @@ -296,7 +296,7 @@ describe('setup Anthropic model step', () => { passwordValue: 'sk-ant-pasted', }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { @@ -317,7 +317,7 @@ describe('setup Anthropic model step', () => { it('does not offer skipping while choosing an Anthropic credential source', async () => { const prompts = makePromptAdapter({ credentialChoice: 'back' }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { prompts, env: {} }, @@ -326,26 +326,26 @@ describe('setup Anthropic model step', () => { expect(result.status).toBe('back'); expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: expect.stringContaining('How should KLO find your Anthropic API key?'), + message: expect.stringContaining('How should KTX find your Anthropic API key?'), options: expect.not.arrayContaining([expect.objectContaining({ value: 'skip' })]), }), ); }); - it('explains why KLO asks for an Anthropic API key', async () => { + it('explains why KTX asks for an Anthropic API key', async () => { const io = makeIo(); const prompts = makePromptAdapter({ credentialChoice: 'back' }); const expectedPromptMessage = [ - 'How should KLO find your Anthropic API key?', + 'How should KTX find your Anthropic API key?', '', [ - 'KLO uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL,', - 'BI metadata, and docs into semantic-layer sources and wiki context. klo.yaml stores an env: or file:', + 'KTX uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL,', + 'BI metadata, and docs into semantic-layer sources and wiki context. ktx.yaml stores an env: or file:', 'reference, not the raw key.', ].join(' '), ].join('\n'); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { prompts, env: {} }, @@ -357,13 +357,13 @@ describe('setup Anthropic model step', () => { message: expectedPromptMessage, }), ); - expect(io.stdout()).not.toContain('KLO uses the key'); + expect(io.stdout()).not.toContain('KTX uses the key'); }); it('does not offer skipping while choosing an Anthropic model', async () => { const prompts = makePromptAdapter({ selectValues: ['env', 'back', 'back'] }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { @@ -376,25 +376,25 @@ describe('setup Anthropic model step', () => { expect(result.status).toBe('back'); expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: expect.stringContaining('Which Anthropic model should KLO use?'), + message: expect.stringContaining('Which Anthropic model should KTX use?'), options: expect.not.arrayContaining([expect.objectContaining({ value: 'skip' })]), }), ); }); - it('explains why KLO asks for an Anthropic model', async () => { + it('explains why KTX asks for an Anthropic model', async () => { const io = makeIo(); const prompts = makePromptAdapter({ credentialChoice: 'env', modelChoice: 'claude-sonnet-4-6' }); const expectedPromptMessage = [ - 'Which Anthropic model should KLO use?', + 'Which Anthropic model should KTX use?', '', [ - 'KLO uses this as the default model for ingest agents that turn schemas, SQL, BI metadata, and docs', + 'KTX uses this as the default model for ingest agents that turn schemas, SQL, BI metadata, and docs', 'into semantic-layer sources and wiki context.', ].join(' '), ].join('\n'); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { @@ -411,7 +411,7 @@ describe('setup Anthropic model step', () => { message: expectedPromptMessage, }), ); - expect(io.stdout()).not.toContain('KLO uses this as the default model'); + expect(io.stdout()).not.toContain('KTX uses this as the default model'); expect(io.stdout()).not.toContain('Setup verifies the selected model now'); }); @@ -420,7 +420,7 @@ describe('setup Anthropic model step', () => { const prompts = makePromptAdapter({ credentialChoice: 'env', modelChoice: 'claude-sonnet-4-6' }); await expect( - runKloSetupAnthropicModelStep({ projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { + runKtxSetupAnthropicModelStep({ projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { prompts, env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, listModels: vi.fn(async () => { @@ -437,7 +437,7 @@ describe('setup Anthropic model step', () => { const io = makeIo(); const prompts = makePromptAdapter({ selectValues: ['env', 'manual'], textValues: [''] }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { @@ -454,7 +454,7 @@ describe('setup Anthropic model step', () => { expect(BUNDLED_ANTHROPIC_MODELS.length).toBeGreaterThan(0); expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: expect.stringContaining('Which Anthropic model should KLO use?'), + message: expect.stringContaining('Which Anthropic model should KTX use?'), options: expect.arrayContaining([ { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (recommended)' }, ]), @@ -476,7 +476,7 @@ describe('setup Anthropic model step', () => { ); const healthCheck = vi.fn(async () => ({ ok: true as const })); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { @@ -498,7 +498,7 @@ describe('setup Anthropic model step', () => { it('does not persist llm completion when the health check fails', async () => { const io = makeIo(); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', @@ -514,7 +514,7 @@ describe('setup Anthropic model step', () => { ); expect(result.status).toBe('failed'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps ?? []).not.toContain('llm'); expect(io.stderr()).toContain('Anthropic model health check failed: 401 invalid x-api-key [redacted]'); expect(io.stderr()).not.toContain('sk-ant-test'); @@ -530,7 +530,7 @@ describe('setup Anthropic model step', () => { .mockResolvedValueOnce({ ok: false as const, message: 'model not found' }) .mockResolvedValueOnce({ ok: true as const }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, io.io, { @@ -549,33 +549,33 @@ describe('setup Anthropic model step', () => { expect(prompts.select).toHaveBeenCalledTimes(4); expect(io.stderr()).toContain('Anthropic model health check failed: model not found'); expect(io.stderr()).toContain('Choose a different credential source or model, or Back.'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm.models.default).toBe('claude-sonnet-4-6'); expect(config.setup?.completed_steps).toContain('llm'); expect(io.stderr()).not.toContain('sk-ant-test'); }); it('leaves setup incomplete when skipped', async () => { - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'disabled', skipLlm: true }, makeIo().io, ); expect(result.status).toBe('skipped'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps ?? []).not.toContain('llm'); }); it('returns back without writing config when Back is selected', async () => { const prompts = makePromptAdapter({ credentialChoice: 'back' }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { prompts, env: {} }, ); expect(result.status).toBe('back'); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm.provider.backend).toBe('none'); }); @@ -585,7 +585,7 @@ describe('setup Anthropic model step', () => { passwordValue: 'sk-ant-pasted', }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { @@ -600,10 +600,10 @@ describe('setup Anthropic model step', () => { expect(prompts.select).toHaveBeenNthCalledWith( 3, expect.objectContaining({ - message: expect.stringContaining('How should KLO find your Anthropic API key?'), + message: expect.stringContaining('How should KTX find your Anthropic API key?'), }), ); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm.provider.backend).toBe('none'); }); @@ -613,7 +613,7 @@ describe('setup Anthropic model step', () => { passwordValues: [undefined], }); - const result = await runKloSetupAnthropicModelStep( + const result = await runKtxSetupAnthropicModelStep( { projectDir: tempDir, inputMode: 'auto', skipLlm: false }, makeIo().io, { @@ -628,10 +628,10 @@ describe('setup Anthropic model step', () => { expect(prompts.password).toHaveBeenCalledWith({ message: 'Anthropic API key\nPress Escape to go back.\n', }); - await expect(readFile(join(tempDir, '.klo/secrets/anthropic-api-key'), 'utf-8')).rejects.toMatchObject({ + await expect(readFile(join(tempDir, '.ktx/secrets/anthropic-api-key'), 'utf-8')).rejects.toMatchObject({ code: 'ENOENT', }); - const config = parseKloProjectConfig(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); expect(config.llm.provider).toMatchObject({ backend: 'anthropic', anthropic: { api_key: 'env:ANTHROPIC_API_KEY' }, @@ -639,10 +639,10 @@ describe('setup Anthropic model step', () => { }); it('preserves already completed llm setup when no model args request changes', async () => { - await mkdir(join(tempDir, '.klo'), { recursive: true }); - await initKloProject({ projectDir: tempDir, projectName: 'warehouse', force: true }); + await mkdir(join(tempDir, '.ktx'), { recursive: true }); + await initKtxProject({ projectDir: tempDir, projectName: 'warehouse', force: true }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: warehouse', 'setup:', @@ -669,7 +669,7 @@ describe('setup Anthropic model step', () => { const healthCheck = vi.fn(async () => ({ ok: true as const })); await expect( - runKloSetupAnthropicModelStep({ projectDir: tempDir, inputMode: 'disabled', skipLlm: false }, makeIo().io, { + runKtxSetupAnthropicModelStep({ projectDir: tempDir, inputMode: 'disabled', skipLlm: false }, makeIo().io, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, healthCheck, }), diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index afb05e02..28908849 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -1,20 +1,20 @@ import { writeFile } from 'node:fs/promises'; import { cancel, isCancel, password, select, text } from '@clack/prompts'; -import { resolveKloConfigReference } from '@klo/context/core'; +import { resolveKtxConfigReference } from '@ktx/context/core'; import { - type KloProjectConfig, - type KloProjectLlmConfig, - loadKloProject, - markKloSetupStepComplete, - serializeKloProjectConfig, -} from '@klo/context/project'; -import { type KloLlmConfig, type KloLlmHealthCheckResult, runKloLlmHealthCheck } from '@klo/llm'; -import type { KloCliIo } from './cli-runtime.js'; + type KtxProjectConfig, + type KtxProjectLlmConfig, + loadKtxProject, + markKtxSetupStepComplete, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import { type KtxLlmConfig, type KtxLlmHealthCheckResult, runKtxLlmHealthCheck } from '@ktx/llm'; +import type { KtxCliIo } from './cli-runtime.js'; import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; import { envCredentialReference, writeProjectLocalSecretReference } from './setup-secrets.js'; -export interface KloSetupModelArgs { +export interface KtxSetupModelArgs { projectDir: string; inputMode: 'auto' | 'disabled'; anthropicApiKeyEnv?: string; @@ -25,7 +25,7 @@ export interface KloSetupModelArgs { skipLlm: boolean; } -export type KloSetupModelResult = +export type KtxSetupModelResult = | { status: 'ready'; projectDir: string } | { status: 'skipped'; projectDir: string } | { status: 'back'; projectDir: string } @@ -38,19 +38,19 @@ export interface AnthropicModelChoice { recommended: boolean; } -export interface KloSetupModelPromptAdapter { +export interface KtxSetupModelPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; text(options: { message: string; placeholder?: string }): Promise; password(options: { message: string }): Promise; cancel(message: string): void; } -export interface KloSetupModelDeps { +export interface KtxSetupModelDeps { env?: NodeJS.ProcessEnv; fetch?: typeof fetch; - prompts?: KloSetupModelPromptAdapter; + prompts?: KtxSetupModelPromptAdapter; listModels?: (apiKey: string) => Promise; - healthCheck?: (config: KloLlmConfig) => Promise; + healthCheck?: (config: KtxLlmConfig) => Promise; } export const BUNDLED_ANTHROPIC_MODEL_REGISTRY_VERSION = '2026-05-07'; @@ -69,12 +69,12 @@ const HIDDEN_ANTHROPIC_MODEL_PATTERNS = [ ]; const ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT = - 'KLO uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL, ' + - 'BI metadata, and docs into semantic-layer sources and wiki context. klo.yaml stores an env: or file: ' + + 'KTX uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL, ' + + 'BI metadata, and docs into semantic-layer sources and wiki context. ktx.yaml stores an env: or file: ' + 'reference, not the raw key.'; const ANTHROPIC_MODEL_PROMPT_CONTEXT = - 'KLO uses this as the default model for ingest agents that turn schemas, SQL, BI metadata, and docs ' + + 'KTX uses this as the default model for ingest agents that turn schemas, SQL, BI metadata, and docs ' + 'into semantic-layer sources and wiki context.'; type AnthropicModelDiscoveryErrorReason = 'authentication' | 'http' | 'empty-response'; @@ -102,7 +102,7 @@ type ChooseModelResult = | { status: 'ready'; model: string } | { status: 'back' | 'missing-input' | 'invalid-credential' }; -function createPromptAdapter(): KloSetupModelPromptAdapter { +function createPromptAdapter(): KtxSetupModelPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -170,7 +170,7 @@ export async function fetchAnthropicModels( return models.map((item, index) => ({ ...item, recommended: index === Math.max(recommendedIndex, 0) })); } -function hasCompletedLlm(config: KloProjectConfig): boolean { +function hasCompletedLlm(config: KtxProjectConfig): boolean { return ( config.setup?.completed_steps.includes('llm') === true && config.llm.provider.backend === 'anthropic' && @@ -180,10 +180,10 @@ function hasCompletedLlm(config: KloProjectConfig): boolean { } function buildProjectLlmConfig( - existing: KloProjectLlmConfig, + existing: KtxProjectLlmConfig, credentialRef: string, model: string, -): KloProjectLlmConfig { +): KtxProjectLlmConfig { return { provider: { backend: 'anthropic', @@ -194,7 +194,7 @@ function buildProjectLlmConfig( }; } -function buildHealthConfig(credentialValue: string, model: string): KloLlmConfig { +function buildHealthConfig(credentialValue: string, model: string): KtxLlmConfig { return { backend: 'anthropic', anthropic: { apiKey: credentialValue }, @@ -204,14 +204,14 @@ function buildHealthConfig(credentialValue: string, model: string): KloLlmConfig } async function chooseCredentialRef( - args: KloSetupModelArgs, - io: KloCliIo, - deps: KloSetupModelDeps, + args: KtxSetupModelArgs, + io: KtxCliIo, + deps: KtxSetupModelDeps, ): Promise<{ status: 'ready'; ref: string; value: string } | { status: 'back' | 'missing-input' }> { const env = deps.env ?? process.env; if (args.anthropicApiKeyEnv) { const ref = envCredentialReference(args.anthropicApiKeyEnv); - const value = resolveKloConfigReference(ref, env); + const value = resolveKtxConfigReference(ref, env); if (!value) { io.stderr.write(`Missing Anthropic API key: ${args.anthropicApiKeyEnv} is not set.\n`); return { status: 'missing-input' }; @@ -222,7 +222,7 @@ async function chooseCredentialRef( const ref = `file:${args.anthropicApiKeyFile}`; let value: string | undefined; try { - value = resolveKloConfigReference(ref, env); + value = resolveKtxConfigReference(ref, env); } catch { value = undefined; } @@ -245,7 +245,7 @@ async function chooseCredentialRef( } while (true) { const choice = await prompts.select({ - message: `How should KLO find your Anthropic API key?\n\n${ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT}`, + message: `How should KTX find your Anthropic API key?\n\n${ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT}`, options: [ { value: 'env', label: 'Use ANTHROPIC_API_KEY from the environment' }, { value: 'paste', label: 'Paste a key and save it as a local secret file' }, @@ -257,7 +257,7 @@ async function chooseCredentialRef( } if (choice === 'paste') { io.stdout.write( - 'KLO will save the key in .klo/secrets/anthropic-api-key with local file permissions, then write a file: reference in klo.yaml.\n', + 'KTX will save the key in .ktx/secrets/anthropic-api-key with local file permissions, then write a file: reference in ktx.yaml.\n', ); const value = await prompts.password({ message: withTextInputNavigation('Anthropic API key') }); if (value === undefined) { @@ -275,7 +275,7 @@ async function chooseCredentialRef( } const ref = envCredentialReference('ANTHROPIC_API_KEY'); - const value = resolveKloConfigReference(ref, env); + const value = resolveKtxConfigReference(ref, env); if (!value) { io.stderr.write('Missing Anthropic API key: ANTHROPIC_API_KEY is not set.\n'); return { status: 'missing-input' }; @@ -285,10 +285,10 @@ async function chooseCredentialRef( } async function chooseModel( - args: KloSetupModelArgs, + args: KtxSetupModelArgs, credentialValue: string, - io: KloCliIo, - deps: KloSetupModelDeps, + io: KtxCliIo, + deps: KtxSetupModelDeps, ): Promise { if (args.anthropicModel) { return { status: 'ready', model: args.anthropicModel }; @@ -326,7 +326,7 @@ async function chooseModel( { value: 'back', label: 'Back' }, ]; const choice = await prompts.select({ - message: `Which Anthropic model should KLO use?\n\n${ANTHROPIC_MODEL_PROMPT_CONTEXT}`, + message: `Which Anthropic model should KTX use?\n\n${ANTHROPIC_MODEL_PROMPT_CONTEXT}`, options: modelOptions, }); if (choice === 'back') { @@ -346,8 +346,8 @@ async function chooseModel( } async function persistLlmConfig(projectDir: string, credentialRef: string, model: string): Promise { - const project = await loadKloProject({ projectDir }); - const config = markKloSetupStepComplete( + const project = await loadKtxProject({ projectDir }); + const config = markKtxSetupStepComplete( { ...project.config, llm: buildProjectLlmConfig(project.config.llm, credentialRef, model), @@ -361,10 +361,10 @@ async function persistLlmConfig(projectDir: string, credentialRef: string, model }, 'llm', ); - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); } -function buildInteractiveRetryArgs(args: KloSetupModelArgs): KloSetupModelArgs { +function buildInteractiveRetryArgs(args: KtxSetupModelArgs): KtxSetupModelArgs { return { projectDir: args.projectDir, inputMode: args.inputMode, @@ -373,17 +373,17 @@ function buildInteractiveRetryArgs(args: KloSetupModelArgs): KloSetupModelArgs { }; } -export async function runKloSetupAnthropicModelStep( - args: KloSetupModelArgs, - io: KloCliIo, - deps: KloSetupModelDeps = {}, -): Promise { +export async function runKtxSetupAnthropicModelStep( + args: KtxSetupModelArgs, + io: KtxCliIo, + deps: KtxSetupModelDeps = {}, +): Promise { if (args.skipLlm) { io.stdout.write('LLM setup skipped.\n'); return { status: 'skipped', projectDir: args.projectDir }; } - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if ( args.forcePrompt !== true && hasCompletedLlm(project.config) && @@ -395,7 +395,7 @@ export async function runKloSetupAnthropicModelStep( return { status: 'ready', projectDir: args.projectDir }; } - const healthCheck = deps.healthCheck ?? ((config: KloLlmConfig) => runKloLlmHealthCheck(config)); + const healthCheck = deps.healthCheck ?? ((config: KtxLlmConfig) => runKtxLlmHealthCheck(config)); let attemptArgs = args; while (true) { diff --git a/packages/cli/src/setup-project.test.ts b/packages/cli/src/setup-project.test.ts index ffaedb5c..6c75d554 100644 --- a/packages/cli/src/setup-project.test.ts +++ b/packages/cli/src/setup-project.test.ts @@ -1,9 +1,9 @@ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { initKloProject, parseKloProjectConfig } from '@klo/context/project'; +import { initKtxProject, parseKtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { type KloSetupProjectPromptAdapter, runKloSetupProjectStep } from './setup-project.js'; +import { type KtxSetupProjectPromptAdapter, runKtxSetupProjectStep } from './setup-project.js'; function makeIo(options: { stdoutIsTty?: boolean } = {}) { let stdout = ''; @@ -34,14 +34,14 @@ function makePromptAdapter(options: { choice?: string; choices?: string[]; textV select: vi.fn(async () => choices.shift() ?? 'exit'), text: vi.fn(async () => textValues.shift() ?? ''), cancel: vi.fn(), - } satisfies KloSetupProjectPromptAdapter; + } satisfies KtxSetupProjectPromptAdapter; } describe('setup project step', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-project-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-project-')); }); afterEach(async () => { @@ -52,26 +52,26 @@ describe('setup project step', () => { const projectDir = join(tempDir, 'warehouse'); const testIo = makeIo(); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir, mode: 'new', inputMode: 'disabled', yes: false }, testIo.io, ); expect(result.status).toBe('ready'); expect(result.projectDir).toBe(projectDir); - const config = parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps).toEqual(['project']); await expect(stat(join(projectDir, '.git'))).resolves.toBeDefined(); - await expect(readFile(join(projectDir, '.klo/.gitignore'), 'utf-8')).resolves.toContain('secrets/'); + await expect(readFile(join(projectDir, '.ktx/.gitignore'), 'utf-8')).resolves.toContain('secrets/'); expect(testIo.stdout()).toContain(`Project: ${projectDir}`); expect(testIo.stderr()).toBe(''); }); it('loads an existing project with --existing and preserves existing setup metadata', async () => { const projectDir = join(tempDir, 'warehouse'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'setup:', @@ -84,13 +84,13 @@ describe('setup project step', () => { 'utf-8', ); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir, mode: 'existing', inputMode: 'disabled', yes: false }, makeIo().io, ); expect(result.status).toBe('ready'); - const config = parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf-8')); expect(config.setup).toEqual({ database_connection_ids: ['warehouse'], completed_steps: ['llm', 'project'], @@ -103,26 +103,26 @@ describe('setup project step', () => { const acceptedIo = makeIo(); await expect( - runKloSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: false }, rejectedIo.io), + runKtxSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: false }, rejectedIo.io), ).resolves.toMatchObject({ status: 'missing-input' }); expect(rejectedIo.stderr()).toContain('Missing setup choice: pass --new or --yes'); - await expect(stat(join(projectDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(projectDir, 'ktx.yaml'))).rejects.toThrow(); await expect( - runKloSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: true }, acceptedIo.io), + runKtxSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: true }, acceptedIo.io), ).resolves.toMatchObject({ status: 'ready', projectDir }); - await expect(stat(join(projectDir, 'klo.yaml'))).resolves.toBeDefined(); + await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined(); }); - it('fails --existing clearly when klo.yaml is missing', async () => { + it('fails --existing clearly when ktx.yaml is missing', async () => { const projectDir = join(tempDir, 'warehouse'); const testIo = makeIo(); await expect( - runKloSetupProjectStep({ projectDir, mode: 'existing', inputMode: 'disabled', yes: false }, testIo.io), + runKtxSetupProjectStep({ projectDir, mode: 'existing', inputMode: 'disabled', yes: false }, testIo.io), ).resolves.toMatchObject({ status: 'missing-input' }); - expect(testIo.stderr()).toContain(`No existing KLO project found at ${projectDir}`); + expect(testIo.stderr()).toContain(`No existing KTX project found at ${projectDir}`); }); it('prompts to use the current directory and creates a project in interactive auto mode', async () => { @@ -130,7 +130,7 @@ describe('setup project step', () => { const prompts = makePromptAdapter({ choice: 'current' }); const testIo = makeIo({ stdoutIsTty: true }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir, mode: 'auto', inputMode: 'auto', yes: false }, testIo.io, { prompts }, @@ -140,7 +140,7 @@ describe('setup project step', () => { expect(result.projectDir).toBe(projectDir); expect(prompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Which KLO project should setup use?', + message: 'Which KTX project should setup use?', options: [ expect.objectContaining({ value: 'current', label: 'Use current directory' }), expect.objectContaining({ value: 'new', label: 'Create a new project folder' }), @@ -149,17 +149,17 @@ describe('setup project step', () => { }), ); expect(prompts.text).not.toHaveBeenCalled(); - const config = parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf-8')); + const config = parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf-8')); expect(config.setup?.completed_steps).toEqual(['project']); }); it('offers an absolute default destination for a new project folder', async () => { const startDir = join(tempDir, 'start'); - const projectDir = join(startDir, 'klo-project'); + const projectDir = join(startDir, 'ktx-project'); const prompts = makePromptAdapter({ choices: ['new', 'default', 'create'] }); const testIo = makeIo({ stdoutIsTty: true }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, testIo.io, { prompts }, @@ -170,7 +170,7 @@ describe('setup project step', () => { expect(prompts.select).toHaveBeenNthCalledWith( 2, expect.objectContaining({ - message: 'Where should KLO create the project?', + message: 'Where should KTX create the project?', options: [ expect.objectContaining({ value: 'default', @@ -183,20 +183,20 @@ describe('setup project step', () => { ); expect(prompts.select).toHaveBeenNthCalledWith( 3, - expect.objectContaining({ message: `Create KLO project at ${projectDir}?` }), + expect.objectContaining({ message: `Create KTX project at ${projectDir}?` }), ); expect(prompts.text).not.toHaveBeenCalled(); - expect(result.status === 'ready' ? result.project.config.project : '').toBe('klo-project'); - expect(testIo.stdout()).toContain(`KLO will create:\n ${projectDir}`); - await expect(stat(join(projectDir, 'klo.yaml'))).resolves.toBeDefined(); + expect(result.status === 'ready' ? result.project.config.project : '').toBe('ktx-project'); + expect(testIo.stdout()).toContain(`KTX will create:\n ${projectDir}`); + await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined(); }); it('prompts for a custom path and resolves it inside the current setup directory', async () => { const startDir = join(tempDir, 'start'); - const projectDir = join(startDir, 'analytics-klo'); - const prompts = makePromptAdapter({ choices: ['new', 'custom', 'create'], textValue: 'analytics-klo' }); + const projectDir = join(startDir, 'analytics-ktx'); + const prompts = makePromptAdapter({ choices: ['new', 'custom', 'create'], textValue: 'analytics-ktx' }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, makeIo({ stdoutIsTty: true }).io, { prompts }, @@ -207,19 +207,19 @@ describe('setup project step', () => { expect(prompts.text).toHaveBeenCalledWith( expect.objectContaining({ message: 'Project folder path\nPress Escape to go back.\n', - placeholder: './analytics-klo, ~/analytics-klo, or /Users/you/projects/analytics-klo', + placeholder: './analytics-ktx, ~/analytics-ktx, or /Users/you/projects/analytics-ktx', }), ); - await expect(stat(join(projectDir, 'klo.yaml'))).resolves.toBeDefined(); + await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined(); }); it('expands a custom home-directory path before creating a new project', async () => { const startDir = join(tempDir, 'start'); const homeDir = join(tempDir, 'home'); - const projectDir = join(homeDir, 'analytics-klo'); - const prompts = makePromptAdapter({ choices: ['new', 'custom', 'create'], textValue: '~/analytics-klo' }); + const projectDir = join(homeDir, 'analytics-ktx'); + const prompts = makePromptAdapter({ choices: ['new', 'custom', 'create'], textValue: '~/analytics-ktx' }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, makeIo({ stdoutIsTty: true }).io, { prompts, homeDir }, @@ -227,19 +227,19 @@ describe('setup project step', () => { expect(result.status).toBe('ready'); expect(result.projectDir).toBe(projectDir); - await expect(stat(join(projectDir, 'klo.yaml'))).resolves.toBeDefined(); + await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined(); }); it('confirms a custom new project path and lets Back return to the project choice', async () => { const startDir = join(tempDir, 'start'); const homeDir = join(tempDir, 'home'); - const customProjectDir = join(homeDir, 'analytics-klo'); + const customProjectDir = join(homeDir, 'analytics-ktx'); const prompts = makePromptAdapter({ choices: ['new', 'custom', 'back', 'exit'], - textValue: '~/analytics-klo', + textValue: '~/analytics-ktx', }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, makeIo({ stdoutIsTty: true }).io, { prompts, homeDir }, @@ -250,7 +250,7 @@ describe('setup project step', () => { expect(prompts.select).toHaveBeenNthCalledWith( 3, expect.objectContaining({ - message: `Create KLO project at ${customProjectDir}?`, + message: `Create KTX project at ${customProjectDir}?`, options: [ expect.objectContaining({ value: 'create', label: 'Create project' }), expect.objectContaining({ value: 'choose-another', label: 'Choose another folder' }), @@ -260,9 +260,9 @@ describe('setup project step', () => { ); expect(prompts.select).toHaveBeenNthCalledWith( 4, - expect.objectContaining({ message: 'Which KLO project should setup use?' }), + expect.objectContaining({ message: 'Which KTX project should setup use?' }), ); - await expect(stat(join(customProjectDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(customProjectDir, 'ktx.yaml'))).rejects.toThrow(); }); it('rejects an empty new folder path without creating a project in the process cwd', async () => { @@ -274,7 +274,7 @@ describe('setup project step', () => { const testIo = makeIo({ stdoutIsTty: true }); await expect( - runKloSetupProjectStep( + runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, testIo.io, { prompts, initProject }, @@ -283,18 +283,18 @@ describe('setup project step', () => { expect(initProject).not.toHaveBeenCalled(); expect(testIo.stderr()).toContain( - 'Enter a relative path like ./analytics-klo, a home path like ~/analytics-klo, or an absolute path.', + 'Enter a relative path like ./analytics-ktx, a home path like ~/analytics-ktx, or an absolute path.', ); }); - it('confirms before creating KLO files inside an existing non-empty folder', async () => { + it('confirms before creating KTX files inside an existing non-empty folder', async () => { const startDir = join(tempDir, 'start'); - const projectDir = join(startDir, 'analytics-klo'); + const projectDir = join(startDir, 'analytics-ktx'); await mkdir(projectDir, { recursive: true }); await writeFile(join(projectDir, 'README.md'), 'Existing project notes\n', 'utf-8'); - const prompts = makePromptAdapter({ choices: ['new', 'custom', 'use-existing'], textValue: 'analytics-klo' }); + const prompts = makePromptAdapter({ choices: ['new', 'custom', 'use-existing'], textValue: 'analytics-ktx' }); - const result = await runKloSetupProjectStep( + const result = await runKtxSetupProjectStep( { projectDir: startDir, mode: 'auto', inputMode: 'auto', yes: false }, makeIo({ stdoutIsTty: true }).io, { prompts }, @@ -307,13 +307,13 @@ describe('setup project step', () => { expect.objectContaining({ message: `That folder already exists and is not empty: ${projectDir}`, options: expect.arrayContaining([ - expect.objectContaining({ value: 'use-existing', label: 'Yes, create KLO files there' }), + expect.objectContaining({ value: 'use-existing', label: 'Yes, create KTX files there' }), expect.objectContaining({ value: 'choose-another', label: 'Choose another folder' }), ]), }), ); await expect(readFile(join(projectDir, 'README.md'), 'utf-8')).resolves.toBe('Existing project notes\n'); - await expect(stat(join(projectDir, 'klo.yaml'))).resolves.toBeDefined(); + await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined(); }); it('prompts to exit and returns cancelled in interactive auto mode', async () => { @@ -321,7 +321,7 @@ describe('setup project step', () => { const prompts = makePromptAdapter({ choice: 'exit' }); await expect( - runKloSetupProjectStep( + runKtxSetupProjectStep( { projectDir, mode: 'auto', inputMode: 'auto', yes: false }, makeIo({ stdoutIsTty: true }).io, { prompts }, @@ -330,6 +330,6 @@ describe('setup project step', () => { expect(prompts.cancel).toHaveBeenCalledWith('Setup cancelled.'); expect(prompts.text).not.toHaveBeenCalled(); - await expect(stat(join(projectDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(projectDir, 'ktx.yaml'))).rejects.toThrow(); }); }); diff --git a/packages/cli/src/setup-project.ts b/packages/cli/src/setup-project.ts index 5cc87a88..094f3f3f 100644 --- a/packages/cli/src/setup-project.ts +++ b/packages/cli/src/setup-project.ts @@ -4,44 +4,44 @@ import { homedir } from 'node:os'; import { basename, join, resolve } from 'node:path'; import { cancel, isCancel, select, text } from '@clack/prompts'; import { - initKloProject, - type KloLocalProject, - loadKloProject, - markKloSetupStepComplete, - mergeKloSetupGitignoreEntries, - serializeKloProjectConfig, -} from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; + initKtxProject, + type KtxLocalProject, + loadKtxProject, + markKtxSetupStepComplete, + mergeKtxSetupGitignoreEntries, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; -export type KloSetupProjectMode = 'auto' | 'new' | 'existing' | 'prompt-new'; -export type KloSetupInputMode = 'auto' | 'disabled'; +export type KtxSetupProjectMode = 'auto' | 'new' | 'existing' | 'prompt-new'; +export type KtxSetupInputMode = 'auto' | 'disabled'; -export interface KloSetupProjectArgs { +export interface KtxSetupProjectArgs { projectDir: string; - mode: KloSetupProjectMode; - inputMode: KloSetupInputMode; + mode: KtxSetupProjectMode; + inputMode: KtxSetupInputMode; yes: boolean; allowBack?: boolean; } -export type KloSetupProjectResult = - | { status: 'ready'; projectDir: string; project: KloLocalProject; confirmedCreation?: boolean } +export type KtxSetupProjectResult = + | { status: 'ready'; projectDir: string; project: KtxLocalProject; confirmedCreation?: boolean } | { status: 'back'; projectDir: string } | { status: 'cancelled'; projectDir: string } | { status: 'missing-input'; projectDir: string }; -export interface KloSetupProjectPromptAdapter { +export interface KtxSetupProjectPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; text(options: { message: string; placeholder?: string }): Promise; cancel(message: string): void; } -export interface KloSetupProjectDeps { - prompts?: KloSetupProjectPromptAdapter; - initProject?: typeof initKloProject; - loadProject?: typeof loadKloProject; +export interface KtxSetupProjectDeps { + prompts?: KtxSetupProjectPromptAdapter; + initProject?: typeof initKtxProject; + loadProject?: typeof loadKtxProject; homeDir?: string; } @@ -51,9 +51,9 @@ type PromptProjectDirResult = | { status: 'missing-input'; projectDir: string } | { status: 'back'; projectDir: string }; -const DEFAULT_NEW_PROJECT_FOLDER_NAME = 'klo-project'; +const DEFAULT_NEW_PROJECT_FOLDER_NAME = 'ktx-project'; -function createClackSetupProjectPromptAdapter(): KloSetupProjectPromptAdapter { +function createClackSetupProjectPromptAdapter(): KtxSetupProjectPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -79,7 +79,7 @@ function createClackSetupProjectPromptAdapter(): KloSetupProjectPromptAdapter { } function hasProjectConfig(projectDir: string): boolean { - return existsSync(join(projectDir, 'klo.yaml')); + return existsSync(join(projectDir, 'ktx.yaml')); } function resolveFromProjectDir(projectDir: string, input: string, homeDir: string): string { @@ -110,40 +110,40 @@ async function existingFolderState( } async function normalizeSetupGitignore(projectDir: string): Promise { - const gitignorePath = join(projectDir, '.klo/.gitignore'); - await mkdir(join(projectDir, '.klo'), { recursive: true }); + const gitignorePath = join(projectDir, '.ktx/.gitignore'); + await mkdir(join(projectDir, '.ktx'), { recursive: true }); const current = existsSync(gitignorePath) ? await readFile(gitignorePath, 'utf-8') : ''; - await writeFile(gitignorePath, mergeKloSetupGitignoreEntries(current), 'utf-8'); + await writeFile(gitignorePath, mergeKtxSetupGitignoreEntries(current), 'utf-8'); } -async function persistProjectStep(project: KloLocalProject): Promise { - const config = markKloSetupStepComplete(project.config, 'project'); - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); +async function persistProjectStep(project: KtxLocalProject): Promise { + const config = markKtxSetupStepComplete(project.config, 'project'); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); await normalizeSetupGitignore(project.projectDir); - return await loadKloProject({ projectDir: project.projectDir }); + return await loadKtxProject({ projectDir: project.projectDir }); } -async function createProject(projectDir: string, deps: KloSetupProjectDeps): Promise { - const initProject = deps.initProject ?? initKloProject; - const initialized = await initProject({ projectDir, projectName: basename(projectDir) || 'klo-project' }); +async function createProject(projectDir: string, deps: KtxSetupProjectDeps): Promise { + const initProject = deps.initProject ?? initKtxProject; + const initialized = await initProject({ projectDir, projectName: basename(projectDir) || 'ktx-project' }); return await persistProjectStep(initialized); } -async function loadExistingProject(projectDir: string, deps: KloSetupProjectDeps): Promise { - const loadProject = deps.loadProject ?? loadKloProject; +async function loadExistingProject(projectDir: string, deps: KtxSetupProjectDeps): Promise { + const loadProject = deps.loadProject ?? loadKtxProject; const project = await loadProject({ projectDir }); return await persistProjectStep(project); } -function printProjectSummary(io: KloCliIo, projectDir: string): void { +function printProjectSummary(io: KtxCliIo, projectDir: string): void { io.stdout.write(`Project: ${projectDir}\n`); } async function promptForNewProjectDir( projectDir: string, homeDir: string, - io: KloCliIo, - prompts: KloSetupProjectPromptAdapter, + io: KtxCliIo, + prompts: KtxSetupProjectPromptAdapter, ): Promise { const defaultProjectDir = join(projectDir, DEFAULT_NEW_PROJECT_FOLDER_NAME); @@ -151,7 +151,7 @@ async function promptForNewProjectDir( io.stdout.write(`Relative paths are resolved from:\n ${projectDir}\n`); io.stdout.write(`Home paths are resolved from:\n ${homeDir}\n`); const destinationChoice = await prompts.select({ - message: 'Where should KLO create the project?', + message: 'Where should KTX create the project?', options: [ { value: 'default', label: `Create the default project folder: ${defaultProjectDir}` }, { value: 'custom', label: 'Enter a custom path' }, @@ -169,7 +169,7 @@ async function promptForNewProjectDir( } else if (destinationChoice === 'custom') { const rawSelectedDir = await prompts.text({ message: withTextInputNavigation('Project folder path'), - placeholder: './analytics-klo, ~/analytics-klo, or /Users/you/projects/analytics-klo', + placeholder: './analytics-ktx, ~/analytics-ktx, or /Users/you/projects/analytics-ktx', }); if (rawSelectedDir === undefined) { continue; @@ -177,7 +177,7 @@ async function promptForNewProjectDir( const trimmedSelectedDir = rawSelectedDir.trim(); if (trimmedSelectedDir.length === 0) { io.stderr.write( - 'Enter a relative path like ./analytics-klo, a home path like ~/analytics-klo, or an absolute path.\n', + 'Enter a relative path like ./analytics-ktx, a home path like ~/analytics-ktx, or an absolute path.\n', ); return { status: 'missing-input', projectDir }; } @@ -196,7 +196,7 @@ async function promptForNewProjectDir( const existingAction = await prompts.select({ message: `That folder already exists and is not empty: ${selectedDir}`, options: [ - { value: 'use-existing', label: 'Yes, create KLO files there' }, + { value: 'use-existing', label: 'Yes, create KTX files there' }, { value: 'choose-another', label: 'Choose another folder' }, { value: 'back', label: 'Back' }, ], @@ -213,10 +213,10 @@ async function promptForNewProjectDir( confirmedCreation = true; } - io.stdout.write(`KLO will create:\n ${selectedDir}\n`); + io.stdout.write(`KTX will create:\n ${selectedDir}\n`); if (state !== 'non-empty-directory') { const createAction = await prompts.select({ - message: `Create KLO project at ${selectedDir}?`, + message: `Create KTX project at ${selectedDir}?`, options: [ { value: 'create', label: 'Create project' }, { value: 'choose-another', label: 'Choose another folder' }, @@ -238,18 +238,18 @@ async function promptForNewProjectDir( } } -export async function runKloSetupProjectStep( - args: KloSetupProjectArgs, - io: KloCliIo, - deps: KloSetupProjectDeps = {}, -): Promise { +export async function runKtxSetupProjectStep( + args: KtxSetupProjectArgs, + io: KtxCliIo, + deps: KtxSetupProjectDeps = {}, +): Promise { const projectDir = resolve(args.projectDir); const homeDir = deps.homeDir ?? homedir(); const exists = hasProjectConfig(projectDir); if (args.mode === 'existing') { if (!exists) { - io.stderr.write(`No existing KLO project found at ${projectDir}. Pass --new to create it.\n`); + io.stderr.write(`No existing KTX project found at ${projectDir}. Pass --new to create it.\n`); return { status: 'missing-input', projectDir }; } const project = await loadExistingProject(projectDir, deps); @@ -321,7 +321,7 @@ export async function runKloSetupProjectStep( ); while (true) { const choice = await prompts.select({ - message: 'Which KLO project should setup use?', + message: 'Which KTX project should setup use?', options: [ { value: 'current', label: 'Use current directory' }, { value: 'new', label: 'Create a new project folder' }, diff --git a/packages/cli/src/setup-ready-menu.test.ts b/packages/cli/src/setup-ready-menu.test.ts index d3b90863..1e64488e 100644 --- a/packages/cli/src/setup-ready-menu.test.ts +++ b/packages/cli/src/setup-ready-menu.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, vi } from 'vitest'; -import { isKloSetupReady, runKloSetupReadyChangeMenu } from './setup-ready-menu.js'; -import type { KloSetupStatus } from './setup.js'; +import { isKtxSetupReady, runKtxSetupReadyChangeMenu } from './setup-ready-menu.js'; +import type { KtxSetupStatus } from './setup.js'; -const readyStatus: KloSetupStatus = { +const readyStatus: KtxSetupStatus = { project: { path: '/tmp/revenue', ready: true }, llm: { backend: 'anthropic', ready: true, model: 'claude-sonnet-4-6' }, embeddings: { backend: 'openai', ready: true, model: 'text-embedding-3-small', dimensions: 1536 }, @@ -14,25 +14,25 @@ const readyStatus: KloSetupStatus = { describe('setup ready menu', () => { it('recognizes a ready setup only when required sections are ready', () => { - expect(isKloSetupReady(readyStatus)).toBe(true); - expect(isKloSetupReady({ ...readyStatus, embeddings: { ready: false } })).toBe(false); - expect(isKloSetupReady({ ...readyStatus, context: { ready: false, status: 'not_started' } })).toBe(false); - expect(isKloSetupReady({ ...readyStatus, agents: [] })).toBe(false); + expect(isKtxSetupReady(readyStatus)).toBe(true); + expect(isKtxSetupReady({ ...readyStatus, embeddings: { ready: false } })).toBe(false); + expect(isKtxSetupReady({ ...readyStatus, context: { ready: false, status: 'not_started' } })).toBe(false); + expect(isKtxSetupReady({ ...readyStatus, agents: [] })).toBe(false); }); it('maps ready-project menu choices to setup sections', async () => { const prompts = { select: vi.fn(async () => 'agents'), cancel: vi.fn() }; - await expect(runKloSetupReadyChangeMenu(readyStatus, { prompts })).resolves.toEqual({ action: 'agents' }); + await expect(runKtxSetupReadyChangeMenu(readyStatus, { prompts })).resolves.toEqual({ action: 'agents' }); expect(prompts.select).toHaveBeenCalledWith({ - message: 'KLO is already set up for /tmp/revenue. What would you like to change?', + message: 'KTX is already set up for /tmp/revenue. What would you like to change?', options: [ { value: 'models', label: 'Models' }, { value: 'embeddings', label: 'Embeddings' }, { value: 'databases', label: 'Primary sources' }, { value: 'sources', label: 'Context sources' }, - { value: 'context', label: 'Rebuild KLO context' }, + { value: 'context', label: 'Rebuild KTX context' }, { value: 'agents', label: 'Agent integration' }, { value: 'exit', label: 'Exit' }, ], diff --git a/packages/cli/src/setup-ready-menu.ts b/packages/cli/src/setup-ready-menu.ts index bbb13eec..675655f2 100644 --- a/packages/cli/src/setup-ready-menu.ts +++ b/packages/cli/src/setup-ready-menu.ts @@ -1,20 +1,20 @@ import { cancel, isCancel, select } from '@clack/prompts'; import { withMenuOptionsSpacing } from './prompt-navigation.js'; -import type { KloSetupStatus } from './setup.js'; +import type { KtxSetupStatus } from './setup.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; -export type KloSetupReadyAction = 'models' | 'embeddings' | 'databases' | 'sources' | 'context' | 'agents' | 'exit'; +export type KtxSetupReadyAction = 'models' | 'embeddings' | 'databases' | 'sources' | 'context' | 'agents' | 'exit'; -export interface KloSetupReadyMenuPromptAdapter { +export interface KtxSetupReadyMenuPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; cancel(message: string): void; } -export interface KloSetupReadyMenuDeps { - prompts?: KloSetupReadyMenuPromptAdapter; +export interface KtxSetupReadyMenuDeps { + prompts?: KtxSetupReadyMenuPromptAdapter; } -export function isKloSetupReady(status: KloSetupStatus): boolean { +export function isKtxSetupReady(status: KtxSetupStatus): boolean { return ( status.project.ready && status.llm.ready && @@ -26,7 +26,7 @@ export function isKloSetupReady(status: KloSetupStatus): boolean { ); } -function createPromptAdapter(): KloSetupReadyMenuPromptAdapter { +function createPromptAdapter(): KtxSetupReadyMenuPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -42,22 +42,22 @@ function createPromptAdapter(): KloSetupReadyMenuPromptAdapter { }; } -export async function runKloSetupReadyChangeMenu( - status: KloSetupStatus, - deps: KloSetupReadyMenuDeps = {}, -): Promise<{ action: KloSetupReadyAction }> { +export async function runKtxSetupReadyChangeMenu( + status: KtxSetupStatus, + deps: KtxSetupReadyMenuDeps = {}, +): Promise<{ action: KtxSetupReadyAction }> { const prompts = deps.prompts ?? createPromptAdapter(); const action = (await prompts.select({ - message: `KLO is already set up for ${status.project.name ?? status.project.path}. What would you like to change?`, + message: `KTX is already set up for ${status.project.name ?? status.project.path}. What would you like to change?`, options: [ { value: 'models', label: 'Models' }, { value: 'embeddings', label: 'Embeddings' }, { value: 'databases', label: 'Primary sources' }, { value: 'sources', label: 'Context sources' }, - { value: 'context', label: 'Rebuild KLO context' }, + { value: 'context', label: 'Rebuild KTX context' }, { value: 'agents', label: 'Agent integration' }, { value: 'exit', label: 'Exit' }, ], - })) as KloSetupReadyAction; + })) as KtxSetupReadyAction; return { action }; } diff --git a/packages/cli/src/setup-secrets.test.ts b/packages/cli/src/setup-secrets.test.ts index 151f7685..16589db0 100644 --- a/packages/cli/src/setup-secrets.test.ts +++ b/packages/cli/src/setup-secrets.test.ts @@ -8,7 +8,7 @@ describe('setup secrets', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-secrets-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-secrets-')); }); afterEach(async () => { @@ -26,11 +26,11 @@ describe('setup secrets', () => { value: 'sk-ant-test', }); - expect(result).toBe(`file:${resolve(tempDir, '.klo/secrets/anthropic-api-key')}`); - await expect(readFile(join(tempDir, '.klo/secrets/anthropic-api-key'), 'utf-8')).resolves.toBe('sk-ant-test\n'); + expect(result).toBe(`file:${resolve(tempDir, '.ktx/secrets/anthropic-api-key')}`); + await expect(readFile(join(tempDir, '.ktx/secrets/anthropic-api-key'), 'utf-8')).resolves.toBe('sk-ant-test\n'); if (process.platform !== 'win32') { - const mode = (await stat(join(tempDir, '.klo/secrets/anthropic-api-key'))).mode & 0o777; + const mode = (await stat(join(tempDir, '.ktx/secrets/anthropic-api-key'))).mode & 0o777; expect(mode).toBe(0o600); } }); diff --git a/packages/cli/src/setup-secrets.ts b/packages/cli/src/setup-secrets.ts index 386210c4..b5357a51 100644 --- a/packages/cli/src/setup-secrets.ts +++ b/packages/cli/src/setup-secrets.ts @@ -14,7 +14,7 @@ export interface WriteProjectLocalSecretReferenceOptions { export async function writeProjectLocalSecretReference( options: WriteProjectLocalSecretReferenceOptions, ): Promise { - const secretsDir = resolve(options.projectDir, '.klo/secrets'); + const secretsDir = resolve(options.projectDir, '.ktx/secrets'); const secretPath = join(secretsDir, options.fileName); await mkdir(secretsDir, { recursive: true }); await writeFile(secretPath, `${options.value.trim()}\n`, { encoding: 'utf-8', mode: 0o600 }); diff --git a/packages/cli/src/setup-sources.test.ts b/packages/cli/src/setup-sources.test.ts index e6b85ed2..b8ff4eed 100644 --- a/packages/cli/src/setup-sources.test.ts +++ b/packages/cli/src/setup-sources.test.ts @@ -2,17 +2,17 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { - initKloProject, - type KloProjectConnectionConfig, - parseKloProjectConfig, - serializeKloProjectConfig, -} from '@klo/context/project'; + initKtxProject, + type KtxProjectConnectionConfig, + parseKtxProjectConfig, + serializeKtxProjectConfig, +} from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { - runKloSetupSourcesStep, - type KloSetupSourcesDeps, - type KloSetupSourcesPromptAdapter, - type KloSetupSourceType, + runKtxSetupSourcesStep, + type KtxSetupSourcesDeps, + type KtxSetupSourcesPromptAdapter, + type KtxSetupSourceType, } from './setup-sources.js'; function makeIo() { @@ -41,7 +41,7 @@ function prompts(values: { multiselect?: string[][]; select?: string[]; text?: Array; -}): KloSetupSourcesPromptAdapter { +}): KtxSetupSourcesPromptAdapter { const multiselectValues = [...(values.multiselect ?? [])]; const selectValues = [...(values.select ?? [])]; const textValues = [...(values.text ?? [])]; @@ -55,7 +55,7 @@ function prompts(values: { } function connectionNamePrompt(label: string): string { - return `Name this ${label} connection\nKLO will use this short name in commands and config. You can rename it now.`; + return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`; } function textInputPrompt(message: string): string { @@ -72,9 +72,9 @@ describe('setup sources step', () => { let projectDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-sources-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-sources-')); projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'sources' }); + await initKtxProject({ projectDir, projectName: 'sources' }); }); afterEach(async () => { @@ -82,14 +82,14 @@ describe('setup sources step', () => { }); async function readConfig() { - return parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf-8')); + return parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf-8')); } async function addPrimarySource() { const config = await readConfig(); await writeFile( - join(projectDir, 'klo.yaml'), - serializeKloProjectConfig({ + join(projectDir, 'ktx.yaml'), + serializeKtxProjectConfig({ ...config, connections: { ...config.connections, @@ -105,11 +105,11 @@ describe('setup sources step', () => { ); } - async function addConnection(connectionId: string, connection: KloProjectConnectionConfig) { + async function addConnection(connectionId: string, connection: KtxProjectConnectionConfig) { const config = await readConfig(); await writeFile( - join(projectDir, 'klo.yaml'), - serializeKloProjectConfig({ + join(projectDir, 'ktx.yaml'), + serializeKtxProjectConfig({ ...config, connections: { ...config.connections, @@ -123,7 +123,7 @@ describe('setup sources step', () => { it('marks optional sources complete when skipped', async () => { const io = makeIo(); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'disabled', runInitialSourceIngest: false, skipSources: true }, io.io, ), @@ -143,7 +143,7 @@ describe('setup sources step', () => { const io = makeIo(); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'disabled', @@ -176,7 +176,7 @@ describe('setup sources step', () => { const io = makeIo(); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'disabled', @@ -211,7 +211,7 @@ describe('setup sources step', () => { await addPrimarySource(); const io = makeIo(); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'disabled', @@ -236,7 +236,7 @@ describe('setup sources step', () => { const testPrompts = prompts({ multiselect: [['back']] }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, io.io, { @@ -248,7 +248,7 @@ describe('setup sources step', () => { expect(testPrompts.multiselect).toHaveBeenCalledWith( expect.objectContaining({ message: - 'Which context sources should KLO ingest?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', + 'Which context sources should KTX ingest?\nUse Up/Down to move, Space to select or unselect, Enter to confirm, Escape to go back, or Ctrl+C to exit.', }), ); const options = vi.mocked(testPrompts.multiselect).mock.calls[0]?.[0].options ?? []; @@ -267,7 +267,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, io.io, { @@ -296,11 +296,11 @@ describe('setup sources step', () => { const testPrompts = prompts({ multiselect: [['dbt']], select: ['git'], - text: ['dbt-main', 'https://github.com/acme-org/klo-dbt-demo', 'main', ''], + text: ['dbt-main', 'https://github.com/acme-org/ktx-dbt-demo', 'main', ''], }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, io.io, { @@ -311,7 +311,7 @@ describe('setup sources step', () => { ), ).resolves.toEqual({ status: 'ready', projectDir, connectionIds: ['dbt-main'] }); - expect(testGitRepo).toHaveBeenCalledWith({ repoUrl: 'https://github.com/acme-org/klo-dbt-demo' }); + expect(testGitRepo).toHaveBeenCalledWith({ repoUrl: 'https://github.com/acme-org/ktx-dbt-demo' }); expect(testPrompts.log).toHaveBeenCalledWith('Repository connected.'); expect(testPrompts.text).toHaveBeenNthCalledWith(4, { message: textInputPrompt( @@ -338,7 +338,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, io.io, { @@ -370,7 +370,7 @@ describe('setup sources step', () => { const validateDbt = vi.fn(async () => ({ ok: true as const, detail: 'project=analytics schemas=2' })); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'disabled', @@ -400,7 +400,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: true, skipSources: false }, io.io, { @@ -414,7 +414,7 @@ describe('setup sources step', () => { expect(runInitialIngest).toHaveBeenCalledTimes(1); expect((await readConfig()).connections['dbt-main']).toMatchObject({ driver: 'dbt', source_dir: '/repo/dbt' }); expect(io.stdout()).toContain('Context source saved without a completed context build for dbt-main.'); - expect(io.stdout()).toContain('Run later: klo ingest dbt-main'); + expect(io.stdout()).toContain('Run later: ktx ingest dbt-main'); }); it('retries initial source ingest from the failure menu', async () => { @@ -428,7 +428,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: true, skipSources: false }, makeIo().io, { @@ -457,7 +457,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, makeIo().io, { @@ -490,10 +490,10 @@ describe('setup sources step', () => { it('offers existing connections for every context source type', async () => { await addPrimarySource(); const cases: Array<{ - source: KloSetupSourceType; + source: KtxSetupSourceType; connectionId: string; - connection: KloProjectConnectionConfig; - deps: KloSetupSourcesDeps; + connection: KtxProjectConnectionConfig; + deps: KtxSetupSourcesDeps; expectedLabel: string; }> = [ { @@ -581,7 +581,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, makeIo().io, { @@ -616,7 +616,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, makeIo().io, { @@ -645,7 +645,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, makeIo().io, { @@ -662,10 +662,10 @@ describe('setup sources step', () => { it('backs up one prompt inside every interactive context source connection', async () => { await addPrimarySource(); const cases: Array<{ - source: KloSetupSourceType; + source: KtxSetupSourceType; select?: string[]; text: Array; - deps: KloSetupSourcesDeps; + deps: KtxSetupSourcesDeps; repeatedSelectMessage?: string; repeatedTextMessage?: string; }> = [ @@ -742,7 +742,7 @@ describe('setup sources step', () => { }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, makeIo().io, { @@ -776,7 +776,7 @@ describe('setup sources step', () => { const testPrompts = prompts({ multiselect: [['notion']] }); await expect( - runKloSetupSourcesStep( + runKtxSetupSourcesStep( { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, io.io, { prompts: testPrompts }, diff --git a/packages/cli/src/setup-sources.ts b/packages/cli/src/setup-sources.ts index 4682ef36..b0e0fe2e 100644 --- a/packages/cli/src/setup-sources.ts +++ b/packages/cli/src/setup-sources.ts @@ -3,8 +3,8 @@ import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { cancel, isCancel, log, multiselect, select, text } from '@clack/prompts'; -import { resolveNotionAuthToken } from '@klo/context/connections'; -import { resolveKloConfigReference } from '@klo/context/core'; +import { resolveNotionAuthToken } from '@ktx/context/connections'; +import { resolveKtxConfigReference } from '@ktx/context/core'; import { cloneOrPull, loadDbtSchemaFiles, @@ -14,27 +14,27 @@ import { parseLookmlStagedDir, parseMetricflowFiles, testRepoConnection, -} from '@klo/context/ingest'; +} from '@ktx/context/ingest'; import { - type KloProjectConfig, - type KloProjectConnectionConfig, - loadKloProject, - markKloSetupStepComplete, - serializeKloProjectConfig, -} from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; -import { runKloConnectionMapping } from './commands/connection-mapping.js'; -import { runKloConnection } from './connection.js'; + type KtxProjectConfig, + type KtxProjectConnectionConfig, + loadKtxProject, + markKtxSetupStepComplete, + serializeKtxProjectConfig, +} from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; +import { runKtxConnectionMapping } from './commands/connection-mapping.js'; +import { runKtxConnection } from './connection.js'; import { withMenuOptionsSpacing, withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js'; -import { runKloPublicIngest } from './public-ingest.js'; +import { runKtxPublicIngest } from './public-ingest.js'; import { withSetupInterruptConfirmation } from './setup-interrupt.js'; -export type KloSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion'; +export type KtxSetupSourceType = 'dbt' | 'metricflow' | 'metabase' | 'looker' | 'lookml' | 'notion'; -export interface KloSetupSourcesArgs { +export interface KtxSetupSourcesArgs { projectDir: string; inputMode: 'auto' | 'disabled'; - source?: KloSetupSourceType; + source?: KtxSetupSourceType; sourceConnectionId?: string; sourcePath?: string; sourceGitUrl?: string; @@ -56,14 +56,14 @@ export interface KloSetupSourcesArgs { skipSources: boolean; } -export type KloSetupSourcesResult = +export type KtxSetupSourcesResult = | { status: 'ready'; projectDir: string; connectionIds: string[] } | { status: 'skipped'; projectDir: string } | { status: 'back'; projectDir: string } | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; -export interface KloSetupSourcesPromptAdapter { +export interface KtxSetupSourcesPromptAdapter { multiselect(options: { message: string; options: Array<{ value: string; label: string }>; @@ -77,25 +77,25 @@ export interface KloSetupSourcesPromptAdapter { export type SourceValidationResult = { ok: true; detail?: string } | { ok: false; message: string }; -export interface KloSetupSourcesDeps { - prompts?: KloSetupSourcesPromptAdapter; +export interface KtxSetupSourcesDeps { + prompts?: KtxSetupSourcesPromptAdapter; testGitRepo?: (args: { repoUrl: string; authToken?: string | null }) => Promise<{ ok: true } | { ok: false; error: string }>; - validateDbt?: (connection: KloProjectConnectionConfig) => Promise; - validateMetricflow?: (connection: KloProjectConnectionConfig) => Promise; + validateDbt?: (connection: KtxProjectConnectionConfig) => Promise; + validateMetricflow?: (connection: KtxProjectConnectionConfig) => Promise; validateMetabase?: (projectDir: string, connectionId: string) => Promise; validateLooker?: (projectDir: string, connectionId: string) => Promise; - validateLookml?: (connection: KloProjectConnectionConfig) => Promise; - validateNotion?: (connection: KloProjectConnectionConfig) => Promise; - runMapping?: (projectDir: string, connectionId: string, io: KloCliIo) => Promise; + validateLookml?: (connection: KtxProjectConnectionConfig) => Promise; + validateNotion?: (connection: KtxProjectConnectionConfig) => Promise; + runMapping?: (projectDir: string, connectionId: string, io: KtxCliIo) => Promise; runInitialIngest?: ( projectDir: string, connectionId: string, - io: KloCliIo, - options: { inputMode: KloSetupSourcesArgs['inputMode'] }, + io: KtxCliIo, + options: { inputMode: KtxSetupSourcesArgs['inputMode'] }, ) => Promise; } -const SOURCE_OPTIONS: Array<{ value: KloSetupSourceType; label: string }> = [ +const SOURCE_OPTIONS: Array<{ value: KtxSetupSourceType; label: string }> = [ { value: 'dbt', label: 'dbt' }, { value: 'metricflow', label: 'MetricFlow' }, { value: 'metabase', label: 'Metabase' }, @@ -105,7 +105,7 @@ const SOURCE_OPTIONS: Array<{ value: KloSetupSourceType; label: string }> = [ ]; const SOURCE_LABELS = Object.fromEntries(SOURCE_OPTIONS.map((option) => [option.value, option.label])) as Record< - KloSetupSourceType, + KtxSetupSourceType, string >; @@ -119,7 +119,7 @@ const PRIMARY_SOURCE_DRIVERS = new Set([ 'snowflake', ]); -function createPromptAdapter(): KloSetupSourcesPromptAdapter { +function createPromptAdapter(): KtxSetupSourcesPromptAdapter { return { async multiselect(options) { const value = await withSetupInterruptConfirmation(() => multiselect(withMenuOptionsSpacing(options))); @@ -160,19 +160,19 @@ function stringField(value: unknown): string | undefined { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined; } -function sourceLabel(source: KloSetupSourceType): string { +function sourceLabel(source: KtxSetupSourceType): string { return SOURCE_LABELS[source]; } -function sourceAdapter(source: KloSetupSourceType): string { +function sourceAdapter(source: KtxSetupSourceType): string { return source; } function connectionNamePrompt(label: string): string { - return `Name this ${label} connection\nKLO will use this short name in commands and config. You can rename it now.`; + return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`; } -function gitAuthAfterFailurePrompt(source: KloSetupSourceType): string { +function gitAuthAfterFailurePrompt(source: KtxSetupSourceType): string { const label = source === 'dbt' ? 'This' : `This ${sourceLabel(source)}`; return [ `${label} repo requires authentication.`, @@ -183,7 +183,7 @@ function gitAuthAfterFailurePrompt(source: KloSetupSourceType): string { ].join('\n'); } -function sourceSubpathPrompt(source: KloSetupSourceType): string { +function sourceSubpathPrompt(source: KtxSetupSourceType): string { if (source === 'dbt') { return [ 'Folder containing dbt_project.yml (optional)', @@ -199,7 +199,7 @@ function sourceSubpathPrompt(source: KloSetupSourceType): string { } async function promptText( - prompts: KloSetupSourcesPromptAdapter, + prompts: KtxSetupSourcesPromptAdapter, options: { message: string; placeholder?: string; initialValue?: string }, ): Promise { return await prompts.text({ ...options, message: withTextInputNavigation(options.message) }); @@ -222,7 +222,7 @@ function credentialRef(value: string | undefined, label: string): string { return ref; } -function repoOrLocalSource(args: KloSetupSourcesArgs): { sourceDir?: string; repoUrl?: string } { +function repoOrLocalSource(args: KtxSetupSourcesArgs): { sourceDir?: string; repoUrl?: string } { if (args.sourcePath && args.sourceGitUrl) { throw new Error('Choose only one source location: --source-path or --source-git-url.'); } @@ -239,19 +239,19 @@ function fileRepoUrl(sourceDir: string): string { return pathToFileURL(sourceDir).toString(); } -async function writeProjectConfig(projectDir: string, config: KloProjectConfig): Promise { - const project = await loadKloProject({ projectDir }); - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); +async function writeProjectConfig(projectDir: string, config: KtxProjectConfig): Promise { + const project = await loadKtxProject({ projectDir }); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); } async function writeSourceConnection( projectDir: string, connectionId: string, - connection: KloProjectConnectionConfig, + connection: KtxProjectConnectionConfig, adapter: string, ): Promise<() => Promise> { assertSafeConnectionId(connectionId); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); const previousConnection = project.config.connections[connectionId]; const hadPreviousConnection = previousConnection !== undefined; const shouldRemoveAdapterOnRollback = !project.config.ingest.adapters.includes(adapter); @@ -268,9 +268,9 @@ async function writeSourceConnection( : [...project.config.ingest.adapters, adapter], }, }; - await writeFile(project.configPath, serializeKloProjectConfig(config), 'utf-8'); + await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8'); return async () => { - const latest = await loadKloProject({ projectDir }); + const latest = await loadKtxProject({ projectDir }); const connections = { ...latest.config.connections }; if (hadPreviousConnection) { connections[connectionId] = previousConnection; @@ -290,9 +290,9 @@ async function writeSourceConnection( }; } -async function ensureSourceAdapterEnabled(projectDir: string, source: KloSetupSourceType): Promise { +async function ensureSourceAdapterEnabled(projectDir: string, source: KtxSetupSourceType): Promise { const adapter = sourceAdapter(source); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); if (project.config.ingest.adapters.includes(adapter)) { return; } @@ -306,15 +306,15 @@ async function ensureSourceAdapterEnabled(projectDir: string, source: KloSetupSo } async function markSourcesComplete(projectDir: string): Promise { - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await writeFile( project.configPath, - serializeKloProjectConfig(markKloSetupStepComplete(project.config, 'sources')), + serializeKtxProjectConfig(markKtxSetupStepComplete(project.config, 'sources')), 'utf-8', ); } -function hasPrimarySource(config: KloProjectConfig): boolean { +function hasPrimarySource(config: KtxProjectConfig): boolean { const setupPrimaryIds = config.setup?.database_connection_ids ?? []; if (setupPrimaryIds.some((connectionId) => Object.hasOwn(config.connections, connectionId))) { return true; @@ -324,7 +324,7 @@ function hasPrimarySource(config: KloProjectConfig): boolean { ); } -function buildDbtConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildDbtConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { const source = repoOrLocalSource(args); return { driver: 'dbt', @@ -341,7 +341,7 @@ function buildDbtConnection(args: KloSetupSourcesArgs): KloProjectConnectionConf }; } -function buildMetricflowConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildMetricflowConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { const source = repoOrLocalSource(args); return { driver: 'metricflow', @@ -356,7 +356,7 @@ function buildMetricflowConnection(args: KloSetupSourcesArgs): KloProjectConnect }; } -function buildMetabaseConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildMetabaseConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { if (!args.sourceUrl) { throw new Error('Missing Metabase URL: pass --source-url.'); } @@ -378,7 +378,7 @@ function buildMetabaseConnection(args: KloSetupSourcesArgs): KloProjectConnectio }; } -function buildLookerConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildLookerConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { if (!args.sourceUrl) { throw new Error('Missing Looker base URL: pass --source-url.'); } @@ -401,7 +401,7 @@ function buildLookerConnection(args: KloSetupSourcesArgs): KloProjectConnectionC }; } -function buildLookmlConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildLookmlConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { const source = repoOrLocalSource(args); return { driver: 'lookml', @@ -417,7 +417,7 @@ function buildLookmlConnection(args: KloSetupSourcesArgs): KloProjectConnectionC }; } -function buildNotionConnection(args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildNotionConnection(args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { const crawlMode = args.notionCrawlMode ?? 'selected_roots'; const rootPageIds = args.notionRootPageIds ?? []; if (crawlMode === 'selected_roots' && rootPageIds.length === 0) { @@ -442,10 +442,10 @@ function sourcePathFromFileRepoUrl(repoUrl: string, subpath?: string): string { return subpath ? join(root, subpath) : root; } -function repoAuthToken(connection: KloProjectConnectionConfig | Record): string | null { +function repoAuthToken(connection: KtxProjectConnectionConfig | Record): string | null { const ref = stringField(connection.auth_token_ref) ?? stringField(connection.authTokenRef); const literal = stringField(connection.authToken) ?? stringField(connection.auth_token); - return literal ?? resolveKloConfigReference(ref, process.env) ?? null; + return literal ?? resolveKtxConfigReference(ref, process.env) ?? null; } async function collectYamlFilesRecursive(sourceRoot: string): Promise> { @@ -461,14 +461,14 @@ async function collectYamlFilesRecursive(sourceRoot: string): Promise { +async function defaultValidateDbt(connection: KtxProjectConnectionConfig): Promise { let sourceDir = stringField(connection.source_dir) ?? stringField(connection.sourceDir); const repoUrl = stringField(connection.repo_url) ?? stringField(connection.repoUrl); if (!sourceDir && repoUrl?.startsWith('file:')) { sourceDir = sourcePathFromFileRepoUrl(repoUrl, stringField(connection.path)); } if (!sourceDir && repoUrl) { - const cacheDir = await mkdtemp(join(tmpdir(), 'klo-setup-dbt-')); + const cacheDir = await mkdtemp(join(tmpdir(), 'ktx-setup-dbt-')); await cloneOrPull({ repoUrl, authToken: repoAuthToken(connection), @@ -488,7 +488,7 @@ async function defaultValidateDbt(connection: KloProjectConnectionConfig): Promi return { ok: true, detail: `project=${info.projectName ?? connection.project_name} schemas=${schemaFiles.length}` }; } -async function defaultValidateMetricflow(connection: KloProjectConnectionConfig): Promise { +async function defaultValidateMetricflow(connection: KtxProjectConnectionConfig): Promise { const metricflow = isRecord(connection.metricflow) ? connection.metricflow : undefined; const repoUrl = stringField(metricflow?.repoUrl); if (!repoUrl) { @@ -513,7 +513,7 @@ async function defaultValidateMetricflow(connection: KloProjectConnectionConfig) } async function defaultValidateMetabase(projectDir: string, connectionId: string): Promise { - const code = await runKloConnection( + const code = await runKtxConnection( { command: 'map', projectDir, sourceConnectionId: connectionId, json: true }, { stdout: { write() {} }, stderr: { write() {} } }, ); @@ -523,7 +523,7 @@ async function defaultValidateMetabase(projectDir: string, connectionId: string) } async function defaultValidateLooker(projectDir: string, connectionId: string): Promise { - const code = await runKloConnectionMapping( + const code = await runKtxConnectionMapping( { command: 'refresh', projectDir, connectionId, autoAccept: true }, { stdout: { write() {} }, stderr: { write() {} } }, ); @@ -532,7 +532,7 @@ async function defaultValidateLooker(projectDir: string, connectionId: string): : { ok: false, message: 'Looker validation failed' }; } -async function defaultValidateLookml(connection: KloProjectConnectionConfig): Promise { +async function defaultValidateLookml(connection: KtxProjectConnectionConfig): Promise { const repoUrl = stringField(connection.repoUrl) ?? stringField(connection.repo_url); if (!repoUrl) { return { ok: false, message: 'LookML setup requires repoUrl.' }; @@ -546,7 +546,7 @@ async function defaultValidateLookml(connection: KloProjectConnectionConfig): Pr return count > 0 ? { ok: true, detail: `lookmlFiles=${count}` } : { ok: false, message: 'No LookML files found' }; } -async function defaultValidateNotion(connection: KloProjectConnectionConfig): Promise { +async function defaultValidateNotion(connection: KtxProjectConnectionConfig): Promise { const token = await resolveNotionAuthToken(String(connection.auth_token_ref)); const client: NotionApi = new NotionClient(token); await client.retrieveBotUser(); @@ -559,17 +559,17 @@ async function defaultValidateNotion(connection: KloProjectConnectionConfig): Pr return { ok: true, detail: `roots=${roots.length}` }; } -async function defaultRunMapping(projectDir: string, connectionId: string, io: KloCliIo): Promise { - return await runKloConnection({ command: 'map', projectDir, sourceConnectionId: connectionId, json: false }, io); +async function defaultRunMapping(projectDir: string, connectionId: string, io: KtxCliIo): Promise { + return await runKtxConnection({ command: 'map', projectDir, sourceConnectionId: connectionId, json: false }, io); } async function defaultRunInitialIngest( projectDir: string, connectionId: string, - io: KloCliIo, - options: { inputMode: KloSetupSourcesArgs['inputMode'] }, + io: KtxCliIo, + options: { inputMode: KtxSetupSourcesArgs['inputMode'] }, ): Promise { - return await runKloPublicIngest( + return await runKtxPublicIngest( { command: 'run', projectDir, @@ -583,11 +583,11 @@ async function defaultRunInitialIngest( } async function runInitialSourceIngestWithRecovery(input: { - args: KloSetupSourcesArgs; + args: KtxSetupSourcesArgs; connectionId: string; - io: KloCliIo; - prompts: KloSetupSourcesPromptAdapter; - deps: KloSetupSourcesDeps; + io: KtxCliIo; + prompts: KtxSetupSourcesPromptAdapter; + deps: KtxSetupSourcesDeps; }): Promise<'ready' | 'continue' | 'back' | 'failed'> { while (true) { input.io.stdout.write(`Building context from ${input.connectionId}. Large sources can take a while.\n`); @@ -619,7 +619,7 @@ async function runInitialSourceIngestWithRecovery(input: { } if (action === 'continue') { input.io.stdout.write(`Context source saved without a completed context build for ${input.connectionId}.\n`); - input.io.stdout.write(`Run later: klo ingest ${input.connectionId}\n`); + input.io.stdout.write(`Run later: ktx ingest ${input.connectionId}\n`); return 'continue'; } return 'back'; @@ -628,21 +628,21 @@ async function runInitialSourceIngestWithRecovery(input: { type SourceLocationChoice = 'path' | 'git'; -type SourcePromptState = KloSetupSourcesArgs & { +type SourcePromptState = KtxSetupSourcesArgs & { sourceLocation?: SourceLocationChoice; }; type SourcePromptStep = (state: SourcePromptState) => Promise<'next' | 'back'>; type InteractiveSourceConnectionChoice = - | { kind: 'existing'; connectionId: string; connection: KloProjectConnectionConfig } - | { kind: 'new'; args: KloSetupSourcesArgs } + | { kind: 'existing'; connectionId: string; connection: KtxProjectConnectionConfig } + | { kind: 'new'; args: KtxSetupSourcesArgs } | 'back'; async function runSourcePromptSteps( initialState: SourcePromptState, stepsForState: (state: SourcePromptState) => SourcePromptStep[], -): Promise { +): Promise { let stepIndex = 0; while (true) { const steps = stepsForState(initialState); @@ -673,9 +673,9 @@ function resetRepoLocationFields(state: SourcePromptState): void { } function connectionIdPromptSteps( - args: KloSetupSourcesArgs, - source: KloSetupSourceType, - prompts: KloSetupSourcesPromptAdapter, + args: KtxSetupSourcesArgs, + source: KtxSetupSourceType, + prompts: KtxSetupSourcesPromptAdapter, defaultConnectionId: string, ): SourcePromptStep[] { if (args.sourceConnectionId) { @@ -698,12 +698,12 @@ function connectionIdPromptSteps( } async function promptForInteractiveSource( - args: KloSetupSourcesArgs, - source: KloSetupSourceType, - prompts: KloSetupSourcesPromptAdapter, + args: KtxSetupSourcesArgs, + source: KtxSetupSourceType, + prompts: KtxSetupSourcesPromptAdapter, defaultConnectionId = `${source}-main`, - testGitRepo: KloSetupSourcesDeps['testGitRepo'] = testRepoConnection, -): Promise { + testGitRepo: KtxSetupSourcesDeps['testGitRepo'] = testRepoConnection, +): Promise { const initialState: SourcePromptState = { ...args, source }; if (args.sourceConnectionId) { initialState.sourceConnectionId = args.sourceConnectionId; @@ -926,8 +926,8 @@ async function promptForInteractiveSource( } function existingConnectionIdsBySource( - connections: Record, - source: KloSetupSourceType, + connections: Record, + source: KtxSetupSourceType, ): string[] { return Object.entries(connections) .filter(([, connection]) => String(connection.driver ?? '').toLowerCase() === source) @@ -936,8 +936,8 @@ function existingConnectionIdsBySource( } function defaultConnectionIdForSource( - connections: Record, - source: KloSetupSourceType, + connections: Record, + source: KtxSetupSourceType, ): string { const base = `${source}-main`; if (!connections[base]) { @@ -951,11 +951,11 @@ function defaultConnectionIdForSource( } async function chooseInteractiveSourceConnection(input: { - args: KloSetupSourcesArgs; - source: KloSetupSourceType; - connections: Record; - prompts: KloSetupSourcesPromptAdapter; - testGitRepo?: KloSetupSourcesDeps['testGitRepo']; + args: KtxSetupSourcesArgs; + source: KtxSetupSourceType; + connections: Record; + prompts: KtxSetupSourcesPromptAdapter; + testGitRepo?: KtxSetupSourcesDeps['testGitRepo']; }): Promise { const existingIds = existingConnectionIdsBySource(input.connections, input.source); const defaultConnectionId = defaultConnectionIdForSource(input.connections, input.source); @@ -995,7 +995,7 @@ async function chooseInteractiveSourceConnection(input: { } } -function buildConnection(source: KloSetupSourceType, args: KloSetupSourcesArgs): KloProjectConnectionConfig { +function buildConnection(source: KtxSetupSourceType, args: KtxSetupSourcesArgs): KtxProjectConnectionConfig { if (source === 'dbt') { return buildDbtConnection(args); } @@ -1015,9 +1015,9 @@ function buildConnection(source: KloSetupSourceType, args: KloSetupSourcesArgs): } async function validateSource( - source: KloSetupSourceType, - args: { projectDir: string; connectionId: string; connection: KloProjectConnectionConfig }, - deps: KloSetupSourcesDeps, + source: KtxSetupSourceType, + args: { projectDir: string; connectionId: string; connection: KtxProjectConnectionConfig }, + deps: KtxSetupSourcesDeps, ): Promise { if (source === 'dbt') { return await (deps.validateDbt ?? defaultValidateDbt)(args.connection); @@ -1037,11 +1037,11 @@ async function validateSource( return await (deps.validateNotion ?? defaultValidateNotion)(args.connection); } -export async function runKloSetupSourcesStep( - args: KloSetupSourcesArgs, - io: KloCliIo, - deps: KloSetupSourcesDeps = {}, -): Promise { +export async function runKtxSetupSourcesStep( + args: KtxSetupSourcesArgs, + io: KtxCliIo, + deps: KtxSetupSourcesDeps = {}, +): Promise { try { if (args.skipSources) { await markSourcesComplete(args.projectDir); @@ -1050,7 +1050,7 @@ export async function runKloSetupSourcesStep( } const prompts = deps.prompts ?? createPromptAdapter(); - const project = await loadKloProject({ projectDir: args.projectDir }); + const project = await loadKtxProject({ projectDir: args.projectDir }); if (!hasPrimarySource(project.config)) { const message = 'Connect a primary source before adding context sources.'; if (args.source) { @@ -1069,7 +1069,7 @@ export async function runKloSetupSourcesStep( : args.inputMode === 'disabled' ? [] : await prompts.multiselect({ - message: withMultiselectNavigation('Which context sources should KLO ingest?'), + message: withMultiselectNavigation('Which context sources should KTX ingest?'), options: [...SOURCE_OPTIONS], required: false, }); @@ -1088,13 +1088,13 @@ export async function runKloSetupSourcesStep( const readyConnectionIds: string[] = []; let returnToSourceSelection = false; - for (const source of selected as KloSetupSourceType[]) { + for (const source of selected as KtxSetupSourceType[]) { const sourceChoice = args.source ? ({ kind: 'new', args } as const) : await chooseInteractiveSourceConnection({ args, source, - connections: (await loadKloProject({ projectDir: args.projectDir })).config.connections, + connections: (await loadKtxProject({ projectDir: args.projectDir })).config.connections, prompts, testGitRepo: deps.testGitRepo, }); diff --git a/packages/cli/src/setup.test.ts b/packages/cli/src/setup.test.ts index d29e39f5..3e772d92 100644 --- a/packages/cli/src/setup.test.ts +++ b/packages/cli/src/setup.test.ts @@ -3,8 +3,8 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { contextBuildCommands, writeKloSetupContextState } from './setup-context.js'; -import { readKloSetupStatus, runKloSetup } from './setup.js'; +import { contextBuildCommands, writeKtxSetupContextState } from './setup-context.js'; +import { readKtxSetupStatus, runKtxSetup } from './setup.js'; function makeIo() { let stdout = ''; @@ -31,7 +31,7 @@ describe('setup status', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-setup-status-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-setup-status-')); }); afterEach(async () => { @@ -39,7 +39,7 @@ describe('setup status', () => { }); it('reports a missing project without creating files', async () => { - const status = await readKloSetupStatus(tempDir); + const status = await readKtxSetupStatus(tempDir); expect(status).toMatchObject({ project: { path: tempDir, ready: false }, @@ -55,7 +55,7 @@ describe('setup status', () => { it('reports deterministic default embeddings as not setup-ready', async () => { await mkdir(tempDir, { recursive: true }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'llm:', @@ -75,7 +75,7 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ project: { path: tempDir, ready: true }, llm: { backend: 'anthropic', ready: true, model: 'claude-sonnet-4-6' }, embeddings: { backend: 'deterministic', ready: false, model: 'deterministic', dimensions: 8 }, @@ -84,7 +84,7 @@ describe('setup status', () => { it('uses setup database connection ids when present', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -109,7 +109,7 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ databases: [ { connectionId: 'warehouse', ready: true }, { connectionId: 'analytics', ready: false }, @@ -119,7 +119,7 @@ describe('setup status', () => { it('reports selected databases as ready only after the database setup step is complete', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -137,12 +137,12 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ databases: [{ connectionId: 'warehouse', ready: false }], }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -161,14 +161,14 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ databases: [{ connectionId: 'warehouse', ready: true }], }); }); it('reports source status from configured source connections', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -189,16 +189,16 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ sources: [{ connectionId: 'docs', type: 'notion', ready: true }], }); }); it('reports agent status from the install manifest', async () => { - await mkdir(join(tempDir, '.klo', 'agents'), { recursive: true }); - await writeFile(join(tempDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + await mkdir(join(tempDir, '.ktx', 'agents'), { recursive: true }); + await writeFile(join(tempDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); await writeFile( - join(tempDir, '.klo/agents/install-manifest.json'), + join(tempDir, '.ktx/agents/install-manifest.json'), JSON.stringify( { version: 1, @@ -213,14 +213,14 @@ describe('setup status', () => { 'utf-8', ); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ agents: [{ target: 'codex', scope: 'project', ready: true }], }); }); it('reports setup-managed context build status and commands', async () => { await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -250,7 +250,7 @@ describe('setup status', () => { ].join('\n'), 'utf-8', ); - await writeKloSetupContextState(tempDir, { + await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-abc123', status: 'running', startedAt: '2026-05-09T10:00:00.000Z', @@ -263,13 +263,13 @@ describe('setup status', () => { commands: contextBuildCommands(tempDir, 'setup-context-local-abc123'), }); - await expect(readKloSetupStatus(tempDir)).resolves.toMatchObject({ + await expect(readKtxSetupStatus(tempDir)).resolves.toMatchObject({ context: { ready: false, status: 'running', runId: 'setup-context-local-abc123', - watchCommand: `klo setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, - statusCommand: `klo setup context status setup-context-local-abc123 --project-dir ${tempDir}`, + watchCommand: `ktx setup context watch setup-context-local-abc123 --project-dir ${tempDir}`, + statusCommand: `ktx setup context status setup-context-local-abc123 --project-dir ${tempDir}`, }, }); }); @@ -278,13 +278,13 @@ describe('setup status', () => { const plainIo = makeIo(); const jsonIo = makeIo(); - await expect(runKloSetup({ command: 'status', projectDir: tempDir, json: false }, plainIo.io)).resolves.toBe(0); - await expect(runKloSetup({ command: 'status', projectDir: tempDir, json: true }, jsonIo.io)).resolves.toBe(0); + await expect(runKtxSetup({ command: 'status', projectDir: tempDir, json: false }, plainIo.io)).resolves.toBe(0); + await expect(runKtxSetup({ command: 'status', projectDir: tempDir, json: true }, jsonIo.io)).resolves.toBe(0); - expect(plainIo.stdout()).toContain(`No KLO project found at ${tempDir}.`); - expect(plainIo.stdout()).toContain('Check another project: klo --project-dir setup status'); - expect(plainIo.stdout()).toContain('Or from that folder: klo setup status'); - expect(plainIo.stdout()).toContain('Create a new KLO project here: klo setup'); + expect(plainIo.stdout()).toContain(`No KTX project found at ${tempDir}.`); + expect(plainIo.stdout()).toContain('Check another project: ktx --project-dir setup status'); + expect(plainIo.stdout()).toContain('Or from that folder: ktx setup status'); + expect(plainIo.stdout()).toContain('Create a new KTX project here: ktx setup'); expect(plainIo.stdout()).not.toContain('Project ready: no'); expect(JSON.parse(jsonIo.stdout())).toMatchObject({ project: { path: tempDir, ready: false } }); expect(plainIo.stderr()).toBe(''); @@ -293,15 +293,15 @@ describe('setup status', () => { it('prints the readiness checklist for an existing project', async () => { const testIo = makeIo(); - await writeFile(join(tempDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); - await expect(runKloSetup({ command: 'status', projectDir: tempDir, json: false }, testIo.io)).resolves.toBe(0); + await expect(runKtxSetup({ command: 'status', projectDir: tempDir, json: false }, testIo.io)).resolves.toBe(0); - expect(testIo.stdout()).toContain(`KLO project: ${tempDir}`); + expect(testIo.stdout()).toContain(`KTX project: ${tempDir}`); expect(testIo.stdout()).toContain('Project ready: yes'); expect(testIo.stdout()).toContain('LLM ready: no'); - expect(testIo.stdout()).toContain('KLO context built: no'); - expect(testIo.stdout()).not.toContain('No KLO project found.'); + expect(testIo.stdout()).toContain('KTX context built: no'); + expect(testIo.stdout()).not.toContain('No KTX project found.'); expect(testIo.stderr()).toBe(''); }); @@ -309,7 +309,7 @@ describe('setup status', () => { const testIo = makeIo(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -328,13 +328,13 @@ describe('setup status', () => { ), ).resolves.toBe(0); - expect(testIo.stdout()).toContain('KLO setup'); + expect(testIo.stdout()).toContain('KTX setup'); expect(testIo.stdout()).toContain(`Project: ${tempDir}`); expect(testIo.stdout()).toContain('Project ready: yes'); expect(testIo.stdout()).toContain('What you can do next:'); expect(testIo.stdout()).toContain('Connect data, then build context.'); - expect(testIo.stdout()).toContain('klo setup'); - expect(testIo.stdout()).not.toContain('klo agent context --json'); + expect(testIo.stdout()).toContain('ktx setup'); + expect(testIo.stdout()).not.toContain('ktx agent context --json'); expect(testIo.stdout()).not.toContain('Optional MCP:'); expect(testIo.stderr()).toBe(''); }); @@ -344,18 +344,18 @@ describe('setup status', () => { const select = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => { const labels = options.options.map((option) => option.label); expect(labels).toEqual([ - 'Set up KLO for my data', + 'Set up KTX for my data', 'Check setup status', - 'Try KLO with packaged demo data', + 'Try KTX with packaged demo data', 'Exit', ]); - expect(labels.indexOf('Try KLO with packaged demo data')).toBe(labels.length - 2); + expect(labels.indexOf('Try KTX with packaged demo data')).toBe(labels.length - 2); return 'exit'; }); const cancel = vi.fn(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -384,24 +384,24 @@ describe('setup status', () => { const missingIo = makeIo(); const existingIo = makeIo(); const missingSelect = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => { - expect(options.options.map((option) => option.label)).not.toContain('Connect a coding agent to KLO'); + expect(options.options.map((option) => option.label)).not.toContain('Connect a coding agent to KTX'); return 'exit'; }); const existingSelect = vi.fn(async (options: { options: Array<{ value: string; label: string }> }) => { const labels = options.options.map((option) => option.label); expect(labels).toEqual([ 'Resume or change an existing setup', - 'Create a new KLO project', - 'Connect a coding agent to KLO', + 'Create a new KTX project', + 'Connect a coding agent to KTX', 'Check setup status', - 'Try KLO with packaged demo data', + 'Try KTX with packaged demo data', 'Exit', ]); return 'exit'; }); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -422,10 +422,10 @@ describe('setup status', () => { ), ).resolves.toBe(0); - await writeFile(join(tempDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -463,7 +463,7 @@ describe('setup status', () => { }; await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -489,25 +489,25 @@ describe('setup status', () => { expect(projectPrompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Which KLO project should setup use?', + message: 'Which KTX project should setup use?', options: expect.arrayContaining([expect.objectContaining({ value: 'back', label: 'Back' })]), }), ); expect(projectPrompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Which KLO project should setup use?', + message: 'Which KTX project should setup use?', options: expect.not.arrayContaining([expect.objectContaining({ value: 'exit', label: 'Exit' })]), }), ); expect(entryPrompts.select).toHaveBeenCalledTimes(2); expect(entryPrompts.cancel).toHaveBeenCalledWith('Setup cancelled.'); expect(projectPrompts.cancel).not.toHaveBeenCalled(); - await expect(stat(join(tempDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(tempDir, 'ktx.yaml'))).rejects.toThrow(); }); it('lets Back from new project creation return to the first setup intent menu', async () => { const existingConfig = 'project: revenue\nconnections: {}\n'; - await writeFile(join(tempDir, 'klo.yaml'), existingConfig, 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), existingConfig, 'utf-8'); const entryChoices = ['new-project', 'exit']; const entryPrompts = { @@ -521,7 +521,7 @@ describe('setup status', () => { }; await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -547,14 +547,14 @@ describe('setup status', () => { expect(projectPrompts.select).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Where should KLO create the project?', + message: 'Where should KTX create the project?', options: expect.arrayContaining([expect.objectContaining({ value: 'back', label: 'Back' })]), }), ); expect(entryPrompts.select).toHaveBeenCalledTimes(2); expect(entryPrompts.cancel).toHaveBeenCalledWith('Setup cancelled.'); expect(projectPrompts.cancel).not.toHaveBeenCalled(); - await expect(readFile(join(tempDir, 'klo.yaml'), 'utf-8')).resolves.toBe(existingConfig); + await expect(readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).resolves.toBe(existingConfig); }); it('creates a separate project when the existing setup menu chooses new project', async () => { @@ -562,7 +562,7 @@ describe('setup status', () => { const newProjectDir = join(tempDir, 'fresh'); await mkdir(existingProjectDir, { recursive: true }); const existingConfig = 'project: revenue\nconnections: {}\n'; - await writeFile(join(existingProjectDir, 'klo.yaml'), existingConfig, 'utf-8'); + await writeFile(join(existingProjectDir, 'ktx.yaml'), existingConfig, 'utf-8'); const projectChoices = ['custom', 'create']; const projectPrompts = { @@ -588,7 +588,7 @@ describe('setup status', () => { })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: existingProjectDir, @@ -619,14 +619,14 @@ describe('setup status', () => { expect(projectPrompts.text).toHaveBeenCalledWith( expect.objectContaining({ message: 'Project folder path\nPress Escape to go back.\n', - placeholder: './analytics-klo, ~/analytics-klo, or /Users/you/projects/analytics-klo', + placeholder: './analytics-ktx, ~/analytics-ktx, or /Users/you/projects/analytics-ktx', }), ); expect(projectPrompts.select).toHaveBeenCalledWith( - expect.objectContaining({ message: 'Where should KLO create the project?' }), + expect.objectContaining({ message: 'Where should KTX create the project?' }), ); - await expect(stat(join(newProjectDir, 'klo.yaml'))).resolves.toBeDefined(); - await expect(readFile(join(existingProjectDir, 'klo.yaml'), 'utf-8')).resolves.toBe(existingConfig); + await expect(stat(join(newProjectDir, 'ktx.yaml'))).resolves.toBeDefined(); + await expect(readFile(join(existingProjectDir, 'ktx.yaml'), 'utf-8')).resolves.toBe(existingConfig); expect(model).toHaveBeenCalledWith(expect.objectContaining({ projectDir: newProjectDir }), expect.anything()); expect(embeddings).toHaveBeenCalledWith(expect.objectContaining({ projectDir: newProjectDir }), expect.anything()); expect(databases).toHaveBeenCalledWith(expect.objectContaining({ projectDir: newProjectDir }), expect.anything()); @@ -637,7 +637,7 @@ describe('setup status', () => { const existingProjectDir = join(tempDir, 'existing'); const newProjectDir = join(tempDir, 'fresh'); await mkdir(existingProjectDir, { recursive: true }); - await writeFile(join(existingProjectDir, 'klo.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); + await writeFile(join(existingProjectDir, 'ktx.yaml'), 'project: revenue\nconnections: {}\n', 'utf-8'); const projectChoices = ['custom', 'create']; const projectPrompts = { @@ -652,7 +652,7 @@ describe('setup status', () => { const testIo = makeIo(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: existingProjectDir, @@ -688,7 +688,7 @@ describe('setup status', () => { const demo = vi.fn(async (_args: { projectDir: string }, _io: unknown) => 0); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -717,14 +717,14 @@ describe('setup status', () => { }), testIo.io, ); - expect(demo.mock.calls[0]?.[0].projectDir).toMatch(/klo-demo-/); + expect(demo.mock.calls[0]?.[0].projectDir).toMatch(/ktx-demo-/); }); it('creates a project through run mode when --new is selected', async () => { const testIo = makeIo(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -743,9 +743,9 @@ describe('setup status', () => { ), ).resolves.toBe(0); - await expect(stat(join(tempDir, 'klo.yaml'))).resolves.toBeDefined(); - expect(await readFile(join(tempDir, 'klo.yaml'), 'utf-8')).toContain('completed_steps:'); - expect(testIo.stdout()).toContain('KLO setup'); + await expect(stat(join(tempDir, 'ktx.yaml'))).resolves.toBeDefined(); + expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).toContain('completed_steps:'); + expect(testIo.stdout()).toContain('KTX setup'); expect(testIo.stdout()).toContain(`Project: ${tempDir}`); expect(testIo.stdout()).toContain('Project ready: yes'); expect(testIo.stderr()).toBe(''); @@ -755,7 +755,7 @@ describe('setup status', () => { const testIo = makeIo(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -775,14 +775,14 @@ describe('setup status', () => { ).resolves.toBe(1); expect(testIo.stderr()).toContain('Missing setup choice'); - await expect(stat(join(tempDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(tempDir, 'ktx.yaml'))).rejects.toThrow(); }); it('returns nonzero when project selection is missing in non-interactive setup', async () => { const testIo = makeIo(); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -802,7 +802,7 @@ describe('setup status', () => { ).resolves.toBe(1); expect(testIo.stderr()).toContain('Missing setup choice'); - await expect(stat(join(tempDir, 'klo.yaml'))).rejects.toThrow(); + await expect(stat(join(tempDir, 'ktx.yaml'))).rejects.toThrow(); }); it('runs the Anthropic model step after project selection succeeds', async () => { @@ -810,7 +810,7 @@ describe('setup status', () => { const model = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -850,7 +850,7 @@ describe('setup status', () => { const embeddings = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -896,7 +896,7 @@ describe('setup status', () => { const embeddings = vi.fn(async () => ({ status: 'back' as const, projectDir: tempDir })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -943,7 +943,7 @@ describe('setup status', () => { }; await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -969,7 +969,7 @@ describe('setup status', () => { expect(databasePrompts.select).not.toHaveBeenCalled(); expect(testIo.stdout()).toContain( - 'KLO cannot work without at least one primary source. Select a source or press Escape to go back.', + 'KTX cannot work without at least one primary source. Select a source or press Escape to go back.', ); expect(embeddings).toHaveBeenCalledTimes(2); expect(embeddings).toHaveBeenNthCalledWith(2, expect.objectContaining({ forcePrompt: true }), testIo.io); @@ -977,7 +977,7 @@ describe('setup status', () => { }); it('lets Back from the first setup step return to the entry menu instead of exiting', async () => { - await writeFile(join(tempDir, 'klo.yaml'), 'project: test\nconnections: {}\n', 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), 'project: test\nconnections: {}\n', 'utf-8'); const testIo = makeIo(); const entryChoices = ['setup', 'exit']; @@ -988,7 +988,7 @@ describe('setup status', () => { const model = vi.fn(async () => ({ status: 'back' as const, projectDir: tempDir })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1028,7 +1028,7 @@ describe('setup status', () => { })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1072,10 +1072,10 @@ describe('setup status', () => { it('runs sources after database setup', async () => { const calls: string[] = []; const io = makeIo(); - await writeFile(join(tempDir, 'klo.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1119,10 +1119,10 @@ describe('setup status', () => { it('runs context after sources and before agents in full setup', async () => { const calls: string[] = []; const io = makeIo(); - await writeFile(join(tempDir, 'klo.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1177,10 +1177,10 @@ describe('setup status', () => { it('runs agent setup after context succeeds in --agents mode', async () => { const calls: string[] = []; const io = makeIo(); - await writeFile(join(tempDir, 'klo.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1230,10 +1230,10 @@ describe('setup status', () => { projectDir: tempDir, installs: [{ target: 'codex' as const, scope: 'project' as const, mode: 'cli' as const }], })); - await writeFile(join(tempDir, 'klo.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1260,16 +1260,16 @@ describe('setup status', () => { ).resolves.toBe(1); expect(agents).not.toHaveBeenCalled(); - expect(io.stderr()).toContain('KLO context is not ready for agents.'); + expect(io.stderr()).toContain('KTX context is not ready for agents.'); }); it('does not install agents when full setup context build is detached', async () => { const calls: string[] = []; const io = makeIo(); - await writeFile(join(tempDir, 'klo.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); + await writeFile(join(tempDir, 'ktx.yaml'), ['project: revenue', 'connections: {}', ''].join('\n'), 'utf-8'); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1308,9 +1308,9 @@ describe('setup status', () => { it('routes a ready project menu selection to agent setup', async () => { const calls: string[] = []; const io = makeIo(); - await mkdir(join(tempDir, '.klo', 'agents'), { recursive: true }); + await mkdir(join(tempDir, '.ktx', 'agents'), { recursive: true }); await writeFile( - join(tempDir, 'klo.yaml'), + join(tempDir, 'ktx.yaml'), [ 'project: revenue', 'setup:', @@ -1338,7 +1338,7 @@ describe('setup status', () => { 'utf-8', ); await writeFile( - join(tempDir, '.klo/agents/install-manifest.json'), + join(tempDir, '.ktx/agents/install-manifest.json'), JSON.stringify( { version: 1, @@ -1352,7 +1352,7 @@ describe('setup status', () => { ), 'utf-8', ); - await writeKloSetupContextState(tempDir, { + await writeKtxSetupContextState(tempDir, { runId: 'setup-context-local-ready', status: 'completed', startedAt: '2026-05-09T10:00:00.000Z', @@ -1367,7 +1367,7 @@ describe('setup status', () => { }); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1426,7 +1426,7 @@ describe('setup status', () => { })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, @@ -1463,7 +1463,7 @@ describe('setup status', () => { const io = makeIo(); const removeAgents = vi.fn(async () => 0); - await expect(runKloSetup({ command: 'remove-agents', projectDir: tempDir }, io.io, { removeAgents })).resolves.toBe( + await expect(runKtxSetup({ command: 'remove-agents', projectDir: tempDir }, io.io, { removeAgents })).resolves.toBe( 0, ); @@ -1476,7 +1476,7 @@ describe('setup status', () => { const embeddings = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir })); await expect( - runKloSetup( + runKtxSetup( { command: 'run', projectDir: tempDir, diff --git a/packages/cli/src/setup.ts b/packages/cli/src/setup.ts index 9d23e4af..09deff37 100644 --- a/packages/cli/src/setup.ts +++ b/packages/cli/src/setup.ts @@ -1,62 +1,62 @@ import { existsSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { cancel, isCancel, select } from '@clack/prompts'; -import { loadKloProject } from '@klo/context/project'; -import type { KloCliIo } from './cli-runtime.js'; -import type { KloDemoArgs } from './demo.js'; +import { loadKtxProject } from '@ktx/context/project'; +import type { KtxCliIo } from './cli-runtime.js'; +import type { KtxDemoArgs } from './demo.js'; import { defaultDemoProjectDir } from './demo-assets.js'; import { formatSetupNextStepLines } from './next-steps.js'; -import { isKloSetupExitError, withSetupInterruptConfirmation } from './setup-interrupt.js'; +import { isKtxSetupExitError, withSetupInterruptConfirmation } from './setup-interrupt.js'; import { - type KloAgentInstallMode, - type KloAgentScope, - type KloAgentTarget, - type KloSetupAgentsDeps, - readKloAgentInstallManifest, - removeKloAgentInstall, - runKloSetupAgentsStep, + type KtxAgentInstallMode, + type KtxAgentScope, + type KtxAgentTarget, + type KtxSetupAgentsDeps, + readKtxAgentInstallManifest, + removeKtxAgentInstall, + runKtxSetupAgentsStep, } from './setup-agents.js'; import { - type KloSetupDatabaseDriver, - type KloSetupDatabasesDeps, - runKloSetupDatabasesStep, + type KtxSetupDatabaseDriver, + type KtxSetupDatabasesDeps, + runKtxSetupDatabasesStep, } from './setup-databases.js'; -import { type KloSetupEmbeddingsDeps, runKloSetupEmbeddingsStep } from './setup-embeddings.js'; -import { type KloSetupModelDeps, runKloSetupAnthropicModelStep } from './setup-models.js'; -import { type KloSetupProjectDeps, runKloSetupProjectStep } from './setup-project.js'; -import { isKloSetupReady, type KloSetupReadyMenuDeps, runKloSetupReadyChangeMenu } from './setup-ready-menu.js'; -import { type KloSetupSourcesDeps, type KloSetupSourceType, runKloSetupSourcesStep } from './setup-sources.js'; +import { type KtxSetupEmbeddingsDeps, runKtxSetupEmbeddingsStep } from './setup-embeddings.js'; +import { type KtxSetupModelDeps, runKtxSetupAnthropicModelStep } from './setup-models.js'; +import { type KtxSetupProjectDeps, runKtxSetupProjectStep } from './setup-project.js'; +import { isKtxSetupReady, type KtxSetupReadyMenuDeps, runKtxSetupReadyChangeMenu } from './setup-ready-menu.js'; +import { type KtxSetupSourcesDeps, type KtxSetupSourceType, runKtxSetupSourcesStep } from './setup-sources.js'; import { withMenuOptionsSpacing } from './prompt-navigation.js'; import { - readKloSetupContextState, - runKloSetupContextCommand, - type KloSetupContextCommandArgs, - type KloSetupContextDeps, - type KloSetupContextResult, - runKloSetupContextStep, + readKtxSetupContextState, + runKtxSetupContextCommand, + type KtxSetupContextCommandArgs, + type KtxSetupContextDeps, + type KtxSetupContextResult, + runKtxSetupContextStep, setupContextStatusFromState, - type KloSetupContextStatusSummary, + type KtxSetupContextStatusSummary, } from './setup-context.js'; -export interface KloSetupStatus { +export interface KtxSetupStatus { project: { path: string; ready: boolean; name?: string }; llm: { backend?: string; ready: boolean; model?: string }; embeddings: { backend?: string; ready: boolean; model?: string; dimensions?: number }; databases: Array<{ connectionId: string; ready: boolean }>; sources: Array<{ connectionId: string; type: string; ready: boolean }>; - context: KloSetupContextStatusSummary; + context: KtxSetupContextStatusSummary; agents: Array<{ target: string; scope: string; ready: boolean }>; } -export type KloSetupArgs = +export type KtxSetupArgs = | { command: 'run'; projectDir: string; mode: 'auto' | 'new' | 'existing'; agents: boolean; - target?: KloAgentTarget; - agentScope?: KloAgentScope; - agentInstallMode?: KloAgentInstallMode; + target?: KtxAgentTarget; + agentScope?: KtxAgentScope; + agentInstallMode?: KtxAgentInstallMode; skipAgents?: boolean; inputMode: 'auto' | 'disabled'; yes: boolean; @@ -68,7 +68,7 @@ export type KloSetupArgs = embeddingApiKeyEnv?: string; embeddingApiKeyFile?: string; skipEmbeddings: boolean; - databaseDrivers?: KloSetupDatabaseDriver[]; + databaseDrivers?: KtxSetupDatabaseDriver[]; databaseConnectionIds?: string[]; databaseConnectionId?: string; databaseUrl?: string; @@ -80,7 +80,7 @@ export type KloSetupArgs = historicSqlServiceAccountPatterns?: string[]; historicSqlRedactionPatterns?: string[]; skipDatabases: boolean; - source?: KloSetupSourceType; + source?: KtxSetupSourceType; sourceConnectionId?: string; sourcePath?: string; sourceGitUrl?: string; @@ -109,46 +109,46 @@ export type KloSetupArgs = | { command: 'context-stop'; projectDir: string; runId?: string } | { command: 'remove-agents'; projectDir: string }; -export interface KloSetupDeps { - project?: KloSetupProjectDeps; +export interface KtxSetupDeps { + project?: KtxSetupProjectDeps; model?: ( - args: Parameters[0], - io: KloCliIo, - ) => Promise>>; - modelDeps?: KloSetupModelDeps; + args: Parameters[0], + io: KtxCliIo, + ) => Promise>>; + modelDeps?: KtxSetupModelDeps; embeddings?: ( - args: Parameters[0], - io: KloCliIo, - ) => Promise>>; - embeddingsDeps?: KloSetupEmbeddingsDeps; + args: Parameters[0], + io: KtxCliIo, + ) => Promise>>; + embeddingsDeps?: KtxSetupEmbeddingsDeps; databases?: ( - args: Parameters[0], - io: KloCliIo, - ) => Promise>>; - databasesDeps?: KloSetupDatabasesDeps; + args: Parameters[0], + io: KtxCliIo, + ) => Promise>>; + databasesDeps?: KtxSetupDatabasesDeps; sources?: ( - args: Parameters[0], - io: KloCliIo, - ) => Promise>>; - sourcesDeps?: KloSetupSourcesDeps; + args: Parameters[0], + io: KtxCliIo, + ) => Promise>>; + sourcesDeps?: KtxSetupSourcesDeps; agents?: ( - args: Parameters[0], - io: KloCliIo, - ) => Promise>>; - agentsDeps?: KloSetupAgentsDeps; - context?: (args: Parameters[0], io: KloCliIo) => Promise; - contextDeps?: KloSetupContextDeps; - removeAgents?: typeof removeKloAgentInstall; - readyMenuDeps?: KloSetupReadyMenuDeps; - entryMenuDeps?: KloSetupEntryMenuDeps; - demo?: (args: KloDemoArgs, io: KloCliIo) => Promise; + args: Parameters[0], + io: KtxCliIo, + ) => Promise>>; + agentsDeps?: KtxSetupAgentsDeps; + context?: (args: Parameters[0], io: KtxCliIo) => Promise; + contextDeps?: KtxSetupContextDeps; + removeAgents?: typeof removeKtxAgentInstall; + readyMenuDeps?: KtxSetupReadyMenuDeps; + entryMenuDeps?: KtxSetupEntryMenuDeps; + demo?: (args: KtxDemoArgs, io: KtxCliIo) => Promise; } const SOURCE_DRIVERS = new Set(['dbt', 'metricflow', 'metabase', 'looker', 'lookml', 'notion']); -type KloSetupEntryAction = 'setup' | 'new-project' | 'agents' | 'status' | 'demo' | 'exit'; -type KloSetupFlowStep = 'models' | 'embeddings' | 'databases' | 'sources' | 'context' | 'agents'; -type KloSetupFlowStatus = +type KtxSetupEntryAction = 'setup' | 'new-project' | 'agents' | 'status' | 'demo' | 'exit'; +type KtxSetupFlowStep = 'models' | 'embeddings' | 'databases' | 'sources' | 'context' | 'agents'; +type KtxSetupFlowStatus = | 'ready' | 'skipped' | 'back' @@ -158,16 +158,16 @@ type KloSetupFlowStatus = | 'paused' | 'interrupted'; -export interface KloSetupEntryMenuPromptAdapter { +export interface KtxSetupEntryMenuPromptAdapter { select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; cancel(message: string): void; } -export interface KloSetupEntryMenuDeps { - prompts?: KloSetupEntryMenuPromptAdapter; +export interface KtxSetupEntryMenuDeps { + prompts?: KtxSetupEntryMenuPromptAdapter; } -function createEntryMenuPromptAdapter(): KloSetupEntryMenuPromptAdapter { +function createEntryMenuPromptAdapter(): KtxSetupEntryMenuPromptAdapter { return { async select(options) { const value = await withSetupInterruptConfirmation(() => select(withMenuOptionsSpacing(options))); @@ -182,39 +182,39 @@ function createEntryMenuPromptAdapter(): KloSetupEntryMenuPromptAdapter { }; } -async function runKloSetupEntryMenu( - status: KloSetupStatus, - deps: KloSetupEntryMenuDeps = {}, -): Promise<{ action: KloSetupEntryAction }> { +async function runKtxSetupEntryMenu( + status: KtxSetupStatus, + deps: KtxSetupEntryMenuDeps = {}, +): Promise<{ action: KtxSetupEntryAction }> { const prompts = deps.prompts ?? createEntryMenuPromptAdapter(); const options = status.project.ready ? [ { value: 'setup', label: 'Resume or change an existing setup' }, - { value: 'new-project', label: 'Create a new KLO project' }, - { value: 'agents', label: 'Connect a coding agent to KLO' }, + { value: 'new-project', label: 'Create a new KTX project' }, + { value: 'agents', label: 'Connect a coding agent to KTX' }, { value: 'status', label: 'Check setup status' }, - { value: 'demo', label: 'Try KLO with packaged demo data' }, + { value: 'demo', label: 'Try KTX with packaged demo data' }, { value: 'exit', label: 'Exit' }, ] : [ - { value: 'setup', label: 'Set up KLO for my data' }, + { value: 'setup', label: 'Set up KTX for my data' }, { value: 'status', label: 'Check setup status' }, - { value: 'demo', label: 'Try KLO with packaged demo data' }, + { value: 'demo', label: 'Try KTX with packaged demo data' }, { value: 'exit', label: 'Exit' }, ]; const action = (await prompts.select({ message: 'What do you want to do?', options, - })) as KloSetupEntryAction; + })) as KtxSetupEntryAction; return { action }; } -async function runKloSetupDemoFromEntryMenu( - args: Extract, - io: KloCliIo, - deps: KloSetupDeps, +async function runKtxSetupDemoFromEntryMenu( + args: Extract, + io: KtxCliIo, + deps: KtxSetupDeps, ): Promise { - const runner = deps.demo ?? (await import('./demo.js')).runKloDemo; + const runner = deps.demo ?? (await import('./demo.js')).runKtxDemo; return await runner( { command: 'seeded', @@ -226,11 +226,11 @@ async function runKloSetupDemoFromEntryMenu( ); } -function llmReady(status: KloSetupStatus['llm']): boolean { +function llmReady(status: KtxSetupStatus['llm']): boolean { return status.backend === 'anthropic' && typeof status.model === 'string' && status.model.length > 0; } -function embeddingsReady(status: KloSetupStatus['embeddings']): boolean { +function embeddingsReady(status: KtxSetupStatus['embeddings']): boolean { return ( status.backend !== undefined && status.backend !== 'none' && @@ -242,7 +242,7 @@ function embeddingsReady(status: KloSetupStatus['embeddings']): boolean { ); } -function sourceConnections(config: Awaited>['config']) { +function sourceConnections(config: Awaited>['config']) { return Object.entries(config.connections) .filter(([, connection]) => SOURCE_DRIVERS.has(String(connection.driver ?? '').toLowerCase())) .map(([connectionId, connection]) => ({ @@ -252,21 +252,21 @@ function sourceConnections(config: Awaited>['c .sort((left, right) => left.connectionId.localeCompare(right.connectionId)); } -export async function readKloSetupStatus(projectDir: string): Promise { +export async function readKtxSetupStatus(projectDir: string): Promise { const resolvedProjectDir = resolve(projectDir); - if (!existsSync(join(resolvedProjectDir, 'klo.yaml'))) { + if (!existsSync(join(resolvedProjectDir, 'ktx.yaml'))) { return { project: { path: resolvedProjectDir, ready: false }, llm: { ready: false }, embeddings: { ready: false }, databases: [], sources: [], - context: setupContextStatusFromState(await readKloSetupContextState(resolvedProjectDir)), + context: setupContextStatusFromState(await readKtxSetupContextState(resolvedProjectDir)), agents: [], }; } - const project = await loadKloProject({ projectDir: resolvedProjectDir }); + const project = await loadKtxProject({ projectDir: resolvedProjectDir }); const llm = { backend: project.config.llm.provider.backend, ready: false, @@ -283,10 +283,10 @@ export async function readKloSetupStatus(projectDir: string): Promise ({ target: install.target, @@ -319,7 +319,7 @@ function formatConnectionList(ids: string[]): string { return ids.length > 0 ? `yes (${ids.join(', ')})` : 'no'; } -function formatContextBuilt(status: KloSetupContextStatusSummary): string { +function formatContextBuilt(status: KtxSetupContextStatusSummary): string { if (status.ready) { return 'yes'; } @@ -330,20 +330,20 @@ function formatContextBuilt(status: KloSetupContextStatusSummary): string { return `${status.status.replaceAll('_', ' ')}${runSuffix}`; } -export function formatKloSetupStatus(status: KloSetupStatus): string { +export function formatKtxSetupStatus(status: KtxSetupStatus): string { if (!status.project.ready) { return [ - `No KLO project found at ${status.project.path}.`, + `No KTX project found at ${status.project.path}.`, '', - 'Check another project: klo --project-dir setup status', - 'Or from that folder: klo setup status', - 'Create a new KLO project here: klo setup', + 'Check another project: ktx --project-dir setup status', + 'Or from that folder: ktx setup status', + 'Create a new KTX project here: ktx setup', '', ].join('\n'); } const lines = [ - `KLO project: ${status.project.path}`, + `KTX project: ${status.project.path}`, `Project ready: ${formatReady(status.project.ready)}`, `LLM ready: ${formatReady(status.llm.ready)}${status.llm.model ? ` (${status.llm.model})` : ''}`, `Embeddings ready: ${formatReady(status.embeddings.ready)}${ @@ -351,7 +351,7 @@ export function formatKloSetupStatus(status: KloSetupStatus): string { }`, `Primary sources configured: ${formatConnectionList(status.databases.map((database) => database.connectionId))}`, `Context sources configured: ${formatConnectionList(status.sources.map((source) => source.connectionId))}`, - `KLO context built: ${formatContextBuilt(status.context)}`, + `KTX context built: ${formatContextBuilt(status.context)}`, `Agent integration ready: ${formatReady(status.agents.some((agent) => agent.ready))}${ status.agents.length > 0 ? ` (${status.agents.map((agent) => `${agent.target}:${agent.scope}`).join(', ')})` : '' }`, @@ -361,14 +361,14 @@ export function formatKloSetupStatus(status: KloSetupStatus): string { } if (!status.context.ready && status.context.status === 'failed' && status.context.detail) { lines.push( - `Retry: ${status.context.retryCommand ?? `klo setup context build --project-dir ${status.project.path}`}`, + `Retry: ${status.context.retryCommand ?? `ktx setup context build --project-dir ${status.project.path}`}`, ); } return `${lines.join('\n')}\n`; } -function setupStatusReady(status: KloSetupStatus): boolean { +function setupStatusReady(status: KtxSetupStatus): boolean { if (!status.project.ready) { return false; } @@ -383,34 +383,34 @@ function setupStatusReady(status: KloSetupStatus): boolean { ); } -function setupHasContextTargets(status: KloSetupStatus): boolean { +function setupHasContextTargets(status: KtxSetupStatus): boolean { return status.databases.length > 0 || status.sources.length > 0; } -function setupContextReady(status: KloSetupStatus): boolean { +function setupContextReady(status: KtxSetupStatus): boolean { return status.context.ready; } -function writeContextNotReadyForAgents(projectDir: string, io: KloCliIo): void { - io.stderr.write('KLO context is not ready for agents.\n\n'); - io.stderr.write(`Build context first:\n klo setup context build --project-dir ${resolve(projectDir)}\n\n`); - io.stderr.write(`Then install agent integration:\n klo setup --agents --project-dir ${resolve(projectDir)}\n`); +function writeContextNotReadyForAgents(projectDir: string, io: KtxCliIo): void { + io.stderr.write('KTX context is not ready for agents.\n\n'); + io.stderr.write(`Build context first:\n ktx setup context build --project-dir ${resolve(projectDir)}\n\n`); + io.stderr.write(`Then install agent integration:\n ktx setup --agents --project-dir ${resolve(projectDir)}\n`); } -export async function runKloSetup(args: KloSetupArgs, io: KloCliIo, deps: KloSetupDeps = {}): Promise { +export async function runKtxSetup(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetupDeps = {}): Promise { try { - return await runKloSetupInner(args, io, deps); + return await runKtxSetupInner(args, io, deps); } catch (error) { - if (isKloSetupExitError(error)) { + if (isKtxSetupExitError(error)) { return 0; } throw error; } } -async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetupDeps = {}): Promise { +async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetupDeps = {}): Promise { if (args.command === 'remove-agents') { - return await (deps.removeAgents ?? removeKloAgentInstall)(args.projectDir, io); + return await (deps.removeAgents ?? removeKtxAgentInstall)(args.projectDir, io); } if ( @@ -419,7 +419,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup args.command === 'context-status' || args.command === 'context-stop' ) { - const commandArgs: KloSetupContextCommandArgs = + const commandArgs: KtxSetupContextCommandArgs = args.command === 'context-build' ? { command: 'build', projectDir: args.projectDir, inputMode: args.inputMode } : args.command === 'context-watch' @@ -437,18 +437,18 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup json: args.json, } : { command: 'stop', projectDir: args.projectDir, ...(args.runId ? { runId: args.runId } : {}) }; - return await runKloSetupContextCommand(commandArgs, io, deps.contextDeps); + return await runKtxSetupContextCommand(commandArgs, io, deps.contextDeps); } if (args.command === 'status') { - const status = await readKloSetupStatus(args.projectDir); - io.stdout.write(args.json ? `${JSON.stringify(status, null, 2)}\n` : formatKloSetupStatus(status)); + const status = await readKtxSetupStatus(args.projectDir); + io.stdout.write(args.json ? `${JSON.stringify(status, null, 2)}\n` : formatKtxSetupStatus(status)); return 0; } - io.stdout.write('KLO setup\n'); - let entryAction: KloSetupEntryAction | undefined; - let projectResult: Awaited>; + io.stdout.write('KTX setup\n'); + let entryAction: KtxSetupEntryAction | undefined; + let projectResult: Awaited>; const canShowEntryMenu = args.showEntryMenu === true && args.inputMode !== 'disabled' && @@ -458,23 +458,23 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup setupLoop: while (true) { entryAction = undefined; if (canShowEntryMenu) { - const status = await readKloSetupStatus(args.projectDir); - entryAction = (await runKloSetupEntryMenu(status, deps.entryMenuDeps)).action; + const status = await readKtxSetupStatus(args.projectDir); + entryAction = (await runKtxSetupEntryMenu(status, deps.entryMenuDeps)).action; if (entryAction === 'exit') { (deps.entryMenuDeps?.prompts ?? createEntryMenuPromptAdapter()).cancel('Setup cancelled.'); return 0; } if (entryAction === 'status') { - io.stdout.write(formatKloSetupStatus(status)); + io.stdout.write(formatKtxSetupStatus(status)); return 0; } if (entryAction === 'demo') { - return await runKloSetupDemoFromEntryMenu(args, io, deps); + return await runKtxSetupDemoFromEntryMenu(args, io, deps); } } const projectMode = entryAction === 'new-project' ? 'prompt-new' : args.mode; - projectResult = await runKloSetupProjectStep( + projectResult = await runKtxSetupProjectStep( { projectDir: args.projectDir, mode: projectMode, @@ -495,10 +495,10 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup } const agentsRequested = args.agents || entryAction === 'agents'; - const currentStatus = await readKloSetupStatus(projectResult.projectDir); + const currentStatus = await readKtxSetupStatus(projectResult.projectDir); let readyAction: string | undefined; - if (args.inputMode !== 'disabled' && !agentsRequested && isKloSetupReady(currentStatus)) { - readyAction = (await runKloSetupReadyChangeMenu(currentStatus, deps.readyMenuDeps)).action; + if (args.inputMode !== 'disabled' && !agentsRequested && isKtxSetupReady(currentStatus)) { + readyAction = (await runKtxSetupReadyChangeMenu(currentStatus, deps.readyMenuDeps)).action; if (readyAction === 'exit') return 0; } @@ -511,15 +511,15 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup const shouldRunAgents = agentsRequested || !runOnly || runOnly === 'agents'; const showPromptInstructions = projectResult.confirmedCreation !== true; - const setupSteps: KloSetupFlowStep[] = agentsRequested + const setupSteps: KtxSetupFlowStep[] = agentsRequested ? ['context'] : ['models', 'embeddings', 'databases', 'sources', 'context']; if (shouldRunAgents && args.skipAgents !== true) { setupSteps.push('agents'); } - const forcePromptSteps = new Set(); - const isNavigableSetupStep = (step: KloSetupFlowStep): boolean => { + const forcePromptSteps = new Set(); + const isNavigableSetupStep = (step: KtxSetupFlowStep): boolean => { if (step === 'models') return !args.skipLlm && shouldRunModels; if (step === 'embeddings') return !args.skipEmbeddings && shouldRunEmbeddings; if (step === 'databases') return !args.skipDatabases && shouldRunDatabases; @@ -541,10 +541,10 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup const step = setupSteps[stepIndex]; if (!step) break; - let stepResult: { status: KloSetupFlowStatus }; + let stepResult: { status: KtxSetupFlowStatus }; if (step === 'models') { const modelRunner = - deps.model ?? ((modelArgs, modelIo) => runKloSetupAnthropicModelStep(modelArgs, modelIo, deps.modelDeps)); + deps.model ?? ((modelArgs, modelIo) => runKtxSetupAnthropicModelStep(modelArgs, modelIo, deps.modelDeps)); stepResult = await modelRunner( { projectDir: projectResult.projectDir, @@ -561,7 +561,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup } else if (step === 'embeddings') { const embeddingsRunner = deps.embeddings ?? - ((embeddingArgs, embeddingIo) => runKloSetupEmbeddingsStep(embeddingArgs, embeddingIo, deps.embeddingsDeps)); + ((embeddingArgs, embeddingIo) => runKtxSetupEmbeddingsStep(embeddingArgs, embeddingIo, deps.embeddingsDeps)); stepResult = await embeddingsRunner( { projectDir: projectResult.projectDir, @@ -578,7 +578,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup } else if (step === 'databases') { const databasesRunner = deps.databases ?? - ((databaseArgs, databaseIo) => runKloSetupDatabasesStep(databaseArgs, databaseIo, deps.databasesDeps)); + ((databaseArgs, databaseIo) => runKtxSetupDatabasesStep(databaseArgs, databaseIo, deps.databasesDeps)); stepResult = await databasesRunner( { projectDir: projectResult.projectDir, @@ -604,7 +604,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup ); } else if (step === 'sources') { const sourcesRunner = - deps.sources ?? ((sourceArgs, sourceIo) => runKloSetupSourcesStep(sourceArgs, sourceIo, deps.sourcesDeps)); + deps.sources ?? ((sourceArgs, sourceIo) => runKtxSetupSourcesStep(sourceArgs, sourceIo, deps.sourcesDeps)); stepResult = await sourcesRunner( { projectDir: projectResult.projectDir, @@ -635,7 +635,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup } else if (step === 'context') { const contextRunner = deps.context ?? - ((contextArgs, contextIo) => runKloSetupContextStep(contextArgs, contextIo, deps.contextDeps)); + ((contextArgs, contextIo) => runKtxSetupContextStep(contextArgs, contextIo, deps.contextDeps)); stepResult = await contextRunner( { projectDir: projectResult.projectDir, @@ -647,7 +647,7 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup ); } else { const agentsRunner = - deps.agents ?? ((agentArgs, agentIo) => runKloSetupAgentsStep(agentArgs, agentIo, deps.agentsDeps)); + deps.agents ?? ((agentArgs, agentIo) => runKtxSetupAgentsStep(agentArgs, agentIo, deps.agentsDeps)); stepResult = await agentsRunner( { projectDir: projectResult.projectDir, @@ -698,8 +698,8 @@ async function runKloSetupInner(args: KloSetupArgs, io: KloCliIo, deps: KloSetup break; } - const status = await readKloSetupStatus(projectResult.projectDir); - io.stdout.write(formatKloSetupStatus(status)); + const status = await readKtxSetupStatus(projectResult.projectDir); + io.stdout.write(formatKtxSetupStatus(status)); io.stdout.write('\nWhat you can do next:\n'); io.stdout.write( `${formatSetupNextStepLines({ diff --git a/packages/cli/src/sl.test.ts b/packages/cli/src/sl.test.ts index 8ee3e517..d623e35b 100644 --- a/packages/cli/src/sl.test.ts +++ b/packages/cli/src/sl.test.ts @@ -2,9 +2,9 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import Database from 'better-sqlite3'; -import { initKloProject } from '@klo/context/project'; +import { initKtxProject } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { runKloSl } from './sl.js'; +import { runKtxSl } from './sl.js'; const ORDERS_YAML = [ 'name: orders', @@ -38,11 +38,11 @@ function makeIo() { }; } -describe('runKloSl', () => { +describe('runKtxSl', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-sl-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-sl-')); }); afterEach(async () => { @@ -51,11 +51,11 @@ describe('runKloSl', () => { it('writes, validates, reads, and lists semantic-layer sources', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const writeIo = makeIo(); await expect( - runKloSl( + runKtxSl( { command: 'write', projectDir, @@ -70,23 +70,23 @@ describe('runKloSl', () => { const validateIo = makeIo(); await expect( - runKloSl({ command: 'validate', projectDir, connectionId: 'warehouse', sourceName: 'orders' }, validateIo.io), + runKtxSl({ command: 'validate', projectDir, connectionId: 'warehouse', sourceName: 'orders' }, validateIo.io), ).resolves.toBe(0); expect(validateIo.stdout()).toContain('Valid semantic-layer source: warehouse/orders'); const readIo = makeIo(); - await expect(runKloSl({ command: 'read', projectDir, connectionId: 'warehouse', sourceName: 'orders' }, readIo.io)) + await expect(runKtxSl({ command: 'read', projectDir, connectionId: 'warehouse', sourceName: 'orders' }, readIo.io)) .resolves.toBe(0); expect(readIo.stdout()).toContain('name: orders'); const listIo = makeIo(); - await expect(runKloSl({ command: 'list', projectDir, connectionId: 'warehouse' }, listIo.io)).resolves.toBe(0); + await expect(runKtxSl({ command: 'list', projectDir, connectionId: 'warehouse' }, listIo.io)).resolves.toBe(0); expect(listIo.stdout()).toContain('warehouse\torders\tcolumns=1\tmeasures=0\tjoins=0'); }); it('runs sl query and prints SQL output', async () => { const projectDir = join(tempDir, 'project'); - const project = await initKloProject({ projectDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', readonly: true }; await project.fileStore.writeFile( 'semantic-layer/warehouse/orders.yaml', @@ -101,8 +101,8 @@ measures: expr: count(*) joins: [] `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add orders source', ); @@ -121,7 +121,7 @@ joins: [] })); await expect( - runKloSl( + runKtxSl( { command: 'query', projectDir: '/tmp/project', @@ -141,7 +141,7 @@ joins: [] it('executes sl query through the injected query executor', async () => { const projectDir = join(tempDir, 'project'); - const project = await initKloProject({ projectDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'postgres://example/db', readonly: true }; await project.fileStore.writeFile( 'semantic-layer/warehouse/orders.yaml', @@ -156,8 +156,8 @@ measures: expr: count(*) joins: [] `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add orders source', ); @@ -185,7 +185,7 @@ joins: [] })); await expect( - runKloSl( + runKtxSl( { command: 'query', projectDir, @@ -224,7 +224,7 @@ joins: [] it('executes sl query against a local SQLite connection through the default executor', async () => { const projectDir = join(tempDir, 'project'); - const project = await initKloProject({ projectDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir, projectName: 'warehouse' }); const dbPath = join(projectDir, 'warehouse.db'); const db = new Database(dbPath); db.exec(` @@ -238,7 +238,7 @@ joins: [] project.config.connections.warehouse = { driver: 'sqlite', path: 'warehouse.db', readonly: true }; await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -265,8 +265,8 @@ measures: expr: count(*) joins: [] `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add orders source', ); @@ -283,7 +283,7 @@ joins: [] generateSources: vi.fn(), })); - const exitCode = await runKloSl( + const exitCode = await runKtxSl( { command: 'query', projectDir, @@ -317,16 +317,16 @@ joins: [] it('emits sl list as a JSON envelope when output=json', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const writeIo = makeIo(); - await runKloSl( + await runKtxSl( { command: 'write', projectDir, connectionId: 'warehouse', sourceName: 'orders', yaml: ORDERS_YAML }, writeIo.io, ); const listIo = makeIo(); - const code = await runKloSl( + const code = await runKtxSl( { command: 'list', projectDir, connectionId: 'warehouse', output: 'json' }, listIo.io, ); @@ -347,16 +347,16 @@ joins: [] it('emits sl list with grouping and Clack-style framing when output=pretty', async () => { const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const writeIo = makeIo(); - await runKloSl( + await runKtxSl( { command: 'write', projectDir, connectionId: 'warehouse', sourceName: 'orders', yaml: ORDERS_YAML }, writeIo.io, ); const listIo = makeIo(); - const code = await runKloSl( + const code = await runKtxSl( { command: 'list', projectDir, connectionId: 'warehouse', output: 'pretty' }, listIo.io, ); diff --git a/packages/cli/src/sl.ts b/packages/cli/src/sl.ts index c7a0d2bf..17087328 100644 --- a/packages/cli/src/sl.ts +++ b/packages/cli/src/sl.ts @@ -1,6 +1,6 @@ -import { createDefaultLocalQueryExecutor, type KloSqlQueryExecutorPort } from '@klo/context/connections'; -import { createPythonSemanticLayerComputePort, type KloSemanticLayerComputePort } from '@klo/context/daemon'; -import { loadKloProject, type KloLocalProject } from '@klo/context/project'; +import { createDefaultLocalQueryExecutor, type KtxSqlQueryExecutorPort } from '@ktx/context/connections'; +import { createPythonSemanticLayerComputePort, type KtxSemanticLayerComputePort } from '@ktx/context/daemon'; +import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project'; import { compileLocalSlQuery, listLocalSlSources, @@ -8,14 +8,14 @@ import { validateLocalSlSource, writeLocalSlSource, type SemanticLayerQueryInput, -} from '@klo/context/sl'; +} from '@ktx/context/sl'; import { profileMark } from './startup-profile.js'; profileMark('module:sl'); type SlQueryFormat = 'json' | 'sql'; -export type KloSlArgs = +export type KtxSlArgs = | { command: 'list'; projectDir: string; connectionId?: string; output?: string; json?: boolean } | { command: 'read'; projectDir: string; connectionId: string; sourceName: string } | { command: 'validate'; projectDir: string; connectionId: string; sourceName: string } @@ -30,20 +30,20 @@ export type KloSlArgs = maxRows?: number; }; -interface KloSlIo { +interface KtxSlIo { stdout: { write(chunk: string): void }; stderr: { write(chunk: string): void }; } -interface KloSlDeps { - loadProject?: typeof loadKloProject; - createSemanticLayerCompute?: () => KloSemanticLayerComputePort; - createQueryExecutor?: () => KloSqlQueryExecutorPort; +interface KtxSlDeps { + loadProject?: typeof loadKtxProject; + createSemanticLayerCompute?: () => KtxSemanticLayerComputePort; + createQueryExecutor?: () => KtxSqlQueryExecutorPort; } -export async function runKloSl(args: KloSlArgs, io: KloSlIo = process, deps: KloSlDeps = {}): Promise { +export async function runKtxSl(args: KtxSlArgs, io: KtxSlIo = process, deps: KtxSlDeps = {}): Promise { try { - const project = await (deps.loadProject ?? loadKloProject)({ projectDir: args.projectDir }); + const project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir }); if (args.command === 'list') { const sources = await listLocalSlSources(project, { connectionId: args.connectionId }); const { resolveOutputMode } = await import('./io/mode.js'); @@ -99,7 +99,7 @@ export async function runKloSl(args: KloSlArgs, io: KloSlIo = process, deps: Klo if (args.command === 'query') { const compute = (deps.createSemanticLayerCompute ?? createPythonSemanticLayerComputePort)(); const queryExecutor = args.execute ? (deps.createQueryExecutor ?? createDefaultLocalQueryExecutor)() : undefined; - const result = await compileLocalSlQuery(project as KloLocalProject, { + const result = await compileLocalSlQuery(project as KtxLocalProject, { connectionId: args.connectionId, query: args.query, compute, diff --git a/packages/cli/src/standalone-smoke.test.ts b/packages/cli/src/standalone-smoke.test.ts index 3beef5ed..27f34b92 100644 --- a/packages/cli/src/standalone-smoke.test.ts +++ b/packages/cli/src/standalone-smoke.test.ts @@ -3,7 +3,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { promisify } from 'node:util'; -import { parseKloProjectConfig } from '@klo/context/project'; +import { parseKtxProjectConfig } from '@ktx/context/project'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import Database from 'better-sqlite3'; @@ -68,7 +68,7 @@ function structuredContent(result: unknown): T { async function writeWarehouseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -115,7 +115,7 @@ function createSqliteWarehouse(dbPath: string): void { async function writeSqliteScanConfig(projectDir: string, dbPath: string, enrich = false): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -162,11 +162,11 @@ async function runSetupNewProject(projectDir: string): Promise { ]); } -describe('standalone built klo CLI smoke', () => { +describe('standalone built ktx CLI smoke', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-standalone-smoke-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-standalone-smoke-')); }); afterEach(async () => { @@ -199,7 +199,7 @@ describe('standalone built klo CLI smoke', () => { ]); expect(run).toMatchObject({ code: 1, stdout: '' }); expect(run.stderr).toContain( - 'klo dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', + 'ktx dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', ); }); @@ -221,7 +221,7 @@ describe('standalone built klo CLI smoke', () => { expect(result.stdout).toContain('Notion:'); expect(result.stdout).toContain('Semantic-layer sources:'); expect(result.stdout).toContain('Knowledge pages:'); - expect(result.stdout).toContain('klo serve --mcp stdio'); + expect(result.stdout).toContain('ktx serve --mcp stdio'); expect(result.stdout).not.toContain(['--mode', 'deterministic'].join(' ')); }); @@ -297,12 +297,12 @@ describe('standalone built klo CLI smoke', () => { ok: false, error: { code: 'agent_sl_search_missing_project', - message: `Semantic-layer search needs an initialized KLO project at ${projectDir}.`, + message: `Semantic-layer search needs an initialized KTX project at ${projectDir}.`, nextSteps: [ - 'klo demo', - `klo setup --project-dir ${projectDir}`, - 'klo ingest ', - `klo agent sl list --json --query "revenue" --project-dir ${projectDir}`, + 'ktx demo', + `ktx setup --project-dir ${projectDir}`, + 'ktx ingest ', + `ktx agent sl list --json --query "revenue" --project-dir ${projectDir}`, ], }, }); @@ -333,10 +333,10 @@ describe('standalone built klo CLI smoke', () => { expect(inspect.stdout).toContain('Report: reports/seeded-demo-report.json'); expect(inspect.stdout).toContain('Replay: replays/replay.memory-flow.v1.json'); expect(inspect.stdout).toContain('Latest replay: seeded (packaged, prebuilt)'); - expect(inspect.stdout).toContain('klo agent tools --json'); - expect(inspect.stdout).toContain('klo agent context --json'); - expect(inspect.stdout).not.toContain('klo ask "your question here"'); - expect(inspect.stdout).toContain('klo serve --mcp stdio'); + expect(inspect.stdout).toContain('ktx agent tools --json'); + expect(inspect.stdout).toContain('ktx agent context --json'); + expect(inspect.stdout).not.toContain('ktx ask "your question here"'); + expect(inspect.stdout).toContain('ktx serve --mcp stdio'); }); it('serves seeded demo wiki and semantic-layer context over stdio MCP', async () => { @@ -351,7 +351,7 @@ describe('standalone built klo CLI smoke', () => { expect(seeded).toMatchObject({ code: 0, stderr: '' }); expect(seeded.stdout).toContain('Mode: seeded'); - const client = new Client({ name: 'klo-seeded-demo-smoke-client', version: '0.0.0' }); + const client = new Client({ name: 'ktx-seeded-demo-smoke-client', version: '0.0.0' }); const transport = new StdioClientTransport({ command: process.execPath, args: [CLI_BIN, 'serve', '--mcp', 'stdio', '--project-dir', projectDir, '--user-id', 'smoke-user'], @@ -410,7 +410,7 @@ describe('standalone built klo CLI smoke', () => { it('runs doctor setup through the built binary', async () => { const result = await runBuiltCli(['dev', 'doctor', 'setup', '--no-input']); - expect(result.stdout).toContain('KLO setup doctor'); + expect(result.stdout).toContain('KTX setup doctor'); expect(result.stdout).toContain('Node 22+'); expect(result.stdout).toContain('Workspace-local CLI'); expect(result.stderr).toBe(''); @@ -425,8 +425,8 @@ describe('standalone built klo CLI smoke', () => { }); expect(result.code).toBe(1); - expect(result.stderr).toContain('klo setup demo --mode full needs ANTHROPIC_API_KEY'); - expect(result.stderr).toContain('klo setup demo --mode seeded --no-input'); + expect(result.stderr).toContain('ktx setup demo --mode full needs ANTHROPIC_API_KEY'); + expect(result.stderr).toContain('ktx setup demo --mode seeded --no-input'); }); it('requires force for demo reset through the built binary', async () => { @@ -438,7 +438,7 @@ describe('standalone built klo CLI smoke', () => { const withoutForce = await runBuiltCli(['setup', 'demo', 'reset', '--project-dir', projectDir, '--no-input']); expect(withoutForce.code).toBe(1); expect(withoutForce.stderr).toContain( - `klo setup demo reset is destructive; pass --force to recreate ${projectDir}`, + `ktx setup demo reset is destructive; pass --force to recreate ${projectDir}`, ); const withForce = await runBuiltCli([ @@ -464,7 +464,7 @@ describe('standalone built klo CLI smoke', () => { const replay = await runBuiltCli(['setup', 'demo', '--mode', 'replay', '--project-dir', projectDir, '--no-input']); expect(replay.code).toBe(1); expect(replay.stderr).toContain(`Demo project is not ready at ${projectDir}: missing demo.db`); - expect(replay.stderr).toContain(`klo setup demo reset --project-dir ${projectDir} --force --no-input`); + expect(replay.stderr).toContain(`ktx setup demo reset --project-dir ${projectDir} --force --no-input`); }); it('runs demo doctor through the built binary', async () => { @@ -474,7 +474,7 @@ describe('standalone built klo CLI smoke', () => { expect(init).toMatchObject({ code: 0, stderr: '' }); const result = await runBuiltCli(['setup', 'demo', 'doctor', '--project-dir', projectDir, '--no-input']); - expect(result.stdout).toContain('KLO demo doctor'); + expect(result.stdout).toContain('KTX demo doctor'); expect(result.stdout).toContain('Demo dataset'); expect(result.stdout).toContain('Demo replay'); expect(result.stdout).toContain('Demo LLM provider'); @@ -658,7 +658,7 @@ describe('standalone built klo CLI smoke', () => { const projectDir = join(tempDir, 'gateway-config-project'); await mkdir(projectDir, { recursive: true }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: gateway-smoke', 'llm:', @@ -667,13 +667,13 @@ describe('standalone built klo CLI smoke', () => { ' gateway:', ' api_key: env:AI_GATEWAY_API_KEY', // pragma: allowlist secret ' models:', - ' default: env:KLO_SCAN_LLM_MODEL', + ' default: env:KTX_SCAN_LLM_MODEL', 'scan:', ' enrichment:', ' mode: llm', ' embeddings:', ' backend: openai', - ' model: env:KLO_SCAN_EMBEDDING_MODEL', + ' model: env:KTX_SCAN_EMBEDDING_MODEL', ' dimensions: 1536', ' openai:', ' api_key: env:OPENAI_API_KEY', // pragma: allowlist secret @@ -683,19 +683,19 @@ describe('standalone built klo CLI smoke', () => { 'utf8', ); - const config = parseKloProjectConfig(await readFile(join(projectDir, 'klo.yaml'), 'utf8')); + const config = parseKtxProjectConfig(await readFile(join(projectDir, 'ktx.yaml'), 'utf8')); expect(config.llm).toEqual({ provider: { backend: 'gateway', gateway: { api_key: 'env:AI_GATEWAY_API_KEY' }, // pragma: allowlist secret }, - models: { default: 'env:KLO_SCAN_LLM_MODEL' }, + models: { default: 'env:KTX_SCAN_LLM_MODEL' }, }); expect(config.scan.enrichment).toEqual({ mode: 'llm', embeddings: { backend: 'openai', - model: 'env:KLO_SCAN_EMBEDDING_MODEL', + model: 'env:KTX_SCAN_EMBEDDING_MODEL', dimensions: 1536, openai: { api_key: 'env:OPENAI_API_KEY' }, // pragma: allowlist secret batchSize: 16, @@ -727,14 +727,14 @@ describe('standalone built klo CLI smoke', () => { expect(add.stdout).toContain('Connection: notion-main'); expect(add.stdout).toContain('Driver: notion'); - const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8'); + const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8'); expect(yaml).toContain('driver: notion'); expect(yaml).toContain('auth_token_ref: env:NOTION_AUTH_TOKEN'); expect(yaml).toContain('crawl_mode: all_accessible'); expect(yaml).toContain('max_pages_per_run: 5'); expect(yaml).not.toContain('ntn_'); - const parsed = parseKloProjectConfig(yaml); + const parsed = parseKtxProjectConfig(yaml); expect(parsed.connections['notion-main']).toMatchObject({ driver: 'notion', auth_token_ref: 'env:NOTION_AUTH_TOKEN', @@ -749,7 +749,7 @@ describe('standalone built klo CLI smoke', () => { expect(init).toMatchObject({ code: 0, stderr: '' }); await writeWarehouseConfig(projectDir); - const client = new Client({ name: 'klo-smoke-client', version: '0.0.0' }); + const client = new Client({ name: 'ktx-smoke-client', version: '0.0.0' }); const transport = new StdioClientTransport({ command: process.execPath, args: [CLI_BIN, 'serve', '--mcp', 'stdio', '--project-dir', projectDir, '--user-id', 'smoke-user'], @@ -818,7 +818,7 @@ describe('standalone built klo CLI smoke', () => { createSqliteWarehouse(dbPath); await writeSqliteScanConfig(projectDir, dbPath); - const client = new Client({ name: 'klo-scan-smoke-client', version: '0.0.0' }); + const client = new Client({ name: 'ktx-scan-smoke-client', version: '0.0.0' }); const transport = new StdioClientTransport({ command: process.execPath, args: [CLI_BIN, 'serve', '--mcp', 'stdio', '--project-dir', projectDir, '--user-id', 'smoke-user'], diff --git a/packages/cli/src/startup-profile.ts b/packages/cli/src/startup-profile.ts index e9616abb..ccfde538 100644 --- a/packages/cli/src/startup-profile.ts +++ b/packages/cli/src/startup-profile.ts @@ -1,4 +1,4 @@ -const enabled = process.env.KLO_PROFILE_STARTUP === '1' || process.env.KLO_PROFILE_STARTUP === 'true'; +const enabled = process.env.KTX_PROFILE_STARTUP === '1' || process.env.KTX_PROFILE_STARTUP === 'true'; const processStart = performance.now() - process.uptime() * 1000; interface StartupProfileEvent { @@ -39,7 +39,7 @@ export function installStartupProfileReporter(): void { process.once('beforeExit', () => { const total = now(); - process.stderr.write('\nKLO startup profile\n'); + process.stderr.write('\nKTX startup profile\n'); for (const event of events) { const elapsed = event.at.toFixed(1).padStart(7); if (event.duration === undefined) { diff --git a/packages/cli/src/viz-fallback.ts b/packages/cli/src/viz-fallback.ts index d00d0779..140d700f 100644 --- a/packages/cli/src/viz-fallback.ts +++ b/packages/cli/src/viz-fallback.ts @@ -2,40 +2,40 @@ import { profileMark } from './startup-profile.js'; profileMark('module:viz-fallback'); -type KloVizFallbackReason = +type KtxVizFallbackReason = | 'stdout-not-tty' | 'term-dumb' | 'stdin-not-tty' | 'stdin-raw-mode-unavailable' | 'renderer-unavailable'; -interface KloVizFallbackIo { +interface KtxVizFallbackIo { stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void }; stdout: { isTTY?: boolean }; stderr: { write(chunk: string): void }; } -interface KloVizFallbackOptions { +interface KtxVizFallbackOptions { requireInput?: boolean; } -type KloVizFallbackDecision = +type KtxVizFallbackDecision = | { shouldDegrade: false; } | { shouldDegrade: true; - reason: KloVizFallbackReason; + reason: KtxVizFallbackReason; message: string; }; -const warnedFallbackReasons = new Set(); +const warnedFallbackReasons = new Set(); export function resolveVizFallback( - io: KloVizFallbackIo, + io: KtxVizFallbackIo, env: NodeJS.ProcessEnv = process.env, - options: KloVizFallbackOptions = {}, -): KloVizFallbackDecision { + options: KtxVizFallbackOptions = {}, +): KtxVizFallbackDecision { if (io.stdout.isTTY !== true) { return { shouldDegrade: true, @@ -71,7 +71,7 @@ export function resolveVizFallback( return { shouldDegrade: false }; } -export function rendererUnavailableVizFallback(): KloVizFallbackDecision { +export function rendererUnavailableVizFallback(): KtxVizFallbackDecision { return { shouldDegrade: true, reason: 'renderer-unavailable', @@ -79,7 +79,7 @@ export function rendererUnavailableVizFallback(): KloVizFallbackDecision { }; } -export function warnVizFallbackOnce(io: KloVizFallbackIo, decision: KloVizFallbackDecision): void { +export function warnVizFallbackOnce(io: KtxVizFallbackIo, decision: KtxVizFallbackDecision): void { if (!decision.shouldDegrade || warnedFallbackReasons.has(decision.reason)) { return; } diff --git a/packages/connector-bigquery/package.json b/packages/connector-bigquery/package.json index 975090c5..cfe8817f 100644 --- a/packages/connector-bigquery/package.json +++ b/packages/connector-bigquery/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-bigquery", + "name": "@ktx/connector-bigquery", "version": "0.0.0-private", - "description": "BigQuery connector package for KLO scan interfaces", + "description": "BigQuery connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -27,7 +27,7 @@ }, "dependencies": { "@google-cloud/bigquery": "^8.1.1", - "@klo/context": "workspace:*" + "@ktx/context": "workspace:*" }, "devDependencies": { "@types/node": "^24.3.0", diff --git a/packages/connector-bigquery/src/connector.test.ts b/packages/connector-bigquery/src/connector.test.ts index a7ade331..a1c23864 100644 --- a/packages/connector-bigquery/src/connector.test.ts +++ b/packages/connector-bigquery/src/connector.test.ts @@ -2,26 +2,26 @@ import { describe, expect, it, vi } from 'vitest'; import { bigQueryConnectionConfigFromConfig, createBigQueryLiveDatabaseIntrospection, - isKloBigQueryConnectionConfig, - type KloBigQueryClient, - KloBigQueryScanConnector, - type KloBigQueryClientFactory, - type KloBigQueryDataset, - type KloBigQueryQueryJob, - type KloBigQueryTableRef, + isKtxBigQueryConnectionConfig, + type KtxBigQueryClient, + KtxBigQueryScanConnector, + type KtxBigQueryClientFactory, + type KtxBigQueryDataset, + type KtxBigQueryQueryJob, + type KtxBigQueryTableRef, } from './index.js'; -function fakeClientFactory(): KloBigQueryClientFactory { - const queryResults = vi.fn(async (): ReturnType => [ +function fakeClientFactory(): KtxBigQueryClientFactory { + const queryResults = vi.fn(async (): ReturnType => [ [{ id: 1, status: 'paid' }], undefined, { schema: { fields: [{ name: 'id', type: 'INT64' }, { name: 'status', type: 'STRING' }] } }, ]); - const createQueryJob = vi.fn(async (input: { query: string }): ReturnType => { + const createQueryJob = vi.fn(async (input: { query: string }): ReturnType => { if (input.query.includes('INFORMATION_SCHEMA.TABLE_CONSTRAINTS')) { return [ { - getQueryResults: async (): ReturnType => [ + getQueryResults: async (): ReturnType => [ [{ table_name: 'orders', column_name: 'id' }], undefined, { schema: { fields: [{ name: 'table_name', type: 'STRING' }, { name: 'column_name', type: 'STRING' }] } }, @@ -32,7 +32,7 @@ function fakeClientFactory(): KloBigQueryClientFactory { if (input.query.includes('APPROX_COUNT_DISTINCT')) { return [ { - getQueryResults: async (): ReturnType => [ + getQueryResults: async (): ReturnType => [ [{ cardinality: 2 }], undefined, { schema: { fields: [{ name: 'cardinality', type: 'INT64' }] } }, @@ -43,7 +43,7 @@ function fakeClientFactory(): KloBigQueryClientFactory { if (input.query.includes('SELECT DISTINCT CAST')) { return [ { - getQueryResults: async (): ReturnType => [ + getQueryResults: async (): ReturnType => [ [{ val: 'open' }, { val: 'paid' }], undefined, { schema: { fields: [{ name: 'val', type: 'STRING' }] } }, @@ -54,7 +54,7 @@ function fakeClientFactory(): KloBigQueryClientFactory { if (input.query.includes('SELECT `status`')) { return [ { - getQueryResults: async (): ReturnType => [ + getQueryResults: async (): ReturnType => [ [{ status: 'paid' }], undefined, { schema: { fields: [{ name: 'status', type: 'STRING' }] } }, @@ -64,7 +64,7 @@ function fakeClientFactory(): KloBigQueryClientFactory { } return [{ getQueryResults: queryResults }]; }); - const getTable = vi.fn(async (): ReturnType => [ + const getTable = vi.fn(async (): ReturnType => [ { metadata: { type: 'TABLE', @@ -80,14 +80,14 @@ function fakeClientFactory(): KloBigQueryClientFactory { }, }, ]); - const tableRef: KloBigQueryTableRef = { id: 'orders', get: getTable }; + const tableRef: KtxBigQueryTableRef = { id: 'orders', get: getTable }; return { createClient: vi.fn(() => ({ - getDatasets: vi.fn(async (): ReturnType => [[{ id: 'analytics' }, { id: 'staging' }]]), + getDatasets: vi.fn(async (): ReturnType => [[{ id: 'analytics' }, { id: 'staging' }]]), dataset: vi.fn( - (datasetId: string): KloBigQueryDataset => ({ + (datasetId: string): KtxBigQueryDataset => ({ get: vi.fn(async () => [{ id: datasetId }]), - getTables: vi.fn(async (): ReturnType => [[tableRef]]), + getTables: vi.fn(async (): ReturnType => [[tableRef]]), }), ), createQueryJob, @@ -103,10 +103,10 @@ const connection = { readonly: true, }; -describe('KloBigQueryScanConnector', () => { +describe('KtxBigQueryScanConnector', () => { it('resolves configuration safely', () => { - expect(isKloBigQueryConnectionConfig(connection)).toBe(true); - expect(isKloBigQueryConnectionConfig({ driver: 'mysql' })).toBe(false); + expect(isKtxBigQueryConnectionConfig(connection)).toBe(true); + expect(isKtxBigQueryConnectionConfig({ driver: 'mysql' })).toBe(false); expect(bigQueryConnectionConfigFromConfig({ connectionId: 'warehouse', connection })).toMatchObject({ projectId: 'project-1', datasetIds: ['analytics'], @@ -121,7 +121,7 @@ describe('KloBigQueryScanConnector', () => { }); it('introspects datasets, table metadata, primary keys, and normalized types', async () => { - const connector = new KloBigQueryScanConnector({ + const connector = new KtxBigQueryScanConnector({ connectionId: 'warehouse', connection, clientFactory: fakeClientFactory(), @@ -186,7 +186,7 @@ describe('KloBigQueryScanConnector', () => { }); it('runs samples, read-only SQL, distinct values, dataset listing, row counts, and cleanup', async () => { - const connector = new KloBigQueryScanConnector({ + const connector = new KtxBigQueryScanConnector({ connectionId: 'warehouse', connection, clientFactory: fakeClientFactory(), @@ -252,7 +252,7 @@ describe('KloBigQueryScanConnector', () => { it('applies maximumBytesBilled to read-only queries when configured', async () => { const clientFactory = fakeClientFactory(); - const connector = new KloBigQueryScanConnector({ + const connector = new KtxBigQueryScanConnector({ connectionId: 'warehouse', connection, clientFactory, @@ -266,7 +266,7 @@ describe('KloBigQueryScanConnector', () => { ), ).resolves.toMatchObject({ rows: [[1, 'paid']], rowCount: 1 }); - const client = vi.mocked(clientFactory.createClient).mock.results[0]?.value as KloBigQueryClient; + const client = vi.mocked(clientFactory.createClient).mock.results[0]?.value as KtxBigQueryClient; expect(client.createQueryJob).toHaveBeenLastCalledWith( expect.objectContaining({ maximumBytesBilled: '123456789', diff --git a/packages/connector-bigquery/src/connector.ts b/packages/connector-bigquery/src/connector.ts index 776528f4..1da246b3 100644 --- a/packages/connector-bigquery/src/connector.ts +++ b/packages/connector-bigquery/src/connector.ts @@ -1,29 +1,29 @@ import { BigQuery, type TableField } from '@google-cloud/bigquery'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { KloBigQueryDialect } from './dialect.js'; +import { KtxBigQueryDialect } from './dialect.js'; -export interface KloBigQueryConnectionConfig { +export interface KtxBigQueryConnectionConfig { driver?: string; dataset_id?: string; dataset_ids?: string[]; @@ -33,35 +33,35 @@ export interface KloBigQueryConnectionConfig { [key: string]: unknown; } -export interface KloBigQueryResolvedConnectionConfig { +export interface KtxBigQueryResolvedConnectionConfig { projectId: string; credentials: Record; datasetIds: string[]; location?: string; } -export interface KloBigQueryReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxBigQueryReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record; } -export interface KloBigQueryColumnDistinctValuesOptions { +export interface KtxBigQueryColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloBigQueryColumnDistinctValuesResult { +export interface KtxBigQueryColumnDistinctValuesResult { values: string[] | null; cardinality: number; } -export interface KloBigQueryQueryJob { +export interface KtxBigQueryQueryJob { getQueryResults(): Promise< [Array>, unknown, { schema?: { fields?: TableField[] } }?, ...unknown[]] >; } -export interface KloBigQueryTableRef { +export interface KtxBigQueryTableRef { id?: string; get(): Promise< [ @@ -78,39 +78,39 @@ export interface KloBigQueryTableRef { >; } -export interface KloBigQueryDataset { +export interface KtxBigQueryDataset { get(): Promise; - getTables(): Promise<[KloBigQueryTableRef[], ...unknown[]]>; + getTables(): Promise<[KtxBigQueryTableRef[], ...unknown[]]>; } -export interface KloBigQueryClient { +export interface KtxBigQueryClient { getDatasets(input?: { maxResults?: number }): Promise<[Array<{ id?: string }>, ...unknown[]]>; - dataset(datasetId: string): KloBigQueryDataset; + dataset(datasetId: string): KtxBigQueryDataset; createQueryJob(input: { query: string; location?: string; params?: Record; maximumBytesBilled?: string; jobTimeoutMs?: number; - }): Promise<[KloBigQueryQueryJob, ...unknown[]]>; + }): Promise<[KtxBigQueryQueryJob, ...unknown[]]>; } -export interface KloBigQueryClientFactory { - createClient(input: { projectId: string; credentials: Record }): KloBigQueryClient; +export interface KtxBigQueryClientFactory { + createClient(input: { projectId: string; credentials: Record }): KtxBigQueryClient; } -export interface KloBigQueryScanConnectorOptions { +export interface KtxBigQueryScanConnectorOptions { connectionId: string; - connection: KloBigQueryConnectionConfig | undefined; - clientFactory?: KloBigQueryClientFactory; + connection: KtxBigQueryConnectionConfig | undefined; + clientFactory?: KtxBigQueryClientFactory; env?: NodeJS.ProcessEnv; now?: () => Date; maxBytesBilled?: number | string; queryTimeoutMs?: number; } -class DefaultBigQueryClientFactory implements KloBigQueryClientFactory { - createClient(input: { projectId: string; credentials: Record }): KloBigQueryClient { +class DefaultBigQueryClientFactory implements KtxBigQueryClientFactory { + createClient(input: { projectId: string; credentials: Record }): KtxBigQueryClient { const client = new BigQuery(input); return { getDatasets: (options) => client.getDatasets(options) as Promise<[Array<{ id?: string }>, ...unknown[]]>, @@ -118,10 +118,10 @@ class DefaultBigQueryClientFactory implements KloBigQueryClientFactory { const dataset = client.dataset(datasetId); return { get: () => dataset.get() as Promise, - getTables: () => dataset.getTables() as Promise<[KloBigQueryTableRef[], ...unknown[]]>, + getTables: () => dataset.getTables() as Promise<[KtxBigQueryTableRef[], ...unknown[]]>, }; }, - createQueryJob: (options) => client.createQueryJob(options) as Promise<[KloBigQueryQueryJob, ...unknown[]]>, + createQueryJob: (options) => client.createQueryJob(options) as Promise<[KtxBigQueryQueryJob, ...unknown[]]>, }; } } @@ -139,15 +139,15 @@ function resolveStringReference(value: string, env: NodeJS.ProcessEnv): string { } function stringConfigValue( - connection: KloBigQueryConnectionConfig | undefined, - key: keyof KloBigQueryConnectionConfig, + connection: KtxBigQueryConnectionConfig | undefined, + key: keyof KtxBigQueryConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(value.trim(), env) : undefined; } -function datasetIds(connection: KloBigQueryConnectionConfig, env: NodeJS.ProcessEnv): string[] { +function datasetIds(connection: KtxBigQueryConnectionConfig, env: NodeJS.ProcessEnv): string[] { if (Array.isArray(connection.dataset_ids) && connection.dataset_ids.length > 0) { return connection.dataset_ids .filter((dataset) => dataset.trim().length > 0) @@ -157,7 +157,7 @@ function datasetIds(connection: KloBigQueryConnectionConfig, env: NodeJS.Process return datasetId ? [datasetId] : []; } -function tableKind(metadataType: string | undefined): KloSchemaTable['kind'] { +function tableKind(metadataType: string | undefined): KtxSchemaTable['kind'] { const type = String(metadataType ?? '').toUpperCase(); if (type === 'VIEW' || type === 'MATERIALIZED_VIEW') { return 'view'; @@ -192,16 +192,16 @@ function normalizeValue(value: unknown): unknown { return value; } -export function isKloBigQueryConnectionConfig(connection: KloBigQueryConnectionConfig | undefined): boolean { +export function isKtxBigQueryConnectionConfig(connection: KtxBigQueryConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'bigquery'; } export function bigQueryConnectionConfigFromConfig(input: { connectionId: string; - connection: KloBigQueryConnectionConfig | undefined; + connection: KtxBigQueryConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloBigQueryResolvedConnectionConfig { - if (!isKloBigQueryConnectionConfig(input.connection)) { +}): KtxBigQueryResolvedConnectionConfig { + if (!isKtxBigQueryConnectionConfig(input.connection)) { throw new Error(`Native BigQuery connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -226,10 +226,10 @@ export function bigQueryConnectionConfigFromConfig(input: { return { projectId, credentials, datasetIds: resolvedDatasetIds, ...(location ? { location } : {}) }; } -export class KloBigQueryScanConnector implements KloScanConnector { +export class KtxBigQueryScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'bigquery' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -240,15 +240,15 @@ export class KloBigQueryScanConnector implements KloScanConnector { }); private readonly connectionId: string; - private readonly resolved: KloBigQueryResolvedConnectionConfig; - private readonly clientFactory: KloBigQueryClientFactory; + private readonly resolved: KtxBigQueryResolvedConnectionConfig; + private readonly clientFactory: KtxBigQueryClientFactory; private readonly now: () => Date; private readonly maxBytesBilled?: number | string; private readonly queryTimeoutMs?: number; - private readonly dialect = new KloBigQueryDialect(); - private client: KloBigQueryClient | null = null; + private readonly dialect = new KtxBigQueryDialect(); + private client: KtxBigQueryClient | null = null; - constructor(options: KloBigQueryScanConnectorOptions) { + constructor(options: KtxBigQueryScanConnectorOptions) { this.connectionId = options.connectionId; this.resolved = bigQueryConnectionConfigFromConfig({ connectionId: options.connectionId, @@ -275,9 +275,9 @@ export class KloBigQueryScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const datasetId of this.resolved.datasetIds) { tables.push(...(await this.introspectDataset(datasetId))); } @@ -296,13 +296,13 @@ export class KloBigQueryScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns)); return { headers: result.headers, headerTypes: result.headerTypes, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -310,11 +310,11 @@ export class KloBigQueryScanConnector implements KloScanConnector { return { values: result.rows.filter((row) => row.length > 0 && row[0] !== null).map((row) => row[0]), nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloBigQueryReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxBigQueryReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = this.dialect.prepareQuery(limitedSql, input.params); @@ -323,10 +323,10 @@ export class KloBigQueryScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloBigQueryColumnDistinctValuesOptions, - ): Promise { + options: KtxBigQueryColumnDistinctValuesOptions, + ): Promise { const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); const cardinality = await this.singleNumber( @@ -356,7 +356,7 @@ export class KloBigQueryScanConnector implements KloScanConnector { return tables.find((table) => table.name === tableName)?.estimatedRows ?? 0; } - qTableName(table: Pick & Partial>): string { + qTableName(table: Pick & Partial>): string { return this.dialect.formatTableName(table); } @@ -373,7 +373,7 @@ export class KloBigQueryScanConnector implements KloScanConnector { this.client = null; } - private getClient(): KloBigQueryClient { + private getClient(): KtxBigQueryClient { if (!this.client) { this.client = this.clientFactory.createClient({ projectId: this.resolved.projectId, @@ -383,7 +383,7 @@ export class KloBigQueryScanConnector implements KloScanConnector { return this.client; } - private async query(sql: string, params?: Record): Promise { + private async query(sql: string, params?: Record): Promise { const [job] = await this.getClient().createQueryJob({ query: sql, ...(this.resolved.location ? { location: this.resolved.location } : {}), @@ -416,11 +416,11 @@ export class KloBigQueryScanConnector implements KloScanConnector { return firstNumber(rows[0]?.[header]); } - private async introspectDataset(datasetId: string): Promise { + private async introspectDataset(datasetId: string): Promise { const dataset = this.getClient().dataset(datasetId); const [tableRefs] = await dataset.getTables(); const primaryKeys = await this.primaryKeys(datasetId); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const tableRef of tableRefs) { const tableName = tableRef.id || ''; const [table] = await tableRef.get(); @@ -471,7 +471,7 @@ export class KloBigQueryScanConnector implements KloScanConnector { return grouped; } - private toSchemaColumn(tableName: string, field: TableField, primaryKeys: Map>): KloSchemaColumn { + private toSchemaColumn(tableName: string, field: TableField, primaryKeys: Map>): KtxSchemaColumn { const nativeType = String(field.type || 'STRING').toUpperCase(); return { name: field.name || '', diff --git a/packages/connector-bigquery/src/dialect.test.ts b/packages/connector-bigquery/src/dialect.test.ts index 236b424e..d2033bd9 100644 --- a/packages/connector-bigquery/src/dialect.test.ts +++ b/packages/connector-bigquery/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloBigQueryDialect } from './dialect.js'; +import { KtxBigQueryDialect } from './dialect.js'; -describe('KloBigQueryDialect', () => { - const dialect = new KloBigQueryDialect(); +describe('KtxBigQueryDialect', () => { + const dialect = new KtxBigQueryDialect(); it('quotes identifiers and formats project.dataset.table names', () => { expect(dialect.quoteIdentifier('order`items')).toBe('`order\\`items`'); diff --git a/packages/connector-bigquery/src/dialect.ts b/packages/connector-bigquery/src/dialect.ts index 7b1058cc..f4cf2cd2 100644 --- a/packages/connector-bigquery/src/dialect.ts +++ b/packages/connector-bigquery/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type BigQueryTableNameRef = Pick & Partial>; +type BigQueryTableNameRef = Pick & Partial>; -export class KloBigQueryDialect { +export class KtxBigQueryDialect { readonly type = 'bigquery'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { TIMESTAMP: 'time', DATETIME: 'time', DATE: 'time', @@ -62,7 +62,7 @@ export class KloBigQueryDialect { return typeMapping[fieldType] || fieldType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-bigquery/src/index.ts b/packages/connector-bigquery/src/index.ts index 04aca98e..e4003530 100644 --- a/packages/connector-bigquery/src/index.ts +++ b/packages/connector-bigquery/src/index.ts @@ -1,18 +1,18 @@ -export { KloBigQueryDialect } from './dialect.js'; +export { KtxBigQueryDialect } from './dialect.js'; export { bigQueryConnectionConfigFromConfig, - isKloBigQueryConnectionConfig, - KloBigQueryScanConnector, - type KloBigQueryClient, - type KloBigQueryClientFactory, - type KloBigQueryColumnDistinctValuesOptions, - type KloBigQueryColumnDistinctValuesResult, - type KloBigQueryConnectionConfig, - type KloBigQueryDataset, - type KloBigQueryQueryJob, - type KloBigQueryReadOnlyQueryInput, - type KloBigQueryResolvedConnectionConfig, - type KloBigQueryScanConnectorOptions, - type KloBigQueryTableRef, + isKtxBigQueryConnectionConfig, + KtxBigQueryScanConnector, + type KtxBigQueryClient, + type KtxBigQueryClientFactory, + type KtxBigQueryColumnDistinctValuesOptions, + type KtxBigQueryColumnDistinctValuesResult, + type KtxBigQueryConnectionConfig, + type KtxBigQueryDataset, + type KtxBigQueryQueryJob, + type KtxBigQueryReadOnlyQueryInput, + type KtxBigQueryResolvedConnectionConfig, + type KtxBigQueryScanConnectorOptions, + type KtxBigQueryTableRef, } from './connector.js'; export { createBigQueryLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-bigquery/src/live-database-introspection.ts b/packages/connector-bigquery/src/live-database-introspection.ts index 854923a2..79271e0e 100644 --- a/packages/connector-bigquery/src/live-database-introspection.ts +++ b/packages/connector-bigquery/src/live-database-introspection.ts @@ -1,14 +1,14 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloBigQueryScanConnector, - type KloBigQueryClientFactory, - type KloBigQueryConnectionConfig, + KtxBigQueryScanConnector, + type KtxBigQueryClientFactory, + type KtxBigQueryConnectionConfig, } from './connector.js'; interface CreateBigQueryLiveDatabaseIntrospectionOptions { - connections: Record; - clientFactory?: KloBigQueryClientFactory; + connections: Record; + clientFactory?: KtxBigQueryClientFactory; now?: () => Date; } @@ -17,8 +17,8 @@ export function createBigQueryLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloBigQueryConnectionConfig | undefined; - const connector = new KloBigQueryScanConnector({ + const connection = options.connections[connectionId] as KtxBigQueryConnectionConfig | undefined; + const connector = new KtxBigQueryScanConnector({ connectionId, connection, clientFactory: options.clientFactory, diff --git a/packages/connector-bigquery/src/package-exports.test.ts b/packages/connector-bigquery/src/package-exports.test.ts index 80ba5c86..71508c5a 100644 --- a/packages/connector-bigquery/src/package-exports.test.ts +++ b/packages/connector-bigquery/src/package-exports.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; import * as connector from './index.js'; -describe('@klo/connector-bigquery exports', () => { +describe('@ktx/connector-bigquery exports', () => { it('exports public connector, dialect, and introspection APIs', () => { - expect(connector.KloBigQueryDialect).toBeTypeOf('function'); - expect(connector.KloBigQueryScanConnector).toBeTypeOf('function'); + expect(connector.KtxBigQueryDialect).toBeTypeOf('function'); + expect(connector.KtxBigQueryScanConnector).toBeTypeOf('function'); expect(connector.bigQueryConnectionConfigFromConfig).toBeTypeOf('function'); expect(connector.createBigQueryLiveDatabaseIntrospection).toBeTypeOf('function'); }); diff --git a/packages/connector-clickhouse/package.json b/packages/connector-clickhouse/package.json index d1fb64e5..99bfae8d 100644 --- a/packages/connector-clickhouse/package.json +++ b/packages/connector-clickhouse/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-clickhouse", + "name": "@ktx/connector-clickhouse", "version": "0.0.0-private", - "description": "ClickHouse connector package for KLO scan interfaces", + "description": "ClickHouse connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -27,7 +27,7 @@ }, "dependencies": { "@clickhouse/client": "^1.18.2", - "@klo/context": "workspace:*" + "@ktx/context": "workspace:*" }, "devDependencies": { "@types/node": "^24.3.0", diff --git a/packages/connector-clickhouse/src/connector.test.ts b/packages/connector-clickhouse/src/connector.test.ts index dcb0c3bd..7ed60efa 100644 --- a/packages/connector-clickhouse/src/connector.test.ts +++ b/packages/connector-clickhouse/src/connector.test.ts @@ -2,9 +2,9 @@ import { describe, expect, it, vi } from 'vitest'; import { clickHouseClientConfigFromConfig, createClickHouseLiveDatabaseIntrospection, - isKloClickHouseConnectionConfig, - KloClickHouseScanConnector, - type KloClickHouseClientFactory, + isKtxClickHouseConnectionConfig, + KtxClickHouseScanConnector, + type KtxClickHouseClientFactory, } from './index.js'; function result(payload: T) { @@ -15,7 +15,7 @@ function result(payload: T) { }; } -function fakeClientFactory(): KloClickHouseClientFactory { +function fakeClientFactory(): KtxClickHouseClientFactory { const query = vi.fn(async (input: { query: string; format: string; query_params?: Record }) => { if (input.query.includes('FROM system.tables')) { return result([ @@ -77,7 +77,7 @@ function fakeClientFactory(): KloClickHouseClientFactory { if (input.query.trim() === 'SELECT 1') { return result({ meta: [{ name: '1', type: 'UInt8' }], data: [[1]], rows: 1 }); } - if (input.query.includes('select * from (select id, event_name from analytics.events) as klo_query_result limit 1')) { + if (input.query.includes('select * from (select id, event_name from analytics.events) as ktx_query_result limit 1')) { return result({ meta: [ { name: 'id', type: 'UInt64' }, @@ -95,12 +95,12 @@ function fakeClientFactory(): KloClickHouseClientFactory { }; } -describe('KloClickHouseScanConnector', () => { +describe('KtxClickHouseScanConnector', () => { it('resolves ClickHouse connection configuration safely', () => { - expect(isKloClickHouseConnectionConfig({ driver: 'clickhouse', host: 'localhost', database: 'analytics' })).toBe( + expect(isKtxClickHouseConnectionConfig({ driver: 'clickhouse', host: 'localhost', database: 'analytics' })).toBe( true, ); - expect(isKloClickHouseConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics' })).toBe(false); + expect(isKtxClickHouseConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics' })).toBe(false); expect( clickHouseClientConfigFromConfig({ connectionId: 'warehouse', @@ -132,7 +132,7 @@ describe('KloClickHouseScanConnector', () => { }); it('introspects schema, primary keys, comments, row counts, and views', async () => { - const connector = new KloClickHouseScanConnector({ + const connector = new KtxClickHouseScanConnector({ connectionId: 'warehouse', connection: { driver: 'clickhouse', @@ -181,7 +181,7 @@ describe('KloClickHouseScanConnector', () => { it('runs samples, distinct values, read-only SQL, row count, schema list, and cleanup', async () => { const clientFactory = fakeClientFactory(); - const connector = new KloClickHouseScanConnector({ + const connector = new KtxClickHouseScanConnector({ connectionId: 'warehouse', connection: { driver: 'clickhouse', diff --git a/packages/connector-clickhouse/src/connector.ts b/packages/connector-clickhouse/src/connector.ts index b410e8ba..ae66443e 100644 --- a/packages/connector-clickhouse/src/connector.ts +++ b/packages/connector-clickhouse/src/connector.ts @@ -1,30 +1,30 @@ import { createClient } from '@clickhouse/client'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; import { readFileSync } from 'node:fs'; import { Agent as HttpsAgent } from 'node:https'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { KloClickHouseDialect } from './dialect.js'; +import { KtxClickHouseDialect } from './dialect.js'; -export interface KloClickHouseConnectionConfig { +export interface KtxClickHouseConnectionConfig { driver?: string; host?: string; port?: number; @@ -38,7 +38,7 @@ export interface KloClickHouseConnectionConfig { [key: string]: unknown; } -export interface KloClickHouseResolvedClientConfig { +export interface KtxClickHouseResolvedClientConfig { host: string; port: number; database: string; @@ -57,49 +57,49 @@ interface ClickHouseResultSet { json(): Promise; } -export interface KloClickHouseClient { +export interface KtxClickHouseClient { query(input: ClickHouseQueryInput): Promise; close(): Promise; } -export interface KloClickHouseClientFactory { - createClient(config: Parameters[0]): KloClickHouseClient; +export interface KtxClickHouseClientFactory { + createClient(config: Parameters[0]): KtxClickHouseClient; } -interface KloClickHouseResolvedEndpoint { +interface KtxClickHouseResolvedEndpoint { host: string; port: number; close?: () => Promise; } -export interface KloClickHouseEndpointResolver { +export interface KtxClickHouseEndpointResolver { resolve(input: { host: string; port: number; - connection: KloClickHouseConnectionConfig; - }): Promise; + connection: KtxClickHouseConnectionConfig; + }): Promise; } -export interface KloClickHouseScanConnectorOptions { +export interface KtxClickHouseScanConnectorOptions { connectionId: string; - connection: KloClickHouseConnectionConfig | undefined; - clientFactory?: KloClickHouseClientFactory; - endpointResolver?: KloClickHouseEndpointResolver; + connection: KtxClickHouseConnectionConfig | undefined; + clientFactory?: KtxClickHouseClientFactory; + endpointResolver?: KtxClickHouseEndpointResolver; env?: NodeJS.ProcessEnv; now?: () => Date; } -export interface KloClickHouseReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxClickHouseReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record; } -export interface KloClickHouseColumnDistinctValuesOptions { +export interface KtxClickHouseColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloClickHouseColumnDistinctValuesResult { +export interface KtxClickHouseColumnDistinctValuesResult { values: string[] | null; cardinality: number; } @@ -134,15 +134,15 @@ interface ClickHouseCompactResponse { rows?: number; } -class DefaultClickHouseClientFactory implements KloClickHouseClientFactory { - createClient(config: Parameters[0]): KloClickHouseClient { +class DefaultClickHouseClientFactory implements KtxClickHouseClientFactory { + createClient(config: Parameters[0]): KtxClickHouseClient { return createClient(config); } } function stringConfigValue( - connection: KloClickHouseConnectionConfig | undefined, - key: keyof KloClickHouseConnectionConfig, + connection: KtxClickHouseConnectionConfig | undefined, + key: keyof KtxClickHouseConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; @@ -166,7 +166,7 @@ function maybeNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined; } -function parseClickHouseUrl(url: string): Partial { +function parseClickHouseUrl(url: string): Partial { const parsed = new URL(url); return { host: parsed.hostname, @@ -178,7 +178,7 @@ function parseClickHouseUrl(url: string): Partial }; } -function tableKind(engine: string): KloSchemaTable['kind'] { +function tableKind(engine: string): KtxSchemaTable['kind'] { return engine === 'View' || engine === 'MaterializedView' ? 'view' : 'table'; } @@ -186,16 +186,16 @@ function isNullableClickHouseType(type: string): boolean { return type.startsWith('Nullable(') || type.startsWith('LowCardinality(Nullable('); } -export function isKloClickHouseConnectionConfig(connection: KloClickHouseConnectionConfig | undefined): boolean { +export function isKtxClickHouseConnectionConfig(connection: KtxClickHouseConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'clickhouse'; } export function clickHouseClientConfigFromConfig(input: { connectionId: string; - connection: KloClickHouseConnectionConfig | undefined; + connection: KtxClickHouseConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloClickHouseResolvedClientConfig { - if (!isKloClickHouseConnectionConfig(input.connection)) { +}): KtxClickHouseResolvedClientConfig { + if (!isKtxClickHouseConnectionConfig(input.connection)) { throw new Error(`Native ClickHouse connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -205,7 +205,7 @@ export function clickHouseClientConfigFromConfig(input: { const env = input.env ?? process.env; const referencedUrl = stringConfigValue(input.connection, 'url', env); const urlConfig = referencedUrl ? parseClickHouseUrl(referencedUrl) : {}; - const merged: KloClickHouseConnectionConfig = { ...urlConfig, ...input.connection }; + const merged: KtxClickHouseConnectionConfig = { ...urlConfig, ...input.connection }; const host = stringConfigValue(merged, 'host', env); const database = stringConfigValue(merged, 'database', env) ?? 'default'; const username = stringConfigValue(merged, 'username', env) ?? stringConfigValue(merged, 'user', env) ?? 'default'; @@ -224,10 +224,10 @@ export function clickHouseClientConfigFromConfig(input: { }; } -export class KloClickHouseScanConnector implements KloScanConnector { +export class KtxClickHouseScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'clickhouse' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -238,16 +238,16 @@ export class KloClickHouseScanConnector implements KloScanConnector { }); private readonly connectionId: string; - private readonly connection: KloClickHouseConnectionConfig; - private readonly clientConfig: KloClickHouseResolvedClientConfig; - private readonly clientFactory: KloClickHouseClientFactory; - private readonly endpointResolver?: KloClickHouseEndpointResolver; + private readonly connection: KtxClickHouseConnectionConfig; + private readonly clientConfig: KtxClickHouseResolvedClientConfig; + private readonly clientFactory: KtxClickHouseClientFactory; + private readonly endpointResolver?: KtxClickHouseEndpointResolver; private readonly now: () => Date; - private readonly dialect = new KloClickHouseDialect(); - private client: KloClickHouseClient | null = null; - private resolvedEndpoint: KloClickHouseResolvedEndpoint | null = null; + private readonly dialect = new KtxClickHouseDialect(); + private client: KtxClickHouseClient | null = null; + private resolvedEndpoint: KtxClickHouseResolvedEndpoint | null = null; - constructor(options: KloClickHouseScanConnectorOptions) { + constructor(options: KtxClickHouseScanConnectorOptions) { this.connectionId = options.connectionId; this.connection = options.connection ?? {}; this.clientConfig = clickHouseClientConfigFromConfig({ @@ -270,7 +270,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const database = this.clientConfig.database; const tables = await this.queryEachRow( @@ -326,7 +326,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns), @@ -334,7 +334,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { return { headers: result.headers, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -343,11 +343,11 @@ export class KloClickHouseScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloClickHouseReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxClickHouseReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = this.dialect.prepareQuery(limitedSql, input.params); @@ -356,10 +356,10 @@ export class KloClickHouseScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloClickHouseColumnDistinctValuesOptions, - ): Promise { + options: KtxClickHouseColumnDistinctValuesOptions, + ): Promise { const sampleSize = options.sampleSize ?? 10000; const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); @@ -397,7 +397,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { return Number(result.rows[0]?.[0] ?? 0); } - qTableName(table: Pick & Partial>): string { + qTableName(table: Pick & Partial>): string { return this.dialect.formatTableName(table); } @@ -428,7 +428,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { } } - private toSchemaTable(table: ClickHouseTableRow, columns: ClickHouseColumnRow[], estimatedRows: number): KloSchemaTable { + private toSchemaTable(table: ClickHouseTableRow, columns: ClickHouseColumnRow[], estimatedRows: number): KtxSchemaTable { const kind = tableKind(table.engine); return { catalog: null, @@ -442,7 +442,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { }; } - private toSchemaColumn(column: ClickHouseColumnRow): KloSchemaColumn { + private toSchemaColumn(column: ClickHouseColumnRow): KtxSchemaColumn { return { name: column.name, nativeType: column.type, @@ -454,7 +454,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { }; } - private async clientForQuery(): Promise { + private async clientForQuery(): Promise { if (!this.client) { const config = { ...this.clientConfig }; if (this.endpointResolver) { @@ -500,7 +500,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { return (await resultSet.json()) as T[]; } - private async query(sql: string, params?: Record): Promise> { + private async query(sql: string, params?: Record): Promise> { const client = await this.clientForQuery(); const resultSet = await client.query({ query: assertReadOnlySql(sql), @@ -519,7 +519,7 @@ export class KloClickHouseScanConnector implements KloScanConnector { private assertConnection(connectionId: string): void { if (connectionId !== this.connectionId) { - throw new Error(`KLO ClickHouse connector ${this.id} cannot serve connection ${connectionId}`); + throw new Error(`KTX ClickHouse connector ${this.id} cannot serve connection ${connectionId}`); } } } diff --git a/packages/connector-clickhouse/src/dialect.test.ts b/packages/connector-clickhouse/src/dialect.test.ts index b6dd3485..14a1032c 100644 --- a/packages/connector-clickhouse/src/dialect.test.ts +++ b/packages/connector-clickhouse/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloClickHouseDialect } from './dialect.js'; +import { KtxClickHouseDialect } from './dialect.js'; -describe('KloClickHouseDialect', () => { - const dialect = new KloClickHouseDialect(); +describe('KtxClickHouseDialect', () => { + const dialect = new KtxClickHouseDialect(); it('quotes identifiers and formats database-qualified table names', () => { expect(dialect.quoteIdentifier('events')).toBe('`events`'); @@ -13,7 +13,7 @@ describe('KloClickHouseDialect', () => { expect(dialect.formatTableName({ catalog: null, db: null, name: 'events' })).toBe('`events`'); }); - it('maps nullable and low-cardinality ClickHouse types to KLO dimension types', () => { + it('maps nullable and low-cardinality ClickHouse types to KTX dimension types', () => { expect(dialect.mapToDimensionType('Nullable(DateTime64(3))')).toBe('time'); expect(dialect.mapToDimensionType('LowCardinality(Nullable(String))')).toBe('string'); expect(dialect.mapToDimensionType('UInt64')).toBe('number'); diff --git a/packages/connector-clickhouse/src/dialect.ts b/packages/connector-clickhouse/src/dialect.ts index 6f61c53e..bff03ae0 100644 --- a/packages/connector-clickhouse/src/dialect.ts +++ b/packages/connector-clickhouse/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type ClickHouseTableNameRef = Pick & Partial>; +type ClickHouseTableNameRef = Pick & Partial>; -export class KloClickHouseDialect { +export class KtxClickHouseDialect { readonly type = 'clickhouse'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { date: 'time', date32: 'time', datetime: 'time', @@ -54,7 +54,7 @@ export class KloClickHouseDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-clickhouse/src/index.ts b/packages/connector-clickhouse/src/index.ts index ae20b643..106df237 100644 --- a/packages/connector-clickhouse/src/index.ts +++ b/packages/connector-clickhouse/src/index.ts @@ -1,16 +1,16 @@ -export { KloClickHouseDialect } from './dialect.js'; +export { KtxClickHouseDialect } from './dialect.js'; export { clickHouseClientConfigFromConfig, - isKloClickHouseConnectionConfig, - KloClickHouseScanConnector, - type KloClickHouseClient, - type KloClickHouseClientFactory, - type KloClickHouseColumnDistinctValuesOptions, - type KloClickHouseColumnDistinctValuesResult, - type KloClickHouseConnectionConfig, - type KloClickHouseEndpointResolver, - type KloClickHouseReadOnlyQueryInput, - type KloClickHouseResolvedClientConfig, - type KloClickHouseScanConnectorOptions, + isKtxClickHouseConnectionConfig, + KtxClickHouseScanConnector, + type KtxClickHouseClient, + type KtxClickHouseClientFactory, + type KtxClickHouseColumnDistinctValuesOptions, + type KtxClickHouseColumnDistinctValuesResult, + type KtxClickHouseConnectionConfig, + type KtxClickHouseEndpointResolver, + type KtxClickHouseReadOnlyQueryInput, + type KtxClickHouseResolvedClientConfig, + type KtxClickHouseScanConnectorOptions, } from './connector.js'; export { createClickHouseLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-clickhouse/src/live-database-introspection.ts b/packages/connector-clickhouse/src/live-database-introspection.ts index 67c4db78..a7b333cd 100644 --- a/packages/connector-clickhouse/src/live-database-introspection.ts +++ b/packages/connector-clickhouse/src/live-database-introspection.ts @@ -1,16 +1,16 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloClickHouseScanConnector, - type KloClickHouseClientFactory, - type KloClickHouseConnectionConfig, - type KloClickHouseEndpointResolver, + KtxClickHouseScanConnector, + type KtxClickHouseClientFactory, + type KtxClickHouseConnectionConfig, + type KtxClickHouseEndpointResolver, } from './connector.js'; interface CreateClickHouseLiveDatabaseIntrospectionOptions { - connections: Record; - clientFactory?: KloClickHouseClientFactory; - endpointResolver?: KloClickHouseEndpointResolver; + connections: Record; + clientFactory?: KtxClickHouseClientFactory; + endpointResolver?: KtxClickHouseEndpointResolver; now?: () => Date; } @@ -19,8 +19,8 @@ export function createClickHouseLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloClickHouseConnectionConfig | undefined; - const connector = new KloClickHouseScanConnector({ + const connection = options.connections[connectionId] as KtxClickHouseConnectionConfig | undefined; + const connector = new KtxClickHouseScanConnector({ connectionId, connection, clientFactory: options.clientFactory, diff --git a/packages/connector-clickhouse/src/package-exports.test.ts b/packages/connector-clickhouse/src/package-exports.test.ts index 6deff566..96ba11d4 100644 --- a/packages/connector-clickhouse/src/package-exports.test.ts +++ b/packages/connector-clickhouse/src/package-exports.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/connector-clickhouse package exports', () => { +describe('@ktx/connector-clickhouse package exports', () => { it('exports public connector APIs during package bootstrap', async () => { const connector = await import('./index.js'); - expect(connector.KloClickHouseDialect).toBeTypeOf('function'); - expect(connector.KloClickHouseScanConnector).toBeTypeOf('function'); + expect(connector.KtxClickHouseDialect).toBeTypeOf('function'); + expect(connector.KtxClickHouseScanConnector).toBeTypeOf('function'); expect(connector.clickHouseClientConfigFromConfig).toBeTypeOf('function'); expect(connector.createClickHouseLiveDatabaseIntrospection).toBeTypeOf('function'); }); diff --git a/packages/connector-mysql/package.json b/packages/connector-mysql/package.json index 9ce7ab96..e4d9e49f 100644 --- a/packages/connector-mysql/package.json +++ b/packages/connector-mysql/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-mysql", + "name": "@ktx/connector-mysql", "version": "0.0.0-private", - "description": "MySQL connector package for KLO scan interfaces", + "description": "MySQL connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*", + "@ktx/context": "workspace:*", "mysql2": "^3.18.1" }, "devDependencies": { diff --git a/packages/connector-mysql/src/connector.test.ts b/packages/connector-mysql/src/connector.test.ts index c9672411..3e7ac1e1 100644 --- a/packages/connector-mysql/src/connector.test.ts +++ b/packages/connector-mysql/src/connector.test.ts @@ -2,17 +2,17 @@ import { describe, expect, it, vi } from 'vitest'; import type { FieldPacket, RowDataPacket } from 'mysql2/promise'; import { createMysqlLiveDatabaseIntrospection, - isKloMysqlConnectionConfig, - KloMysqlScanConnector, + isKtxMysqlConnectionConfig, + KtxMysqlScanConnector, mysqlConnectionPoolConfigFromConfig, - type KloMysqlPoolFactory, + type KtxMysqlPoolFactory, } from './index.js'; function mysqlResult(rows: Record[], fields: Array<{ name: string; type?: number }>): [RowDataPacket[], FieldPacket[]] { return [rows as RowDataPacket[], fields as FieldPacket[]]; } -function fakePoolFactory(): KloMysqlPoolFactory { +function fakePoolFactory(): KtxMysqlPoolFactory { const query = vi.fn(async (sql: string, params?: unknown): Promise<[RowDataPacket[], FieldPacket[]]> => { if (sql.includes('INFORMATION_SCHEMA.TABLES')) { return mysqlResult( @@ -57,7 +57,7 @@ function fakePoolFactory(): KloMysqlPoolFactory { if (sql.includes('SELECT `id`, `status` FROM `analytics`.`orders` LIMIT 1')) { return mysqlResult([{ id: 10, status: 'paid' }], [{ name: 'id', type: 3 }, { name: 'status', type: 253 }]); } - if (sql.includes('select * from (select id, status from analytics.orders) as klo_query_result limit 1')) { + if (sql.includes('select * from (select id, status from analytics.orders) as ktx_query_result limit 1')) { return mysqlResult([{ id: 10, status: 'paid' }], [{ name: 'id', type: 3 }, { name: 'status', type: 253 }]); } if (sql.includes('SELECT `status` FROM `analytics`.`orders`')) { @@ -90,10 +90,10 @@ function fakePoolFactory(): KloMysqlPoolFactory { }; } -describe('KloMysqlScanConnector', () => { +describe('KtxMysqlScanConnector', () => { it('resolves MySQL connection configuration safely', () => { - expect(isKloMysqlConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics', readonly: true })).toBe(true); - expect(isKloMysqlConnectionConfig({ driver: 'postgres', host: 'localhost', database: 'analytics' })).toBe(false); + expect(isKtxMysqlConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics', readonly: true })).toBe(true); + expect(isKtxMysqlConnectionConfig({ driver: 'postgres', host: 'localhost', database: 'analytics' })).toBe(false); expect( mysqlConnectionPoolConfigFromConfig({ connectionId: 'warehouse', @@ -125,7 +125,7 @@ describe('KloMysqlScanConnector', () => { }); it('introspects schema, primary keys, comments, row counts, views, and foreign keys', async () => { - const connector = new KloMysqlScanConnector({ + const connector = new KtxMysqlScanConnector({ connectionId: 'warehouse', connection: { driver: 'mysql', @@ -184,7 +184,7 @@ describe('KloMysqlScanConnector', () => { it('runs samples, distinct values, read-only SQL, row count, schema list, and cleanup', async () => { const poolFactory = fakePoolFactory(); - const connector = new KloMysqlScanConnector({ + const connector = new KtxMysqlScanConnector({ connectionId: 'warehouse', connection: { driver: 'mysql', diff --git a/packages/connector-mysql/src/connector.ts b/packages/connector-mysql/src/connector.ts index 1adb701a..5b96da4d 100644 --- a/packages/connector-mysql/src/connector.ts +++ b/packages/connector-mysql/src/connector.ts @@ -2,29 +2,29 @@ import mysql, { type FieldPacket, type Pool, type RowDataPacket } from 'mysql2/p import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaForeignKey, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; -import { KloMysqlDialect } from './dialect.js'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaForeignKey, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; +import { KtxMysqlDialect } from './dialect.js'; -export interface KloMysqlConnectionConfig { +export interface KtxMysqlConnectionConfig { driver?: string; host?: string; port?: number; @@ -38,7 +38,7 @@ export interface KloMysqlConnectionConfig { [key: string]: unknown; } -export interface KloMysqlPoolConfig { +export interface KtxMysqlPoolConfig { host: string; port: number; database: string; @@ -49,50 +49,50 @@ export interface KloMysqlPoolConfig { ssl?: { rejectUnauthorized: boolean }; } -interface KloMysqlConnection { +interface KtxMysqlConnection { query(sql: string, params?: unknown): Promise<[RowDataPacket[], FieldPacket[]]>; release(): void; } -interface KloMysqlPool { - getConnection(): Promise; +interface KtxMysqlPool { + getConnection(): Promise; end(): Promise; } -export interface KloMysqlPoolFactory { - createPool(config: KloMysqlPoolConfig): KloMysqlPool; +export interface KtxMysqlPoolFactory { + createPool(config: KtxMysqlPoolConfig): KtxMysqlPool; } -interface KloMysqlResolvedEndpoint { +interface KtxMysqlResolvedEndpoint { host: string; port: number; close?: () => Promise; } -export interface KloMysqlEndpointResolver { - resolve(input: { host: string; port: number; connection: KloMysqlConnectionConfig }): Promise; +export interface KtxMysqlEndpointResolver { + resolve(input: { host: string; port: number; connection: KtxMysqlConnectionConfig }): Promise; } -export interface KloMysqlScanConnectorOptions { +export interface KtxMysqlScanConnectorOptions { connectionId: string; - connection: KloMysqlConnectionConfig | undefined; - poolFactory?: KloMysqlPoolFactory; - endpointResolver?: KloMysqlEndpointResolver; + connection: KtxMysqlConnectionConfig | undefined; + poolFactory?: KtxMysqlPoolFactory; + endpointResolver?: KtxMysqlEndpointResolver; env?: NodeJS.ProcessEnv; now?: () => Date; } -export interface KloMysqlReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxMysqlReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record | unknown[]; } -export interface KloMysqlColumnDistinctValuesOptions { +export interface KtxMysqlColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloMysqlColumnDistinctValuesResult { +export interface KtxMysqlColumnDistinctValuesResult { values: string[] | null; cardinality: number; } @@ -138,15 +138,15 @@ interface MysqlDistinctValueRow extends RowDataPacket { val: unknown; } -class DefaultMysqlPoolFactory implements KloMysqlPoolFactory { - createPool(config: KloMysqlPoolConfig): KloMysqlPool { +class DefaultMysqlPoolFactory implements KtxMysqlPoolFactory { + createPool(config: KtxMysqlPoolConfig): KtxMysqlPool { return mysql.createPool(config) as Pool; } } function stringConfigValue( - connection: KloMysqlConnectionConfig | undefined, - key: keyof KloMysqlConnectionConfig, + connection: KtxMysqlConnectionConfig | undefined, + key: keyof KtxMysqlConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; @@ -170,7 +170,7 @@ function maybeNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined; } -function parseMysqlUrl(url: string): Partial { +function parseMysqlUrl(url: string): Partial { const parsed = new URL(url); const sslParam = parsed.searchParams.get('ssl') ?? parsed.searchParams.get('sslmode'); return { @@ -225,16 +225,16 @@ function queryParams(params: Record | unknown[] | undefined): u return Array.isArray(params) ? params : Object.values(params); } -export function isKloMysqlConnectionConfig(connection: KloMysqlConnectionConfig | undefined): boolean { +export function isKtxMysqlConnectionConfig(connection: KtxMysqlConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'mysql'; } export function mysqlConnectionPoolConfigFromConfig(input: { connectionId: string; - connection: KloMysqlConnectionConfig | undefined; + connection: KtxMysqlConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloMysqlPoolConfig { - if (!isKloMysqlConnectionConfig(input.connection)) { +}): KtxMysqlPoolConfig { + if (!isKtxMysqlConnectionConfig(input.connection)) { throw new Error(`Native MySQL connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -244,7 +244,7 @@ export function mysqlConnectionPoolConfigFromConfig(input: { const env = input.env ?? process.env; const referencedUrl = stringConfigValue(input.connection, 'url', env); const urlConfig = referencedUrl ? parseMysqlUrl(referencedUrl) : {}; - const merged: KloMysqlConnectionConfig = { ...urlConfig, ...input.connection }; + const merged: KtxMysqlConnectionConfig = { ...urlConfig, ...input.connection }; const host = stringConfigValue(merged, 'host', env); const database = stringConfigValue(merged, 'database', env); const user = stringConfigValue(merged, 'username', env) ?? stringConfigValue(merged, 'user', env); @@ -272,10 +272,10 @@ export function mysqlConnectionPoolConfigFromConfig(input: { }; } -export class KloMysqlScanConnector implements KloScanConnector { +export class KtxMysqlScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'mysql' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -286,16 +286,16 @@ export class KloMysqlScanConnector implements KloScanConnector { }); private readonly connectionId: string; - private readonly connection: KloMysqlConnectionConfig; - private readonly poolConfig: KloMysqlPoolConfig; - private readonly poolFactory: KloMysqlPoolFactory; - private readonly endpointResolver?: KloMysqlEndpointResolver; + private readonly connection: KtxMysqlConnectionConfig; + private readonly poolConfig: KtxMysqlPoolConfig; + private readonly poolFactory: KtxMysqlPoolFactory; + private readonly endpointResolver?: KtxMysqlEndpointResolver; private readonly now: () => Date; - private readonly dialect = new KloMysqlDialect(); - private pool: KloMysqlPool | null = null; - private resolvedEndpoint: KloMysqlResolvedEndpoint | null = null; + private readonly dialect = new KtxMysqlDialect(); + private pool: KtxMysqlPool | null = null; + private resolvedEndpoint: KtxMysqlResolvedEndpoint | null = null; - constructor(options: KloMysqlScanConnectorOptions) { + constructor(options: KtxMysqlScanConnectorOptions) { this.connectionId = options.connectionId; this.connection = options.connection ?? {}; this.poolConfig = mysqlConnectionPoolConfigFromConfig({ @@ -318,7 +318,7 @@ export class KloMysqlScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const database = this.poolConfig.database; const tables = await this.queryRaw( @@ -382,13 +382,13 @@ export class KloMysqlScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns)); return { headers: result.headers, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -397,11 +397,11 @@ export class KloMysqlScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloMysqlReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxMysqlReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = Array.isArray(input.params) @@ -412,10 +412,10 @@ export class KloMysqlScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloMysqlColumnDistinctValuesOptions, - ): Promise { + options: KtxMysqlColumnDistinctValuesOptions, + ): Promise { const sampleSize = options.sampleSize ?? 10000; const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); @@ -448,7 +448,7 @@ export class KloMysqlScanConnector implements KloScanConnector { return Number(rows[0]?.count ?? 0); } - qTableName(table: Pick & Partial>): string { + qTableName(table: Pick & Partial>): string { return this.dialect.formatTableName(table); } @@ -482,7 +482,7 @@ export class KloMysqlScanConnector implements KloScanConnector { columns: MysqlColumnRow[], primaryKeysByTable: Map>, foreignKeysByTable: Map, - ): KloSchemaTable { + ): KtxSchemaTable { const tableName = table.TABLE_NAME; const kind = table.TABLE_TYPE === 'VIEW' ? 'view' : 'table'; const estimatedRows = kind === 'view' ? null : Number(table.TABLE_ROWS ?? 0); @@ -498,7 +498,7 @@ export class KloMysqlScanConnector implements KloScanConnector { }; } - private toSchemaColumn(column: MysqlColumnRow, primaryKeys: Set): KloSchemaColumn { + private toSchemaColumn(column: MysqlColumnRow, primaryKeys: Set): KtxSchemaColumn { return { name: column.COLUMN_NAME, nativeType: column.DATA_TYPE, @@ -510,7 +510,7 @@ export class KloMysqlScanConnector implements KloScanConnector { }; } - private toSchemaForeignKey(row: MysqlForeignKeyRow): KloSchemaForeignKey { + private toSchemaForeignKey(row: MysqlForeignKeyRow): KtxSchemaForeignKey { return { fromColumn: row.COLUMN_NAME, toCatalog: null, @@ -521,7 +521,7 @@ export class KloMysqlScanConnector implements KloScanConnector { }; } - private async poolForQuery(): Promise { + private async poolForQuery(): Promise { if (!this.pool) { const config = { ...this.poolConfig }; if (this.endpointResolver) { @@ -552,7 +552,7 @@ export class KloMysqlScanConnector implements KloScanConnector { private async query( sql: string, params?: Record | unknown[], - ): Promise> { + ): Promise> { const pool = await this.poolForQuery(); const connection = await pool.getConnection(); try { @@ -572,7 +572,7 @@ export class KloMysqlScanConnector implements KloScanConnector { private assertConnection(connectionId: string): void { if (connectionId !== this.connectionId) { - throw new Error(`KLO MySQL connector ${this.id} cannot serve connection ${connectionId}`); + throw new Error(`KTX MySQL connector ${this.id} cannot serve connection ${connectionId}`); } } } diff --git a/packages/connector-mysql/src/dialect.test.ts b/packages/connector-mysql/src/dialect.test.ts index f82dc59a..cf15527b 100644 --- a/packages/connector-mysql/src/dialect.test.ts +++ b/packages/connector-mysql/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloMysqlDialect } from './dialect.js'; +import { KtxMysqlDialect } from './dialect.js'; -describe('KloMysqlDialect', () => { - const dialect = new KloMysqlDialect(); +describe('KtxMysqlDialect', () => { + const dialect = new KtxMysqlDialect(); it('quotes identifiers and formats database-qualified table names', () => { expect(dialect.quoteIdentifier('orders')).toBe('`orders`'); @@ -13,7 +13,7 @@ describe('KloMysqlDialect', () => { expect(dialect.formatTableName({ catalog: null, db: null, name: 'orders' })).toBe('`orders`'); }); - it('maps native MySQL types to KLO dimension types', () => { + it('maps native MySQL types to KTX dimension types', () => { expect(dialect.mapToDimensionType('tinyint(1)')).toBe('boolean'); expect(dialect.mapToDimensionType('int')).toBe('number'); expect(dialect.mapToDimensionType('decimal(10,2)')).toBe('number'); diff --git a/packages/connector-mysql/src/dialect.ts b/packages/connector-mysql/src/dialect.ts index 48d48657..d641beb6 100644 --- a/packages/connector-mysql/src/dialect.ts +++ b/packages/connector-mysql/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type MysqlTableNameRef = Pick & Partial>; +type MysqlTableNameRef = Pick & Partial>; -export class KloMysqlDialect { +export class KtxMysqlDialect { readonly type = 'mysql'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { datetime: 'time', timestamp: 'time', date: 'time', @@ -50,7 +50,7 @@ export class KloMysqlDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-mysql/src/index.ts b/packages/connector-mysql/src/index.ts index baae22c6..ea5da962 100644 --- a/packages/connector-mysql/src/index.ts +++ b/packages/connector-mysql/src/index.ts @@ -1,15 +1,15 @@ -export { KloMysqlDialect } from './dialect.js'; +export { KtxMysqlDialect } from './dialect.js'; export { - isKloMysqlConnectionConfig, - KloMysqlScanConnector, + isKtxMysqlConnectionConfig, + KtxMysqlScanConnector, mysqlConnectionPoolConfigFromConfig, - type KloMysqlColumnDistinctValuesOptions, - type KloMysqlColumnDistinctValuesResult, - type KloMysqlConnectionConfig, - type KloMysqlEndpointResolver, - type KloMysqlPoolConfig, - type KloMysqlPoolFactory, - type KloMysqlReadOnlyQueryInput, - type KloMysqlScanConnectorOptions, + type KtxMysqlColumnDistinctValuesOptions, + type KtxMysqlColumnDistinctValuesResult, + type KtxMysqlConnectionConfig, + type KtxMysqlEndpointResolver, + type KtxMysqlPoolConfig, + type KtxMysqlPoolFactory, + type KtxMysqlReadOnlyQueryInput, + type KtxMysqlScanConnectorOptions, } from './connector.js'; export { createMysqlLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-mysql/src/live-database-introspection.ts b/packages/connector-mysql/src/live-database-introspection.ts index 590e10b5..b88d2fa6 100644 --- a/packages/connector-mysql/src/live-database-introspection.ts +++ b/packages/connector-mysql/src/live-database-introspection.ts @@ -1,16 +1,16 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloMysqlScanConnector, - type KloMysqlConnectionConfig, - type KloMysqlEndpointResolver, - type KloMysqlPoolFactory, + KtxMysqlScanConnector, + type KtxMysqlConnectionConfig, + type KtxMysqlEndpointResolver, + type KtxMysqlPoolFactory, } from './connector.js'; interface CreateMysqlLiveDatabaseIntrospectionOptions { - connections: Record; - poolFactory?: KloMysqlPoolFactory; - endpointResolver?: KloMysqlEndpointResolver; + connections: Record; + poolFactory?: KtxMysqlPoolFactory; + endpointResolver?: KtxMysqlEndpointResolver; now?: () => Date; } @@ -19,8 +19,8 @@ export function createMysqlLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloMysqlConnectionConfig | undefined; - const connector = new KloMysqlScanConnector({ + const connection = options.connections[connectionId] as KtxMysqlConnectionConfig | undefined; + const connector = new KtxMysqlScanConnector({ connectionId, connection, poolFactory: options.poolFactory, diff --git a/packages/connector-mysql/src/package-exports.test.ts b/packages/connector-mysql/src/package-exports.test.ts index 3887b94b..14bbd872 100644 --- a/packages/connector-mysql/src/package-exports.test.ts +++ b/packages/connector-mysql/src/package-exports.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/connector-mysql package exports', () => { +describe('@ktx/connector-mysql package exports', () => { it('exports the native MySQL scan surface', async () => { const connector = await import('./index.js'); - expect(connector.KloMysqlDialect).toBeTypeOf('function'); - expect(connector.KloMysqlScanConnector).toBeTypeOf('function'); + expect(connector.KtxMysqlDialect).toBeTypeOf('function'); + expect(connector.KtxMysqlScanConnector).toBeTypeOf('function'); expect(connector.createMysqlLiveDatabaseIntrospection).toBeTypeOf('function'); - expect(connector.isKloMysqlConnectionConfig).toBeTypeOf('function'); + expect(connector.isKtxMysqlConnectionConfig).toBeTypeOf('function'); expect(connector.mysqlConnectionPoolConfigFromConfig).toBeTypeOf('function'); }); }); diff --git a/packages/connector-postgres/package.json b/packages/connector-postgres/package.json index f1a5a4eb..71834abe 100644 --- a/packages/connector-postgres/package.json +++ b/packages/connector-postgres/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-postgres", + "name": "@ktx/connector-postgres", "version": "0.0.0-private", - "description": "PostgreSQL connector package for KLO scan interfaces", + "description": "PostgreSQL connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*", + "@ktx/context": "workspace:*", "pg": "^8.19.0" }, "devDependencies": { diff --git a/packages/connector-postgres/src/connector.test.ts b/packages/connector-postgres/src/connector.test.ts index 6132feae..3bdfc109 100644 --- a/packages/connector-postgres/src/connector.test.ts +++ b/packages/connector-postgres/src/connector.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it, vi } from 'vitest'; import { createPostgresLiveDatabaseIntrospection, - isKloPostgresConnectionConfig, - KloPostgresScanConnector, + isKtxPostgresConnectionConfig, + KtxPostgresScanConnector, postgresPoolConfigFromConfig, - type KloPostgresPoolFactory, + type KtxPostgresPoolFactory, } from './index.js'; interface FakeQueryResult { @@ -12,7 +12,7 @@ interface FakeQueryResult { fields?: Array<{ name: string; dataTypeID: number }>; } -function fakePoolFactory(results: Map): KloPostgresPoolFactory { +function fakePoolFactory(results: Map): KtxPostgresPoolFactory { const query = vi.fn(async (sql: string, params?: unknown[]) => { const normalized = sql.replace(/\s+/g, ' ').trim(); for (const [key, value] of results.entries()) { @@ -100,11 +100,11 @@ function metadataResults(): Map { ]); } -describe('KloPostgresScanConnector', () => { +describe('KtxPostgresScanConnector', () => { it('resolves configuration safely', () => { - expect(isKloPostgresConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe(true); - expect(isKloPostgresConnectionConfig({ driver: 'postgresql', host: 'db', database: 'analytics' })).toBe(true); - expect(isKloPostgresConnectionConfig({ driver: 'mysql', host: 'db' })).toBe(false); + expect(isKtxPostgresConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe(true); + expect(isKtxPostgresConnectionConfig({ driver: 'postgresql', host: 'db', database: 'analytics' })).toBe(true); + expect(isKtxPostgresConnectionConfig({ driver: 'mysql', host: 'db' })).toBe(false); expect( postgresPoolConfigFromConfig({ connectionId: 'warehouse', @@ -138,7 +138,7 @@ describe('KloPostgresScanConnector', () => { }); it('introspects schemas, tables, views, primary keys, comments, row counts, and foreign keys', async () => { - const connector = new KloPostgresScanConnector({ + const connector = new KtxPostgresScanConnector({ connectionId: 'warehouse', connection: { driver: 'postgres', @@ -197,7 +197,7 @@ describe('KloPostgresScanConnector', () => { }); it('runs samples, distinct values, statistics, read-only SQL, and schema listing', async () => { - const connector = new KloPostgresScanConnector({ + const connector = new KtxPostgresScanConnector({ connectionId: 'warehouse', connection: { driver: 'postgres', @@ -298,7 +298,7 @@ describe('KloPostgresScanConnector', () => { it('does not end the pool before introspection completes', async () => { let endCalled = false; - const endAwarePoolFactory: KloPostgresPoolFactory = { + const endAwarePoolFactory: KtxPostgresPoolFactory = { createPool() { const inner = fakePoolFactory(metadataResults()).createPool({ max: 1, diff --git a/packages/connector-postgres/src/connector.ts b/packages/connector-postgres/src/connector.ts index 7a59f725..a780663f 100644 --- a/packages/connector-postgres/src/connector.ts +++ b/packages/connector-postgres/src/connector.ts @@ -1,28 +1,28 @@ import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaForeignKey, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaForeignKey, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; import { Pool } from 'pg'; -import { KloPostgresDialect } from './dialect.js'; +import { KtxPostgresDialect } from './dialect.js'; const PG_OID_TYPE_MAP: Record = { 16: 'boolean', @@ -45,7 +45,7 @@ const PG_OID_TYPE_MAP: Record = { 1016: 'bigint[]', }; -export interface KloPostgresConnectionConfig { +export interface KtxPostgresConnectionConfig { driver?: string; host?: string; port?: number; @@ -62,7 +62,7 @@ export interface KloPostgresConnectionConfig { [key: string]: unknown; } -export interface KloPostgresPoolConfig { +export interface KtxPostgresPoolConfig { host?: string; port?: number; database?: string; @@ -76,72 +76,72 @@ export interface KloPostgresPoolConfig { ssl?: { rejectUnauthorized: boolean }; } -interface KloPostgresQueryResult { +interface KtxPostgresQueryResult { fields?: Array<{ name: string; dataTypeID: number }>; rows: Record[]; } -interface KloPostgresClient { - query(sql: string, params?: unknown[]): Promise; +interface KtxPostgresClient { + query(sql: string, params?: unknown[]): Promise; release(): void; } -interface KloPostgresPool { - connect(): Promise; +interface KtxPostgresPool { + connect(): Promise; end(): Promise; } -export interface KloPostgresPoolFactory { - createPool(config: KloPostgresPoolConfig): KloPostgresPool; +export interface KtxPostgresPoolFactory { + createPool(config: KtxPostgresPoolConfig): KtxPostgresPool; } -interface KloPostgresResolvedEndpoint { +interface KtxPostgresResolvedEndpoint { host: string; port: number; close?: () => Promise; } -export interface KloPostgresEndpointResolver { +export interface KtxPostgresEndpointResolver { resolve(input: { host: string; port: number; - connection: KloPostgresConnectionConfig; - }): Promise; + connection: KtxPostgresConnectionConfig; + }): Promise; } -export interface KloPostgresScanConnectorOptions { +export interface KtxPostgresScanConnectorOptions { connectionId: string; - connection: KloPostgresConnectionConfig | undefined; - poolFactory?: KloPostgresPoolFactory; - endpointResolver?: KloPostgresEndpointResolver; + connection: KtxPostgresConnectionConfig | undefined; + poolFactory?: KtxPostgresPoolFactory; + endpointResolver?: KtxPostgresEndpointResolver; env?: NodeJS.ProcessEnv; now?: () => Date; } -export interface KloPostgresReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxPostgresReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record | unknown[]; } -export interface KloPostgresColumnDistinctValuesOptions { +export interface KtxPostgresColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloPostgresColumnDistinctValuesResult { +export interface KtxPostgresColumnDistinctValuesResult { values: string[] | null; cardinality: number; } -export interface KloPostgresColumnStatisticsResult { +export interface KtxPostgresColumnStatisticsResult { cardinalityByColumn: Map; } -export interface KloPostgresTableSampleResult extends KloTableSampleResult { +export interface KtxPostgresTableSampleResult extends KtxTableSampleResult { headerTypes?: string[]; } -type PostgresTableRef = Pick & Partial>; +type PostgresTableRef = Pick & Partial>; interface PostgresTableRow { table_name: string; @@ -190,8 +190,8 @@ interface PostgresStatsRow { estimated_cardinality: unknown; } -class DefaultPostgresPoolFactory implements KloPostgresPoolFactory { - createPool(config: KloPostgresPoolConfig): KloPostgresPool { +class DefaultPostgresPoolFactory implements KtxPostgresPoolFactory { + createPool(config: KtxPostgresPoolConfig): KtxPostgresPool { return new Pool(config); } } @@ -216,7 +216,7 @@ function primaryKeyMap(rows: PostgresPrimaryKeyRow[]): Map> return grouped; } -function queryRows(result: KloPostgresQueryResult): unknown[][] { +function queryRows(result: KtxPostgresQueryResult): unknown[][] { const headers = (result.fields ?? []).map((field) => field.name); return result.rows.map((row) => headers.map((header) => row[header])); } @@ -227,8 +227,8 @@ function finiteNumber(value: unknown): number | null { } function stringConfigValue( - connection: KloPostgresConnectionConfig | undefined, - key: keyof KloPostgresConnectionConfig, + connection: KtxPostgresConnectionConfig | undefined, + key: keyof KtxPostgresConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; @@ -251,7 +251,7 @@ function numberValue(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined; } -function parsePostgresUrl(url: string): Partial { +function parsePostgresUrl(url: string): Partial { const parsed = new URL(url); return { host: parsed.hostname, @@ -262,29 +262,29 @@ function parsePostgresUrl(url: string): Partial { }; } -function schemasFromConnection(connection: KloPostgresConnectionConfig): string[] { +function schemasFromConnection(connection: KtxPostgresConnectionConfig): string[] { if (Array.isArray(connection.schemas) && connection.schemas.length > 0) { return connection.schemas.filter((schema): schema is string => typeof schema === 'string' && schema.length > 0); } return typeof connection.schema === 'string' && connection.schema.length > 0 ? [connection.schema] : ['public']; } -function searchPathSchemasFromConnection(connection: KloPostgresConnectionConfig): string[] { +function searchPathSchemasFromConnection(connection: KtxPostgresConnectionConfig): string[] { const schemas = schemasFromConnection(connection); return schemas.includes('public') ? schemas : [...schemas, 'public']; } -export function isKloPostgresConnectionConfig(connection: KloPostgresConnectionConfig | undefined): boolean { +export function isKtxPostgresConnectionConfig(connection: KtxPostgresConnectionConfig | undefined): boolean { const driver = String(connection?.driver ?? '').toLowerCase(); return driver === 'postgres' || driver === 'postgresql'; } export function postgresPoolConfigFromConfig(input: { connectionId: string; - connection: KloPostgresConnectionConfig | undefined; + connection: KtxPostgresConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloPostgresPoolConfig { - if (!isKloPostgresConnectionConfig(input.connection)) { +}): KtxPostgresPoolConfig { + if (!isKtxPostgresConnectionConfig(input.connection)) { throw new Error(`Native PostgreSQL connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -294,7 +294,7 @@ export function postgresPoolConfigFromConfig(input: { const env = input.env ?? process.env; const referencedUrl = stringConfigValue(input.connection, 'url', env); const urlConfig = referencedUrl ? parsePostgresUrl(referencedUrl) : {}; - const merged: KloPostgresConnectionConfig = { ...urlConfig, ...input.connection }; + const merged: KtxPostgresConnectionConfig = { ...urlConfig, ...input.connection }; const host = stringConfigValue(merged, 'host', env); const database = stringConfigValue(merged, 'database', env); const user = stringConfigValue(merged, 'username', env) ?? stringConfigValue(merged, 'user', env); @@ -310,7 +310,7 @@ export function postgresPoolConfigFromConfig(input: { throw new Error(`Native PostgreSQL connector requires connections.${input.connectionId}.username, user, or url`); } - const config: KloPostgresPoolConfig = { + const config: KtxPostgresPoolConfig = { max: 10, idleTimeoutMillis: 30_000, connectionTimeoutMillis: 10_000, @@ -328,10 +328,10 @@ export function postgresPoolConfigFromConfig(input: { return config; } -export class KloPostgresScanConnector implements KloScanConnector { +export class KtxPostgresScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'postgres' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: true, @@ -342,16 +342,16 @@ export class KloPostgresScanConnector implements KloScanConnector { }); private readonly connectionId: string; - private readonly connection: KloPostgresConnectionConfig; - private readonly poolConfig: KloPostgresPoolConfig; - private readonly poolFactory: KloPostgresPoolFactory; - private readonly endpointResolver?: KloPostgresEndpointResolver; + private readonly connection: KtxPostgresConnectionConfig; + private readonly poolConfig: KtxPostgresPoolConfig; + private readonly poolFactory: KtxPostgresPoolFactory; + private readonly endpointResolver?: KtxPostgresEndpointResolver; private readonly now: () => Date; - private readonly dialect = new KloPostgresDialect(); - private pool: KloPostgresPool | null = null; - private resolvedEndpoint: KloPostgresResolvedEndpoint | null = null; + private readonly dialect = new KtxPostgresDialect(); + private pool: KtxPostgresPool | null = null; + private resolvedEndpoint: KtxPostgresResolvedEndpoint | null = null; - constructor(options: KloPostgresScanConnectorOptions) { + constructor(options: KtxPostgresScanConnectorOptions) { this.connectionId = options.connectionId; this.connection = options.connection ?? {}; this.poolConfig = postgresPoolConfigFromConfig({ @@ -374,10 +374,10 @@ export class KloPostgresScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const schemas = schemasFromConnection(this.connection); - const allTables: KloSchemaTable[] = []; + const allTables: KtxSchemaTable[] = []; for (const schema of schemas) { const tables = await this.loadSchemaTables(schema); allTables.push(...tables); @@ -398,7 +398,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns)); return { @@ -409,7 +409,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -418,7 +418,7 @@ export class KloPostgresScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { const stats = await this.getColumnStatistics(input.table); const value = stats?.cardinalityByColumn.get(input.column); return value === undefined @@ -426,7 +426,7 @@ export class KloPostgresScanConnector implements KloScanConnector { : { min: null, max: null, average: null, nullCount: null, distinctCount: value }; } - async executeReadOnly(input: KloPostgresReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxPostgresReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = Array.isArray(input.params) @@ -437,10 +437,10 @@ export class KloPostgresScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloPostgresColumnDistinctValuesOptions, - ): Promise { + options: KtxPostgresColumnDistinctValuesOptions, + ): Promise { const sampleSize = options.sampleSize ?? 10000; const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); @@ -466,7 +466,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - async getColumnStatistics(table: KloTableRef): Promise { + async getColumnStatistics(table: KtxTableRef): Promise { const schema = table.db ?? schemasFromConnection(this.connection)[0] ?? 'public'; const sql = this.dialect.generateColumnStatisticsQuery(schema, table.name); if (!sql) { @@ -522,7 +522,7 @@ export class KloPostgresScanConnector implements KloScanConnector { } } - private async loadSchemaTables(schema: string): Promise { + private async loadSchemaTables(schema: string): Promise { const tables = await this.queryRaw( ` SELECT @@ -617,7 +617,7 @@ export class KloPostgresScanConnector implements KloScanConnector { columns: PostgresColumnRow[], primaryKeys: Set, foreignKeys: PostgresForeignKeyRow[], - ): KloSchemaTable { + ): KtxSchemaTable { const kind = table.table_kind === 'v' ? 'view' : 'table'; return { catalog: null, @@ -631,7 +631,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - private toSchemaColumn(column: PostgresColumnRow, primaryKeys: Set): KloSchemaColumn { + private toSchemaColumn(column: PostgresColumnRow, primaryKeys: Set): KtxSchemaColumn { return { name: column.column_name, nativeType: column.data_type, @@ -643,7 +643,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - private toSchemaForeignKey(row: PostgresForeignKeyRow): KloSchemaForeignKey { + private toSchemaForeignKey(row: PostgresForeignKeyRow): KtxSchemaForeignKey { return { fromColumn: row.column_name, toCatalog: null, @@ -654,7 +654,7 @@ export class KloPostgresScanConnector implements KloScanConnector { }; } - private async getPool(): Promise { + private async getPool(): Promise { if (!this.pool) { let config = { ...this.poolConfig }; if (this.endpointResolver) { @@ -682,7 +682,7 @@ export class KloPostgresScanConnector implements KloScanConnector { } } - private async query(sql: string, params?: Record | unknown[]): Promise { + private async query(sql: string, params?: Record | unknown[]): Promise { const pool = await this.getPool(); const client = await pool.connect(); try { diff --git a/packages/connector-postgres/src/dialect.test.ts b/packages/connector-postgres/src/dialect.test.ts index 576170d7..ffe85497 100644 --- a/packages/connector-postgres/src/dialect.test.ts +++ b/packages/connector-postgres/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloPostgresDialect } from './dialect.js'; +import { KtxPostgresDialect } from './dialect.js'; -describe('KloPostgresDialect', () => { - const dialect = new KloPostgresDialect(); +describe('KtxPostgresDialect', () => { + const dialect = new KtxPostgresDialect(); it('quotes identifiers and formats schema-qualified tables', () => { expect(dialect.quoteIdentifier('order"items')).toBe('"order""items"'); @@ -10,7 +10,7 @@ describe('KloPostgresDialect', () => { expect(dialect.formatTableName({ catalog: null, db: null, name: 'orders' })).toBe('"orders"'); }); - it('maps native PostgreSQL types to KLO dimension types', () => { + it('maps native PostgreSQL types to KTX dimension types', () => { expect(dialect.mapToDimensionType('timestamp with time zone')).toBe('time'); expect(dialect.mapToDimensionType('numeric(12,2)')).toBe('number'); expect(dialect.mapToDimensionType('uuid')).toBe('string'); diff --git a/packages/connector-postgres/src/dialect.ts b/packages/connector-postgres/src/dialect.ts index 4b9cdba8..989d780c 100644 --- a/packages/connector-postgres/src/dialect.ts +++ b/packages/connector-postgres/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type PostgresTableNameRef = Pick & Partial>; +type PostgresTableNameRef = Pick & Partial>; -export class KloPostgresDialect { +export class KtxPostgresDialect { readonly type = 'postgresql'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { timestamp: 'time', 'timestamp without time zone': 'time', 'timestamp with time zone': 'time', @@ -54,7 +54,7 @@ export class KloPostgresDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-postgres/src/historic-sql-query-client.test.ts b/packages/connector-postgres/src/historic-sql-query-client.test.ts index 2423f52b..f02cb9c3 100644 --- a/packages/connector-postgres/src/historic-sql-query-client.test.ts +++ b/packages/connector-postgres/src/historic-sql-query-client.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it, vi } from 'vitest'; -import { KloPostgresHistoricSqlQueryClient } from './historic-sql-query-client.js'; -import type { KloPostgresPoolConfig, KloPostgresPoolFactory } from './connector.js'; +import { KtxPostgresHistoricSqlQueryClient } from './historic-sql-query-client.js'; +import type { KtxPostgresPoolConfig, KtxPostgresPoolFactory } from './connector.js'; -describe('KloPostgresHistoricSqlQueryClient', () => { +describe('KtxPostgresHistoricSqlQueryClient', () => { it('executes parameterized read-only SQL through the native Postgres connector pool', async () => { const queryCalls: Array<{ sql: string; params?: unknown[] }> = []; const release = vi.fn(); const end = vi.fn(async () => {}); - const poolFactory: KloPostgresPoolFactory = { - createPool(_config: KloPostgresPoolConfig) { + const poolFactory: KtxPostgresPoolFactory = { + createPool(_config: KtxPostgresPoolConfig) { return { async connect() { return { @@ -26,7 +26,7 @@ describe('KloPostgresHistoricSqlQueryClient', () => { }; }, }; - const client = new KloPostgresHistoricSqlQueryClient({ + const client = new KtxPostgresHistoricSqlQueryClient({ connectionId: 'warehouse', connection: { driver: 'postgres', diff --git a/packages/connector-postgres/src/historic-sql-query-client.ts b/packages/connector-postgres/src/historic-sql-query-client.ts index 363dc6a2..b0e620ed 100644 --- a/packages/connector-postgres/src/historic-sql-query-client.ts +++ b/packages/connector-postgres/src/historic-sql-query-client.ts @@ -1,15 +1,15 @@ -import type { KloPostgresQueryClient } from '@klo/context/ingest'; -import { KloPostgresScanConnector, type KloPostgresScanConnectorOptions } from './connector.js'; +import type { KtxPostgresQueryClient } from '@ktx/context/ingest'; +import { KtxPostgresScanConnector, type KtxPostgresScanConnectorOptions } from './connector.js'; -export type KloPostgresHistoricSqlQueryClientOptions = KloPostgresScanConnectorOptions; +export type KtxPostgresHistoricSqlQueryClientOptions = KtxPostgresScanConnectorOptions; -export class KloPostgresHistoricSqlQueryClient implements KloPostgresQueryClient { +export class KtxPostgresHistoricSqlQueryClient implements KtxPostgresQueryClient { private readonly connectionId: string; - private readonly connector: KloPostgresScanConnector; + private readonly connector: KtxPostgresScanConnector; - constructor(options: KloPostgresHistoricSqlQueryClientOptions) { + constructor(options: KtxPostgresHistoricSqlQueryClientOptions) { this.connectionId = options.connectionId; - this.connector = new KloPostgresScanConnector(options); + this.connector = new KtxPostgresScanConnector(options); } async executeQuery( diff --git a/packages/connector-postgres/src/index.ts b/packages/connector-postgres/src/index.ts index e90b0d96..b32e085d 100644 --- a/packages/connector-postgres/src/index.ts +++ b/packages/connector-postgres/src/index.ts @@ -1,21 +1,21 @@ -export { KloPostgresDialect } from './dialect.js'; +export { KtxPostgresDialect } from './dialect.js'; export { - isKloPostgresConnectionConfig, - KloPostgresScanConnector, + isKtxPostgresConnectionConfig, + KtxPostgresScanConnector, postgresPoolConfigFromConfig, - type KloPostgresColumnDistinctValuesOptions, - type KloPostgresColumnDistinctValuesResult, - type KloPostgresColumnStatisticsResult, - type KloPostgresConnectionConfig, - type KloPostgresEndpointResolver, - type KloPostgresPoolConfig, - type KloPostgresPoolFactory, - type KloPostgresReadOnlyQueryInput, - type KloPostgresScanConnectorOptions, - type KloPostgresTableSampleResult, + type KtxPostgresColumnDistinctValuesOptions, + type KtxPostgresColumnDistinctValuesResult, + type KtxPostgresColumnStatisticsResult, + type KtxPostgresConnectionConfig, + type KtxPostgresEndpointResolver, + type KtxPostgresPoolConfig, + type KtxPostgresPoolFactory, + type KtxPostgresReadOnlyQueryInput, + type KtxPostgresScanConnectorOptions, + type KtxPostgresTableSampleResult, } from './connector.js'; export { - KloPostgresHistoricSqlQueryClient, - type KloPostgresHistoricSqlQueryClientOptions, + KtxPostgresHistoricSqlQueryClient, + type KtxPostgresHistoricSqlQueryClientOptions, } from './historic-sql-query-client.js'; export { createPostgresLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-postgres/src/live-database-introspection.ts b/packages/connector-postgres/src/live-database-introspection.ts index 21da327a..ad025676 100644 --- a/packages/connector-postgres/src/live-database-introspection.ts +++ b/packages/connector-postgres/src/live-database-introspection.ts @@ -1,16 +1,16 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloPostgresScanConnector, - type KloPostgresConnectionConfig, - type KloPostgresEndpointResolver, - type KloPostgresPoolFactory, + KtxPostgresScanConnector, + type KtxPostgresConnectionConfig, + type KtxPostgresEndpointResolver, + type KtxPostgresPoolFactory, } from './connector.js'; interface CreatePostgresLiveDatabaseIntrospectionOptions { - connections: Record; - poolFactory?: KloPostgresPoolFactory; - endpointResolver?: KloPostgresEndpointResolver; + connections: Record; + poolFactory?: KtxPostgresPoolFactory; + endpointResolver?: KtxPostgresEndpointResolver; now?: () => Date; } @@ -19,8 +19,8 @@ export function createPostgresLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloPostgresConnectionConfig | undefined; - const connector = new KloPostgresScanConnector({ + const connection = options.connections[connectionId] as KtxPostgresConnectionConfig | undefined; + const connector = new KtxPostgresScanConnector({ connectionId, connection, poolFactory: options.poolFactory, diff --git a/packages/connector-postgres/src/package-exports.test.ts b/packages/connector-postgres/src/package-exports.test.ts index 66e86944..c216b736 100644 --- a/packages/connector-postgres/src/package-exports.test.ts +++ b/packages/connector-postgres/src/package-exports.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/connector-postgres package exports', () => { +describe('@ktx/connector-postgres package exports', () => { it('exports the connector, dialect, and live-database adapter', async () => { const connector = await import('./index.js'); - expect(connector.KloPostgresDialect).toBeTypeOf('function'); - expect(connector.KloPostgresScanConnector).toBeTypeOf('function'); - expect(connector.KloPostgresHistoricSqlQueryClient).toBeTypeOf('function'); + expect(connector.KtxPostgresDialect).toBeTypeOf('function'); + expect(connector.KtxPostgresScanConnector).toBeTypeOf('function'); + expect(connector.KtxPostgresHistoricSqlQueryClient).toBeTypeOf('function'); expect(connector.createPostgresLiveDatabaseIntrospection).toBeTypeOf('function'); - expect(connector.isKloPostgresConnectionConfig).toBeTypeOf('function'); + expect(connector.isKtxPostgresConnectionConfig).toBeTypeOf('function'); expect(connector.postgresPoolConfigFromConfig).toBeTypeOf('function'); }); }); diff --git a/packages/connector-posthog/package.json b/packages/connector-posthog/package.json index 39c1e3c3..da2de540 100644 --- a/packages/connector-posthog/package.json +++ b/packages/connector-posthog/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-posthog", + "name": "@ktx/connector-posthog", "version": "0.0.0-private", - "description": "PostHog connector package for KLO scan interfaces", + "description": "PostHog connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*" + "@ktx/context": "workspace:*" }, "devDependencies": { "@types/node": "^24.3.0", diff --git a/packages/connector-posthog/src/connector.test.ts b/packages/connector-posthog/src/connector.test.ts index d56a7925..69dc7223 100644 --- a/packages/connector-posthog/src/connector.test.ts +++ b/packages/connector-posthog/src/connector.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it, vi } from 'vitest'; import { createPostHogLiveDatabaseIntrospection, - isKloPostHogConnectionConfig, - KloPostHogScanConnector, + isKtxPostHogConnectionConfig, + KtxPostHogScanConnector, postHogConnectionConfigFromConfig, - type KloPostHogConnectionConfig, - type KloPostHogFetch, + type KtxPostHogConnectionConfig, + type KtxPostHogFetch, } from './index.js'; function jsonResponse(body: unknown, status = 200): Response { @@ -17,7 +17,7 @@ function jsonResponse(body: unknown, status = 200): Response { } as Response; } -function fakeFetch(queries: string[] = []): KloPostHogFetch { +function fakeFetch(queries: string[] = []): KtxPostHogFetch { return vi.fn(async (_url: string, init?: RequestInit) => { const body = JSON.parse(String(init?.body ?? '{}')) as { query?: { kind?: string; query?: string } }; const sql = body.query?.query ?? ''; @@ -160,13 +160,13 @@ function fakeFetch(queries: string[] = []): KloPostHogFetch { }); } return jsonResponse({ results: [['$pageview']], columns: ['event'], types: [['event', 'String']], error: null, hogql: sql }); - }) as KloPostHogFetch; + }) as KtxPostHogFetch; } const posthogApiKeyEnv = ['POSTHOG', 'API', 'KEY'].join('_'); const fixtureToken = ['phx', 'fixture'].join('_'); const env = { [posthogApiKeyEnv]: fixtureToken }; -const connection: KloPostHogConnectionConfig & { driver: string } = { +const connection: KtxPostHogConnectionConfig & { driver: string } = { driver: 'posthog', ['api_' + 'key']: `env:${posthogApiKeyEnv}`, project_id: '157881', @@ -174,10 +174,10 @@ const connection: KloPostHogConnectionConfig & { driver: string } = { readonly: true, }; -describe('KloPostHogScanConnector', () => { +describe('KtxPostHogScanConnector', () => { it('resolves configuration safely', () => { - expect(isKloPostHogConnectionConfig(connection)).toBe(true); - expect(isKloPostHogConnectionConfig({ driver: 'mysql' })).toBe(false); + expect(isKtxPostHogConnectionConfig(connection)).toBe(true); + expect(isKtxPostHogConnectionConfig({ driver: 'mysql' })).toBe(false); const resolved = postHogConnectionConfigFromConfig({ connectionId: 'product', connection, @@ -195,7 +195,7 @@ describe('KloPostHogScanConnector', () => { }); it('introspects schema metadata, hidden tables, descriptions, primary keys, and normalized types', async () => { - const connector = new KloPostHogScanConnector({ + const connector = new KtxPostHogScanConnector({ connectionId: 'product', connection, env, @@ -269,7 +269,7 @@ describe('KloPostHogScanConnector', () => { it('runs samples, read-only SQL, event-stream discovery, row counts, and cleanup', async () => { const queries: string[] = []; - const connector = new KloPostHogScanConnector({ + const connector = new KtxPostHogScanConnector({ connectionId: 'product', connection, env, diff --git a/packages/connector-posthog/src/connector.ts b/packages/connector-posthog/src/connector.ts index 06a0cad3..0ac2b37c 100644 --- a/packages/connector-posthog/src/connector.ts +++ b/packages/connector-posthog/src/connector.ts @@ -1,36 +1,36 @@ import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloEventPropertyDiscovery, - type KloEventPropertyDiscoveryInput, - type KloEventPropertyValuesInput, - type KloEventPropertyValuesResult, - type KloEventStreamDiscoveryPort, - type KloEventTypeDiscovery, - type KloEventTypeDiscoveryInput, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; -import { KloPostHogDialect, type KloPostHogSampleColumnInfo } from './dialect.js'; -import { getKloPostHogColumnDescription, getKloPostHogTableDescription } from './schema-descriptions.js'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxEventPropertyDiscovery, + type KtxEventPropertyDiscoveryInput, + type KtxEventPropertyValuesInput, + type KtxEventPropertyValuesResult, + type KtxEventStreamDiscoveryPort, + type KtxEventTypeDiscovery, + type KtxEventTypeDiscoveryInput, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; +import { KtxPostHogDialect, type KtxPostHogSampleColumnInfo } from './dialect.js'; +import { getKtxPostHogColumnDescription, getKtxPostHogTableDescription } from './schema-descriptions.js'; -export interface KloPostHogConnectionConfig { +export interface KtxPostHogConnectionConfig { driver?: string; api_key?: string; apiKey?: string; @@ -42,34 +42,34 @@ export interface KloPostHogConnectionConfig { [key: string]: unknown; } -export interface KloPostHogResolvedConnectionConfig { +export interface KtxPostHogResolvedConnectionConfig { apiKey: string; projectId: string; baseUrl: string; } -export type KloPostHogFetch = (url: string, init?: RequestInit) => Promise; +export type KtxPostHogFetch = (url: string, init?: RequestInit) => Promise; -export interface KloPostHogScanConnectorOptions { +export interface KtxPostHogScanConnectorOptions { connectionId: string; - connection: KloPostHogConnectionConfig | undefined; + connection: KtxPostHogConnectionConfig | undefined; env?: NodeJS.ProcessEnv; - fetch?: KloPostHogFetch; + fetch?: KtxPostHogFetch; sleep?: (ms: number) => Promise; now?: () => Date; } -export interface KloPostHogReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxPostHogReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record; } -export interface KloPostHogColumnDistinctValuesOptions { +export interface KtxPostHogColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloPostHogColumnDistinctValuesResult { +export interface KtxPostHogColumnDistinctValuesResult { values: string[] | null; cardinality: number; } @@ -119,7 +119,7 @@ const excludedTables = new Set([ ]); const hiddenTablesToProbe = ['person_distinct_ids', 'cohort_people', 'static_cohort_people']; -export function isKloPostHogConnectionConfig(connection: KloPostHogConnectionConfig | undefined): boolean { +export function isKtxPostHogConnectionConfig(connection: KtxPostHogConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'posthog'; } @@ -136,8 +136,8 @@ function resolveStringReference(value: string, env: NodeJS.ProcessEnv): string { } function stringConfigValue( - connection: KloPostHogConnectionConfig | undefined, - key: keyof KloPostHogConnectionConfig, + connection: KtxPostHogConnectionConfig | undefined, + key: keyof KtxPostHogConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; @@ -146,10 +146,10 @@ function stringConfigValue( export function postHogConnectionConfigFromConfig(input: { connectionId: string; - connection: KloPostHogConnectionConfig | undefined; + connection: KtxPostHogConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloPostHogResolvedConnectionConfig { - if (!isKloPostHogConnectionConfig(input.connection)) { +}): KtxPostHogResolvedConnectionConfig { + if (!isKtxPostHogConnectionConfig(input.connection)) { throw new Error(`Native PostHog connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -174,10 +174,10 @@ export function postHogConnectionConfigFromConfig(input: { }; } -export class KloPostHogScanConnector implements KloScanConnector { +export class KtxPostHogScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'posthog' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -188,20 +188,20 @@ export class KloPostHogScanConnector implements KloScanConnector { estimatedRowCounts: true, }); - readonly eventStreamDiscovery: KloEventStreamDiscoveryPort = { + readonly eventStreamDiscovery: KtxEventStreamDiscoveryPort = { listEventTypes: (input, ctx) => this.listEventTypes(input, ctx), listPropertyKeys: (input, ctx) => this.listPropertyKeys(input, ctx), listPropertyValues: (input, ctx) => this.listPropertyValues(input, ctx), }; private readonly connectionId: string; - private readonly resolved: KloPostHogResolvedConnectionConfig; - private readonly fetchImpl: KloPostHogFetch; + private readonly resolved: KtxPostHogResolvedConnectionConfig; + private readonly fetchImpl: KtxPostHogFetch; private readonly sleep: (ms: number) => Promise; private readonly now: () => Date; - private readonly dialect = new KloPostHogDialect(); + private readonly dialect = new KtxPostHogDialect(); - constructor(options: KloPostHogScanConnectorOptions) { + constructor(options: KtxPostHogScanConnectorOptions) { this.connectionId = options.connectionId; this.resolved = postHogConnectionConfigFromConfig({ connectionId: options.connectionId, @@ -219,10 +219,10 @@ export class KloPostHogScanConnector implements KloScanConnector { return response.error ? { success: false, error: response.error } : { success: true }; } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const response = await this.makeRequest('/query', { query: { kind: 'DatabaseSchemaQuery' } }); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const [tableName, tableInfo] of Object.entries(response.tables ?? {})) { if (!allowedTableTypes.has(tableInfo.type) || excludedTables.has(tableName)) { continue; @@ -246,9 +246,9 @@ export class KloPostHogScanConnector implements KloScanConnector { } async sampleTable( - input: KloTableSampleInput & { columnMetadata?: KloPostHogSampleColumnInfo[] }, - _ctx: KloScanContext, - ): Promise { + input: KtxTableSampleInput & { columnMetadata?: KtxPostHogSampleColumnInfo[] }, + _ctx: KtxScanContext, + ): Promise { this.assertConnection(input.connectionId); const sql = input.columnMetadata ? this.dialect.generateSampleQueryWithMetadata(this.qTableName(input.table), input.limit, input.columnMetadata) @@ -257,7 +257,7 @@ export class KloPostHogScanConnector implements KloScanConnector { return { headers: result.headers, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -266,11 +266,11 @@ export class KloPostHogScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloPostHogReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxPostHogReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = this.dialect.prepareQuery(limitedSql, input.params); @@ -284,10 +284,10 @@ export class KloPostHogScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloPostHogColumnDistinctValuesOptions, - ): Promise { + options: KtxPostHogColumnDistinctValuesOptions, + ): Promise { const sampleSize = options.sampleSize ?? 10000; const tableName = this.qTableName(table); const cardinalityResult = await this.query( @@ -317,9 +317,9 @@ export class KloPostHogScanConnector implements KloScanConnector { } private async listEventTypes( - input: KloEventTypeDiscoveryInput, - _ctx: KloScanContext, - ): Promise { + input: KtxEventTypeDiscoveryInput, + _ctx: KtxScanContext, + ): Promise { this.assertConnection(input.connectionId); const limit = this.positiveInteger(input.limit, 'limit'); const lookbackDays = this.positiveInteger(input.lookbackDays ?? 30, 'lookbackDays'); @@ -345,9 +345,9 @@ export class KloPostHogScanConnector implements KloScanConnector { } private async listPropertyKeys( - input: KloEventPropertyDiscoveryInput, - _ctx: KloScanContext, - ): Promise { + input: KtxEventPropertyDiscoveryInput, + _ctx: KtxScanContext, + ): Promise { this.assertConnection(input.connectionId); const sampleSize = this.positiveInteger(input.sampleSize, 'sampleSize'); const limit = this.positiveInteger(input.limit, 'limit'); @@ -374,9 +374,9 @@ export class KloPostHogScanConnector implements KloScanConnector { } private async listPropertyValues( - input: KloEventPropertyValuesInput, - _ctx: KloScanContext, - ): Promise { + input: KtxEventPropertyValuesInput, + _ctx: KtxScanContext, + ): Promise { this.assertConnection(input.connectionId); const limit = this.positiveInteger(input.limit, 'limit'); const maxCardinality = this.positiveInteger(input.maxCardinality ?? 1000, 'maxCardinality'); @@ -421,7 +421,7 @@ export class KloPostHogScanConnector implements KloScanConnector { async cleanup(): Promise {} - qTableName(table: Pick): string { + qTableName(table: Pick): string { return this.dialect.formatTableName(table); } @@ -429,21 +429,21 @@ export class KloPostHogScanConnector implements KloScanConnector { return this.dialect.quoteIdentifier(identifier); } - private toSchemaTable(tableName: string, tableInfo: PostHogSchemaTable): KloSchemaTable { + private toSchemaTable(tableName: string, tableInfo: PostHogSchemaTable): KtxSchemaTable { return { catalog: this.resolved.projectId, db: null, name: tableName, kind: tableName === 'events' ? 'event_stream' : 'table', - comment: getKloPostHogTableDescription(tableName) ?? null, + comment: getKtxPostHogTableDescription(tableName) ?? null, estimatedRows: tableInfo.row_count ?? null, columns: this.extractColumns(tableName, tableInfo.fields), foreignKeys: [], }; } - private async discoverHiddenTables(): Promise { - const tables: KloSchemaTable[] = []; + private async discoverHiddenTables(): Promise { + const tables: KtxSchemaTable[] = []; for (const tableName of hiddenTablesToProbe) { const result = await this.query(`SELECT * FROM ${tableName} LIMIT 0`); if (result.error) { @@ -454,7 +454,7 @@ export class KloPostHogScanConnector implements KloScanConnector { db: null, name: tableName, kind: 'table', - comment: getKloPostHogTableDescription(tableName) ?? null, + comment: getKtxPostHogTableDescription(tableName) ?? null, estimatedRows: null, columns: result.headers.map((header) => ({ name: header, @@ -463,7 +463,7 @@ export class KloPostHogScanConnector implements KloScanConnector { dimensionType: 'string', nullable: true, primaryKey: false, - comment: getKloPostHogColumnDescription(tableName, header) ?? null, + comment: getKtxPostHogColumnDescription(tableName, header) ?? null, })), foreignKeys: [], }); @@ -471,8 +471,8 @@ export class KloPostHogScanConnector implements KloScanConnector { return tables; } - private extractColumns(tableName: string, fields: Record): KloSchemaColumn[] { - const columns: KloSchemaColumn[] = []; + private extractColumns(tableName: string, fields: Record): KtxSchemaColumn[] { + const columns: KtxSchemaColumn[] = []; for (const [fieldName, fieldInfo] of Object.entries(fields)) { if ( fieldInfo.type === 'lazy_table' || @@ -490,7 +490,7 @@ export class KloPostHogScanConnector implements KloScanConnector { dimensionType: this.dialect.mapToDimensionType(nativeType), nullable: this.isNullableField(tableName, fieldName, fieldInfo.type), primaryKey: this.isPrimaryKeyField(tableName, fieldName), - comment: getKloPostHogColumnDescription(tableName, fieldName) ?? null, + comment: getKtxPostHogColumnDescription(tableName, fieldName) ?? null, }); } return columns; @@ -527,7 +527,7 @@ export class KloPostHogScanConnector implements KloScanConnector { ); } - private async query(sql: string, params?: Record): Promise { + private async query(sql: string, params?: Record): Promise { const response = await this.makeRequest('/query', { query: { kind: 'HogQLQuery', diff --git a/packages/connector-posthog/src/dialect.test.ts b/packages/connector-posthog/src/dialect.test.ts index 0aea3f7a..5c5b2c43 100644 --- a/packages/connector-posthog/src/dialect.test.ts +++ b/packages/connector-posthog/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloPostHogDialect } from './dialect.js'; +import { KtxPostHogDialect } from './dialect.js'; -describe('KloPostHogDialect', () => { - const dialect = new KloPostHogDialect(); +describe('KtxPostHogDialect', () => { + const dialect = new KtxPostHogDialect(); it('quotes identifiers, formats table names, maps types, and prepares HogQL params', () => { expect(dialect.quoteIdentifier('weird`name')).toBe('`weird\\`name`'); diff --git a/packages/connector-posthog/src/dialect.ts b/packages/connector-posthog/src/dialect.ts index af1786e7..36f6edee 100644 --- a/packages/connector-posthog/src/dialect.ts +++ b/packages/connector-posthog/src/dialect.ts @@ -1,16 +1,16 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type PostHogTableNameRef = Pick & Partial>; +type PostHogTableNameRef = Pick & Partial>; -export interface KloPostHogSampleColumnInfo { +export interface KtxPostHogSampleColumnInfo { name: string; parentColumnId: string | null; } -export class KloPostHogDialect { +export class KtxPostHogDialect { readonly type = 'posthog'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { datetime64: 'time', datetime: 'time', date: 'time', @@ -67,7 +67,7 @@ export class KloPostHogDialect { return typeMapping[cleanType] ?? cleanType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } @@ -93,7 +93,7 @@ export class KloPostHogDialect { return `SELECT ${columnList} FROM ${tableName} ORDER BY rand() LIMIT ${limit}`; } - generateSampleQueryWithMetadata(tableName: string, limit: number, columnMetadata?: KloPostHogSampleColumnInfo[]): string { + generateSampleQueryWithMetadata(tableName: string, limit: number, columnMetadata?: KtxPostHogSampleColumnInfo[]): string { if (!columnMetadata || columnMetadata.length === 0) { return this.generateSampleQuery(tableName, limit); } diff --git a/packages/connector-posthog/src/index.ts b/packages/connector-posthog/src/index.ts index e6aeed4e..7fa61ebb 100644 --- a/packages/connector-posthog/src/index.ts +++ b/packages/connector-posthog/src/index.ts @@ -1,19 +1,19 @@ -export { KloPostHogDialect, type KloPostHogSampleColumnInfo } from './dialect.js'; +export { KtxPostHogDialect, type KtxPostHogSampleColumnInfo } from './dialect.js'; export { - getKloPostHogColumnDescription, - getKloPostHogPropertyDescription, - getKloPostHogTableDescription, + getKtxPostHogColumnDescription, + getKtxPostHogPropertyDescription, + getKtxPostHogTableDescription, } from './schema-descriptions.js'; export { - isKloPostHogConnectionConfig, - KloPostHogScanConnector, + isKtxPostHogConnectionConfig, + KtxPostHogScanConnector, postHogConnectionConfigFromConfig, - type KloPostHogColumnDistinctValuesOptions, - type KloPostHogColumnDistinctValuesResult, - type KloPostHogConnectionConfig, - type KloPostHogFetch, - type KloPostHogReadOnlyQueryInput, - type KloPostHogResolvedConnectionConfig, - type KloPostHogScanConnectorOptions, + type KtxPostHogColumnDistinctValuesOptions, + type KtxPostHogColumnDistinctValuesResult, + type KtxPostHogConnectionConfig, + type KtxPostHogFetch, + type KtxPostHogReadOnlyQueryInput, + type KtxPostHogResolvedConnectionConfig, + type KtxPostHogScanConnectorOptions, } from './connector.js'; export { createPostHogLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-posthog/src/live-database-introspection.ts b/packages/connector-posthog/src/live-database-introspection.ts index 6baf0b81..04828a19 100644 --- a/packages/connector-posthog/src/live-database-introspection.ts +++ b/packages/connector-posthog/src/live-database-introspection.ts @@ -1,11 +1,11 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; -import { KloPostHogScanConnector, type KloPostHogConnectionConfig, type KloPostHogFetch } from './connector.js'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; +import { KtxPostHogScanConnector, type KtxPostHogConnectionConfig, type KtxPostHogFetch } from './connector.js'; interface CreatePostHogLiveDatabaseIntrospectionOptions { - connections: Record; + connections: Record; env?: NodeJS.ProcessEnv; - fetch?: KloPostHogFetch; + fetch?: KtxPostHogFetch; sleep?: (ms: number) => Promise; now?: () => Date; } @@ -15,8 +15,8 @@ export function createPostHogLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloPostHogConnectionConfig | undefined; - const connector = new KloPostHogScanConnector({ + const connection = options.connections[connectionId] as KtxPostHogConnectionConfig | undefined; + const connector = new KtxPostHogScanConnector({ connectionId, connection, env: options.env, diff --git a/packages/connector-posthog/src/package-exports.test.ts b/packages/connector-posthog/src/package-exports.test.ts index 006dfb25..f9d822ae 100644 --- a/packages/connector-posthog/src/package-exports.test.ts +++ b/packages/connector-posthog/src/package-exports.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; import * as posthog from './index.js'; -describe('@klo/connector-posthog package exports', () => { +describe('@ktx/connector-posthog package exports', () => { it('exports the connector, dialect, descriptions, and live-database adapter', () => { - expect(posthog.KloPostHogDialect).toBeTypeOf('function'); - expect(posthog.KloPostHogScanConnector).toBeTypeOf('function'); + expect(posthog.KtxPostHogDialect).toBeTypeOf('function'); + expect(posthog.KtxPostHogScanConnector).toBeTypeOf('function'); expect(posthog.createPostHogLiveDatabaseIntrospection).toBeTypeOf('function'); - expect(posthog.getKloPostHogPropertyDescription('$browser')).toBe('User browser name.'); + expect(posthog.getKtxPostHogPropertyDescription('$browser')).toBe('User browser name.'); }); }); diff --git a/packages/connector-posthog/src/schema-descriptions.ts b/packages/connector-posthog/src/schema-descriptions.ts index 6a15ad65..d333fcb4 100644 --- a/packages/connector-posthog/src/schema-descriptions.ts +++ b/packages/connector-posthog/src/schema-descriptions.ts @@ -86,14 +86,14 @@ const PROPERTY_DESCRIPTIONS: Record = { $feature_flag_response: 'Feature flag value or variant.', }; -export function getKloPostHogTableDescription(tableName: string): string | undefined { +export function getKtxPostHogTableDescription(tableName: string): string | undefined { return TABLE_DESCRIPTIONS[tableName]; } -export function getKloPostHogColumnDescription(tableName: string, columnName: string): string | undefined { +export function getKtxPostHogColumnDescription(tableName: string, columnName: string): string | undefined { return COLUMN_DESCRIPTIONS[`${tableName}.${columnName}`]; } -export function getKloPostHogPropertyDescription(propertyKey: string): string | null { +export function getKtxPostHogPropertyDescription(propertyKey: string): string | null { return PROPERTY_DESCRIPTIONS[propertyKey] ?? null; } diff --git a/packages/connector-snowflake/package.json b/packages/connector-snowflake/package.json index 5003d56c..bec2e1dc 100644 --- a/packages/connector-snowflake/package.json +++ b/packages/connector-snowflake/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-snowflake", + "name": "@ktx/connector-snowflake", "version": "0.0.0-private", - "description": "Snowflake connector package for KLO scan interfaces", + "description": "Snowflake connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*", + "@ktx/context": "workspace:*", "snowflake-sdk": "^2.3.4" }, "devDependencies": { diff --git a/packages/connector-snowflake/src/connector.test.ts b/packages/connector-snowflake/src/connector.test.ts index 62bd6944..282b32bc 100644 --- a/packages/connector-snowflake/src/connector.test.ts +++ b/packages/connector-snowflake/src/connector.test.ts @@ -1,15 +1,15 @@ import { describe, expect, it, vi } from 'vitest'; import { createSnowflakeLiveDatabaseIntrospection, - isKloSnowflakeConnectionConfig, - KloSnowflakeScanConnector, + isKtxSnowflakeConnectionConfig, + KtxSnowflakeScanConnector, snowflakeConnectionConfigFromConfig, - type KloSnowflakeDriver, - type KloSnowflakeDriverFactory, + type KtxSnowflakeDriver, + type KtxSnowflakeDriverFactory, } from './index.js'; -function fakeDriverFactory(): KloSnowflakeDriverFactory { - const driver: KloSnowflakeDriver = { +function fakeDriverFactory(): KtxSnowflakeDriverFactory { + const driver: KtxSnowflakeDriver = { test: vi.fn(async () => ({ success: true })), query: vi.fn(async (sql: string) => { if (sql.includes('TABLE_CONSTRAINTS')) { @@ -24,7 +24,7 @@ function fakeDriverFactory(): KloSnowflakeDriverFactory { rowCount: 1, }; } - if (sql.includes('select * from (select ID, STATUS from ORDERS) as klo_query_result limit 1')) { + if (sql.includes('select * from (select ID, STATUS from ORDERS) as ktx_query_result limit 1')) { return { headers: ['ID', 'STATUS'], rows: [[1, 'paid']], totalRows: 1, rowCount: 1 }; } if (sql.includes('SELECT "STATUS" FROM "ANALYTICS"."PUBLIC"."ORDERS"')) { @@ -65,10 +65,10 @@ function fakeDriverFactory(): KloSnowflakeDriverFactory { return { createDriver: vi.fn(() => driver) }; } -describe('KloSnowflakeScanConnector', () => { +describe('KtxSnowflakeScanConnector', () => { it('resolves Snowflake connection configuration safely', () => { expect( - isKloSnowflakeConnectionConfig({ + isKtxSnowflakeConnectionConfig({ driver: 'snowflake', account: 'acct', warehouse: 'WH', @@ -77,7 +77,7 @@ describe('KloSnowflakeScanConnector', () => { readonly: true, }), ).toBe(true); - expect(isKloSnowflakeConnectionConfig({ driver: 'bigquery' })).toBe(false); + expect(isKtxSnowflakeConnectionConfig({ driver: 'bigquery' })).toBe(false); expect( snowflakeConnectionConfigFromConfig({ connectionId: 'warehouse', @@ -110,7 +110,7 @@ describe('KloSnowflakeScanConnector', () => { }); it('introspects schema, primary keys, comments, row counts, and dimensions', async () => { - const connector = new KloSnowflakeScanConnector({ + const connector = new KtxSnowflakeScanConnector({ connectionId: 'warehouse', connection: { driver: 'snowflake', @@ -170,7 +170,7 @@ describe('KloSnowflakeScanConnector', () => { it('supports read-only query, sampling, distinct values, row counts, schema listing, and cleanup', async () => { const driverFactory = fakeDriverFactory(); - const connector = new KloSnowflakeScanConnector({ + const connector = new KtxSnowflakeScanConnector({ connectionId: 'warehouse', connection: { driver: 'snowflake', @@ -223,7 +223,7 @@ describe('KloSnowflakeScanConnector', () => { await expect(connector.getTableRowCount('ORDERS')).resolves.toBe(12); await expect(connector.listSchemas()).resolves.toEqual(['PUBLIC', 'MART']); await connector.cleanup(); - const driver = (driverFactory.createDriver as ReturnType).mock.results[0]?.value as KloSnowflakeDriver; + const driver = (driverFactory.createDriver as ReturnType).mock.results[0]?.value as KtxSnowflakeDriver; expect(driver.cleanup).toHaveBeenCalledTimes(1); }); diff --git a/packages/connector-snowflake/src/connector.ts b/packages/connector-snowflake/src/connector.ts index 95b68ebe..31dd18a0 100644 --- a/packages/connector-snowflake/src/connector.ts +++ b/packages/connector-snowflake/src/connector.ts @@ -2,29 +2,29 @@ import { createPrivateKey } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -import { assertReadOnlySql, limitSqlForExecution } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; import * as snowflake from 'snowflake-sdk'; -import { KloSnowflakeDialect } from './dialect.js'; +import { KtxSnowflakeDialect } from './dialect.js'; -export interface KloSnowflakeConnectionConfig { +export interface KtxSnowflakeConnectionConfig { driver?: string; authMethod?: 'password' | 'rsa'; account?: string; @@ -41,7 +41,7 @@ export interface KloSnowflakeConnectionConfig { [key: string]: unknown; } -export interface KloSnowflakeResolvedConnectionConfig { +export interface KtxSnowflakeResolvedConnectionConfig { authMethod: 'password' | 'rsa'; account: string; warehouse: string; @@ -54,64 +54,64 @@ export interface KloSnowflakeResolvedConnectionConfig { role?: string; } -export interface KloSnowflakeRawColumnMetadata { +export interface KtxSnowflakeRawColumnMetadata { name: string; type: string; nullable: boolean; comment: string | null; } -export interface KloSnowflakeRawTableMetadata { +export interface KtxSnowflakeRawTableMetadata { name: string; catalog: string; db: string; rowCount: number | null; comment: string | null; - columns: KloSnowflakeRawColumnMetadata[]; + columns: KtxSnowflakeRawColumnMetadata[]; } -export interface KloSnowflakeDriver { +export interface KtxSnowflakeDriver { test(): Promise<{ success: boolean; error?: string }>; - query(sql: string, params?: unknown): Promise; - getSchemaMetadata(schemaName?: string): Promise; + query(sql: string, params?: unknown): Promise; + getSchemaMetadata(schemaName?: string): Promise; listSchemas(): Promise; cleanup(): Promise; } -export interface KloSnowflakeDriverFactory { +export interface KtxSnowflakeDriverFactory { createDriver(input: { - resolved: KloSnowflakeResolvedConnectionConfig; - sdkOptionsProvider?: KloSnowflakeSdkOptionsProvider; - }): KloSnowflakeDriver; + resolved: KtxSnowflakeResolvedConnectionConfig; + sdkOptionsProvider?: KtxSnowflakeSdkOptionsProvider; + }): KtxSnowflakeDriver; } -export interface KloSnowflakeSdkOptionsProvider { +export interface KtxSnowflakeSdkOptionsProvider { resolve(input: { account: string; - connection: KloSnowflakeConnectionConfig; + connection: KtxSnowflakeConnectionConfig; }): Promise<{ sdkOptions: Record; close?: () => Promise } | undefined>; } -export interface KloSnowflakeScanConnectorOptions { +export interface KtxSnowflakeScanConnectorOptions { connectionId: string; - connection: KloSnowflakeConnectionConfig | undefined; - driverFactory?: KloSnowflakeDriverFactory; - sdkOptionsProvider?: KloSnowflakeSdkOptionsProvider; + connection: KtxSnowflakeConnectionConfig | undefined; + driverFactory?: KtxSnowflakeDriverFactory; + sdkOptionsProvider?: KtxSnowflakeSdkOptionsProvider; env?: NodeJS.ProcessEnv; now?: () => Date; } -export interface KloSnowflakeReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxSnowflakeReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record; } -export interface KloSnowflakeColumnDistinctValuesOptions { +export interface KtxSnowflakeColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloSnowflakeColumnDistinctValuesResult { +export interface KtxSnowflakeColumnDistinctValuesResult { values: string[] | null; cardinality: number; } @@ -131,15 +131,15 @@ function resolveStringReference(value: string, env: NodeJS.ProcessEnv): string { } function stringConfigValue( - connection: KloSnowflakeConnectionConfig | undefined, - key: keyof KloSnowflakeConnectionConfig, + connection: KtxSnowflakeConnectionConfig | undefined, + key: keyof KtxSnowflakeConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(value.trim(), env) : undefined; } -function schemaNames(connection: KloSnowflakeConnectionConfig, env: NodeJS.ProcessEnv): string[] { +function schemaNames(connection: KtxSnowflakeConnectionConfig, env: NodeJS.ProcessEnv): string[] { if (Array.isArray(connection.schema_names) && connection.schema_names.length > 0) { return connection.schema_names .filter((schema) => schema.trim().length > 0) @@ -189,16 +189,16 @@ function toSnowflakeBinds(params: unknown[] | undefined): snowflake.Binds | unde return params?.map((value) => toSnowflakeBind(value)); } -export function isKloSnowflakeConnectionConfig(connection: KloSnowflakeConnectionConfig | undefined): boolean { +export function isKtxSnowflakeConnectionConfig(connection: KtxSnowflakeConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'snowflake'; } export function snowflakeConnectionConfigFromConfig(input: { connectionId: string; - connection: KloSnowflakeConnectionConfig | undefined; + connection: KtxSnowflakeConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloSnowflakeResolvedConnectionConfig { - if (!isKloSnowflakeConnectionConfig(input.connection)) { +}): KtxSnowflakeResolvedConnectionConfig { + if (!isKtxSnowflakeConnectionConfig(input.connection)) { throw new Error(`Native Snowflake connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -222,7 +222,7 @@ export function snowflakeConnectionConfigFromConfig(input: { if (!username) { throw new Error(`Native Snowflake connector requires connections.${input.connectionId}.username`); } - const resolved: KloSnowflakeResolvedConnectionConfig = { + const resolved: KtxSnowflakeResolvedConnectionConfig = { authMethod, account, warehouse, @@ -252,21 +252,21 @@ export function snowflakeConnectionConfigFromConfig(input: { return resolved; } -class DefaultSnowflakeDriverFactory implements KloSnowflakeDriverFactory { +class DefaultSnowflakeDriverFactory implements KtxSnowflakeDriverFactory { createDriver(input: { - resolved: KloSnowflakeResolvedConnectionConfig; - sdkOptionsProvider?: KloSnowflakeSdkOptionsProvider; - }): KloSnowflakeDriver { + resolved: KtxSnowflakeResolvedConnectionConfig; + sdkOptionsProvider?: KtxSnowflakeSdkOptionsProvider; + }): KtxSnowflakeDriver { return new SnowflakeSdkDriver(input.resolved, input.sdkOptionsProvider); } } -class SnowflakeSdkDriver implements KloSnowflakeDriver { +class SnowflakeSdkDriver implements KtxSnowflakeDriver { private closeSdkOptions: Array<() => Promise> = []; constructor( - private readonly resolved: KloSnowflakeResolvedConnectionConfig, - private readonly sdkOptionsProvider?: KloSnowflakeSdkOptionsProvider, + private readonly resolved: KtxSnowflakeResolvedConnectionConfig, + private readonly sdkOptionsProvider?: KtxSnowflakeSdkOptionsProvider, ) {} async test(): Promise<{ success: boolean; error?: string }> { @@ -282,7 +282,7 @@ class SnowflakeSdkDriver implements KloSnowflakeDriver { ]); } - async query(sql: string, params?: unknown): Promise { + async query(sql: string, params?: unknown): Promise { let connection: snowflake.Connection | null = null; try { connection = await this.createConnection(); @@ -298,7 +298,7 @@ class SnowflakeSdkDriver implements KloSnowflakeDriver { } } - async getSchemaMetadata(schemaName = this.resolved.schemas[0] ?? 'PUBLIC'): Promise { + async getSchemaMetadata(schemaName = this.resolved.schemas[0] ?? 'PUBLIC'): Promise { const tablesResult = await this.query( ` SELECT TABLE_NAME, TABLE_TYPE, COMMENT, ROW_COUNT @@ -317,7 +317,7 @@ class SnowflakeSdkDriver implements KloSnowflakeDriver { `, [schemaName, this.resolved.database], ); - const columnsByTable = new Map(); + const columnsByTable = new Map(); for (const row of columnsResult.rows) { const tableName = String(row[0]); const columns = columnsByTable.get(tableName) ?? []; @@ -465,10 +465,10 @@ class SnowflakeSdkDriver implements KloSnowflakeDriver { } } -export class KloSnowflakeScanConnector implements KloScanConnector { +export class KtxSnowflakeScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'snowflake' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -478,13 +478,13 @@ export class KloSnowflakeScanConnector implements KloScanConnector { estimatedRowCounts: true, }); - private readonly resolved: KloSnowflakeResolvedConnectionConfig; - private readonly driverFactory: KloSnowflakeDriverFactory; - private readonly dialect = new KloSnowflakeDialect(); + private readonly resolved: KtxSnowflakeResolvedConnectionConfig; + private readonly driverFactory: KtxSnowflakeDriverFactory; + private readonly dialect = new KtxSnowflakeDialect(); private readonly now: () => Date; - private driverInstance: KloSnowflakeDriver | null = null; + private driverInstance: KtxSnowflakeDriver | null = null; - constructor(private readonly options: KloSnowflakeScanConnectorOptions) { + constructor(private readonly options: KtxSnowflakeScanConnectorOptions) { this.resolved = snowflakeConnectionConfigFromConfig(options); this.driverFactory = options.driverFactory ?? new DefaultSnowflakeDriverFactory(); this.now = options.now ?? (() => new Date()); @@ -495,9 +495,9 @@ export class KloSnowflakeScanConnector implements KloScanConnector { return this.getDriver().test(); } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const schemaName of this.resolved.schemas) { const rawTables = await this.getDriver().getSchemaMetadata(schemaName); const primaryKeys = await this.primaryKeys(rawTables.map((table) => table.name), schemaName); @@ -520,7 +520,7 @@ export class KloSnowflakeScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.getDriver().query( this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns), @@ -528,7 +528,7 @@ export class KloSnowflakeScanConnector implements KloScanConnector { return { headers: result.headers, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.getDriver().query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -540,11 +540,11 @@ export class KloSnowflakeScanConnector implements KloScanConnector { }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloSnowflakeReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxSnowflakeReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForExecution(assertReadOnlySql(input.sql), input.maxRows); const prepared = this.dialect.prepareQuery(limitedSql, input.params); @@ -552,10 +552,10 @@ export class KloSnowflakeScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloSnowflakeColumnDistinctValuesOptions, - ): Promise { + options: KtxSnowflakeColumnDistinctValuesOptions, + ): Promise { const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); const cardinality = await this.singleNumber( @@ -582,7 +582,7 @@ export class KloSnowflakeScanConnector implements KloScanConnector { return tables.find((table) => table.name === tableName)?.rowCount ?? 0; } - qTableName(table: Pick & Partial>): string { + qTableName(table: Pick & Partial>): string { return this.dialect.formatTableName(table); } @@ -601,7 +601,7 @@ export class KloSnowflakeScanConnector implements KloScanConnector { } } - private getDriver(): KloSnowflakeDriver { + private getDriver(): KtxSnowflakeDriver { if (!this.driverInstance) { this.driverInstance = this.driverFactory.createDriver({ resolved: this.resolved, @@ -642,7 +642,7 @@ export class KloSnowflakeScanConnector implements KloScanConnector { return grouped; } - private toSchemaTable(table: KloSnowflakeRawTableMetadata, primaryKeys: Map>): KloSchemaTable { + private toSchemaTable(table: KtxSnowflakeRawTableMetadata, primaryKeys: Map>): KtxSchemaTable { return { catalog: table.catalog, db: table.db, @@ -657,9 +657,9 @@ export class KloSnowflakeScanConnector implements KloScanConnector { private toSchemaColumn( tableName: string, - column: KloSnowflakeRawColumnMetadata, + column: KtxSnowflakeRawColumnMetadata, primaryKeys: Map>, - ): KloSchemaColumn { + ): KtxSchemaColumn { return { name: column.name, nativeType: column.type, diff --git a/packages/connector-snowflake/src/dialect.test.ts b/packages/connector-snowflake/src/dialect.test.ts index 88168409..991a30b5 100644 --- a/packages/connector-snowflake/src/dialect.test.ts +++ b/packages/connector-snowflake/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloSnowflakeDialect } from './dialect.js'; +import { KtxSnowflakeDialect } from './dialect.js'; -describe('KloSnowflakeDialect', () => { - const dialect = new KloSnowflakeDialect(); +describe('KtxSnowflakeDialect', () => { + const dialect = new KtxSnowflakeDialect(); it('quotes identifiers and formats database.schema.table names', () => { expect(dialect.quoteIdentifier('order"items')).toBe('"order""items"'); diff --git a/packages/connector-snowflake/src/dialect.ts b/packages/connector-snowflake/src/dialect.ts index 96a52b36..b105a49d 100644 --- a/packages/connector-snowflake/src/dialect.ts +++ b/packages/connector-snowflake/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type SnowflakeTableNameRef = Pick & Partial>; +type SnowflakeTableNameRef = Pick & Partial>; -export class KloSnowflakeDialect { +export class KtxSnowflakeDialect { readonly type = 'snowflake'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { TIMESTAMP_NTZ: 'time', TIMESTAMP_LTZ: 'time', TIMESTAMP_TZ: 'time', @@ -58,7 +58,7 @@ export class KloSnowflakeDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-snowflake/src/index.ts b/packages/connector-snowflake/src/index.ts index 68534792..ea96b0cb 100644 --- a/packages/connector-snowflake/src/index.ts +++ b/packages/connector-snowflake/src/index.ts @@ -1,18 +1,18 @@ -export { KloSnowflakeDialect } from './dialect.js'; +export { KtxSnowflakeDialect } from './dialect.js'; export { - isKloSnowflakeConnectionConfig, - KloSnowflakeScanConnector, + isKtxSnowflakeConnectionConfig, + KtxSnowflakeScanConnector, snowflakeConnectionConfigFromConfig, - type KloSnowflakeColumnDistinctValuesOptions, - type KloSnowflakeColumnDistinctValuesResult, - type KloSnowflakeConnectionConfig, - type KloSnowflakeDriver, - type KloSnowflakeDriverFactory, - type KloSnowflakeRawColumnMetadata, - type KloSnowflakeRawTableMetadata, - type KloSnowflakeReadOnlyQueryInput, - type KloSnowflakeResolvedConnectionConfig, - type KloSnowflakeScanConnectorOptions, - type KloSnowflakeSdkOptionsProvider, + type KtxSnowflakeColumnDistinctValuesOptions, + type KtxSnowflakeColumnDistinctValuesResult, + type KtxSnowflakeConnectionConfig, + type KtxSnowflakeDriver, + type KtxSnowflakeDriverFactory, + type KtxSnowflakeRawColumnMetadata, + type KtxSnowflakeRawTableMetadata, + type KtxSnowflakeReadOnlyQueryInput, + type KtxSnowflakeResolvedConnectionConfig, + type KtxSnowflakeScanConnectorOptions, + type KtxSnowflakeSdkOptionsProvider, } from './connector.js'; export { createSnowflakeLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-snowflake/src/live-database-introspection.ts b/packages/connector-snowflake/src/live-database-introspection.ts index 03ca2aaf..419fa5e4 100644 --- a/packages/connector-snowflake/src/live-database-introspection.ts +++ b/packages/connector-snowflake/src/live-database-introspection.ts @@ -1,16 +1,16 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloSnowflakeScanConnector, - type KloSnowflakeConnectionConfig, - type KloSnowflakeDriverFactory, - type KloSnowflakeSdkOptionsProvider, + KtxSnowflakeScanConnector, + type KtxSnowflakeConnectionConfig, + type KtxSnowflakeDriverFactory, + type KtxSnowflakeSdkOptionsProvider, } from './connector.js'; interface CreateSnowflakeLiveDatabaseIntrospectionOptions { - connections: Record; - driverFactory?: KloSnowflakeDriverFactory; - sdkOptionsProvider?: KloSnowflakeSdkOptionsProvider; + connections: Record; + driverFactory?: KtxSnowflakeDriverFactory; + sdkOptionsProvider?: KtxSnowflakeSdkOptionsProvider; now?: () => Date; } @@ -19,8 +19,8 @@ export function createSnowflakeLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloSnowflakeConnectionConfig | undefined; - const connector = new KloSnowflakeScanConnector({ + const connection = options.connections[connectionId] as KtxSnowflakeConnectionConfig | undefined; + const connector = new KtxSnowflakeScanConnector({ connectionId, connection, driverFactory: options.driverFactory, diff --git a/packages/connector-snowflake/src/package-exports.test.ts b/packages/connector-snowflake/src/package-exports.test.ts index 5653a1fc..985068bf 100644 --- a/packages/connector-snowflake/src/package-exports.test.ts +++ b/packages/connector-snowflake/src/package-exports.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; import * as connector from './index.js'; -describe('@klo/connector-snowflake package exports', () => { +describe('@ktx/connector-snowflake package exports', () => { it('exports public connector, dialect, and introspection APIs', () => { - expect(connector.KloSnowflakeDialect).toBeTypeOf('function'); - expect(connector.KloSnowflakeScanConnector).toBeTypeOf('function'); + expect(connector.KtxSnowflakeDialect).toBeTypeOf('function'); + expect(connector.KtxSnowflakeScanConnector).toBeTypeOf('function'); expect(connector.snowflakeConnectionConfigFromConfig).toBeTypeOf('function'); expect(connector.createSnowflakeLiveDatabaseIntrospection).toBeTypeOf('function'); }); diff --git a/packages/connector-sqlite/package.json b/packages/connector-sqlite/package.json index dcb98155..7ec39625 100644 --- a/packages/connector-sqlite/package.json +++ b/packages/connector-sqlite/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-sqlite", + "name": "@ktx/connector-sqlite", "version": "0.0.0-private", - "description": "SQLite connector package for KLO scan interfaces", + "description": "SQLite connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*", + "@ktx/context": "workspace:*", "better-sqlite3": "^12.6.2" }, "devDependencies": { diff --git a/packages/connector-sqlite/src/connector.test.ts b/packages/connector-sqlite/src/connector.test.ts index d19ee577..4bc26ec9 100644 --- a/packages/connector-sqlite/src/connector.test.ts +++ b/packages/connector-sqlite/src/connector.test.ts @@ -6,17 +6,17 @@ import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createSqliteLiveDatabaseIntrospection, - isKloSqliteConnectionConfig, - KloSqliteScanConnector, + isKtxSqliteConnectionConfig, + KtxSqliteScanConnector, sqliteDatabasePathFromConfig, } from './index.js'; -describe('KloSqliteScanConnector', () => { +describe('KtxSqliteScanConnector', () => { let tempDir: string; let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-connector-sqlite-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-connector-sqlite-')); dbPath = join(tempDir, 'warehouse.db'); const db = new Database(dbPath); db.exec(` @@ -47,14 +47,14 @@ describe('KloSqliteScanConnector', () => { }); it('resolves SQLite path configuration safely', () => { - const originalDatabaseUrl = process.env.KLO_SQLITE_TEST_URL; + const originalDatabaseUrl = process.env.KTX_SQLITE_TEST_URL; const pointerPath = join(tempDir, 'sqlite-path.txt'); - process.env.KLO_SQLITE_TEST_URL = `sqlite:${dbPath}`; + process.env.KTX_SQLITE_TEST_URL = `sqlite:${dbPath}`; writeFileSync(pointerPath, dbPath, 'utf-8'); try { - expect(isKloSqliteConnectionConfig({ driver: 'sqlite', path: 'warehouse.db', readonly: true })).toBe(true); - expect(isKloSqliteConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe( + expect(isKtxSqliteConnectionConfig({ driver: 'sqlite', path: 'warehouse.db', readonly: true })).toBe(true); + expect(isKtxSqliteConnectionConfig({ driver: 'postgres', url: 'env:DATABASE_URL', readonly: true })).toBe( false, ); expect( @@ -68,7 +68,7 @@ describe('KloSqliteScanConnector', () => { sqliteDatabasePathFromConfig({ connectionId: 'warehouse', projectDir: tempDir, - connection: { driver: 'sqlite', url: 'env:KLO_SQLITE_TEST_URL', readonly: true }, + connection: { driver: 'sqlite', url: 'env:KTX_SQLITE_TEST_URL', readonly: true }, }), ).toBe(dbPath); expect( @@ -94,15 +94,15 @@ describe('KloSqliteScanConnector', () => { ).toThrow('Native SQLite connector requires connections.warehouse.readonly: true'); } finally { if (originalDatabaseUrl === undefined) { - delete process.env.KLO_SQLITE_TEST_URL; + delete process.env.KTX_SQLITE_TEST_URL; } else { - process.env.KLO_SQLITE_TEST_URL = originalDatabaseUrl; + process.env.KTX_SQLITE_TEST_URL = originalDatabaseUrl; } } }); it('introspects schema, primary keys, row counts, views, and foreign keys', async () => { - const connector = new KloSqliteScanConnector({ + const connector = new KtxSqliteScanConnector({ connectionId: 'warehouse', connection: { driver: 'sqlite', path: dbPath, readonly: true }, now: () => new Date('2026-04-29T10:00:00.000Z'), @@ -149,7 +149,7 @@ describe('KloSqliteScanConnector', () => { }); it('runs samples, distinct values, statistics, and read-only SQL', async () => { - const connector = new KloSqliteScanConnector({ + const connector = new KtxSqliteScanConnector({ connectionId: 'warehouse', connection: { driver: 'sqlite', path: dbPath, readonly: true }, }); diff --git a/packages/connector-sqlite/src/connector.ts b/packages/connector-sqlite/src/connector.ts index aab3463d..c42db002 100644 --- a/packages/connector-sqlite/src/connector.ts +++ b/packages/connector-sqlite/src/connector.ts @@ -3,28 +3,28 @@ import { existsSync, readFileSync, statSync } from 'node:fs'; import { homedir } from 'node:os'; import { isAbsolute, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { assertReadOnlySql, limitSqlForExecution, normalizeQueryRows } from '@klo/context/connections'; +import { assertReadOnlySql, limitSqlForExecution, normalizeQueryRows } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaForeignKey, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; -import { KloSqliteDialect } from './dialect.js'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaForeignKey, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; +import { KtxSqliteDialect } from './dialect.js'; -export interface KloSqliteConnectionConfig { +export interface KtxSqliteConnectionConfig { driver?: string; path?: string; url?: string; @@ -36,24 +36,24 @@ export interface KloSqliteConnectionConfig { export interface SqliteDatabasePathInput { connectionId: string; projectDir?: string; - connection: KloSqliteConnectionConfig | undefined; + connection: KtxSqliteConnectionConfig | undefined; } -export interface KloSqliteScanConnectorOptions extends SqliteDatabasePathInput { +export interface KtxSqliteScanConnectorOptions extends SqliteDatabasePathInput { now?: () => Date; } -export interface KloSqliteReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxSqliteReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record | unknown[]; } -export interface KloSqliteColumnDistinctValuesOptions { +export interface KtxSqliteColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloSqliteColumnDistinctValuesResult { +export interface KtxSqliteColumnDistinctValuesResult { values: string[] | null; cardinality: number; } @@ -81,14 +81,14 @@ interface SqliteForeignKeyRow { } function stringConfigValue( - connection: KloSqliteConnectionConfig | undefined, - key: keyof KloSqliteConnectionConfig, + connection: KtxSqliteConnectionConfig | undefined, + key: keyof KtxSqliteConnectionConfig, ): string | undefined { const value = connection?.[key]; return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(key, value.trim()) : undefined; } -function resolveStringReference(key: keyof KloSqliteConnectionConfig, value: string): string { +function resolveStringReference(key: keyof KtxSqliteConnectionConfig, value: string): string { if (value.startsWith('env:')) { return process.env[value.slice('env:'.length)] ?? ''; } @@ -135,13 +135,13 @@ function stripLeadingSqlComments(sql: string): string { return sql.slice(index); } -export function isKloSqliteConnectionConfig(connection: KloSqliteConnectionConfig | undefined): boolean { +export function isKtxSqliteConnectionConfig(connection: KtxSqliteConnectionConfig | undefined): boolean { const driver = String(connection?.driver ?? '').toLowerCase(); return driver === 'sqlite' || driver === 'sqlite3'; } export function sqliteDatabasePathFromConfig(input: SqliteDatabasePathInput): string { - if (!isKloSqliteConnectionConfig(input.connection)) { + if (!isKtxSqliteConnectionConfig(input.connection)) { throw new Error(`Native SQLite connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -157,10 +157,10 @@ export function sqliteDatabasePathFromConfig(input: SqliteDatabasePathInput): st return isAbsolute(configuredPath) ? configuredPath : resolve(input.projectDir ?? process.cwd(), configuredPath); } -export class KloSqliteScanConnector implements KloScanConnector { +export class KtxSqliteScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'sqlite' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -173,10 +173,10 @@ export class KloSqliteScanConnector implements KloScanConnector { private readonly connectionId: string; private readonly dbPath: string; private readonly now: () => Date; - private readonly dialect = new KloSqliteDialect(); + private readonly dialect = new KtxSqliteDialect(); private db: Database.Database | null = null; - constructor(options: KloSqliteScanConnectorOptions) { + constructor(options: KtxSqliteScanConnectorOptions) { this.connectionId = options.connectionId; this.dbPath = sqliteDatabasePathFromConfig(options); this.now = options.now ?? (() => new Date()); @@ -195,7 +195,7 @@ export class KloSqliteScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const database = this.database(); const rawTables = database @@ -220,13 +220,13 @@ export class KloSqliteScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns)); return { headers: result.headers, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -235,21 +235,21 @@ export class KloSqliteScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloSqliteReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxSqliteReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = this.query(limitSqlForExecution(stripLeadingSqlComments(input.sql), input.maxRows), input.params); return { ...result, rowCount: result.rows.length }; } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloSqliteColumnDistinctValuesOptions, - ): Promise { + options: KtxSqliteColumnDistinctValuesOptions, + ): Promise { const sampleSize = options.sampleSize ?? 10000; const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); @@ -281,7 +281,7 @@ export class KloSqliteScanConnector implements KloScanConnector { return Number(result.rows[0]?.[0] ?? 0); } - qTableName(table: Pick): string { + qTableName(table: Pick): string { return this.dialect.formatTableName(table); } @@ -303,7 +303,7 @@ export class KloSqliteScanConnector implements KloScanConnector { return this.db; } - private query(sql: string, params?: Record | unknown[]): Omit { + private query(sql: string, params?: Record | unknown[]): Omit { const statement = this.database().prepare(assertReadOnlySql(sql)); const rows = (params ? statement.all(params) : statement.all()) as unknown[]; return { @@ -313,7 +313,7 @@ export class KloSqliteScanConnector implements KloScanConnector { }; } - private readTable(database: Database.Database, table: SqliteMasterRow): KloSchemaTable { + private readTable(database: Database.Database, table: SqliteMasterRow): KtxSchemaTable { const columns = database .prepare(`PRAGMA table_info(${this.dialect.quoteIdentifier(table.name)})`) .all() as SqliteTableInfoRow[]; @@ -350,7 +350,7 @@ export class KloSqliteScanConnector implements KloScanConnector { }; } - private mapForeignKeys(rows: SqliteForeignKeyRow[]): KloSchemaForeignKey[] { + private mapForeignKeys(rows: SqliteForeignKeyRow[]): KtxSchemaForeignKey[] { return rows .sort((a, b) => a.id - b.id || a.seq - b.seq) .map((row) => ({ @@ -365,7 +365,7 @@ export class KloSqliteScanConnector implements KloScanConnector { private assertConnection(connectionId: string): void { if (connectionId !== this.connectionId) { - throw new Error(`KLO SQLite connector ${this.id} cannot serve connection ${connectionId}`); + throw new Error(`KTX SQLite connector ${this.id} cannot serve connection ${connectionId}`); } } } diff --git a/packages/connector-sqlite/src/dialect.test.ts b/packages/connector-sqlite/src/dialect.test.ts index b48f3e35..cefed21a 100644 --- a/packages/connector-sqlite/src/dialect.test.ts +++ b/packages/connector-sqlite/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloSqliteDialect } from './dialect.js'; +import { KtxSqliteDialect } from './dialect.js'; -describe('KloSqliteDialect', () => { - const dialect = new KloSqliteDialect(); +describe('KtxSqliteDialect', () => { + const dialect = new KtxSqliteDialect(); it('quotes identifiers and formats single-file SQLite table names', () => { expect(dialect.quoteIdentifier('orders')).toBe('"orders"'); @@ -10,7 +10,7 @@ describe('KloSqliteDialect', () => { expect(dialect.formatTableName({ catalog: 'ignored', db: 'ignored', name: 'orders' })).toBe('"orders"'); }); - it('maps native SQLite types to KLO dimension types', () => { + it('maps native SQLite types to KTX dimension types', () => { expect(dialect.mapToDimensionType('INTEGER')).toBe('number'); expect(dialect.mapToDimensionType('numeric(10,2)')).toBe('number'); expect(dialect.mapToDimensionType('timestamp')).toBe('time'); diff --git a/packages/connector-sqlite/src/dialect.ts b/packages/connector-sqlite/src/dialect.ts index 928a3dea..a61a1ca1 100644 --- a/packages/connector-sqlite/src/dialect.ts +++ b/packages/connector-sqlite/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type SqliteTableNameRef = Pick & Partial>; +type SqliteTableNameRef = Pick & Partial>; -export class KloSqliteDialect { +export class KtxSqliteDialect { readonly type = 'sqlite'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { DATETIME: 'time', DATE: 'time', TIMESTAMP: 'time', @@ -36,7 +36,7 @@ export class KloSqliteDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-sqlite/src/index.ts b/packages/connector-sqlite/src/index.ts index 037506e8..653a9e81 100644 --- a/packages/connector-sqlite/src/index.ts +++ b/packages/connector-sqlite/src/index.ts @@ -1,13 +1,13 @@ -export { KloSqliteDialect } from './dialect.js'; +export { KtxSqliteDialect } from './dialect.js'; export { - isKloSqliteConnectionConfig, - KloSqliteScanConnector, + isKtxSqliteConnectionConfig, + KtxSqliteScanConnector, sqliteDatabasePathFromConfig, - type KloSqliteColumnDistinctValuesOptions, - type KloSqliteColumnDistinctValuesResult, - type KloSqliteConnectionConfig, - type KloSqliteReadOnlyQueryInput, - type KloSqliteScanConnectorOptions, + type KtxSqliteColumnDistinctValuesOptions, + type KtxSqliteColumnDistinctValuesResult, + type KtxSqliteConnectionConfig, + type KtxSqliteReadOnlyQueryInput, + type KtxSqliteScanConnectorOptions, type SqliteDatabasePathInput, } from './connector.js'; export { diff --git a/packages/connector-sqlite/src/live-database-introspection.ts b/packages/connector-sqlite/src/live-database-introspection.ts index e3546965..6e45ac7d 100644 --- a/packages/connector-sqlite/src/live-database-introspection.ts +++ b/packages/connector-sqlite/src/live-database-introspection.ts @@ -1,10 +1,10 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; -import { KloSqliteScanConnector, type KloSqliteConnectionConfig } from './connector.js'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; +import { KtxSqliteScanConnector, type KtxSqliteConnectionConfig } from './connector.js'; export interface CreateSqliteLiveDatabaseIntrospectionOptions { projectDir?: string; - connections: Record; + connections: Record; now?: () => Date; } @@ -13,8 +13,8 @@ export function createSqliteLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloSqliteConnectionConfig | undefined; - const connector = new KloSqliteScanConnector({ + const connection = options.connections[connectionId] as KtxSqliteConnectionConfig | undefined; + const connector = new KtxSqliteScanConnector({ connectionId, connection, projectDir: options.projectDir, diff --git a/packages/connector-sqlite/src/package-exports.test.ts b/packages/connector-sqlite/src/package-exports.test.ts index 0947ae3c..bc6ff6fb 100644 --- a/packages/connector-sqlite/src/package-exports.test.ts +++ b/packages/connector-sqlite/src/package-exports.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/connector-sqlite package exports', () => { +describe('@ktx/connector-sqlite package exports', () => { it('exports the native SQLite scan connector surface', async () => { const connector = await import('./index.js'); - expect(connector.KloSqliteDialect).toBeTypeOf('function'); - expect(connector.KloSqliteScanConnector).toBeTypeOf('function'); + expect(connector.KtxSqliteDialect).toBeTypeOf('function'); + expect(connector.KtxSqliteScanConnector).toBeTypeOf('function'); expect(connector.createSqliteLiveDatabaseIntrospection).toBeTypeOf('function'); - expect(connector.isKloSqliteConnectionConfig).toBeTypeOf('function'); + expect(connector.isKtxSqliteConnectionConfig).toBeTypeOf('function'); expect(connector.sqliteDatabasePathFromConfig).toBeTypeOf('function'); }); }); diff --git a/packages/connector-sqlserver/package.json b/packages/connector-sqlserver/package.json index fea995ce..085903e3 100644 --- a/packages/connector-sqlserver/package.json +++ b/packages/connector-sqlserver/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/connector-sqlserver", + "name": "@ktx/connector-sqlserver", "version": "0.0.0-private", - "description": "SQL Server connector package for KLO scan interfaces", + "description": "SQL Server connector package for KTX scan interfaces", "private": true, "type": "module", "engines": { @@ -26,7 +26,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/context": "workspace:*", + "@ktx/context": "workspace:*", "mssql": "^12.2.0" }, "devDependencies": { diff --git a/packages/connector-sqlserver/src/connector.test.ts b/packages/connector-sqlserver/src/connector.test.ts index 813a6f69..eebab0ba 100644 --- a/packages/connector-sqlserver/src/connector.test.ts +++ b/packages/connector-sqlserver/src/connector.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it, vi } from 'vitest'; import { createSqlServerLiveDatabaseIntrospection, - isKloSqlServerConnectionConfig, - KloSqlServerScanConnector, + isKtxSqlServerConnectionConfig, + KtxSqlServerScanConnector, sqlServerConnectionPoolConfigFromConfig, - type KloSqlServerPoolFactory, - type KloSqlServerQueryResult, + type KtxSqlServerPoolFactory, + type KtxSqlServerQueryResult, } from './index.js'; function recordset>( @@ -17,12 +17,12 @@ function recordset>( return withColumns; } -function result>(rows: T[], columnNames: string[]): KloSqlServerQueryResult { +function result>(rows: T[], columnNames: string[]): KtxSqlServerQueryResult { return { recordset: recordset(rows, columnNames) }; } -function fakePoolFactory(): KloSqlServerPoolFactory { - const query = vi.fn(async (sql: string): Promise => { +function fakePoolFactory(): KtxSqlServerPoolFactory { + const query = vi.fn(async (sql: string): Promise => { if (sql.includes('INFORMATION_SCHEMA.TABLES')) { return result( [ @@ -102,7 +102,7 @@ function fakePoolFactory(): KloSqlServerPoolFactory { if (sql.includes('SELECT TOP 1 [id], [status] FROM [dbo].[orders]')) { return result([{ id: 10, status: 'paid' }], ['id', 'status']); } - if (sql.includes('SELECT TOP 1 * FROM (select id, status from dbo.orders) AS klo_query_result')) { + if (sql.includes('SELECT TOP 1 * FROM (select id, status from dbo.orders) AS ktx_query_result')) { return result([{ id: 10, status: 'paid' }], ['id', 'status']); } if (sql.includes('SELECT TOP 5 [status] FROM [dbo].[orders]')) { @@ -138,17 +138,17 @@ function fakePoolFactory(): KloSqlServerPoolFactory { }; } -describe('KloSqlServerScanConnector', () => { +describe('KtxSqlServerScanConnector', () => { it('resolves SQL Server connection configuration safely', () => { expect( - isKloSqlServerConnectionConfig({ + isKtxSqlServerConnectionConfig({ driver: 'sqlserver', host: 'localhost', database: 'analytics', readonly: true, }), ).toBe(true); - expect(isKloSqlServerConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics' })).toBe(false); + expect(isKtxSqlServerConnectionConfig({ driver: 'mysql', host: 'localhost', database: 'analytics' })).toBe(false); expect( sqlServerConnectionPoolConfigFromConfig({ connectionId: 'warehouse', @@ -178,7 +178,7 @@ describe('KloSqlServerScanConnector', () => { }); it('introspects schema, primary keys, comments, row counts, views, and foreign keys', async () => { - const connector = new KloSqlServerScanConnector({ + const connector = new KtxSqlServerScanConnector({ connectionId: 'warehouse', connection: { driver: 'sqlserver', @@ -238,7 +238,7 @@ describe('KloSqlServerScanConnector', () => { it('runs samples, distinct values, read-only SQL, row count, schema list, and cleanup', async () => { const poolFactory = fakePoolFactory(); - const connector = new KloSqlServerScanConnector({ + const connector = new KtxSqlServerScanConnector({ connectionId: 'warehouse', connection: { driver: 'sqlserver', diff --git a/packages/connector-sqlserver/src/connector.ts b/packages/connector-sqlserver/src/connector.ts index ce5c491b..1f31286d 100644 --- a/packages/connector-sqlserver/src/connector.ts +++ b/packages/connector-sqlserver/src/connector.ts @@ -1,30 +1,30 @@ -import { assertReadOnlySql } from '@klo/context/connections'; +import { assertReadOnlySql } from '@ktx/context/connections'; import { - createKloConnectorCapabilities, - type KloColumnSampleInput, - type KloColumnSampleResult, - type KloColumnStatsInput, - type KloColumnStatsResult, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaColumn, - type KloSchemaForeignKey, - type KloSchemaSnapshot, - type KloSchemaTable, - type KloTableRef, - type KloTableSampleInput, - type KloTableSampleResult, -} from '@klo/context/scan'; + createKtxConnectorCapabilities, + type KtxColumnSampleInput, + type KtxColumnSampleResult, + type KtxColumnStatsInput, + type KtxColumnStatsResult, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaColumn, + type KtxSchemaForeignKey, + type KtxSchemaSnapshot, + type KtxSchemaTable, + type KtxTableRef, + type KtxTableSampleInput, + type KtxTableSampleResult, +} from '@ktx/context/scan'; import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; import sql from 'mssql'; -import { KloSqlServerDialect } from './dialect.js'; +import { KtxSqlServerDialect } from './dialect.js'; -export interface KloSqlServerConnectionConfig { +export interface KtxSqlServerConnectionConfig { driver?: string; host?: string; port?: number; @@ -40,7 +40,7 @@ export interface KloSqlServerConnectionConfig { [key: string]: unknown; } -export interface KloSqlServerPoolConfig { +export interface KtxSqlServerPoolConfig { server: string; port: number; database: string; @@ -50,63 +50,63 @@ export interface KloSqlServerPoolConfig { pool: { max: number; min: number; idleTimeoutMillis: number }; } -export interface KloSqlServerQueryResult { +export interface KtxSqlServerQueryResult { recordset?: Array> & { columns?: Record }; } -interface KloSqlServerRequest { - input(name: string, value: unknown): KloSqlServerRequest; - query(query: string): Promise; +interface KtxSqlServerRequest { + input(name: string, value: unknown): KtxSqlServerRequest; + query(query: string): Promise; } -export interface KloSqlServerPool { - request(): KloSqlServerRequest; +export interface KtxSqlServerPool { + request(): KtxSqlServerRequest; close(): Promise; } -export interface KloSqlServerPoolFactory { - createPool(config: KloSqlServerPoolConfig): Promise; +export interface KtxSqlServerPoolFactory { + createPool(config: KtxSqlServerPoolConfig): Promise; } -interface KloSqlServerResolvedEndpoint { +interface KtxSqlServerResolvedEndpoint { host: string; port: number; close?: () => Promise; } -export interface KloSqlServerEndpointResolver { +export interface KtxSqlServerEndpointResolver { resolve(input: { host: string; port: number; - connection: KloSqlServerConnectionConfig; - }): Promise; + connection: KtxSqlServerConnectionConfig; + }): Promise; } -export interface KloSqlServerScanConnectorOptions { +export interface KtxSqlServerScanConnectorOptions { connectionId: string; - connection: KloSqlServerConnectionConfig | undefined; - poolFactory?: KloSqlServerPoolFactory; - endpointResolver?: KloSqlServerEndpointResolver; + connection: KtxSqlServerConnectionConfig | undefined; + poolFactory?: KtxSqlServerPoolFactory; + endpointResolver?: KtxSqlServerEndpointResolver; env?: NodeJS.ProcessEnv; now?: () => Date; } -export interface KloSqlServerReadOnlyQueryInput extends KloReadOnlyQueryInput { +export interface KtxSqlServerReadOnlyQueryInput extends KtxReadOnlyQueryInput { params?: Record; } -export interface KloSqlServerColumnDistinctValuesOptions { +export interface KtxSqlServerColumnDistinctValuesOptions { maxCardinality: number; limit: number; sampleSize?: number; } -export interface KloSqlServerColumnDistinctValuesResult { +export interface KtxSqlServerColumnDistinctValuesResult { values: string[] | null; cardinality: number; } -interface KloSqlServerTableSampleResult extends KloTableSampleResult { +interface KtxSqlServerTableSampleResult extends KtxTableSampleResult { headerTypes?: string[]; } @@ -128,8 +128,8 @@ function sqlTypeDeclaration(type: unknown): string { function sqlRecordset( rows: Array> | undefined, columns: Record | undefined, -): NonNullable { - const recordset = [...(rows ?? [])] as NonNullable; +): NonNullable { + const recordset = [...(rows ?? [])] as NonNullable; recordset.columns = Object.fromEntries( Object.entries(columns ?? {}).map(([name, metadata]) => [ name, @@ -139,8 +139,8 @@ function sqlRecordset( return recordset; } -class DefaultSqlServerPoolFactory implements KloSqlServerPoolFactory { - async createPool(config: KloSqlServerPoolConfig): Promise { +class DefaultSqlServerPoolFactory implements KtxSqlServerPoolFactory { + async createPool(config: KtxSqlServerPoolConfig): Promise { const pool = await new sql.ConnectionPool(config as sql.config).connect(); return { request() { @@ -164,8 +164,8 @@ class DefaultSqlServerPoolFactory implements KloSqlServerPoolFactory { } function stringConfigValue( - connection: KloSqlServerConnectionConfig | undefined, - key: keyof KloSqlServerConnectionConfig, + connection: KtxSqlServerConnectionConfig | undefined, + key: keyof KtxSqlServerConnectionConfig, env: NodeJS.ProcessEnv, ): string | undefined { const value = connection?.[key]; @@ -184,7 +184,7 @@ function resolveStringReference(value: string, env: NodeJS.ProcessEnv): string { return value; } -function parseSqlServerUrl(url: string): Partial { +function parseSqlServerUrl(url: string): Partial { const parsed = new URL(url); return { host: parsed.hostname, @@ -200,7 +200,7 @@ function maybeNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined; } -function schemaNames(connection: KloSqlServerConnectionConfig, env: NodeJS.ProcessEnv): string[] { +function schemaNames(connection: KtxSqlServerConnectionConfig, env: NodeJS.ProcessEnv): string[] { if (Array.isArray(connection.schemas) && connection.schemas.length > 0) { return connection.schemas.filter((schema) => schema.trim().length > 0).map((schema) => resolveStringReference(schema, env)); } @@ -230,19 +230,19 @@ function limitSqlForSqlServerExecution(sqlText: string, maxRows: number | undefi if (!Number.isInteger(maxRows) || maxRows <= 0) { throw new Error('maxRows must be a positive integer.'); } - return `SELECT TOP ${maxRows} * FROM (${trimmed}) AS klo_query_result`; + return `SELECT TOP ${maxRows} * FROM (${trimmed}) AS ktx_query_result`; } -export function isKloSqlServerConnectionConfig(connection: KloSqlServerConnectionConfig | undefined): boolean { +export function isKtxSqlServerConnectionConfig(connection: KtxSqlServerConnectionConfig | undefined): boolean { return String(connection?.driver ?? '').toLowerCase() === 'sqlserver'; } export function sqlServerConnectionPoolConfigFromConfig(input: { connectionId: string; - connection: KloSqlServerConnectionConfig | undefined; + connection: KtxSqlServerConnectionConfig | undefined; env?: NodeJS.ProcessEnv; -}): KloSqlServerPoolConfig { - if (!isKloSqlServerConnectionConfig(input.connection)) { +}): KtxSqlServerPoolConfig { + if (!isKtxSqlServerConnectionConfig(input.connection)) { throw new Error(`Native SQL Server connector cannot run driver "${input.connection?.driver ?? 'unknown'}"`); } if (input.connection?.readonly !== true) { @@ -252,7 +252,7 @@ export function sqlServerConnectionPoolConfigFromConfig(input: { const env = input.env ?? process.env; const referencedUrl = stringConfigValue(input.connection, 'url', env); const urlConfig = referencedUrl ? parseSqlServerUrl(referencedUrl) : {}; - const merged: KloSqlServerConnectionConfig = { ...urlConfig, ...input.connection }; + const merged: KtxSqlServerConnectionConfig = { ...urlConfig, ...input.connection }; const server = stringConfigValue(merged, 'host', env); const database = stringConfigValue(merged, 'database', env); const user = stringConfigValue(merged, 'username', env) ?? stringConfigValue(merged, 'user', env); @@ -278,10 +278,10 @@ export function sqlServerConnectionPoolConfigFromConfig(input: { }; } -export class KloSqlServerScanConnector implements KloScanConnector { +export class KtxSqlServerScanConnector implements KtxScanConnector { readonly id: string; readonly driver = 'sqlserver' as const; - readonly capabilities = createKloConnectorCapabilities({ + readonly capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: false, @@ -292,17 +292,17 @@ export class KloSqlServerScanConnector implements KloScanConnector { }); private readonly connectionId: string; - private readonly connection: KloSqlServerConnectionConfig; - private readonly poolConfig: KloSqlServerPoolConfig; + private readonly connection: KtxSqlServerConnectionConfig; + private readonly poolConfig: KtxSqlServerPoolConfig; private readonly schemas: string[]; - private readonly poolFactory: KloSqlServerPoolFactory; - private readonly endpointResolver?: KloSqlServerEndpointResolver; + private readonly poolFactory: KtxSqlServerPoolFactory; + private readonly endpointResolver?: KtxSqlServerEndpointResolver; private readonly now: () => Date; - private readonly dialect = new KloSqlServerDialect(); - private pool: KloSqlServerPool | null = null; - private resolvedEndpoint: KloSqlServerResolvedEndpoint | null = null; + private readonly dialect = new KtxSqlServerDialect(); + private pool: KtxSqlServerPool | null = null; + private resolvedEndpoint: KtxSqlServerResolvedEndpoint | null = null; - constructor(options: KloSqlServerScanConnectorOptions) { + constructor(options: KtxSqlServerScanConnectorOptions) { this.connectionId = options.connectionId; this.connection = options.connection ?? {}; const env = options.env ?? process.env; @@ -327,9 +327,9 @@ export class KloSqlServerScanConnector implements KloScanConnector { } } - async introspect(input: KloScanInput, _ctx: KloScanContext): Promise { + async introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const schemaName of this.schemas) { tables.push(...(await this.introspectSchema(schemaName))); } @@ -349,13 +349,13 @@ export class KloSqlServerScanConnector implements KloScanConnector { }; } - async sampleTable(input: KloTableSampleInput, _ctx: KloScanContext): Promise { + async sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query(this.dialect.generateSampleQuery(this.qTableName(input.table), input.limit, input.columns)); return { headers: result.headers, headerTypes: result.headerTypes, rows: result.rows, totalRows: result.totalRows }; } - async sampleColumn(input: KloColumnSampleInput, _ctx: KloScanContext): Promise { + async sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const result = await this.query( this.dialect.generateColumnSampleQuery(this.qTableName(input.table), input.column, input.limit), @@ -364,11 +364,11 @@ export class KloSqlServerScanConnector implements KloScanConnector { return { values, nullCount: null, distinctCount: null }; } - async columnStats(_input: KloColumnStatsInput, _ctx: KloScanContext): Promise { + async columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise { return null; } - async executeReadOnly(input: KloSqlServerReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxSqlServerReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.assertConnection(input.connectionId); const limitedSql = limitSqlForSqlServerExecution(input.sql, input.maxRows); const prepared = this.dialect.prepareQuery(limitedSql, input.params); @@ -377,10 +377,10 @@ export class KloSqlServerScanConnector implements KloScanConnector { } async getColumnDistinctValues( - table: KloTableRef, + table: KtxTableRef, columnName: string, - options: KloSqlServerColumnDistinctValuesOptions, - ): Promise { + options: KtxSqlServerColumnDistinctValuesOptions, + ): Promise { const tableName = this.qTableName(table); const quotedColumn = this.dialect.quoteIdentifier(columnName); const cardinalityRows = await this.queryRaw<{ cardinality: unknown }>( @@ -418,7 +418,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { return firstNumber(rows[0]?.row_count) ?? 0; } - qTableName(table: Pick & Partial>): string { + qTableName(table: Pick & Partial>): string { return this.dialect.formatTableName(table); } @@ -452,7 +452,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { } } - private async introspectSchema(schemaName: string): Promise { + private async introspectSchema(schemaName: string): Promise { const tables = await this.queryRaw<{ table_name: string; table_type: string }>( ` SELECT TABLE_NAME AS table_name, TABLE_TYPE AS table_type @@ -613,7 +613,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { column: { table_name: string; column_name: string; data_type: string; is_nullable: string }, primaryKeys: Set, comments: Map, - ): KloSchemaColumn { + ): KtxSchemaColumn { return { name: column.column_name, nativeType: column.data_type, @@ -631,7 +631,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { referenced_table_name: string; referenced_column_name: string; constraint_name: string; - }): KloSchemaForeignKey { + }): KtxSchemaForeignKey { return { fromColumn: row.column_name, toCatalog: this.poolConfig.database, @@ -642,7 +642,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { }; } - private async poolForQuery(): Promise { + private async poolForQuery(): Promise { if (!this.pool) { const config = { ...this.poolConfig }; if (this.endpointResolver) { @@ -671,7 +671,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { return (result.recordset ?? []) as T[]; } - private async query(query: string, params?: Record): Promise> { + private async query(query: string, params?: Record): Promise> { const pool = await this.poolForQuery(); const request = pool.request(); if (params) { @@ -695,7 +695,7 @@ export class KloSqlServerScanConnector implements KloScanConnector { private assertConnection(connectionId: string): void { if (connectionId !== this.connectionId) { - throw new Error(`KLO SQL Server connector ${this.id} cannot serve connection ${connectionId}`); + throw new Error(`KTX SQL Server connector ${this.id} cannot serve connection ${connectionId}`); } } } diff --git a/packages/connector-sqlserver/src/dialect.test.ts b/packages/connector-sqlserver/src/dialect.test.ts index 64db720c..5c855340 100644 --- a/packages/connector-sqlserver/src/dialect.test.ts +++ b/packages/connector-sqlserver/src/dialect.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { KloSqlServerDialect } from './dialect.js'; +import { KtxSqlServerDialect } from './dialect.js'; -describe('KloSqlServerDialect', () => { - const dialect = new KloSqlServerDialect(); +describe('KtxSqlServerDialect', () => { + const dialect = new KtxSqlServerDialect(); it('quotes identifiers and formats schema-qualified table names', () => { expect(dialect.quoteIdentifier('events')).toBe('[events]'); @@ -11,7 +11,7 @@ describe('KloSqlServerDialect', () => { expect(dialect.formatTableName({ catalog: null, db: null, name: 'events' })).toBe('[events]'); }); - it('maps SQL Server types to KLO dimension types', () => { + it('maps SQL Server types to KTX dimension types', () => { expect(dialect.mapToDimensionType('datetime2')).toBe('time'); expect(dialect.mapToDimensionType('decimal(18, 2)')).toBe('number'); expect(dialect.mapToDimensionType('bigint')).toBe('number'); diff --git a/packages/connector-sqlserver/src/dialect.ts b/packages/connector-sqlserver/src/dialect.ts index 19f650b0..67897876 100644 --- a/packages/connector-sqlserver/src/dialect.ts +++ b/packages/connector-sqlserver/src/dialect.ts @@ -1,11 +1,11 @@ -import type { KloSchemaDimensionType, KloTableRef } from '@klo/context/scan'; +import type { KtxSchemaDimensionType, KtxTableRef } from '@ktx/context/scan'; -type SqlServerTableNameRef = Pick & Partial>; +type SqlServerTableNameRef = Pick & Partial>; -export class KloSqlServerDialect { +export class KtxSqlServerDialect { readonly type = 'sqlserver'; - private readonly typeMappings: Record = { + private readonly typeMappings: Record = { datetime: 'time', datetime2: 'time', date: 'time', @@ -48,7 +48,7 @@ export class KloSqlServerDialect { return nativeType; } - mapToDimensionType(nativeType: string): KloSchemaDimensionType { + mapToDimensionType(nativeType: string): KtxSchemaDimensionType { if (!nativeType) { return 'string'; } diff --git a/packages/connector-sqlserver/src/index.ts b/packages/connector-sqlserver/src/index.ts index e7db465c..cbea4d95 100644 --- a/packages/connector-sqlserver/src/index.ts +++ b/packages/connector-sqlserver/src/index.ts @@ -1,17 +1,17 @@ -export { KloSqlServerDialect } from './dialect.js'; +export { KtxSqlServerDialect } from './dialect.js'; export { - isKloSqlServerConnectionConfig, - KloSqlServerScanConnector, + isKtxSqlServerConnectionConfig, + KtxSqlServerScanConnector, sqlServerConnectionPoolConfigFromConfig, - type KloSqlServerColumnDistinctValuesOptions, - type KloSqlServerColumnDistinctValuesResult, - type KloSqlServerConnectionConfig, - type KloSqlServerEndpointResolver, - type KloSqlServerPool, - type KloSqlServerPoolConfig, - type KloSqlServerPoolFactory, - type KloSqlServerQueryResult, - type KloSqlServerReadOnlyQueryInput, - type KloSqlServerScanConnectorOptions, + type KtxSqlServerColumnDistinctValuesOptions, + type KtxSqlServerColumnDistinctValuesResult, + type KtxSqlServerConnectionConfig, + type KtxSqlServerEndpointResolver, + type KtxSqlServerPool, + type KtxSqlServerPoolConfig, + type KtxSqlServerPoolFactory, + type KtxSqlServerQueryResult, + type KtxSqlServerReadOnlyQueryInput, + type KtxSqlServerScanConnectorOptions, } from './connector.js'; export { createSqlServerLiveDatabaseIntrospection } from './live-database-introspection.js'; diff --git a/packages/connector-sqlserver/src/live-database-introspection.ts b/packages/connector-sqlserver/src/live-database-introspection.ts index d74100b2..7c7a2b53 100644 --- a/packages/connector-sqlserver/src/live-database-introspection.ts +++ b/packages/connector-sqlserver/src/live-database-introspection.ts @@ -1,16 +1,16 @@ -import type { LiveDatabaseIntrospectionPort } from '@klo/context/ingest'; -import type { KloProjectConnectionConfig } from '@klo/context/project'; +import type { LiveDatabaseIntrospectionPort } from '@ktx/context/ingest'; +import type { KtxProjectConnectionConfig } from '@ktx/context/project'; import { - KloSqlServerScanConnector, - type KloSqlServerConnectionConfig, - type KloSqlServerEndpointResolver, - type KloSqlServerPoolFactory, + KtxSqlServerScanConnector, + type KtxSqlServerConnectionConfig, + type KtxSqlServerEndpointResolver, + type KtxSqlServerPoolFactory, } from './connector.js'; interface CreateSqlServerLiveDatabaseIntrospectionOptions { - connections: Record; - poolFactory?: KloSqlServerPoolFactory; - endpointResolver?: KloSqlServerEndpointResolver; + connections: Record; + poolFactory?: KtxSqlServerPoolFactory; + endpointResolver?: KtxSqlServerEndpointResolver; now?: () => Date; } @@ -19,8 +19,8 @@ export function createSqlServerLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { return { async extractSchema(connectionId: string) { - const connection = options.connections[connectionId] as KloSqlServerConnectionConfig | undefined; - const connector = new KloSqlServerScanConnector({ + const connection = options.connections[connectionId] as KtxSqlServerConnectionConfig | undefined; + const connector = new KtxSqlServerScanConnector({ connectionId, connection, poolFactory: options.poolFactory, diff --git a/packages/connector-sqlserver/src/package-exports.test.ts b/packages/connector-sqlserver/src/package-exports.test.ts index 72d2cbbc..0755738c 100644 --- a/packages/connector-sqlserver/src/package-exports.test.ts +++ b/packages/connector-sqlserver/src/package-exports.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/connector-sqlserver package exports', () => { +describe('@ktx/connector-sqlserver package exports', () => { it('exports public connector APIs during package bootstrap', async () => { const connector = await import('./index.js'); - expect(connector.KloSqlServerDialect).toBeTypeOf('function'); - expect(connector.KloSqlServerScanConnector).toBeTypeOf('function'); + expect(connector.KtxSqlServerDialect).toBeTypeOf('function'); + expect(connector.KtxSqlServerScanConnector).toBeTypeOf('function'); expect(connector.createSqlServerLiveDatabaseIntrospection).toBeTypeOf('function'); expect(connector.sqlServerConnectionPoolConfigFromConfig).toBeTypeOf('function'); }); diff --git a/packages/context/package.json b/packages/context/package.json index a4866027..b85947e5 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,5 +1,5 @@ { - "name": "@klo/context", + "name": "@ktx/context", "version": "0.0.0-private", "description": "Core context library for database agents", "private": true, @@ -127,7 +127,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@klo/llm": "workspace:*", + "@ktx/llm": "workspace:*", "@looker/sdk": "^26.6.1", "@looker/sdk-node": "^26.6.1", "@looker/sdk-rtl": "^21.6.5", diff --git a/packages/context/prompts/memory_agent_bundle_ingest_work_unit.md b/packages/context/prompts/memory_agent_bundle_ingest_work_unit.md index 2d296f5a..b0fa8997 100644 --- a/packages/context/prompts/memory_agent_bundle_ingest_work_unit.md +++ b/packages/context/prompts/memory_agent_bundle_ingest_work_unit.md @@ -1,9 +1,9 @@ -You are processing ONE WorkUnit of a multi-file ingest bundle. The WorkUnit gives you a slice of raw source files (LookML views, dbt/MetricFlow YAMLs, Metabase card JSONs, or similar) and you must translate that slice into KLO semantic-layer sources and/or knowledge wiki pages, in one pass. Prior WorkUnits in this same job may have already written SL sources and wiki pages; their writes are visible on the working branch and searchable via `wiki_sl_search`. +You are processing ONE WorkUnit of a multi-file ingest bundle. The WorkUnit gives you a slice of raw source files (LookML views, dbt/MetricFlow YAMLs, Metabase card JSONs, or similar) and you must translate that slice into KTX semantic-layer sources and/or knowledge wiki pages, in one pass. Prior WorkUnits in this same job may have already written SL sources and wiki pages; their writes are visible on the working branch and searchable via `wiki_sl_search`. -Assertive. The bundle was explicitly submitted for ingest. Default to capturing everything the raw files declare that maps cleanly to KLO: one SL source per table/view, one wiki page per non-obvious business rule or alias. Do not abandon a WorkUnit because "some content overlaps with another WU"; use `ingest_triage` to reconcile, do not skip. +Assertive. The bundle was explicitly submitted for ingest. Default to capturing everything the raw files declare that maps cleanly to KTX: one SL source per table/view, one wiki page per non-obvious business rule or alias. Do not abandon a WorkUnit because "some content overlaps with another WU"; use `ingest_triage` to reconcile, do not skip. diff --git a/packages/context/prompts/memory_agent_external_ingest.md b/packages/context/prompts/memory_agent_external_ingest.md index 9a5ecc39..edee6f75 100644 --- a/packages/context/prompts/memory_agent_external_ingest.md +++ b/packages/context/prompts/memory_agent_external_ingest.md @@ -1,5 +1,5 @@ -You are ingesting an external technical artifact (a LookML view, dbt model, schema description, business glossary, or other reference document) into KLO organizational memory. The user has explicitly submitted this content for bulk ingest. Assume it is intentional and worth capturing. +You are ingesting an external technical artifact (a LookML view, dbt model, schema description, business glossary, or other reference document) into KTX organizational memory. The user has explicitly submitted this content for bulk ingest. Assume it is intentional and worth capturing. @@ -18,7 +18,7 @@ A single artifact typically produces multiple actions: one SL source per table/v -All wiki writes go to the GLOBAL scope — they will be visible to every user of this KLO project. Phrase wiki pages as objective business knowledge, not personal preference. The `wiki_write` tool handles scope selection automatically for external ingest. +All wiki writes go to the GLOBAL scope — they will be visible to every user of this KTX project. Phrase wiki pages as objective business knowledge, not personal preference. The `wiki_write` tool handles scope selection automatically for external ingest. diff --git a/packages/context/scripts/pglite-hybrid-search-spike.mjs b/packages/context/scripts/pglite-hybrid-search-spike.mjs index 98e419f7..500c34af 100644 --- a/packages/context/scripts/pglite-hybrid-search-spike.mjs +++ b/packages/context/scripts/pglite-hybrid-search-spike.mjs @@ -8,8 +8,8 @@ import { fileURLToPath } from 'node:url'; const require = createRequire(import.meta.url); const scriptDir = dirname(fileURLToPath(import.meta.url)); const contextDir = resolve(scriptDir, '..'); -const kloRoot = resolve(contextDir, '../..'); -const docsDir = join(kloRoot, 'docs'); +const ktxRoot = resolve(contextDir, '../..'); +const docsDir = join(ktxRoot, 'docs'); const reportPath = join(docsDir, 'hybrid-search-pglite-spike.md'); async function timed(label, fn) { @@ -63,7 +63,7 @@ async function packageInfo(packageName) { return { name: packageName, version: packageJson.version, - path: relative(kloRoot, packageDir), + path: relative(ktxRoot, packageDir), bytes: await directoryBytes(packageDir), }; } @@ -164,7 +164,7 @@ async function main() { }); const { PGlite, vector, pg_trgm } = importTimer.value; - const tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-report-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-report-')); const dataDir = join(tempDir, 'pgdata'); let db; diff --git a/packages/context/scripts/pglite-owner-process-prototype.mjs b/packages/context/scripts/pglite-owner-process-prototype.mjs index cef6bb52..66a3e51f 100644 --- a/packages/context/scripts/pglite-owner-process-prototype.mjs +++ b/packages/context/scripts/pglite-owner-process-prototype.mjs @@ -12,8 +12,8 @@ import { Client } from 'pg'; const scriptDir = dirname(fileURLToPath(import.meta.url)); const contextDir = resolve(scriptDir, '..'); -const kloRoot = resolve(contextDir, '../..'); -const reportPath = join(kloRoot, 'docs', 'hybrid-search-pglite-owner-process.md'); +const ktxRoot = resolve(contextDir, '../..'); +const reportPath = join(ktxRoot, 'docs', 'hybrid-search-pglite-owner-process.md'); async function timed(label, fn) { const started = performance.now(); @@ -98,7 +98,7 @@ async function createOwner(dataDir, port) { port, user: 'postgres', database: 'postgres', - application_name: 'klo-pglite-owner-report', + application_name: 'ktx-pglite-owner-report', connectionTimeoutMillis: 5_000, }, }; @@ -222,7 +222,7 @@ async function stopOwner(owner) { } async function main() { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-owner-report-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-owner-report-')); const dataDir = join(tempDir, 'pgdata'); const port = await allocatePort(); diff --git a/packages/context/scripts/pglite-sl-search-prototype.mjs b/packages/context/scripts/pglite-sl-search-prototype.mjs index ec78adda..43cb0e6e 100644 --- a/packages/context/scripts/pglite-sl-search-prototype.mjs +++ b/packages/context/scripts/pglite-sl-search-prototype.mjs @@ -12,8 +12,8 @@ import { Client } from 'pg'; const scriptDir = dirname(fileURLToPath(import.meta.url)); const contextDir = resolve(scriptDir, '..'); -const kloRoot = resolve(contextDir, '../..'); -const reportPath = join(kloRoot, 'docs', 'hybrid-search-pglite-sl-adapter-prototype.md'); +const ktxRoot = resolve(contextDir, '../..'); +const reportPath = join(ktxRoot, 'docs', 'hybrid-search-pglite-sl-adapter-prototype.md'); async function timed(label, fn) { const started = performance.now(); @@ -91,7 +91,7 @@ async function createOwner(dataDir, port) { port, user: 'postgres', database: 'postgres', - application_name: 'klo-pglite-sl-prototype-report', + application_name: 'ktx-pglite-sl-prototype-report', connectionTimeoutMillis: 5_000, }, }; @@ -193,7 +193,7 @@ async function stopOwner(owner) { } async function main() { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-sl-prototype-report-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-sl-prototype-report-')); const dataDir = join(tempDir, 'pgdata'); const port = await allocatePort(); let owner; diff --git a/packages/context/scripts/relationship-benchmark-report.mjs b/packages/context/scripts/relationship-benchmark-report.mjs index 0d1bf0f8..43c86568 100644 --- a/packages/context/scripts/relationship-benchmark-report.mjs +++ b/packages/context/scripts/relationship-benchmark-report.mjs @@ -1,13 +1,13 @@ import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { - KLO_RELATIONSHIP_BENCHMARK_MODES, - buildKloRelationshipBenchmarkReport, - currentKloRelationshipBenchmarkDetector, - formatKloRelationshipBenchmarkReportMarkdown, - kloRelationshipBenchmarkDetectorWithLlm, - loadKloRelationshipBenchmarkFixtures, - runKloRelationshipBenchmarkSuite, + KTX_RELATIONSHIP_BENCHMARK_MODES, + buildKtxRelationshipBenchmarkReport, + currentKtxRelationshipBenchmarkDetector, + formatKtxRelationshipBenchmarkReportMarkdown, + ktxRelationshipBenchmarkDetectorWithLlm, + loadKtxRelationshipBenchmarkFixtures, + runKtxRelationshipBenchmarkSuite, } from '../dist/scan/index.js'; const scriptDir = dirname(fileURLToPath(import.meta.url)); @@ -15,38 +15,38 @@ const packageRoot = resolve(scriptDir, '..'); const fixtureRoot = join(packageRoot, 'test/fixtures/relationship-benchmarks'); async function buildDetector() { - const backend = process.env.KLO_BENCHMARK_LLM_BACKEND; + const backend = process.env.KTX_BENCHMARK_LLM_BACKEND; if (!backend || backend === 'none') { - return currentKloRelationshipBenchmarkDetector(); + return currentKtxRelationshipBenchmarkDetector(); } if (backend !== 'vertex') { - throw new Error(`Unsupported KLO_BENCHMARK_LLM_BACKEND: ${backend}`); + throw new Error(`Unsupported KTX_BENCHMARK_LLM_BACKEND: ${backend}`); } - const project = process.env.KLO_BENCHMARK_VERTEX_PROJECT; - const location = process.env.KLO_BENCHMARK_VERTEX_LOCATION; - const model = process.env.KLO_BENCHMARK_LLM_MODEL ?? 'claude-sonnet-4-6'; + const project = process.env.KTX_BENCHMARK_VERTEX_PROJECT; + const location = process.env.KTX_BENCHMARK_VERTEX_LOCATION; + const model = process.env.KTX_BENCHMARK_LLM_MODEL ?? 'claude-sonnet-4-6'; if (!project || !location) { - throw new Error('KLO_BENCHMARK_VERTEX_PROJECT and KLO_BENCHMARK_VERTEX_LOCATION are required for vertex backend'); + throw new Error('KTX_BENCHMARK_VERTEX_PROJECT and KTX_BENCHMARK_VERTEX_LOCATION are required for vertex backend'); } - const { createKloLlmProvider } = await import('@klo/llm'); - const provider = createKloLlmProvider({ + const { createKtxLlmProvider } = await import('@ktx/llm'); + const provider = createKtxLlmProvider({ backend: 'vertex', vertex: { project, location }, modelSlots: { default: model }, }); - return kloRelationshipBenchmarkDetectorWithLlm(provider); + return ktxRelationshipBenchmarkDetectorWithLlm(provider); } -const fixtures = await loadKloRelationshipBenchmarkFixtures(fixtureRoot); +const fixtures = await loadKtxRelationshipBenchmarkFixtures(fixtureRoot); const detector = await buildDetector(); -const suite = await runKloRelationshipBenchmarkSuite({ +const suite = await runKtxRelationshipBenchmarkSuite({ fixtures, detector, }); -const report = buildKloRelationshipBenchmarkReport({ +const report = buildKtxRelationshipBenchmarkReport({ fixtures, suite, - modes: KLO_RELATIONSHIP_BENCHMARK_MODES, + modes: KTX_RELATIONSHIP_BENCHMARK_MODES, }); -process.stdout.write(formatKloRelationshipBenchmarkReportMarkdown(report)); +process.stdout.write(formatKtxRelationshipBenchmarkReportMarkdown(report)); diff --git a/packages/context/skills/dbt_ingest/SKILL.md b/packages/context/skills/dbt_ingest/SKILL.md index bc6190c3..0f7f7904 100644 --- a/packages/context/skills/dbt_ingest/SKILL.md +++ b/packages/context/skills/dbt_ingest/SKILL.md @@ -1,16 +1,16 @@ --- name: dbt_ingest -description: Map dbt `schema.yml` / `properties.yml` models and sources into KLO semantic-layer overlays and column notes. Covers `sources:` vs `models:`, column `data_tests` (not_null, unique, accepted_values, relationships), and how bundle-time writes complement manifest backfill from git sync. Load when the WorkUnit's `skillNames` includes `dbt_ingest` or when raw files are dbt YAML under `models/` / `sources/`. +description: Map dbt `schema.yml` / `properties.yml` models and sources into KTX semantic-layer overlays and column notes. Covers `sources:` vs `models:`, column `data_tests` (not_null, unique, accepted_values, relationships), and how bundle-time writes complement manifest backfill from git sync. Load when the WorkUnit's `skillNames` includes `dbt_ingest` or when raw files are dbt YAML under `models/` / `sources/`. callers: [memory_agent] --- -# dbt → KLO (bundle ingest) +# dbt → KTX (bundle ingest) Use this skill for **uploaded** dbt projects (`dbt_project.yml` at stage root, `models/**`, `sources/**`, `schema.yml`). There is **no** `fetch()` in v1 — scheduled `dbt parse` / `manifest.json` pulls are out of scope; host-provided dbt sync may still backfill structured test metadata into `_schema` on the next sync. ## Mapping (models / sources → SL) -| dbt | KLO | Notes | +| dbt | KTX | Notes | |-----|--------|--------| | `models:` entry with `columns:` | **Overlay** on the manifest table with the same name (after `wiki_sl_search` / `sl_describe_table`) | One SL source per physical table; model name may differ from DB name — resolve with `read_raw_file` + warehouse context. | | `sources:` → `tables:` | Same as models; use `identifier` when present instead of logical `name`. | Schema + name must match how the connection sees tables. | diff --git a/packages/context/skills/knowledge_capture/SKILL.md b/packages/context/skills/knowledge_capture/SKILL.md index 8265c950..ba6fc125 100644 --- a/packages/context/skills/knowledge_capture/SKILL.md +++ b/packages/context/skills/knowledge_capture/SKILL.md @@ -1,6 +1,6 @@ --- name: knowledge_capture -description: KLO's knowledge base — wiki pages for durable, reusable business knowledge. Covers capture workflow for user preferences, metric definitions, organizational conventions, and cross-references between knowledge 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. +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 knowledge 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. callers: [memory_agent] --- diff --git a/packages/context/skills/looker_ingest/SKILL.md b/packages/context/skills/looker_ingest/SKILL.md index ded805eb..fec63d96 100644 --- a/packages/context/skills/looker_ingest/SKILL.md +++ b/packages/context/skills/looker_ingest/SKILL.md @@ -1,12 +1,12 @@ --- name: looker_ingest -description: Extract durable KLO knowledge and semantic-layer contribution proposals from staged Looker runtime dashboard, Look, and explore JSON. Load for WorkUnits whose raw files are under explores/, dashboards/, or looks/. +description: Extract durable KTX knowledge and semantic-layer contribution proposals from staged Looker runtime dashboard, Look, and explore JSON. Load for WorkUnits whose raw files are under explores/, dashboards/, or looks/. callers: [memory_agent] --- # Looker Runtime Ingest -Looker runtime ingest turns API-staged dashboards, Looks, and explores into durable KLO memory. Runtime entities are evidence. They are not themselves the final knowledge shape. +Looker runtime ingest turns API-staged dashboards, Looks, and explores into durable KTX memory. Runtime entities are evidence. They are not themselves the final knowledge shape. ## Required Workflow @@ -72,7 +72,7 @@ The staged explore file carries warehouse target fields populated before the WU - `rawSqlTableName`: Looker's verbatim `sql_table_name`. Keep it as provenance only. - `targetTable`: the parsed target-table union. Use this as the sole branch condition. -When `targetTable.ok === true`, the explore has a complete KLO backing target. Before writing: +When `targetTable.ok === true`, the explore has a complete KTX backing target. Before writing: 1. Use `targetTable.catalog`, `targetTable.schema`, and `targetTable.name` for `source_tables` preflight matching through `sl_discover` or `sl_read_source`. 2. Use Looker field `sql`, labels, descriptions, and type metadata to derive source columns, measures, segments, joins, and grain. diff --git a/packages/context/skills/lookml_ingest/SKILL.md b/packages/context/skills/lookml_ingest/SKILL.md index fe6d753b..9e5a8da9 100644 --- a/packages/context/skills/lookml_ingest/SKILL.md +++ b/packages/context/skills/lookml_ingest/SKILL.md @@ -1,16 +1,16 @@ --- name: lookml_ingest -description: Map a LookML view/model/explore into KLO semantic layer sources. Covers the LookML to KLO 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. +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. callers: [memory_agent] --- -# LookML to KLO Semantic Layer +# LookML to KTX Semantic Layer 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. ## Mapping table -| LookML | KLO form | Notes | +| LookML | KTX form | Notes | |---|---|---| | `view: X { sql_table_name: …; measure:/dimension:/join: }` | **Overlay** at `/X.yaml` with `measures`, `columns` (computed), `joins`, `segments` | Manifest-backed; inherit grain/columns | | `view: X { derived_table: { sql: … } }` | **Standalone** with top-level `sql:`, explicit `grain:` + `columns:` | No manifest entry exists | @@ -23,7 +23,7 @@ Type map: `date`/`datetime`/`timestamp` → `time`; `yesno` → `boolean`; `numb ## Decision rules -LookML writes target the run connection directly. Unlike Looker runtime ingestion, the LookML adapter is configured on the warehouse KLO connection, so do not look for `targetWarehouseConnectionId` and do not route through a mapping array. +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. Before any SL write, inspect the WorkUnit notes. @@ -56,7 +56,7 @@ A prior replay hallucinated `date_date`, `date_week` into `sql:`, `columns:`, an 2. If the table isn't in the manifest, fall back to `sql_execution({ sql: "SELECT column_name FROM .INFORMATION_SCHEMA.COLUMNS WHERE table_name = ''" })` (session shape — a connection is already pinned by the ingest session). 3. Use only those names in `sql:`, `columns:`, and `grain:`. Map each `dimension_group` to ONE `{ name: , type: time, role: time }` entry — never one per timeframe. -| LookML input | KLO `columns:` entry | +| LookML input | KTX `columns:` entry | |---|---| | `dimension_group: month { type: time; timeframes: [month]; sql: ${TABLE}.month_date ;; }` | `{ name: month_date, type: time, role: time }` | | `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` | @@ -94,7 +94,7 @@ explore: fct_labs { } ``` -KLO overlay at `/fct_labs.yaml`: +KTX overlay at `/fct_labs.yaml`: ```yaml name: fct_labs diff --git a/packages/context/skills/metabase_ingest/SKILL.md b/packages/context/skills/metabase_ingest/SKILL.md index b33ed488..3223818a 100644 --- a/packages/context/skills/metabase_ingest/SKILL.md +++ b/packages/context/skills/metabase_ingest/SKILL.md @@ -1,12 +1,12 @@ --- name: metabase_ingest -description: Convert Metabase questions, models, and metrics into KLO 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/.json` files under a Metabase bundle. +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/.json` files under a Metabase bundle. callers: [memory_agent] --- -# Metabase to KLO Semantic Layer +# Metabase to KTX Semantic Layer -Each WorkUnit represents one Metabase collection's cards for one Metabase database (mapped to exactly one KLO connection). Every `cards/.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/.json` tells you the target KLO connection. +Each WorkUnit represents one Metabase collection's cards for one Metabase database (mapped to exactly one KTX connection). Every `cards/.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/.json` tells you the target KTX connection. ## Context format @@ -194,7 +194,7 @@ If a source is derived from multiple cards (e.g. a generalized source for a near ## Quality standards -Source definitions must follow klo-sl YAML conventions: +Source definitions must follow ktx-sl YAML conventions: - `source_type`: `"table"` (physical table/view) or `"sql"` (arbitrary SQL / derived view). - `table`: required when `source_type: "table"` (e.g. `"public.orders"`). - `sql`: required when `source_type: "sql"`. diff --git a/packages/context/skills/metricflow_ingest/SKILL.md b/packages/context/skills/metricflow_ingest/SKILL.md index 5c56161b..5a24cda8 100644 --- a/packages/context/skills/metricflow_ingest/SKILL.md +++ b/packages/context/skills/metricflow_ingest/SKILL.md @@ -1,16 +1,16 @@ --- name: metricflow_ingest -description: Map a MetricFlow semantic_model or metric into KLO semantic layer sources. Covers the MetricFlow to KLO 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:`. +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:`. callers: [memory_agent] --- -# MetricFlow to KLO Semantic Layer +# MetricFlow to KTX Semantic Layer -A MetricFlow `semantic_model` maps to an SL source; MetricFlow `measures` map to KLO measures; MetricFlow `entities` map to KLO `joins`; MetricFlow `metrics` (top-level) map to KLO 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. +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. ## Mapping table -| MetricFlow | KLO form | Notes | +| MetricFlow | KTX form | Notes | |---|---|---| | `semantic_model: X { model: ref('t') }` with measures + dimensions | **Overlay** at `/X.yaml` with `measures`, `columns` (computed), `joins` | The `model:` ref resolves to a manifest table. | | `semantic_model: X { model: source('s','t') }` | **Overlay** at `/X.yaml` over table `t`. | Same shape; `source()` still resolves to a physical table. | @@ -23,11 +23,11 @@ A MetricFlow `semantic_model` maps to an SL source; MetricFlow `measures` map to | `metrics: [{ type: simple, filter: }]` | **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`. | | `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. | | `metrics: [{ type: ratio, type_params: { numerator, denominator } }]` | Same as derived; `expr: "numerator / NULLIF(denominator, 0)"` if no explicit expr | Safe-division by default. | -| `metrics: [{ type: cumulative, type_params: { window, grain_to_date } }]` | **Standalone** source with a window-function SQL; reference the resulting column as a normal measure | KLO SL has no first-class cumulative primitive (spec Non-goals). | -| `metrics: [{ type: conversion }]` | **Flag for human** — do NOT write. Emit a wiki note describing the intended semantics. | No KLO equivalent in v1. | +| `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). | +| `metrics: [{ type: conversion }]` | **Flag for human** — do NOT write. Emit a wiki note describing the intended semantics. | No KTX equivalent in v1. | | Metric not mappable | Wiki page `-definition.md` with the full YAML body quoted | Capture the intent even if we can't emit SL. | -Type map: MetricFlow `time` to KLO `time`; `categorical` to `string`; `number` to `number`; `boolean` to `boolean`. Follow `expr` over `name` when both differ — `expr` is the physical column. +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. ## Flattening `extends:` @@ -55,7 +55,7 @@ After every `sl_write_source`, call `sl_validate`. The warehouse will reject inv ## Cumulative metrics — sql-standalone fallback -KLO 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: +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: ```yaml # MetricFlow input: @@ -68,7 +68,7 @@ metrics: ``` ```yaml -# KLO standalone output: +# KTX standalone output: name: cum_revenue_7d source_type: sql sql: | @@ -106,7 +106,7 @@ Do NOT emit SL for this. Instead: - Write a wiki page at `knowledge/global/-intent.md` quoting the full YAML body and a one-line explanation of the intended semantics (base event → conversion event within window). - Call `emit_unmapped_fallback` with `rawPath` set to the MetricFlow file path, `reason: "conversion_metric_unsupported"`, and `fallback: "flagged"`. -When KLO SL gains conversion primitives, re-ingesting will find the prior wiki note (via `priorProvenance`) and replace it with an SL source. +When KTX SL gains conversion primitives, re-ingesting will find the prior wiki note (via `priorProvenance`) and replace it with an SL source. ## Provenance markers @@ -137,7 +137,7 @@ semantic_models: ``` ```yaml -# KLO overlay at /orders.yaml: +# KTX overlay at /orders.yaml: # name: orders description: Order fact table. @@ -179,7 +179,7 @@ metrics: ``` ```yaml -# KLO overlay at /orders_ext.yaml (one file; inheritance flattened): +# KTX overlay at /orders_ext.yaml (one file; inheritance flattened): # # # @@ -217,7 +217,7 @@ metrics: metrics: [{name: revenue}, {name: cost}] ``` -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 KLO 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: +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: ```yaml # /sales.yaml diff --git a/packages/context/skills/notion_synthesize/SKILL.md b/packages/context/skills/notion_synthesize/SKILL.md index 3de8ee0d..c3fdf3cd 100644 --- a/packages/context/skills/notion_synthesize/SKILL.md +++ b/packages/context/skills/notion_synthesize/SKILL.md @@ -1,6 +1,6 @@ --- name: notion_synthesize -description: Synthesize durable KLO 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. +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. callers: [memory_agent] --- diff --git a/packages/context/skills/sl/SKILL.md b/packages/context/skills/sl/SKILL.md index 128c579c..2719b9d4 100644 --- a/packages/context/skills/sl/SKILL.md +++ b/packages/context/skills/sl/SKILL.md @@ -1,11 +1,11 @@ --- name: sl -description: KLO'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 `semantic_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). +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 `semantic_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). --- # Semantic Layer -KLO'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. +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. This skill covers two parts: - **Part 1** — Schema reference (what an SL source looks like). diff --git a/packages/context/skills/sl_capture/SKILL.md b/packages/context/skills/sl_capture/SKILL.md index af8cee15..1ad92174 100644 --- a/packages/context/skills/sl_capture/SKILL.md +++ b/packages/context/skills/sl_capture/SKILL.md @@ -1,6 +1,6 @@ --- name: sl_capture -description: How to capture new reusable patterns into KLO'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. +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. callers: [memory_agent] --- diff --git a/packages/context/src/agent/agent-runner.service.ts b/packages/context/src/agent/agent-runner.service.ts index 75bdbefb..c394fd75 100644 --- a/packages/context/src/agent/agent-runner.service.ts +++ b/packages/context/src/agent/agent-runner.service.ts @@ -1,7 +1,7 @@ -import { KloMessageBuilder, type KloLlmProvider, type KloModelRole } from '@klo/llm'; +import { KtxMessageBuilder, type KtxLlmProvider, type KtxModelRole } from '@ktx/llm'; import { generateText, stepCountIs, type TelemetrySettings, type Tool } from 'ai'; -import { noopLogger, type KloLogger } from '../core/index.js'; -import { summarizeKloLlmDebugRequest, type KloLlmDebugRequestRecorder } from '../llm/index.js'; +import { noopLogger, type KtxLogger } from '../core/index.js'; +import { summarizeKtxLlmDebugRequest, type KtxLlmDebugRequestRecorder } from '../llm/index.js'; export type RunLoopStopReason = 'budget' | 'natural' | 'error'; @@ -11,7 +11,7 @@ export interface RunLoopStepInfo { } export interface RunLoopParams { - modelRole: KloModelRole; + modelRole: KtxModelRole; systemPrompt: string; userPrompt: string; toolSet: Record; @@ -30,14 +30,14 @@ export interface AgentTelemetryPort { } export interface AgentRunnerServiceDeps { - llmProvider: KloLlmProvider; + llmProvider: KtxLlmProvider; telemetry?: AgentTelemetryPort; - debugRequestRecorder?: KloLlmDebugRequestRecorder; - logger?: KloLogger; + debugRequestRecorder?: KtxLlmDebugRequestRecorder; + logger?: KtxLogger; } export class AgentRunnerService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; constructor(private readonly deps: AgentRunnerServiceDeps) { this.logger = deps.logger ?? noopLogger; @@ -47,7 +47,7 @@ export class AgentRunnerService { let stepIndex = 0; try { const model = this.deps.llmProvider.getModel(params.modelRole); - const builder = new KloMessageBuilder(this.deps.llmProvider); + const builder = new KtxMessageBuilder(this.deps.llmProvider); const built = builder.wrapSimple({ system: params.systemPrompt, messages: [{ role: 'user', content: params.userPrompt }], @@ -56,8 +56,8 @@ export class AgentRunnerService { }); await this.deps.debugRequestRecorder?.record( - summarizeKloLlmDebugRequest({ - operationName: params.telemetryTags.operationName ?? 'klo-agent-runner', + summarizeKtxLlmDebugRequest({ + operationName: params.telemetryTags.operationName ?? 'ktx-agent-runner', source: params.telemetryTags.source, jobId: params.telemetryTags.jobId, unitKey: params.telemetryTags.unitKey, diff --git a/packages/context/src/connections/index.ts b/packages/context/src/connections/index.ts index 3f833617..ce24a2c7 100644 --- a/packages/context/src/connections/index.ts +++ b/packages/context/src/connections/index.ts @@ -1,7 +1,7 @@ export type { - KloSqlQueryExecutionInput, - KloSqlQueryExecutionResult, - KloSqlQueryExecutorPort, + KtxSqlQueryExecutionInput, + KtxSqlQueryExecutionResult, + KtxSqlQueryExecutorPort, } from './query-executor.js'; export { createDefaultLocalQueryExecutor, type DefaultLocalQueryExecutorOptions } from './local-query-executor.js'; export { normalizeQueryRows } from './query-executor.js'; @@ -17,11 +17,11 @@ export { type LocalWarehouseDescriptor, } from './local-warehouse-descriptor.js'; export { - KLO_NOTION_ORG_KNOWLEDGE_WARNING, + KTX_NOTION_ORG_KNOWLEDGE_WARNING, notionConnectionToPullConfig, parseNotionConnectionConfig, redactNotionConnectionConfig, resolveNotionAuthToken, - type KloNotionConnectionConfig, - type RedactedKloNotionConnectionConfig, + type KtxNotionConnectionConfig, + type RedactedKtxNotionConnectionConfig, } from './notion-config.js'; diff --git a/packages/context/src/connections/local-query-executor.ts b/packages/context/src/connections/local-query-executor.ts index 46d95b4b..9b5f2032 100644 --- a/packages/context/src/connections/local-query-executor.ts +++ b/packages/context/src/connections/local-query-executor.ts @@ -1,26 +1,26 @@ import { createPostgresQueryExecutor } from './postgres-query-executor.js'; import type { - KloSqlQueryExecutionInput, - KloSqlQueryExecutionResult, - KloSqlQueryExecutorPort, + KtxSqlQueryExecutionInput, + KtxSqlQueryExecutionResult, + KtxSqlQueryExecutorPort, } from './query-executor.js'; import { createSqliteQueryExecutor } from './sqlite-query-executor.js'; export interface DefaultLocalQueryExecutorOptions { - postgres?: KloSqlQueryExecutorPort; - sqlite?: KloSqlQueryExecutorPort; + postgres?: KtxSqlQueryExecutorPort; + sqlite?: KtxSqlQueryExecutorPort; } -function driverFor(input: KloSqlQueryExecutionInput): string { +function driverFor(input: KtxSqlQueryExecutionInput): string { return String(input.connection?.driver ?? '').toLowerCase(); } -export function createDefaultLocalQueryExecutor(options: DefaultLocalQueryExecutorOptions = {}): KloSqlQueryExecutorPort { +export function createDefaultLocalQueryExecutor(options: DefaultLocalQueryExecutorOptions = {}): KtxSqlQueryExecutorPort { const postgres = options.postgres ?? createPostgresQueryExecutor(); const sqlite = options.sqlite ?? createSqliteQueryExecutor(); return { - async execute(input: KloSqlQueryExecutionInput): Promise { + async execute(input: KtxSqlQueryExecutionInput): Promise { const driver = driverFor(input); if (driver === 'postgres' || driver === 'postgresql') { return postgres.execute(input); diff --git a/packages/context/src/connections/local-warehouse-descriptor.ts b/packages/context/src/connections/local-warehouse-descriptor.ts index 9a4bee8c..c2cc6516 100644 --- a/packages/context/src/connections/local-warehouse-descriptor.ts +++ b/packages/context/src/connections/local-warehouse-descriptor.ts @@ -1,4 +1,4 @@ -import type { KloProjectConnectionConfig } from '../project/config.js'; +import type { KtxProjectConnectionConfig } from '../project/config.js'; import type { ConnectionType } from './connection-type.js'; export interface LocalWarehouseDescriptor { @@ -32,7 +32,7 @@ const DRIVER_TO_CONNECTION_TYPE: Record = { export function localConnectionToWarehouseDescriptor( id: string, - connection: KloProjectConnectionConfig | undefined, + connection: KtxProjectConnectionConfig | undefined, ): LocalWarehouseDescriptor | null { if (!connection) { return null; @@ -74,7 +74,7 @@ export function localConnectionToWarehouseDescriptor( return info; } -export function localConnectionTypeForConfig(id: string, connection: KloProjectConnectionConfig | undefined): string { +export function localConnectionTypeForConfig(id: string, connection: KtxProjectConnectionConfig | undefined): string { const descriptor = localConnectionToWarehouseDescriptor(id, connection); if (descriptor) { return descriptor.connection_type; @@ -85,7 +85,7 @@ export function localConnectionTypeForConfig(id: string, connection: KloProjectC export function localConnectionInfoFromConfig( id: string, - connection: KloProjectConnectionConfig | undefined, + connection: KtxProjectConnectionConfig | undefined, ): LocalConnectionInfo | null { if (!connection) { return null; diff --git a/packages/context/src/connections/notion-config.test.ts b/packages/context/src/connections/notion-config.test.ts index 38772ec4..33d1e110 100644 --- a/packages/context/src/connections/notion-config.test.ts +++ b/packages/context/src/connections/notion-config.test.ts @@ -13,7 +13,7 @@ describe('standalone Notion connection config', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-notion-config-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-notion-config-')); }); afterEach(async () => { diff --git a/packages/context/src/connections/notion-config.ts b/packages/context/src/connections/notion-config.ts index 96ce453e..b09cf968 100644 --- a/packages/context/src/connections/notion-config.ts +++ b/packages/context/src/connections/notion-config.ts @@ -2,17 +2,17 @@ import { readFile } from 'node:fs/promises'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; import { type NotionPullConfig, notionPullConfigSchema } from '../ingest/adapters/notion/types.js'; -import type { KloProjectConnectionConfig } from '../project/config.js'; +import type { KtxProjectConnectionConfig } from '../project/config.js'; -export const KLO_NOTION_ORG_KNOWLEDGE_WARNING = +export const KTX_NOTION_ORG_KNOWLEDGE_WARNING = 'Anything accessible to this Notion integration can become organization knowledge.'; -type KloNotionCrawlMode = 'all_accessible' | 'selected_roots'; +type KtxNotionCrawlMode = 'all_accessible' | 'selected_roots'; -export interface KloNotionConnectionConfig extends KloProjectConnectionConfig { +export interface KtxNotionConnectionConfig extends KtxProjectConnectionConfig { driver: 'notion'; auth_token_ref: string; - crawl_mode: KloNotionCrawlMode; + crawl_mode: KtxNotionCrawlMode; root_page_ids: string[]; root_database_ids: string[]; root_data_source_ids: string[]; @@ -22,17 +22,17 @@ export interface KloNotionConnectionConfig extends KloProjectConnectionConfig { last_successful_cursor: string | null; } -export interface RedactedKloNotionConnectionConfig { +export interface RedactedKtxNotionConnectionConfig { driver: 'notion'; hasAuthToken: boolean; - crawlMode: KloNotionCrawlMode; + crawlMode: KtxNotionCrawlMode; rootPageIds: string[]; rootDatabaseIds: string[]; rootDataSourceIds: string[]; maxPagesPerRun: number; maxKnowledgeCreatesPerRun: number; maxKnowledgeUpdatesPerRun: number; - warning: typeof KLO_NOTION_ORG_KNOWLEDGE_WARNING; + warning: typeof KTX_NOTION_ORG_KNOWLEDGE_WARNING; } interface ResolveNotionTokenOptions { @@ -84,7 +84,7 @@ function boundedInteger(value: unknown, fallback: number, name: string, min: num return parsed; } -export function parseNotionConnectionConfig(raw: unknown): KloNotionConnectionConfig { +export function parseNotionConnectionConfig(raw: unknown): KtxNotionConnectionConfig { const input = record(raw); if (input.driver !== 'notion') { throw new Error('Notion connection config requires driver: notion'); @@ -135,7 +135,7 @@ export function parseNotionConnectionConfig(raw: unknown): KloNotionConnectionCo }; } -export function redactNotionConnectionConfig(config: KloNotionConnectionConfig): RedactedKloNotionConnectionConfig { +export function redactNotionConnectionConfig(config: KtxNotionConnectionConfig): RedactedKtxNotionConnectionConfig { return { driver: 'notion', hasAuthToken: Boolean(config.auth_token_ref), @@ -146,7 +146,7 @@ export function redactNotionConnectionConfig(config: KloNotionConnectionConfig): maxPagesPerRun: config.max_pages_per_run, maxKnowledgeCreatesPerRun: config.max_knowledge_creates_per_run, maxKnowledgeUpdatesPerRun: config.max_knowledge_updates_per_run, - warning: KLO_NOTION_ORG_KNOWLEDGE_WARNING, + warning: KTX_NOTION_ORG_KNOWLEDGE_WARNING, }; } @@ -179,7 +179,7 @@ export async function resolveNotionAuthToken( } export async function notionConnectionToPullConfig( - config: KloNotionConnectionConfig, + config: KtxNotionConnectionConfig, options: ResolveNotionTokenOptions = {}, ): Promise { return notionPullConfigSchema.parse({ diff --git a/packages/context/src/connections/postgres-query-executor.test.ts b/packages/context/src/connections/postgres-query-executor.test.ts index 6fc1a3e5..2c52d97e 100644 --- a/packages/context/src/connections/postgres-query-executor.test.ts +++ b/packages/context/src/connections/postgres-query-executor.test.ts @@ -45,7 +45,7 @@ describe('createPostgresQueryExecutor', () => { expect(client.connect).toHaveBeenCalledTimes(1); expect(calls[0]).toBe('BEGIN READ ONLY'); expect(calls[1]).toEqual({ - text: 'select * from (select status, count(*) as order_count from public.orders group by status) as klo_query_result limit 50', + text: 'select * from (select status, count(*) as order_count from public.orders group by status) as ktx_query_result limit 50', rowMode: 'array', }); expect(calls[2]).toBe('COMMIT'); diff --git a/packages/context/src/connections/postgres-query-executor.ts b/packages/context/src/connections/postgres-query-executor.ts index 95466be0..2ab142a5 100644 --- a/packages/context/src/connections/postgres-query-executor.ts +++ b/packages/context/src/connections/postgres-query-executor.ts @@ -1,8 +1,8 @@ import { Client, type ClientConfig } from 'pg'; import type { - KloSqlQueryExecutionInput, - KloSqlQueryExecutionResult, - KloSqlQueryExecutorPort, + KtxSqlQueryExecutionInput, + KtxSqlQueryExecutionResult, + KtxSqlQueryExecutorPort, } from './query-executor.js'; import { limitSqlForExecution } from './read-only-sql.js'; @@ -24,7 +24,7 @@ interface PostgresQueryExecutorOptions { clientFactory?: (config: ClientConfig) => PgClientLike; } -function connectionDriver(input: KloSqlQueryExecutionInput): string { +function connectionDriver(input: KtxSqlQueryExecutionInput): string { return String(input.connection?.driver ?? '').toLowerCase(); } @@ -32,10 +32,10 @@ function createDefaultClient(config: ClientConfig): PgClientLike { return new Client(config); } -export function createPostgresQueryExecutor(options: PostgresQueryExecutorOptions = {}): KloSqlQueryExecutorPort { +export function createPostgresQueryExecutor(options: PostgresQueryExecutorOptions = {}): KtxSqlQueryExecutorPort { const clientFactory = options.clientFactory ?? createDefaultClient; return { - async execute(input: KloSqlQueryExecutionInput): Promise { + async execute(input: KtxSqlQueryExecutionInput): Promise { const driver = connectionDriver(input); if (driver !== 'postgres' && driver !== 'postgresql') { throw new Error(`Local Postgres execution cannot run driver "${input.connection?.driver ?? 'unknown'}".`); @@ -52,7 +52,7 @@ export function createPostgresQueryExecutor(options: PostgresQueryExecutorOption statement_timeout: options.statementTimeoutMs ?? 30_000, query_timeout: options.queryTimeoutMs ?? 35_000, connectionTimeoutMillis: options.connectionTimeoutMs ?? 5_000, - application_name: 'klo-local-query', + application_name: 'ktx-local-query', }); await client.connect(); try { diff --git a/packages/context/src/connections/query-executor.ts b/packages/context/src/connections/query-executor.ts index f7ec2ce0..d3ca68bc 100644 --- a/packages/context/src/connections/query-executor.ts +++ b/packages/context/src/connections/query-executor.ts @@ -1,14 +1,14 @@ -import type { KloProjectConnectionConfig } from '../project/index.js'; +import type { KtxProjectConnectionConfig } from '../project/index.js'; -export interface KloSqlQueryExecutionInput { +export interface KtxSqlQueryExecutionInput { connectionId: string; projectDir?: string; - connection: KloProjectConnectionConfig | undefined; + connection: KtxProjectConnectionConfig | undefined; sql: string; maxRows?: number; } -export interface KloSqlQueryExecutionResult { +export interface KtxSqlQueryExecutionResult { headers: string[]; rows: unknown[][]; totalRows: number; @@ -16,8 +16,8 @@ export interface KloSqlQueryExecutionResult { rowCount: number | null; } -export interface KloSqlQueryExecutorPort { - execute(input: KloSqlQueryExecutionInput): Promise; +export interface KtxSqlQueryExecutorPort { + execute(input: KtxSqlQueryExecutionInput): Promise; } export function normalizeQueryRows(rows: unknown[]): unknown[][] { diff --git a/packages/context/src/connections/read-only-sql.test.ts b/packages/context/src/connections/read-only-sql.test.ts index f2fea2bb..217bf23d 100644 --- a/packages/context/src/connections/read-only-sql.test.ts +++ b/packages/context/src/connections/read-only-sql.test.ts @@ -20,7 +20,7 @@ describe('assertReadOnlySql', () => { describe('limitSqlForExecution', () => { it('wraps compiled SQL and strips trailing semicolons', () => { expect(limitSqlForExecution('select * from public.orders; ', 25)).toBe( - 'select * from (select * from public.orders) as klo_query_result limit 25', + 'select * from (select * from public.orders) as ktx_query_result limit 25', ); }); diff --git a/packages/context/src/connections/read-only-sql.ts b/packages/context/src/connections/read-only-sql.ts index f12aa661..fe71a0c3 100644 --- a/packages/context/src/connections/read-only-sql.ts +++ b/packages/context/src/connections/read-only-sql.ts @@ -18,5 +18,5 @@ export function limitSqlForExecution(sql: string, maxRows: number | undefined): if (!Number.isInteger(maxRows) || maxRows <= 0) { throw new Error('maxRows must be a positive integer.'); } - return `select * from (${trimmed}) as klo_query_result limit ${maxRows}`; + return `select * from (${trimmed}) as ktx_query_result limit ${maxRows}`; } diff --git a/packages/context/src/connections/sqlite-query-executor.test.ts b/packages/context/src/connections/sqlite-query-executor.test.ts index fc664496..3046f9bb 100644 --- a/packages/context/src/connections/sqlite-query-executor.test.ts +++ b/packages/context/src/connections/sqlite-query-executor.test.ts @@ -11,7 +11,7 @@ describe('createSqliteQueryExecutor', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-sqlite-query-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-sqlite-query-')); dbPath = join(tempDir, 'warehouse.db'); const db = new Database(dbPath); db.exec(` @@ -81,23 +81,23 @@ describe('createSqliteQueryExecutor', () => { }); it('resolves env references for SQLite database urls', async () => { - const originalDatabaseUrl = process.env.KLO_SQLITE_TEST_URL; - process.env.KLO_SQLITE_TEST_URL = `sqlite:${dbPath}`; + const originalDatabaseUrl = process.env.KTX_SQLITE_TEST_URL; + process.env.KTX_SQLITE_TEST_URL = `sqlite:${dbPath}`; try { expect( sqliteDatabasePathFromConnection({ connectionId: 'warehouse', projectDir: tempDir, - connection: { driver: 'sqlite', url: 'env:KLO_SQLITE_TEST_URL', readonly: true }, + connection: { driver: 'sqlite', url: 'env:KTX_SQLITE_TEST_URL', readonly: true }, sql: 'select 1', }), ).toBe(dbPath); } finally { if (originalDatabaseUrl === undefined) { - delete process.env.KLO_SQLITE_TEST_URL; + delete process.env.KTX_SQLITE_TEST_URL; } else { - process.env.KLO_SQLITE_TEST_URL = originalDatabaseUrl; + process.env.KTX_SQLITE_TEST_URL = originalDatabaseUrl; } } }); diff --git a/packages/context/src/connections/sqlite-query-executor.ts b/packages/context/src/connections/sqlite-query-executor.ts index 12e60afc..d32a37ba 100644 --- a/packages/context/src/connections/sqlite-query-executor.ts +++ b/packages/context/src/connections/sqlite-query-executor.ts @@ -4,16 +4,16 @@ import Database from 'better-sqlite3'; import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import type { - KloSqlQueryExecutionInput, - KloSqlQueryExecutionResult, - KloSqlQueryExecutorPort, + KtxSqlQueryExecutionInput, + KtxSqlQueryExecutionResult, + KtxSqlQueryExecutorPort, } from './query-executor.js'; import { normalizeQueryRows } from './query-executor.js'; import { limitSqlForExecution } from './read-only-sql.js'; type SqliteConnectionConfig = Record | undefined; -function connectionDriver(input: KloSqlQueryExecutionInput): string { +function connectionDriver(input: KtxSqlQueryExecutionInput): string { return String(input.connection?.driver ?? '').toLowerCase(); } @@ -49,7 +49,7 @@ function sqlitePathFromUrl(url: string): string { return url; } -export function sqliteDatabasePathFromConnection(input: KloSqlQueryExecutionInput): string { +export function sqliteDatabasePathFromConnection(input: KtxSqlQueryExecutionInput): string { const driver = connectionDriver(input); if (driver !== 'sqlite' && driver !== 'sqlite3') { throw new Error(`Local SQLite execution cannot run driver "${input.connection?.driver ?? 'unknown'}".`); @@ -70,9 +70,9 @@ export function sqliteDatabasePathFromConnection(input: KloSqlQueryExecutionInpu return isAbsolute(candidate) ? candidate : resolve(input.projectDir ?? process.cwd(), candidate); } -export function createSqliteQueryExecutor(): KloSqlQueryExecutorPort { +export function createSqliteQueryExecutor(): KtxSqlQueryExecutorPort { return { - async execute(input: KloSqlQueryExecutionInput): Promise { + async execute(input: KtxSqlQueryExecutionInput): Promise { const sql = limitSqlForExecution(input.sql, input.maxRows); const dbPath = sqliteDatabasePathFromConnection(input); const db = new Database(dbPath, { readonly: true, fileMustExist: true }); diff --git a/packages/context/src/core/config-reference.test.ts b/packages/context/src/core/config-reference.test.ts index 865c164c..f12d0bd9 100644 --- a/packages/context/src/core/config-reference.test.ts +++ b/packages/context/src/core/config-reference.test.ts @@ -2,33 +2,33 @@ import { mkdir, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { resolveKloConfigReference, resolveKloHomePath } from './config-reference.js'; +import { resolveKtxConfigReference, resolveKtxHomePath } from './config-reference.js'; -describe('KLO config references', () => { +describe('KTX config references', () => { it('resolves env references without returning empty values', () => { - expect(resolveKloConfigReference('env:AI_GATEWAY_API_KEY', { AI_GATEWAY_API_KEY: ' gateway-key ' })).toBe( + expect(resolveKtxConfigReference('env:AI_GATEWAY_API_KEY', { AI_GATEWAY_API_KEY: ' gateway-key ' })).toBe( 'gateway-key', ); - expect(resolveKloConfigReference('env:AI_GATEWAY_API_KEY', { AI_GATEWAY_API_KEY: ' ' })).toBeUndefined(); - expect(resolveKloConfigReference('env:AI_GATEWAY_API_KEY', {})).toBeUndefined(); + expect(resolveKtxConfigReference('env:AI_GATEWAY_API_KEY', { AI_GATEWAY_API_KEY: ' ' })).toBeUndefined(); + expect(resolveKtxConfigReference('env:AI_GATEWAY_API_KEY', {})).toBeUndefined(); }); it('resolves file references and trims file content', async () => { - const dir = join(tmpdir(), `klo-config-reference-${process.pid}`); + const dir = join(tmpdir(), `ktx-config-reference-${process.pid}`); await mkdir(dir, { recursive: true }); const keyPath = join(dir, 'gateway-key.txt'); await writeFile(keyPath, 'file-gateway-key\n', 'utf8'); - expect(resolveKloConfigReference(`file:${keyPath}`, {})).toBe('file-gateway-key'); + expect(resolveKtxConfigReference(`file:${keyPath}`, {})).toBe('file-gateway-key'); }); it('returns literal values unchanged after trimming blank-only values', () => { - expect(resolveKloConfigReference('provider/model', {})).toBe('provider/model'); - expect(resolveKloConfigReference(' ', {})).toBeUndefined(); - expect(resolveKloConfigReference(undefined, {})).toBeUndefined(); + expect(resolveKtxConfigReference('provider/model', {})).toBe('provider/model'); + expect(resolveKtxConfigReference(' ', {})).toBeUndefined(); + expect(resolveKtxConfigReference(undefined, {})).toBeUndefined(); }); it('resolves home-prefixed paths', () => { - expect(resolveKloHomePath('~/klo/key.txt')).toContain('/klo/key.txt'); + expect(resolveKtxHomePath('~/ktx/key.txt')).toContain('/ktx/key.txt'); }); }); diff --git a/packages/context/src/core/config-reference.ts b/packages/context/src/core/config-reference.ts index ba96d41b..7aa69e69 100644 --- a/packages/context/src/core/config-reference.ts +++ b/packages/context/src/core/config-reference.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { resolve } from 'node:path'; -export function resolveKloHomePath(path: string): string { +export function resolveKtxHomePath(path: string): string { if (path === '~') { return homedir(); } @@ -14,7 +14,7 @@ export function resolveKloHomePath(path: string): string { return resolve(path); } -export function resolveKloConfigReference(value: string | undefined, env: NodeJS.ProcessEnv): string | undefined { +export function resolveKtxConfigReference(value: string | undefined, env: NodeJS.ProcessEnv): string | undefined { if (!value) { return undefined; } @@ -26,7 +26,7 @@ export function resolveKloConfigReference(value: string | undefined, env: NodeJS } if (value.startsWith('file:')) { - const filePath = resolveKloHomePath(value.slice('file:'.length).trim()); + const filePath = resolveKtxHomePath(value.slice('file:'.length).trim()); const fileValue = readFileSync(filePath, 'utf8').trim(); return fileValue.length > 0 ? fileValue : undefined; } diff --git a/packages/context/src/core/config.ts b/packages/context/src/core/config.ts index 847d30ab..21e5a277 100644 --- a/packages/context/src/core/config.ts +++ b/packages/context/src/core/config.ts @@ -1,10 +1,10 @@ -export interface KloStorageConfig { +export interface KtxStorageConfig { configDir?: string; homeDir?: string; worktreesDir?: string; } -export interface KloGitConfig { +export interface KtxGitConfig { userName: string; userEmail: string; bootstrapMessage?: string; @@ -12,31 +12,31 @@ export interface KloGitConfig { bootstrapAuthorEmail?: string; } -export interface KloCoreConfig { - storage: KloStorageConfig; - git: KloGitConfig; +export interface KtxCoreConfig { + storage: KtxStorageConfig; + git: KtxGitConfig; } -export interface KloLogger { +export interface KtxLogger { debug(message: string): void; log(message: string): void; warn(message: string): void; error(message: string, error?: unknown): void; } -export const noopLogger: KloLogger = { +export const noopLogger: KtxLogger = { debug: () => undefined, log: () => undefined, warn: () => undefined, error: () => undefined, }; -export function resolveConfigDir(config: KloCoreConfig): string { +export function resolveConfigDir(config: KtxCoreConfig): string { const homeDir = config.storage.homeDir ?? '/tmp'; - return config.storage.configDir ?? `${homeDir}/klo/config`; + return config.storage.configDir ?? `${homeDir}/ktx/config`; } -export function resolveWorktreesDir(config: KloCoreConfig): string { +export function resolveWorktreesDir(config: KtxCoreConfig): string { const homeDir = config.storage.homeDir ?? '/tmp'; return config.storage.worktreesDir ?? `${homeDir}/.worktrees`; } diff --git a/packages/context/src/core/embedding.ts b/packages/context/src/core/embedding.ts index 71bc7b09..242eb91e 100644 --- a/packages/context/src/core/embedding.ts +++ b/packages/context/src/core/embedding.ts @@ -1,4 +1,4 @@ -export interface KloEmbeddingPort { +export interface KtxEmbeddingPort { maxBatchSize: number; computeEmbedding(text: string): Promise; computeEmbeddingsBulk(texts: string[]): Promise; diff --git a/packages/context/src/core/file-store.ts b/packages/context/src/core/file-store.ts index 407b6842..28fc78a0 100644 --- a/packages/context/src/core/file-store.ts +++ b/packages/context/src/core/file-store.ts @@ -1,18 +1,18 @@ -export interface KloFileWriteResult { +export interface KtxFileWriteResult { commitHash?: string | null; [key: string]: unknown; } -export interface KloFileReadResult { +export interface KtxFileReadResult { content: string; [key: string]: unknown; } -export interface KloFileListResult { +export interface KtxFileListResult { files: string[]; } -export interface KloFileHistoryEntry { +export interface KtxFileHistoryEntry { sha?: string; message?: string; author?: string; @@ -20,7 +20,7 @@ export interface KloFileHistoryEntry { [key: string]: unknown; } -export interface KloFileStorePort { +export interface KtxFileStorePort { writeFile( path: string, content: string, @@ -28,16 +28,16 @@ export interface KloFileStorePort { authorEmail: string, commitMessage: string, options?: { skipLock?: boolean }, - ): Promise; - readFile(path: string): Promise; + ): Promise; + readFile(path: string): Promise; deleteFile( path: string, author: string, authorEmail: string, commitMessage: string, options?: { skipLock?: boolean }, - ): Promise; - listFiles(path: string, recursive?: boolean): Promise; - getFileHistory(path: string): Promise; + ): Promise; + listFiles(path: string, recursive?: boolean): Promise; + getFileHistory(path: string): Promise; forWorktree(workdir: string): TSelf; } diff --git a/packages/context/src/core/git.service.assert-worktree-clean.test.ts b/packages/context/src/core/git.service.assert-worktree-clean.test.ts index 9567e408..db7d7bd3 100644 --- a/packages/context/src/core/git.service.assert-worktree-clean.test.ts +++ b/packages/context/src/core/git.service.assert-worktree-clean.test.ts @@ -3,7 +3,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { SimpleGit } from 'simple-git'; -import type { KloCoreConfig } from './config.js'; +import type { KtxCoreConfig } from './config.js'; import { createSimpleGit } from './git-env.js'; import { GitService } from './git.service.js'; @@ -21,7 +21,7 @@ describe('GitService.assertWorktreeClean', () => { await writeFile(join(workdir, 'init'), 'init'); await git.add('.'); await git.commit('init'); - const coreConfig: KloCoreConfig = { + const coreConfig: KtxCoreConfig = { storage: { configDir: workdir, homeDir: workdir }, git: { userName: 'Test', userEmail: 't@test' }, }; diff --git a/packages/context/src/core/git.service.delete-directories.test.ts b/packages/context/src/core/git.service.delete-directories.test.ts index 1eb5ac99..b6156349 100644 --- a/packages/context/src/core/git.service.delete-directories.test.ts +++ b/packages/context/src/core/git.service.delete-directories.test.ts @@ -3,7 +3,7 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { SimpleGit } from 'simple-git'; -import type { KloCoreConfig } from './config.js'; +import type { KtxCoreConfig } from './config.js'; import { createSimpleGit } from './git-env.js'; import { GitService } from './git.service.js'; @@ -22,7 +22,7 @@ describe('GitService.deleteDirectories', () => { await git.add('.'); await git.commit('init'); - const coreConfig: KloCoreConfig = { + const coreConfig: KtxCoreConfig = { storage: { configDir: workdir, homeDir: workdir }, git: { userName: 'Test', userEmail: 't@test' }, }; diff --git a/packages/context/src/core/git.service.reset-hard.test.ts b/packages/context/src/core/git.service.reset-hard.test.ts index aa256519..e688b8b3 100644 --- a/packages/context/src/core/git.service.reset-hard.test.ts +++ b/packages/context/src/core/git.service.reset-hard.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { SimpleGit } from 'simple-git'; -import type { KloCoreConfig } from './config.js'; +import type { KtxCoreConfig } from './config.js'; import { createSimpleGit } from './git-env.js'; import { GitService } from './git.service.js'; @@ -21,7 +21,7 @@ describe('GitService.resetHardTo', () => { await writeFile(join(workdir, 'init'), 'init'); await git.add('.'); await git.commit('init'); - const coreConfig: KloCoreConfig = { + const coreConfig: KtxCoreConfig = { storage: { configDir: workdir, homeDir: workdir }, git: { userName: 'Test', userEmail: 't@test' }, }; diff --git a/packages/context/src/core/git.service.test.ts b/packages/context/src/core/git.service.test.ts index c4668a1a..308bbd4d 100644 --- a/packages/context/src/core/git.service.test.ts +++ b/packages/context/src/core/git.service.test.ts @@ -2,7 +2,7 @@ import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import type { KloCoreConfig } from './config.js'; +import type { KtxCoreConfig } from './config.js'; import { GitService } from './git.service.js'; // These tests drive a real git repo inside a temp directory — simple-git shells out to the @@ -15,7 +15,7 @@ describe('GitService', () => { beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), 'git-service-spec-')); - const coreConfig: KloCoreConfig = { + const coreConfig: KtxCoreConfig = { storage: { configDir: tempDir, homeDir: tempDir }, git: { userName: 'Test User', diff --git a/packages/context/src/core/git.service.ts b/packages/context/src/core/git.service.ts index 16388969..5da67e59 100644 --- a/packages/context/src/core/git.service.ts +++ b/packages/context/src/core/git.service.ts @@ -1,7 +1,7 @@ import { promises as fs } from 'node:fs'; import { join } from 'node:path'; import type { SimpleGit } from 'simple-git'; -import { noopLogger, resolveConfigDir, type KloCoreConfig, type KloLogger } from './config.js'; +import { noopLogger, resolveConfigDir, type KtxCoreConfig, type KtxLogger } from './config.js'; import { createSimpleGit } from './git-env.js'; export interface GitCommitInfo { @@ -32,13 +32,13 @@ export type SquashMergeResult = | { ok: false; conflict: true; conflictPaths: string[] }; export class GitService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private git!: SimpleGit; private configDir: string; constructor( - private readonly config: KloCoreConfig, - logger?: KloLogger, + private readonly config: KtxCoreConfig, + logger?: KtxLogger, ) { this.logger = logger ?? noopLogger; this.configDir = resolveConfigDir(config); @@ -73,10 +73,10 @@ export class GitService { // can rely on `revParseHead()` returning a SHA. Idempotent: skip if HEAD already exists. const head = await this.revParseHead(); if (!head) { - await this.git.commit(this.config.git.bootstrapMessage ?? 'Initialize klo project repository', { + await this.git.commit(this.config.git.bootstrapMessage ?? 'Initialize ktx project repository', { '--allow-empty': null, - '--author': `${this.config.git.bootstrapAuthor ?? 'klo system'} <${ - this.config.git.bootstrapAuthorEmail ?? 'system@klo.local' + '--author': `${this.config.git.bootstrapAuthor ?? 'ktx system'} <${ + this.config.git.bootstrapAuthorEmail ?? 'system@ktx.local' }>`, }); this.logger.log('Wrote bootstrap commit to config repo'); @@ -676,7 +676,7 @@ export class GitService { /** * Remove the worktree entry and its on-disk directory. Uses `--force` because session - * worktrees are klo-internal — a clean working tree is not required. + * worktrees are ktx-internal — a clean working tree is not required. */ async removeWorktree(path: string): Promise { try { diff --git a/packages/context/src/core/index.ts b/packages/context/src/core/index.ts index 77f68e4a..892ef98e 100644 --- a/packages/context/src/core/index.ts +++ b/packages/context/src/core/index.ts @@ -1,19 +1,19 @@ -export type { KloCoreConfig, KloGitConfig, KloLogger, KloStorageConfig } from './config.js'; +export type { KtxCoreConfig, KtxGitConfig, KtxLogger, KtxStorageConfig } from './config.js'; export { noopLogger, resolveConfigDir, resolveWorktreesDir } from './config.js'; -export { resolveKloConfigReference, resolveKloHomePath } from './config-reference.js'; -export type { KloEmbeddingPort } from './embedding.js'; +export { resolveKtxConfigReference, resolveKtxHomePath } from './config-reference.js'; +export type { KtxEmbeddingPort } from './embedding.js'; export { - REDACTED_KLO_CREDENTIAL_VALUE, - redactKloSensitiveMetadata, - redactKloSensitiveText, - redactKloSensitiveValue, + REDACTED_KTX_CREDENTIAL_VALUE, + redactKtxSensitiveMetadata, + redactKtxSensitiveText, + redactKtxSensitiveValue, } from './redaction.js'; export type { - KloFileHistoryEntry, - KloFileListResult, - KloFileReadResult, - KloFileStorePort, - KloFileWriteResult, + KtxFileHistoryEntry, + KtxFileListResult, + KtxFileReadResult, + KtxFileStorePort, + KtxFileWriteResult, } from './file-store.js'; export type { GitCommitInfo, SquashMergeResult, WorktreeEntry } from './git.service.js'; export { GitService } from './git.service.js'; diff --git a/packages/context/src/core/redaction.ts b/packages/context/src/core/redaction.ts index 545fb50e..87241742 100644 --- a/packages/context/src/core/redaction.ts +++ b/packages/context/src/core/redaction.ts @@ -1,4 +1,4 @@ -export const REDACTED_KLO_CREDENTIAL_VALUE = ''; +export const REDACTED_KTX_CREDENTIAL_VALUE = ''; const SENSITIVE_FIELD_NAME = /(password|secret|token|api[_-]?key|private[_-]?key|passphrase|credential|authorization|url)/i; const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)([^@\s/]+)(@)/gi; @@ -11,37 +11,37 @@ function isSensitiveField(key: string): boolean { return SENSITIVE_FIELD_NAME.test(key); } -export function redactKloSensitiveValue(key: string, value: unknown): unknown { +export function redactKtxSensitiveValue(key: string, value: unknown): unknown { if (isSensitiveField(key)) { - return REDACTED_KLO_CREDENTIAL_VALUE; + return REDACTED_KTX_CREDENTIAL_VALUE; } if (Array.isArray(value)) { - return value.map((item) => redactKloSensitiveValue(key, item)); + return value.map((item) => redactKtxSensitiveValue(key, item)); } if (isRecord(value)) { - return redactKloSensitiveMetadata(value); + return redactKtxSensitiveMetadata(value); } return value; } -export function redactKloSensitiveMetadata(metadata: Record): Record { +export function redactKtxSensitiveMetadata(metadata: Record): Record { const redacted: Record = {}; for (const [key, value] of Object.entries(metadata)) { if (Array.isArray(value)) { redacted[key] = value.map((item) => - isRecord(item) ? redactKloSensitiveMetadata(item) : redactKloSensitiveValue(key, item), + isRecord(item) ? redactKtxSensitiveMetadata(item) : redactKtxSensitiveValue(key, item), ); continue; } if (isRecord(value)) { - redacted[key] = redactKloSensitiveValue(key, value); + redacted[key] = redactKtxSensitiveValue(key, value); continue; } - redacted[key] = redactKloSensitiveValue(key, value); + redacted[key] = redactKtxSensitiveValue(key, value); } return redacted; } -export function redactKloSensitiveText(value: string): string { - return value.replace(URL_CREDENTIAL_PATTERN, `$1${REDACTED_KLO_CREDENTIAL_VALUE}$3`); +export function redactKtxSensitiveText(value: string): string { + return value.replace(URL_CREDENTIAL_PATTERN, `$1${REDACTED_KTX_CREDENTIAL_VALUE}$3`); } diff --git a/packages/context/src/core/session-worktree.service.test.ts b/packages/context/src/core/session-worktree.service.test.ts index 38353a38..3cb66742 100644 --- a/packages/context/src/core/session-worktree.service.test.ts +++ b/packages/context/src/core/session-worktree.service.test.ts @@ -2,7 +2,7 @@ import { mkdtemp, realpath, rm, stat } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { KloCoreConfig } from './config.js'; +import type { KtxCoreConfig } from './config.js'; import { GitService } from './git.service.js'; import { SessionWorktreeService, type WorktreeConfigPort } from './session-worktree.service.js'; @@ -20,7 +20,7 @@ describe('SessionWorktreeService', () => { homeDir = await mkdtemp(join(tmpdir(), 'sws-spec-')); homeDir = await realpath(homeDir); - const coreConfig: KloCoreConfig = { + const coreConfig: KtxCoreConfig = { storage: { configDir: homeDir, homeDir }, git: { userName: 'System User', @@ -113,7 +113,7 @@ describe('SessionWorktreeService', () => { await expect(stat(session.workdir)).resolves.toBeTruthy(); const { readFile } = await import('node:fs/promises'); - const raw = await readFile(join(session.workdir, '.klo-outcome'), 'utf-8'); + const raw = await readFile(join(session.workdir, '.ktx-outcome'), 'utf-8'); const parsed = JSON.parse(raw); expect(parsed.outcome).toBe('conflict'); expect(parsed.chatId).toBe('chat-cleanup-conflict'); diff --git a/packages/context/src/core/session-worktree.service.ts b/packages/context/src/core/session-worktree.service.ts index 0ae43576..f001d58b 100644 --- a/packages/context/src/core/session-worktree.service.ts +++ b/packages/context/src/core/session-worktree.service.ts @@ -1,6 +1,6 @@ import { mkdir, stat, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { noopLogger, resolveWorktreesDir, type KloCoreConfig, type KloLogger } from './config.js'; +import { noopLogger, resolveWorktreesDir, type KtxCoreConfig, type KtxLogger } from './config.js'; import { GitService } from './git.service.js'; export type SessionOutcome = 'success' | 'empty' | 'conflict' | 'crash'; @@ -28,14 +28,14 @@ export interface SessionWorktree { } export interface SessionWorktreeServiceDeps> { - coreConfig: KloCoreConfig; + coreConfig: KtxCoreConfig; gitService: GitService; configService: TConfig; - logger?: KloLogger; + logger?: KtxLogger; } export class SessionWorktreeService = WorktreeConfigPort> { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private readonly worktreesRoot: string; constructor(private readonly deps: SessionWorktreeServiceDeps) { @@ -101,7 +101,7 @@ export class SessionWorktreeService ...(extra?.conflictPaths ? { conflictPaths: extra.conflictPaths } : {}), }; try { - await writeFile(join(session.workdir, '.klo-outcome'), JSON.stringify(payload, null, 2), 'utf-8'); + await writeFile(join(session.workdir, '.ktx-outcome'), JSON.stringify(payload, null, 2), 'utf-8'); } catch (error) { this.logger.warn( `cleanup(${outcome}) failed to write sentinel for ${session.chatId}: ${ diff --git a/packages/context/src/daemon/semantic-layer-compute.ts b/packages/context/src/daemon/semantic-layer-compute.ts index 6305ce1f..d6e2f0b1 100644 --- a/packages/context/src/daemon/semantic-layer-compute.ts +++ b/packages/context/src/daemon/semantic-layer-compute.ts @@ -4,21 +4,21 @@ import { URL } from 'node:url'; import { spawn } from 'node:child_process'; import type { SemanticLayerQueryInput, SemanticLayerSource } from '../sl/index.js'; -export interface KloSemanticLayerComputeQueryResult { +export interface KtxSemanticLayerComputeQueryResult { sql: string; dialect: string; columns: Array>; plan: Record; } -export interface KloSemanticLayerComputeValidationResult { +export interface KtxSemanticLayerComputeValidationResult { valid: boolean; errors: string[]; warnings: string[]; perSourceWarnings: Record; } -export interface KloSemanticLayerSourceGenerationColumnInput { +export interface KtxSemanticLayerSourceGenerationColumnInput { name: string; type: string; primaryKey?: boolean; @@ -26,15 +26,15 @@ export interface KloSemanticLayerSourceGenerationColumnInput { comment?: string | null; } -export interface KloSemanticLayerSourceGenerationTableInput { +export interface KtxSemanticLayerSourceGenerationTableInput { name: string; catalog?: string | null; db?: string | null; comment?: string | null; - columns: KloSemanticLayerSourceGenerationColumnInput[]; + columns: KtxSemanticLayerSourceGenerationColumnInput[]; } -export interface KloSemanticLayerSourceGenerationLinkInput { +export interface KtxSemanticLayerSourceGenerationLinkInput { fromTable: string; fromColumn: string; toTable: string; @@ -42,57 +42,57 @@ export interface KloSemanticLayerSourceGenerationLinkInput { relationshipType: string; } -export interface KloSemanticLayerSourceGenerationInput { - tables: KloSemanticLayerSourceGenerationTableInput[]; - links: KloSemanticLayerSourceGenerationLinkInput[]; +export interface KtxSemanticLayerSourceGenerationInput { + tables: KtxSemanticLayerSourceGenerationTableInput[]; + links: KtxSemanticLayerSourceGenerationLinkInput[]; dialect?: string; } -export interface KloSemanticLayerSourceGenerationResult { +export interface KtxSemanticLayerSourceGenerationResult { sources: Array>; sourceCount: number; } -export interface KloSemanticLayerComputePort { +export interface KtxSemanticLayerComputePort { query(input: { sources: Array | SemanticLayerSource>; query: SemanticLayerQueryInput; dialect: string; - }): Promise; + }): Promise; validateSources(input: { sources: Array | SemanticLayerSource>; dialect: string; recentlyTouched?: string[]; - }): Promise; - generateSources(input: KloSemanticLayerSourceGenerationInput): Promise; + }): Promise; + generateSources(input: KtxSemanticLayerSourceGenerationInput): Promise; } -export type KloDaemonCommand = 'semantic-query' | 'semantic-validate' | 'semantic-generate-sources'; +export type KtxDaemonCommand = 'semantic-query' | 'semantic-validate' | 'semantic-generate-sources'; -export type KloDaemonJsonRunner = ( - subcommand: KloDaemonCommand, +export type KtxDaemonJsonRunner = ( + subcommand: KtxDaemonCommand, payload: Record, ) => Promise>; -export type KloDaemonHttpJsonRunner = (path: string, payload: Record) => Promise>; +export type KtxDaemonHttpJsonRunner = (path: string, payload: Record) => Promise>; export interface PythonSemanticLayerComputeOptions { command?: string; args?: string[]; cwd?: string; env?: NodeJS.ProcessEnv; - runJson?: KloDaemonJsonRunner; + runJson?: KtxDaemonJsonRunner; } export interface HttpSemanticLayerComputeOptions { baseUrl: string; - requestJson?: KloDaemonHttpJsonRunner; + requestJson?: KtxDaemonHttpJsonRunner; } function parseJsonObject(raw: string, subcommand: string): Record { const parsed = JSON.parse(raw) as unknown; if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error(`klo-daemon ${subcommand} returned non-object JSON`); + throw new Error(`ktx-daemon ${subcommand} returned non-object JSON`); } return parsed as Record; } @@ -100,8 +100,8 @@ function parseJsonObject(raw: string, subcommand: string): Record> & Pick, -): KloDaemonJsonRunner { - return async (subcommand: KloDaemonCommand, payload: Record): Promise> => +): KtxDaemonJsonRunner { + return async (subcommand: KtxDaemonCommand, payload: Record): Promise> => new Promise((resolve, reject) => { const child = spawn(options.command, [...options.args, subcommand], { cwd: options.cwd, @@ -118,7 +118,7 @@ function runProcessJson( const stdoutText = Buffer.concat(stdout).toString('utf8').trim(); const stderrText = Buffer.concat(stderr).toString('utf8').trim(); if (code !== 0) { - reject(new Error(`klo-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); + reject(new Error(`ktx-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); return; } try { @@ -135,7 +135,7 @@ function normalizedBaseUrl(baseUrl: string): string { return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; } -function postJson(baseUrl: string): KloDaemonHttpJsonRunner { +function postJson(baseUrl: string): KtxDaemonHttpJsonRunner { return async (path, payload) => new Promise((resolve, reject) => { const target = new URL(path.replace(/^\//, ''), normalizedBaseUrl(baseUrl)); @@ -158,7 +158,7 @@ function postJson(baseUrl: string): KloDaemonHttpJsonRunner { const text = Buffer.concat(chunks).toString('utf8'); const statusCode = response.statusCode ?? 0; if (statusCode < 200 || statusCode >= 300) { - reject(new Error(`klo-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); + reject(new Error(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); return; } try { @@ -190,7 +190,7 @@ function recordArray(value: unknown): Array> { : []; } -function sourceGenerationPayload(input: KloSemanticLayerSourceGenerationInput): Record { +function sourceGenerationPayload(input: KtxSemanticLayerSourceGenerationInput): Record { return { tables: input.tables.map((table) => ({ name: table.name, @@ -216,7 +216,7 @@ function sourceGenerationPayload(input: KloSemanticLayerSourceGenerationInput): }; } -function sourceGenerationResult(raw: Record): KloSemanticLayerSourceGenerationResult { +function sourceGenerationResult(raw: Record): KtxSemanticLayerSourceGenerationResult { return { sources: recordArray(raw.sources), sourceCount: typeof raw.source_count === 'number' ? raw.source_count : recordArray(raw.sources).length, @@ -225,9 +225,9 @@ function sourceGenerationResult(raw: Record): KloSemanticLayerS export function createPythonSemanticLayerComputePort( options: PythonSemanticLayerComputeOptions = {}, -): KloSemanticLayerComputePort { +): KtxSemanticLayerComputePort { const command = options.command ?? 'python'; - const args = options.args ?? ['-m', 'klo_daemon']; + const args = options.args ?? ['-m', 'ktx_daemon']; const runJson = options.runJson ?? runProcessJson({ command, args, cwd: options.cwd, env: options.env }); return { @@ -266,7 +266,7 @@ export function createPythonSemanticLayerComputePort( export function createHttpSemanticLayerComputePort( options: HttpSemanticLayerComputeOptions, -): KloSemanticLayerComputePort { +): KtxSemanticLayerComputePort { const requestJson = options.requestJson ?? postJson(options.baseUrl); return { diff --git a/packages/context/src/index.test.ts b/packages/context/src/index.test.ts index 5845c0fc..255ad3a6 100644 --- a/packages/context/src/index.test.ts +++ b/packages/context/src/index.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { kloContextPackageInfo } from './index.js'; +import { ktxContextPackageInfo } from './index.js'; -describe('kloContextPackageInfo', () => { +describe('ktxContextPackageInfo', () => { it('identifies the context package', () => { - expect(kloContextPackageInfo).toEqual({ - name: '@klo/context', + expect(ktxContextPackageInfo).toEqual({ + name: '@ktx/context', version: '0.0.0-private', }); }); diff --git a/packages/context/src/index.ts b/packages/context/src/index.ts index 8125f9d3..d78e88d5 100644 --- a/packages/context/src/index.ts +++ b/packages/context/src/index.ts @@ -1,10 +1,10 @@ -export interface KloContextPackageInfo { - name: '@klo/context'; +export interface KtxContextPackageInfo { + name: '@ktx/context'; version: '0.0.0-private'; } -export const kloContextPackageInfo: KloContextPackageInfo = { - name: '@klo/context', +export const ktxContextPackageInfo: KtxContextPackageInfo = { + name: '@ktx/context', version: '0.0.0-private', }; @@ -36,107 +36,107 @@ export * from './prompts/index.js'; export * from './search/index.js'; export * from './sql-analysis/index.js'; export type { - KloColumnAnalysisResult, - KloColumnDescriptionPromptInput, - KloColumnEmbeddingForeignKeys, - KloColumnEmbeddingTextInput, - KloColumnSampleInput, - KloColumnSampleResult, - KloColumnSampleUpdate, - KloColumnStatsInput, - KloColumnStatsResult, - KloConnectionDriver, - KloConnectorCapabilities, - KloCredentialEnvelope, - KloCredentialEnvReference, - KloCredentialFileReference, - KloDataDictionaryColumnState, - KloDataDictionarySampleDecision, - KloDataDictionarySettings, - KloDataDictionarySkipReason, - KloDataSourceDescriptionPromptInput, - KloDescriptionCachePort, - KloDescriptionColumn, - KloDescriptionColumnTable, - KloDescriptionGenerationSettings, - KloDescriptionGeneratorOptions, - KloDescriptionSource, - KloDescriptionTableInput, - KloDescriptionUpdate, - KloEmbeddingPort as KloScanEmbeddingPort, - KloEmbeddingUpdate, - KloEnrichedColumn, - KloEnrichedRelationship, - KloEnrichedSchema, - KloEnrichedTable, - KloEnrichmentScanPhaseResult, - KloGenerateColumnDescriptionsInput, - KloGenerateDataSourceDescriptionInput, - KloGenerateTableDescriptionInput, - KloOptionalConnectorCapabilities, - KloProgressPort, - KloQueryResult as KloScanQueryResult, - KloReadOnlyQueryInput, - KloRelationshipEndpoint, - KloRelationshipSource, - KloRelationshipType, - KloRelationshipUpdate, - KloResolvedCredentialEnvelope, - KloScanArtifactPaths, - KloScanConnector, - KloScanContext, - KloScanDiffSummary, - KloScanEnrichmentSummary, - KloScanInput, - KloScanLoggerPort, - KloScanMetadataStore, - KloScanMode, - KloScanOrchestratorOptions, - KloScanOrchestratorRunInput, - KloScanOrchestratorRunResult, - KloScanRelationshipSummary, - KloScanReport, - KloScanTrigger, - KloScanWarning, - KloScanWarningCode, - KloSchemaColumn, - KloSchemaDimensionType, - KloSchemaForeignKey, - KloSchemaScope, - KloSchemaSnapshot, - KloSchemaTable, - KloSchemaTableKind, - KloSkippedRelationship, - KloStructuralScanPhaseResult, - KloStructuralSyncPlan, - KloStructuralSyncStats, - KloTableDescriptionPromptInput, - KloTableRef, - KloTableSampleInput, - KloTableSampleResult, - KloColumnTypeMapping, + KtxColumnAnalysisResult, + KtxColumnDescriptionPromptInput, + KtxColumnEmbeddingForeignKeys, + KtxColumnEmbeddingTextInput, + KtxColumnSampleInput, + KtxColumnSampleResult, + KtxColumnSampleUpdate, + KtxColumnStatsInput, + KtxColumnStatsResult, + KtxConnectionDriver, + KtxConnectorCapabilities, + KtxCredentialEnvelope, + KtxCredentialEnvReference, + KtxCredentialFileReference, + KtxDataDictionaryColumnState, + KtxDataDictionarySampleDecision, + KtxDataDictionarySettings, + KtxDataDictionarySkipReason, + KtxDataSourceDescriptionPromptInput, + KtxDescriptionCachePort, + KtxDescriptionColumn, + KtxDescriptionColumnTable, + KtxDescriptionGenerationSettings, + KtxDescriptionGeneratorOptions, + KtxDescriptionSource, + KtxDescriptionTableInput, + KtxDescriptionUpdate, + KtxEmbeddingPort as KtxScanEmbeddingPort, + KtxEmbeddingUpdate, + KtxEnrichedColumn, + KtxEnrichedRelationship, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxEnrichmentScanPhaseResult, + KtxGenerateColumnDescriptionsInput, + KtxGenerateDataSourceDescriptionInput, + KtxGenerateTableDescriptionInput, + KtxOptionalConnectorCapabilities, + KtxProgressPort, + KtxQueryResult as KtxScanQueryResult, + KtxReadOnlyQueryInput, + KtxRelationshipEndpoint, + KtxRelationshipSource, + KtxRelationshipType, + KtxRelationshipUpdate, + KtxResolvedCredentialEnvelope, + KtxScanArtifactPaths, + KtxScanConnector, + KtxScanContext, + KtxScanDiffSummary, + KtxScanEnrichmentSummary, + KtxScanInput, + KtxScanLoggerPort, + KtxScanMetadataStore, + KtxScanMode, + KtxScanOrchestratorOptions, + KtxScanOrchestratorRunInput, + KtxScanOrchestratorRunResult, + KtxScanRelationshipSummary, + KtxScanReport, + KtxScanTrigger, + KtxScanWarning, + KtxScanWarningCode, + KtxSchemaColumn, + KtxSchemaDimensionType, + KtxSchemaForeignKey, + KtxSchemaScope, + KtxSchemaSnapshot, + KtxSchemaTable, + KtxSchemaTableKind, + KtxSkippedRelationship, + KtxStructuralScanPhaseResult, + KtxStructuralSyncPlan, + KtxStructuralSyncStats, + KtxTableDescriptionPromptInput, + KtxTableRef, + KtxTableSampleInput, + KtxTableSampleResult, + KtxColumnTypeMapping, } from './scan/index.js'; export { - appendKloWordLimitInstruction, - buildKloColumnDescriptionPrompt, - buildKloColumnEmbeddingText, - buildKloDataSourceDescriptionPrompt, - buildKloTableDescriptionPrompt, - createKloConnectorCapabilities, - defaultKloDataDictionarySettings, - inferKloDimensionType, - isKloDataDictionaryCandidate, - kloColumnTypeMappingFromNative, - KloDescriptionGenerator, - KloScanOrchestrator, - normalizeKloNativeType, - REDACTED_KLO_CREDENTIAL_VALUE, - redactKloCredentialEnvelope, - redactKloCredentialValue, - redactKloScanMetadata, - redactKloScanReport, - redactKloScanWarning, - shouldKloSampleColumnForDictionary, + appendKtxWordLimitInstruction, + buildKtxColumnDescriptionPrompt, + buildKtxColumnEmbeddingText, + buildKtxDataSourceDescriptionPrompt, + buildKtxTableDescriptionPrompt, + createKtxConnectorCapabilities, + defaultKtxDataDictionarySettings, + inferKtxDimensionType, + isKtxDataDictionaryCandidate, + ktxColumnTypeMappingFromNative, + KtxDescriptionGenerator, + KtxScanOrchestrator, + normalizeKtxNativeType, + REDACTED_KTX_CREDENTIAL_VALUE, + redactKtxCredentialEnvelope, + redactKtxCredentialValue, + redactKtxScanMetadata, + redactKtxScanReport, + redactKtxScanWarning, + shouldKtxSampleColumnForDictionary, } from './scan/index.js'; export * from './skills/index.js'; export * from './sl/index.js'; diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.test.ts b/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.test.ts index b8df30a2..ce83e974 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.test.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { DbtParsedTable } from './parse-schema.js'; -import { findMatchingKloTable, matchDbtTables, type DbtHostTableLite } from './match-tables.js'; +import { findMatchingKtxTable, matchDbtTables, type DbtHostTableLite } from './match-tables.js'; const hostTables: DbtHostTableLite[] = [ { id: '1', name: 'orders', catalog: 'warehouse', db: 'analytics', columns: [{ id: 'c1', name: 'id' }] }, @@ -23,20 +23,20 @@ function table(input: Partial): DbtParsedTable { describe('dbt descriptions table matching', () => { it('uses schema plus name first and checks catalog when dbt database is present', () => { expect( - findMatchingKloTable(table({ database: 'warehouse', schema: 'analytics' }), hostTables, null)?.id, + findMatchingKtxTable(table({ database: 'warehouse', schema: 'analytics' }), hostTables, null)?.id, ).toBe('1'); }); it('does not fall back to name-only for source tables', () => { - expect(findMatchingKloTable(table({ resourceType: 'source' }), hostTables, null)).toBeUndefined(); + expect(findMatchingKtxTable(table({ resourceType: 'source' }), hostTables, null)).toBeUndefined(); }); it('uses targetSchema for models and name-only only when unique', () => { - expect(findMatchingKloTable(table({ resourceType: 'model' }), hostTables, 'staging')?.id).toBe('2'); - expect(findMatchingKloTable(table({ name: 'customers', resourceType: 'model' }), hostTables, null)?.id).toBe( + expect(findMatchingKtxTable(table({ resourceType: 'model' }), hostTables, 'staging')?.id).toBe('2'); + expect(findMatchingKtxTable(table({ name: 'customers', resourceType: 'model' }), hostTables, null)?.id).toBe( '3', ); - expect(findMatchingKloTable(table({ resourceType: 'model' }), hostTables, null)).toBeUndefined(); + expect(findMatchingKtxTable(table({ resourceType: 'model' }), hostTables, null)).toBeUndefined(); }); it('summarizes matched columns and descriptions', () => { diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.ts b/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.ts index 08da04f6..ca8414ae 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/match-tables.ts @@ -29,7 +29,7 @@ export function matchDbtTables( targetSchema?: string | null, ): DbtTableMatch[] { return dbtTables.map((dbtTable) => { - const hostTable = findMatchingKloTable(dbtTable, hostTables, targetSchema); + const hostTable = findMatchingKtxTable(dbtTable, hostTables, targetSchema); if (!hostTable) { return { @@ -63,7 +63,7 @@ export function matchDbtTables( }); } -export function findMatchingKloTable( +export function findMatchingKtxTable( dbtTable: DbtParsedTable, hostTables: DbtHostTableLite[], targetSchema?: string | null, diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/parse-schema.ts b/packages/context/src/ingest/adapters/dbt-descriptions/parse-schema.ts index 86b92ba7..a8f07a72 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/parse-schema.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/parse-schema.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; import { parse as parseYaml } from 'yaml'; -import { type KloLogger, noopLogger } from '../../../core/index.js'; +import { type KtxLogger, noopLogger } from '../../../core/index.js'; import { resolveJinjaVariables } from '../../dbt-shared/project-vars.js'; export interface DbtParsedColumn { @@ -65,7 +65,7 @@ interface ParseDbtSchemaOptions { path?: string; variables?: Map; projectName?: string | null; - logger?: KloLogger; + logger?: KtxLogger; } interface DbtSchemaYaml { @@ -133,7 +133,7 @@ export function parseDbtSchemaFile(content: string, options: ParseDbtSchemaOptio export function parseDbtSchemaFiles( files: DbtSchemaFile[], variables?: Map, - options: { projectName?: string | null; logger?: KloLogger } = {}, + options: { projectName?: string | null; logger?: KtxLogger } = {}, ): DbtSchemaParseResult { return new DbtSchemaParser(options.logger ?? noopLogger).parseFiles(files, variables, options.projectName ?? null); } @@ -147,7 +147,7 @@ export function computeDbtSchemaHash(files: DbtSchemaFile[]): string { } class DbtSchemaParser { - constructor(private readonly logger: KloLogger) {} + constructor(private readonly logger: KtxLogger) {} parseFile(yamlContent: string, options: ParseDbtSchemaOptions = {}): DbtSchemaParseResult { this.logger.debug(`Parsing schema file: ${options.path ?? 'unknown'}`); diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/to-description-updates.ts b/packages/context/src/ingest/adapters/dbt-descriptions/to-description-updates.ts index b63c6e74..70c895be 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/to-description-updates.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/to-description-updates.ts @@ -1,10 +1,10 @@ -import type { KloDescriptionUpdate } from '../../../scan/enrichment-types.js'; -import { findMatchingKloTable, type DbtHostTableLite } from './match-tables.js'; +import type { KtxDescriptionUpdate } from '../../../scan/enrichment-types.js'; +import { findMatchingKtxTable, type DbtHostTableLite } from './match-tables.js'; import type { DbtSchemaParseResult } from './parse-schema.js'; export interface DbtDescriptionUpdates { - dbt: KloDescriptionUpdate[]; - aiInvalidations: KloDescriptionUpdate[]; + dbt: KtxDescriptionUpdate[]; + aiInvalidations: KtxDescriptionUpdate[]; } export function toDescriptionUpdates(input: { @@ -13,11 +13,11 @@ export function toDescriptionUpdates(input: { hostTables: DbtHostTableLite[]; targetSchema: string | null; }): DbtDescriptionUpdates { - const dbt: KloDescriptionUpdate[] = []; - const aiInvalidations: KloDescriptionUpdate[] = []; + const dbt: KtxDescriptionUpdate[] = []; + const aiInvalidations: KtxDescriptionUpdate[] = []; for (const dbtTable of input.parseResult.tables) { - const hostTable = findMatchingKloTable(dbtTable, input.hostTables, input.targetSchema); + const hostTable = findMatchingKtxTable(dbtTable, input.hostTables, input.targetSchema); if (!hostTable) { continue; } diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/to-metadata-updates.ts b/packages/context/src/ingest/adapters/dbt-descriptions/to-metadata-updates.ts index ce7c7d82..6ebe4a8f 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/to-metadata-updates.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/to-metadata-updates.ts @@ -1,5 +1,5 @@ -import type { KloMetadataUpdate } from '../../../scan/enrichment-types.js'; -import { findMatchingKloTable, type DbtHostTableLite } from './match-tables.js'; +import type { KtxMetadataUpdate } from '../../../scan/enrichment-types.js'; +import { findMatchingKtxTable, type DbtHostTableLite } from './match-tables.js'; import type { DbtSchemaParseResult } from './parse-schema.js'; export function toMetadataUpdates(input: { @@ -7,11 +7,11 @@ export function toMetadataUpdates(input: { parseResult: DbtSchemaParseResult; hostTables: DbtHostTableLite[]; targetSchema: string | null; -}): KloMetadataUpdate[] { - const updates: KloMetadataUpdate[] = []; +}): KtxMetadataUpdate[] { + const updates: KtxMetadataUpdate[] = []; for (const dbtTable of input.parseResult.tables) { - const hostTable = findMatchingKloTable(dbtTable, input.hostTables, input.targetSchema); + const hostTable = findMatchingKtxTable(dbtTable, input.hostTables, input.targetSchema); if (!hostTable) { continue; } diff --git a/packages/context/src/ingest/adapters/dbt-descriptions/to-relationship-updates.ts b/packages/context/src/ingest/adapters/dbt-descriptions/to-relationship-updates.ts index d09ce05f..9f649434 100644 --- a/packages/context/src/ingest/adapters/dbt-descriptions/to-relationship-updates.ts +++ b/packages/context/src/ingest/adapters/dbt-descriptions/to-relationship-updates.ts @@ -1,9 +1,9 @@ -import type { KloJoinUpdate } from '../../../scan/enrichment-types.js'; +import type { KtxJoinUpdate } from '../../../scan/enrichment-types.js'; import type { DbtHostTableLite } from './match-tables.js'; import type { DbtSchemaParseResult } from './parse-schema.js'; export interface DbtRelationshipUpdates { - joins: KloJoinUpdate[]; + joins: KtxJoinUpdate[]; skippedNoMatch: number; } @@ -19,7 +19,7 @@ export function toRelationshipUpdates(input: { tablesByName.set(table.name.toLowerCase(), table); } - const joins: KloJoinUpdate[] = []; + const joins: KtxJoinUpdate[] = []; let skippedNoMatch = 0; for (const relationship of input.parseResult.relationships) { diff --git a/packages/context/src/ingest/adapters/dbt/dbt.adapter.ts b/packages/context/src/ingest/adapters/dbt/dbt.adapter.ts index feb31b28..8ad71ac4 100644 --- a/packages/context/src/ingest/adapters/dbt/dbt.adapter.ts +++ b/packages/context/src/ingest/adapters/dbt/dbt.adapter.ts @@ -31,7 +31,7 @@ export class DbtSourceAdapter implements SourceAdapter { } await fetchDbtRepo({ config, - cacheDir: join(this.options.homeDir ?? '.klo/cache', 'dbt', ctx.connectionId), + cacheDir: join(this.options.homeDir ?? '.ktx/cache', 'dbt', ctx.connectionId), stagedDir, }); } diff --git a/packages/context/src/ingest/adapters/dbt/fetch.test.ts b/packages/context/src/ingest/adapters/dbt/fetch.test.ts index 8905d1af..eebff7c6 100644 --- a/packages/context/src/ingest/adapters/dbt/fetch.test.ts +++ b/packages/context/src/ingest/adapters/dbt/fetch.test.ts @@ -8,7 +8,7 @@ describe('fetchDbtRepo', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-dbt-fetch-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-dbt-fetch-')); }); afterEach(async () => { diff --git a/packages/context/src/ingest/adapters/historic-sql/postgres-pgss-query-history-reader.ts b/packages/context/src/ingest/adapters/historic-sql/postgres-pgss-query-history-reader.ts index c0c7dd70..116233dc 100644 --- a/packages/context/src/ingest/adapters/historic-sql/postgres-pgss-query-history-reader.ts +++ b/packages/context/src/ingest/adapters/historic-sql/postgres-pgss-query-history-reader.ts @@ -4,7 +4,7 @@ import { HistoricSqlVersionUnsupportedError, } from './errors.js'; import type { - KloPostgresQueryClient, + KtxPostgresQueryClient, PostgresPgssProbeResult, PostgresPgssReader, PostgresPgssRow, @@ -58,19 +58,19 @@ const POSTGRES_EXTENSION_REMEDIATION = [ const POSTGRES_GRANTS_REMEDIATION = 'GRANT pg_read_all_stats TO ;'; -function queryClient(client: unknown): KloPostgresQueryClient { +function queryClient(client: unknown): KtxPostgresQueryClient { if ( client && typeof client === 'object' && 'executeQuery' in client && typeof (client as { executeQuery?: unknown }).executeQuery === 'function' ) { - return client as KloPostgresQueryClient; + return client as KtxPostgresQueryClient; } throw new Error('Historic SQL Postgres PGSS reader requires a query client with executeQuery(sql, params?)'); } -async function execute(client: KloPostgresQueryClient, sql: string, params?: unknown[]): Promise { +async function execute(client: KtxPostgresQueryClient, sql: string, params?: unknown[]): Promise { const result = await client.executeQuery(sql, params); if ('error' in result && typeof result.error === 'string' && result.error.length > 0) { throw new Error(result.error); diff --git a/packages/context/src/ingest/adapters/historic-sql/stage-pgss-golden.test.ts b/packages/context/src/ingest/adapters/historic-sql/stage-pgss-golden.test.ts index 329e6dcd..98a907fd 100644 --- a/packages/context/src/ingest/adapters/historic-sql/stage-pgss-golden.test.ts +++ b/packages/context/src/ingest/adapters/historic-sql/stage-pgss-golden.test.ts @@ -4,7 +4,7 @@ import { dirname, join, relative } from 'node:path'; import { describe, expect, it } from 'vitest'; import type { SqlAnalysisPort } from '../../../sql-analysis/index.js'; import { stagePgStatStatementsTemplates, writePgssBaselineAtomic, type PgssBaseline } from './stage-pgss.js'; -import type { HistoricSqlPullConfig, KloPostgresQueryClient, PostgresPgssReader, PostgresPgssRow } from './types.js'; +import type { HistoricSqlPullConfig, KtxPostgresQueryClient, PostgresPgssReader, PostgresPgssRow } from './types.js'; const FIXTURE_ROOT = join(__dirname, '__fixtures__/postgres'); @@ -45,7 +45,7 @@ async function tempDir(prefix: string): Promise { return mkdtemp(join(tmpdir(), prefix)); } -function fakePgClient(): KloPostgresQueryClient { +function fakePgClient(): KtxPostgresQueryClient { return { async executeQuery() { return { headers: [], rows: [] }; diff --git a/packages/context/src/ingest/adapters/historic-sql/stage-pgss.test.ts b/packages/context/src/ingest/adapters/historic-sql/stage-pgss.test.ts index 7589f8a7..901a0ae2 100644 --- a/packages/context/src/ingest/adapters/historic-sql/stage-pgss.test.ts +++ b/packages/context/src/ingest/adapters/historic-sql/stage-pgss.test.ts @@ -11,7 +11,7 @@ import { type PgssBaseline, } from './stage-pgss.js'; import { historicSqlManifestSchema, historicSqlMetadataSchema, historicSqlUsageSchema } from './types.js'; -import type { KloPostgresQueryClient, PostgresPgssReader, PostgresPgssRow } from './types.js'; +import type { KtxPostgresQueryClient, PostgresPgssReader, PostgresPgssRow } from './types.js'; async function tempDir(prefix: string): Promise { return mkdtemp(join(tmpdir(), prefix)); @@ -21,7 +21,7 @@ async function readJson(root: string, relPath: string): Promise { return JSON.parse(await readFile(join(root, relPath), 'utf-8')) as T; } -function fakePgClient(): KloPostgresQueryClient { +function fakePgClient(): KtxPostgresQueryClient { return { async executeQuery() { return { headers: [], rows: [] }; diff --git a/packages/context/src/ingest/adapters/historic-sql/stage-pgss.ts b/packages/context/src/ingest/adapters/historic-sql/stage-pgss.ts index 75a3e18f..f33fa6db 100644 --- a/packages/context/src/ingest/adapters/historic-sql/stage-pgss.ts +++ b/packages/context/src/ingest/adapters/historic-sql/stage-pgss.ts @@ -10,7 +10,7 @@ import { type HistoricSqlMetadata, type HistoricSqlPullConfig, type HistoricSqlUsage, - type KloPostgresQueryClient, + type KtxPostgresQueryClient, type PostgresPgssAggregateRow, type PostgresPgssReader, type PostgresPgssRow, @@ -43,7 +43,7 @@ export type PgssBaseline = z.infer; export interface StagePgStatStatementsTemplatesInput { stagedDir: string; connectionId: string; - queryClient: KloPostgresQueryClient; + queryClient: KtxPostgresQueryClient; reader: PostgresPgssReader; sqlAnalysis: SqlAnalysisPort; pullConfig: HistoricSqlPullConfig; @@ -95,7 +95,7 @@ function pgssTemplateId(row: Pick): string } export function pgssBaselinePath(rootDir: string | undefined, connectionId: string): string { - return join(rootDir ?? join(process.cwd(), '.klo/cache/historic-sql'), connectionId, 'pgss-baseline.json'); + return join(rootDir ?? join(process.cwd(), '.ktx/cache/historic-sql'), connectionId, 'pgss-baseline.json'); } export async function readPgssBaseline(path: string): Promise { diff --git a/packages/context/src/ingest/adapters/historic-sql/types.ts b/packages/context/src/ingest/adapters/historic-sql/types.ts index 81104cf5..0cd3d01a 100644 --- a/packages/context/src/ingest/adapters/historic-sql/types.ts +++ b/packages/context/src/ingest/adapters/historic-sql/types.ts @@ -45,7 +45,7 @@ export interface HistoricSqlQueryHistoryReader { ): AsyncIterable; } -export interface KloPostgresQueryClient { +export interface KtxPostgresQueryClient { executeQuery(sql: string, params?: unknown[]): Promise<{ headers: string[]; rows: unknown[][]; totalRows?: number }>; } @@ -61,9 +61,9 @@ export interface PostgresPgssSnapshot { } export interface PostgresPgssReader { - probe(client: KloPostgresQueryClient): Promise; + probe(client: KtxPostgresQueryClient): Promise; readSnapshot( - client: KloPostgresQueryClient, + client: KtxPostgresQueryClient, options: { minCalls: number; maxTemplates: number }, ): Promise; } @@ -101,7 +101,7 @@ export interface HistoricSqlSourceAdapterDeps { reader: HistoricSqlQueryHistoryReader; queryClient: unknown; postgresReader?: PostgresPgssReader; - postgresQueryClient?: KloPostgresQueryClient; + postgresQueryClient?: KtxPostgresQueryClient; postgresBaselineRootDir?: string; now?: () => Date; onPullSucceeded?: (ctx: { diff --git a/packages/context/src/ingest/adapters/live-database/chunk.test.ts b/packages/context/src/ingest/adapters/live-database/chunk.test.ts index 259d7a9c..2e38be9a 100644 --- a/packages/context/src/ingest/adapters/live-database/chunk.test.ts +++ b/packages/context/src/ingest/adapters/live-database/chunk.test.ts @@ -2,11 +2,11 @@ import { mkdtemp } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import type { KloSchemaSnapshot } from '../../../scan/types.js'; +import type { KtxSchemaSnapshot } from '../../../scan/types.js'; import { chunkLiveDatabaseStagedDir } from './chunk.js'; import { liveDatabaseTablePath, writeLiveDatabaseSnapshot } from './stage.js'; -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'conn-1', driver: 'postgres', @@ -60,7 +60,7 @@ function snapshot(): KloSchemaSnapshot { describe('chunkLiveDatabaseStagedDir', () => { it('emits one work unit per table on the first run', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-chunk-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-chunk-')); await writeLiveDatabaseSnapshot(dir, snapshot()); const result = await chunkLiveDatabaseStagedDir(dir); @@ -75,7 +75,7 @@ describe('chunkLiveDatabaseStagedDir', () => { }); it('keeps only changed tables during incremental syncs and records table evictions', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-diff-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-diff-')); await writeLiveDatabaseSnapshot(dir, snapshot()); const ordersPath = liveDatabaseTablePath({ catalog: null, db: 'public', name: 'orders' }); const customersPath = liveDatabaseTablePath({ catalog: null, db: 'public', name: 'customers' }); @@ -92,7 +92,7 @@ describe('chunkLiveDatabaseStagedDir', () => { }); it('fans out all table work units when the foreign-key index changes', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-fk-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-fk-')); await writeLiveDatabaseSnapshot(dir, snapshot()); const result = await chunkLiveDatabaseStagedDir(dir, { diff --git a/packages/context/src/ingest/adapters/live-database/chunk.ts b/packages/context/src/ingest/adapters/live-database/chunk.ts index 3348b98a..f260fb3d 100644 --- a/packages/context/src/ingest/adapters/live-database/chunk.ts +++ b/packages/context/src/ingest/adapters/live-database/chunk.ts @@ -1,8 +1,8 @@ import type { ChunkResult, DiffSet, WorkUnit } from '../../types.js'; -import type { KloSchemaTable } from '../../../scan/types.js'; +import type { KtxSchemaTable } from '../../../scan/types.js'; import { LIVE_DATABASE_FOREIGN_KEYS_FILE, LIVE_DATABASE_META_FILE, readLiveDatabaseTableFiles } from './stage.js'; -function unitKey(table: KloSchemaTable): string { +function unitKey(table: KtxSchemaTable): string { const parts = [table.catalog, table.db, table.name] .filter((part): part is string => typeof part === 'string' && part.length > 0) .map((part) => @@ -15,7 +15,7 @@ function unitKey(table: KloSchemaTable): string { return `live-database-${parts.join('-') || 'table'}`; } -function displayName(table: KloSchemaTable): string { +function displayName(table: KtxSchemaTable): string { return [table.catalog, table.db, table.name].filter(Boolean).join('.'); } diff --git a/packages/context/src/ingest/adapters/live-database/daemon-introspection.ts b/packages/context/src/ingest/adapters/live-database/daemon-introspection.ts index 48077949..531c1a66 100644 --- a/packages/context/src/ingest/adapters/live-database/daemon-introspection.ts +++ b/packages/context/src/ingest/adapters/live-database/daemon-introspection.ts @@ -2,25 +2,25 @@ import { spawn } from 'node:child_process'; import { request as httpRequest } from 'node:http'; import { request as httpsRequest } from 'node:https'; import { URL } from 'node:url'; -import type { KloProjectConnectionConfig } from '../../../project/config.js'; -import type { KloSchemaColumn, KloSchemaForeignKey, KloSchemaSnapshot, KloSchemaTable } from '../../../scan/types.js'; -import { inferKloDimensionType, normalizeKloNativeType } from '../../../scan/type-normalization.js'; +import type { KtxProjectConnectionConfig } from '../../../project/config.js'; +import type { KtxSchemaColumn, KtxSchemaForeignKey, KtxSchemaSnapshot, KtxSchemaTable } from '../../../scan/types.js'; +import { inferKtxDimensionType, normalizeKtxNativeType } from '../../../scan/type-normalization.js'; import type { LiveDatabaseIntrospectionPort } from './types.js'; -export type KloDaemonDatabaseIntrospectionCommand = 'database-introspect'; +export type KtxDaemonDatabaseIntrospectionCommand = 'database-introspect'; -export type KloDaemonDatabaseJsonRunner = ( - subcommand: KloDaemonDatabaseIntrospectionCommand, +export type KtxDaemonDatabaseJsonRunner = ( + subcommand: KtxDaemonDatabaseIntrospectionCommand, payload: Record, ) => Promise>; -export type KloDaemonDatabaseHttpJsonRunner = ( +export type KtxDaemonDatabaseHttpJsonRunner = ( path: string, payload: Record, ) => Promise>; export interface DaemonLiveDatabaseIntrospectionOptions { - connections: Record; + connections: Record; schemas?: string[]; statementTimeoutMs?: number; connectionTimeoutSeconds?: number; @@ -29,8 +29,8 @@ export interface DaemonLiveDatabaseIntrospectionOptions { cwd?: string; env?: NodeJS.ProcessEnv; baseUrl?: string; - runJson?: KloDaemonDatabaseJsonRunner; - requestJson?: KloDaemonDatabaseHttpJsonRunner; + runJson?: KtxDaemonDatabaseJsonRunner; + requestJson?: KtxDaemonDatabaseHttpJsonRunner; now?: () => Date; } @@ -39,7 +39,7 @@ const DEFAULT_SCHEMAS = ['public']; function parseJsonObject(raw: string, subcommand: string): Record { const parsed = JSON.parse(raw) as unknown; if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error(`klo-daemon ${subcommand} returned non-object JSON`); + throw new Error(`ktx-daemon ${subcommand} returned non-object JSON`); } return parsed as Record; } @@ -47,7 +47,7 @@ function parseJsonObject(raw: string, subcommand: string): Record> & Pick, -): KloDaemonDatabaseJsonRunner { +): KtxDaemonDatabaseJsonRunner { return async (subcommand, payload) => new Promise((resolve, reject) => { const child = spawn(options.command, [...options.args, subcommand], { @@ -65,7 +65,7 @@ function runProcessJson( const stdoutText = Buffer.concat(stdout).toString('utf8').trim(); const stderrText = Buffer.concat(stderr).toString('utf8').trim(); if (code !== 0) { - reject(new Error(`klo-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); + reject(new Error(`ktx-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); return; } try { @@ -82,7 +82,7 @@ function normalizedBaseUrl(baseUrl: string): string { return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; } -function postJson(baseUrl: string): KloDaemonDatabaseHttpJsonRunner { +function postJson(baseUrl: string): KtxDaemonDatabaseHttpJsonRunner { return async (path, payload) => new Promise((resolve, reject) => { const target = new URL(path.replace(/^\//, ''), normalizedBaseUrl(baseUrl)); @@ -105,7 +105,7 @@ function postJson(baseUrl: string): KloDaemonDatabaseHttpJsonRunner { const text = Buffer.concat(chunks).toString('utf8'); const statusCode = response.statusCode ?? 0; if (statusCode < 200 || statusCode >= 300) { - reject(new Error(`klo-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); + reject(new Error(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); return; } try { @@ -135,7 +135,7 @@ function recordArray(value: unknown): Array> { function requiredString(value: unknown, field: string): string { if (typeof value !== 'string' || value.length === 0) { - throw new Error(`klo-daemon database introspection response is missing string field ${field}`); + throw new Error(`ktx-daemon database introspection response is missing string field ${field}`); } return value; } @@ -154,9 +154,9 @@ function normalizeDriver(driver: unknown): string { } function requirePostgresConnection( - connections: Record, + connections: Record, connectionId: string, -): KloProjectConnectionConfig & { url: string } { +): KtxProjectConnectionConfig & { url: string } { const connection = connections[connectionId]; const driver = normalizeDriver(connection?.driver); if (driver !== 'postgres') { @@ -168,23 +168,23 @@ function requirePostgresConnection( if (typeof connection.url !== 'string' || connection.url.trim().length === 0) { throw new Error(`Local live-database ingest requires connections.${connectionId}.url.`); } - return connection as KloProjectConnectionConfig & { url: string }; + return connection as KtxProjectConnectionConfig & { url: string }; } -function mapColumn(raw: Record): KloSchemaColumn { +function mapColumn(raw: Record): KtxSchemaColumn { const nativeType = requiredString(raw.type, 'tables[].columns[].type'); return { name: requiredString(raw.name, 'tables[].columns[].name'), nativeType, - normalizedType: normalizeKloNativeType(nativeType), - dimensionType: inferKloDimensionType(nativeType), + normalizedType: normalizeKtxNativeType(nativeType), + dimensionType: inferKtxDimensionType(nativeType), nullable: raw.nullable !== false ? true : false, primaryKey: raw.primary_key === true, comment: nullableString(raw.comment), }; } -function mapForeignKey(raw: Record): KloSchemaForeignKey { +function mapForeignKey(raw: Record): KtxSchemaForeignKey { return { fromColumn: requiredString(raw.from_column, 'tables[].foreign_keys[].from_column'), toCatalog: null, @@ -195,7 +195,7 @@ function mapForeignKey(raw: Record): KloSchemaForeignKey { }; } -function mapTable(raw: Record): KloSchemaTable { +function mapTable(raw: Record): KtxSchemaTable { return { catalog: nullableString(raw.catalog), db: nullableString(raw.db), @@ -211,7 +211,7 @@ function mapTable(raw: Record): KloSchemaTable { function mapDaemonSnapshot( raw: Record, input: { connectionId: string; extractedAt: string; schemas: string[] }, -): KloSchemaSnapshot { +): KtxSchemaSnapshot { return { connectionId: requiredString(raw.connection_id, 'connection_id') || input.connectionId, driver: 'postgres', @@ -227,13 +227,13 @@ export function createDaemonLiveDatabaseIntrospection( ): LiveDatabaseIntrospectionPort { const schemas = options.schemas ?? DEFAULT_SCHEMAS; const command = options.command ?? 'python'; - const args = options.args ?? ['-m', 'klo_daemon']; + const args = options.args ?? ['-m', 'ktx_daemon']; const runJson = options.runJson ?? runProcessJson({ command, args, cwd: options.cwd, env: options.env }); const requestJson = options.requestJson ?? (options.baseUrl ? postJson(options.baseUrl) : undefined); const now = options.now ?? (() => new Date()); return { - async extractSchema(connectionId: string): Promise { + async extractSchema(connectionId: string): Promise { const connection = requirePostgresConnection(options.connections, connectionId); const payload = { connection_id: connectionId, diff --git a/packages/context/src/ingest/adapters/live-database/extracted-schema.test.ts b/packages/context/src/ingest/adapters/live-database/extracted-schema.test.ts index 13147ad5..69bd062d 100644 --- a/packages/context/src/ingest/adapters/live-database/extracted-schema.test.ts +++ b/packages/context/src/ingest/adapters/live-database/extracted-schema.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import type { KloSchemaSnapshot } from '../../../scan/types.js'; -import { buildLiveDatabaseTableNaturalKey, kloSchemaSnapshotToExtractedSchema } from './extracted-schema.js'; +import type { KtxSchemaSnapshot } from '../../../scan/types.js'; +import { buildLiveDatabaseTableNaturalKey, ktxSchemaSnapshotToExtractedSchema } from './extracted-schema.js'; -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'conn-1', driver: 'postgres', @@ -72,9 +72,9 @@ function snapshot(): KloSchemaSnapshot { }; } -describe('kloSchemaSnapshotToExtractedSchema', () => { +describe('ktxSchemaSnapshotToExtractedSchema', () => { it('preserves structural table, column, comment, and key metadata', () => { - const extracted = kloSchemaSnapshotToExtractedSchema(snapshot()); + const extracted = ktxSchemaSnapshotToExtractedSchema(snapshot()); expect(extracted.tables).toEqual([ { diff --git a/packages/context/src/ingest/adapters/live-database/extracted-schema.ts b/packages/context/src/ingest/adapters/live-database/extracted-schema.ts index 35f39cca..39950c69 100644 --- a/packages/context/src/ingest/adapters/live-database/extracted-schema.ts +++ b/packages/context/src/ingest/adapters/live-database/extracted-schema.ts @@ -1,4 +1,4 @@ -import type { KloSchemaSnapshot, KloSchemaTable } from '../../../scan/types.js'; +import type { KtxSchemaSnapshot, KtxSchemaTable } from '../../../scan/types.js'; export interface LiveDatabaseExtractedForeignKey { fromTable: string; @@ -30,11 +30,11 @@ export interface LiveDatabaseExtractedSchema { tables: LiveDatabaseExtractedTable[]; } -export function buildLiveDatabaseTableNaturalKey(table: Pick): string { +export function buildLiveDatabaseTableNaturalKey(table: Pick): string { return `${table.catalog ?? ''}|${table.db ?? ''}|${table.name}`; } -export function kloSchemaSnapshotToExtractedSchema(snapshot: KloSchemaSnapshot): LiveDatabaseExtractedSchema { +export function ktxSchemaSnapshotToExtractedSchema(snapshot: KtxSchemaSnapshot): LiveDatabaseExtractedSchema { return { connectionId: snapshot.connectionId, tables: snapshot.tables.map((table) => ({ diff --git a/packages/context/src/ingest/adapters/live-database/live-database.adapter.test.ts b/packages/context/src/ingest/adapters/live-database/live-database.adapter.test.ts index f3c3935c..7e7a3f74 100644 --- a/packages/context/src/ingest/adapters/live-database/live-database.adapter.test.ts +++ b/packages/context/src/ingest/adapters/live-database/live-database.adapter.test.ts @@ -39,7 +39,7 @@ describe('LiveDatabaseSourceAdapter', () => { introspection: { extractSchema }, now: () => new Date('2026-04-27T00:00:00.000Z'), }); - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-adapter-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-adapter-')); await adapter.fetch(undefined, dir, { connectionId: 'conn-1', sourceKey: 'live-database' }); diff --git a/packages/context/src/ingest/adapters/live-database/stage.test.ts b/packages/context/src/ingest/adapters/live-database/stage.test.ts index 4869a516..297071ae 100644 --- a/packages/context/src/ingest/adapters/live-database/stage.test.ts +++ b/packages/context/src/ingest/adapters/live-database/stage.test.ts @@ -10,9 +10,9 @@ import { readLiveDatabaseTableFiles, writeLiveDatabaseSnapshot, } from './stage.js'; -import type { KloSchemaSnapshot } from '../../../scan/types.js'; +import type { KtxSchemaSnapshot } from '../../../scan/types.js'; -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'conn-1', driver: 'postgres', @@ -93,7 +93,7 @@ function snapshot(): KloSchemaSnapshot { describe('live-database staged snapshot files', () => { it('writes deterministic metadata, table, and foreign-key files', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-stage-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-stage-')); await writeLiveDatabaseSnapshot(dir, snapshot()); await expect(readFile(join(dir, LIVE_DATABASE_META_FILE), 'utf8')).resolves.toContain('"connectionId": "conn-1"'); @@ -122,7 +122,7 @@ describe('live-database staged snapshot files', () => { }); it('redacts sensitive snapshot metadata before writing connection metadata', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-redacted-stage-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-redacted-stage-')); await writeLiveDatabaseSnapshot(dir, { ...snapshot(), metadata: { @@ -146,7 +146,7 @@ describe('live-database staged snapshot files', () => { }); it('returns false for a directory that is missing live database metadata', async () => { - const dir = await mkdtemp(join(tmpdir(), 'klo-live-db-empty-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-live-db-empty-')); expect(await detectLiveDatabaseStagedDir(dir)).toBe(false); }); }); diff --git a/packages/context/src/ingest/adapters/live-database/stage.ts b/packages/context/src/ingest/adapters/live-database/stage.ts index cc3af2d9..03e60f5a 100644 --- a/packages/context/src/ingest/adapters/live-database/stage.ts +++ b/packages/context/src/ingest/adapters/live-database/stage.ts @@ -2,8 +2,8 @@ import { Buffer } from 'node:buffer'; import type { Dirent } from 'node:fs'; import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'; import { join, relative } from 'node:path'; -import { redactKloSensitiveMetadata } from '../../../core/redaction.js'; -import type { KloSchemaSnapshot, KloSchemaTable, KloTableRef } from '../../../scan/types.js'; +import { redactKtxSensitiveMetadata } from '../../../core/redaction.js'; +import type { KtxSchemaSnapshot, KtxSchemaTable, KtxTableRef } from '../../../scan/types.js'; export const LIVE_DATABASE_META_FILE = 'connection.json'; export const LIVE_DATABASE_FOREIGN_KEYS_FILE = 'foreign-keys.json'; @@ -11,7 +11,7 @@ const LIVE_DATABASE_TABLES_DIR = 'tables'; interface LiveDatabaseTableFile { path: string; - table: KloSchemaTable; + table: KtxSchemaTable; } interface ForeignKeyIndexEntry { @@ -29,11 +29,11 @@ function encodePathPart(value: string | null | undefined): string { return Buffer.from(value ?? '_', 'utf8').toString('base64url'); } -function tableSortKey(table: KloTableRef): string { +function tableSortKey(table: KtxTableRef): string { return `${table.catalog ?? ''}\u0000${table.db ?? ''}\u0000${table.name}`; } -export function liveDatabaseTablePath(table: KloTableRef): string { +export function liveDatabaseTablePath(table: KtxTableRef): string { return `${LIVE_DATABASE_TABLES_DIR}/${encodePathPart(table.catalog)}.${encodePathPart(table.db)}.${encodePathPart( table.name, )}.json`; @@ -62,7 +62,7 @@ function stableJson(value: unknown): string { return `${JSON.stringify(value, null, 2)}\n`; } -function foreignKeyIndex(snapshot: KloSchemaSnapshot): ForeignKeyIndexEntry[] { +function foreignKeyIndex(snapshot: KtxSchemaSnapshot): ForeignKeyIndexEntry[] { const entries: ForeignKeyIndexEntry[] = []; for (const table of snapshot.tables) { for (const fk of table.foreignKeys) { @@ -88,7 +88,7 @@ function foreignKeyIndex(snapshot: KloSchemaSnapshot): ForeignKeyIndexEntry[] { return entries; } -export async function writeLiveDatabaseSnapshot(stagedDir: string, snapshot: KloSchemaSnapshot): Promise { +export async function writeLiveDatabaseSnapshot(stagedDir: string, snapshot: KtxSchemaSnapshot): Promise { await mkdir(join(stagedDir, LIVE_DATABASE_TABLES_DIR), { recursive: true }); const sortedTables = [...snapshot.tables].sort((a, b) => tableSortKey(a).localeCompare(tableSortKey(b))); const metadata = { @@ -96,7 +96,7 @@ export async function writeLiveDatabaseSnapshot(stagedDir: string, snapshot: Klo driver: snapshot.driver, extractedAt: snapshot.extractedAt, scope: snapshot.scope, - metadata: redactKloSensitiveMetadata(snapshot.metadata), + metadata: redactKtxSensitiveMetadata(snapshot.metadata), tableCount: sortedTables.length, }; await writeFile(join(stagedDir, LIVE_DATABASE_META_FILE), stableJson(metadata)); @@ -115,7 +115,7 @@ export async function readLiveDatabaseTableFiles(stagedDir: string): Promise path.endsWith('.json'))) { const path = `${LIVE_DATABASE_TABLES_DIR}/${file}`; const raw = await readFile(join(stagedDir, path), 'utf8'); - const parsed = JSON.parse(raw) as KloSchemaTable; + const parsed = JSON.parse(raw) as KtxSchemaTable; if (parsed && typeof parsed.name === 'string' && Array.isArray(parsed.columns)) { out.push({ path, table: parsed }); } diff --git a/packages/context/src/ingest/adapters/live-database/types.ts b/packages/context/src/ingest/adapters/live-database/types.ts index 6ce0d150..b9846b1b 100644 --- a/packages/context/src/ingest/adapters/live-database/types.ts +++ b/packages/context/src/ingest/adapters/live-database/types.ts @@ -1,7 +1,7 @@ -import type { KloSchemaSnapshot } from '../../../scan/types.js'; +import type { KtxSchemaSnapshot } from '../../../scan/types.js'; export interface LiveDatabaseIntrospectionPort { - extractSchema(connectionId: string): Promise; + extractSchema(connectionId: string): Promise; } export interface LiveDatabaseSourceAdapterDeps { diff --git a/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.test.ts b/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.test.ts index 3f1ea6a9..0da13d53 100644 --- a/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.test.ts +++ b/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.test.ts @@ -39,6 +39,6 @@ describe('createDaemonLookerTableIdentifierParser', () => { requestJson: async () => ({ results: null }), }); - await expect(parser.parse([])).rejects.toThrow('klo-daemon table identifier parser returned invalid results'); + await expect(parser.parse([])).rejects.toThrow('ktx-daemon table identifier parser returned invalid results'); }); }); diff --git a/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.ts b/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.ts index 711c9e8b..8c88912e 100644 --- a/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.ts +++ b/packages/context/src/ingest/adapters/looker/daemon-table-identifier-parser.ts @@ -7,14 +7,14 @@ import type { LookerTableIdentifierParser, } from './mapping.js'; -export type KloDaemonTableIdentifierHttpJsonRunner = ( +export type KtxDaemonTableIdentifierHttpJsonRunner = ( path: string, payload: Record, ) => Promise>; export interface DaemonLookerTableIdentifierParserOptions { baseUrl: string; - requestJson?: KloDaemonTableIdentifierHttpJsonRunner; + requestJson?: KtxDaemonTableIdentifierHttpJsonRunner; } export function createDaemonLookerTableIdentifierParser( @@ -25,7 +25,7 @@ export function createDaemonLookerTableIdentifierParser( async parse(items: LookerTableIdentifierParseItem[]): Promise> { const raw = await requestJson('/sql/parse-table-identifier', { items }); if (!raw.results || typeof raw.results !== 'object' || Array.isArray(raw.results)) { - throw new Error('klo-daemon table identifier parser returned invalid results'); + throw new Error('ktx-daemon table identifier parser returned invalid results'); } return raw.results as Record; }, @@ -36,7 +36,7 @@ function normalizedBaseUrl(baseUrl: string): string { return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; } -function postJson(baseUrl: string): KloDaemonTableIdentifierHttpJsonRunner { +function postJson(baseUrl: string): KtxDaemonTableIdentifierHttpJsonRunner { return async (path, payload) => new Promise((resolve, reject) => { const target = new URL(path.replace(/^\//, ''), normalizedBaseUrl(baseUrl)); @@ -59,13 +59,13 @@ function postJson(baseUrl: string): KloDaemonTableIdentifierHttpJsonRunner { const text = Buffer.concat(chunks).toString('utf8'); const statusCode = response.statusCode ?? 0; if (statusCode < 200 || statusCode >= 300) { - reject(new Error(`klo-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); + reject(new Error(`ktx-daemon HTTP ${path} failed with ${statusCode}: ${text}`)); return; } try { const parsed = JSON.parse(text) as unknown; if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { - reject(new Error(`klo-daemon HTTP ${path} returned non-object JSON`)); + reject(new Error(`ktx-daemon HTTP ${path} returned non-object JSON`)); return; } resolve(parsed as Record); diff --git a/packages/context/src/ingest/adapters/looker/factory.test.ts b/packages/context/src/ingest/adapters/looker/factory.test.ts index e049fc34..d68be942 100644 --- a/packages/context/src/ingest/adapters/looker/factory.test.ts +++ b/packages/context/src/ingest/adapters/looker/factory.test.ts @@ -29,7 +29,7 @@ function sdk(): LookerSdkPort { } describe('DefaultLookerConnectionClientFactory', () => { - it('resolves credentials by Looker connection id and creates a KLO Looker client', async () => { + it('resolves credentials by Looker connection id and creates a KTX Looker client', async () => { const fakeSdk = sdk(); const resolver: LookerCredentialResolver = { resolve: vi.fn().mockResolvedValue({ diff --git a/packages/context/src/ingest/adapters/looker/local-looker.adapter.ts b/packages/context/src/ingest/adapters/looker/local-looker.adapter.ts index 7736f425..c1869da9 100644 --- a/packages/context/src/ingest/adapters/looker/local-looker.adapter.ts +++ b/packages/context/src/ingest/adapters/looker/local-looker.adapter.ts @@ -1,4 +1,4 @@ -import type { KloLocalProject, KloProjectConnectionConfig } from '../../../project/index.js'; +import type { KtxLocalProject, KtxProjectConnectionConfig } from '../../../project/index.js'; import { DefaultLookerClientFactory, DefaultLookerConnectionClientFactory, @@ -19,7 +19,7 @@ function resolveEnvReference(ref: string, env: NodeJS.ProcessEnv): string | null export function lookerCredentialsFromLocalConnection( connectionId: string, - connection: KloProjectConnectionConfig | undefined, + connection: KtxProjectConnectionConfig | undefined, env: NodeJS.ProcessEnv = process.env, ) { if (!connection || String(connection.driver).toLowerCase() !== 'looker') { @@ -46,7 +46,7 @@ export function lookerCredentialsFromLocalConnection( } export function createLocalLookerCredentialResolver( - project: KloLocalProject, + project: KtxLocalProject, env: NodeJS.ProcessEnv = process.env, ): LookerCredentialResolver { return { @@ -57,7 +57,7 @@ export function createLocalLookerCredentialResolver( } export function createLocalLookerSourceAdapter( - project: KloLocalProject, + project: KtxLocalProject, env: NodeJS.ProcessEnv = process.env, ): LookerSourceAdapter { const connectionFactory = new DefaultLookerConnectionClientFactory(createLocalLookerCredentialResolver(project, env)); diff --git a/packages/context/src/ingest/adapters/looker/local-runtime-store.test.ts b/packages/context/src/ingest/adapters/looker/local-runtime-store.test.ts index 7eecfccd..3f9bbdc5 100644 --- a/packages/context/src/ingest/adapters/looker/local-runtime-store.test.ts +++ b/packages/context/src/ingest/adapters/looker/local-runtime-store.test.ts @@ -6,7 +6,7 @@ import { LocalLookerRuntimeStore } from './local-runtime-store.js'; describe('LocalLookerRuntimeStore', () => { async function store() { - const dir = await mkdtemp(join(tmpdir(), 'klo-looker-store-')); + const dir = await mkdtemp(join(tmpdir(), 'ktx-looker-store-')); return new LocalLookerRuntimeStore({ dbPath: join(dir, 'db.sqlite'), now: () => new Date('2026-05-05T12:00:00.000Z'), @@ -23,7 +23,7 @@ describe('LocalLookerRuntimeStore', () => { await local.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'bq_reporting', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', source: 'cli', }); @@ -34,7 +34,7 @@ describe('LocalLookerRuntimeStore', () => { await expect(local.readMappings('prod-looker')).resolves.toEqual([ { lookerConnectionName: 'bq_reporting', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', lookerHost: null, lookerDatabase: null, lookerDialect: null, @@ -47,7 +47,7 @@ describe('LocalLookerRuntimeStore', () => { await local.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'bq_reporting', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', source: 'cli', }); @@ -67,7 +67,7 @@ describe('LocalLookerRuntimeStore', () => { await expect(local.listConnectionMappings('prod-looker')).resolves.toEqual([ { lookerConnectionName: 'bq_reporting', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', lookerHost: 'bigquery.googleapis.com', lookerDatabase: 'analytics', lookerDialect: 'bigquery_standard_sql', @@ -85,30 +85,30 @@ describe('LocalLookerRuntimeStore', () => { await local.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'manual', - kloConnectionId: 'cli-warehouse', + ktxConnectionId: 'cli-warehouse', source: 'cli', }); await local.applyYamlBootstrap({ lookerConnectionId: 'prod-looker', mappings: [ - { lookerConnectionName: 'analytics', kloConnectionId: 'yaml-warehouse' }, - { lookerConnectionName: 'manual', kloConnectionId: 'yaml-warehouse' }, + { lookerConnectionName: 'analytics', ktxConnectionId: 'yaml-warehouse' }, + { lookerConnectionName: 'manual', ktxConnectionId: 'yaml-warehouse' }, ], }); await expect(local.listConnectionMappings('prod-looker')).resolves.toMatchObject([ { lookerConnectionName: 'analytics', - kloConnectionId: 'yaml-warehouse', + ktxConnectionId: 'yaml-warehouse', lookerHost: 'looker-db.test', lookerDatabase: 'warehouse', lookerDialect: 'postgres', - source: 'klo.yaml', + source: 'ktx.yaml', }, { lookerConnectionName: 'manual', - kloConnectionId: 'cli-warehouse', + ktxConnectionId: 'cli-warehouse', source: 'cli', }, ]); diff --git a/packages/context/src/ingest/adapters/looker/local-runtime-store.ts b/packages/context/src/ingest/adapters/looker/local-runtime-store.ts index 7230d336..a7bc7111 100644 --- a/packages/context/src/ingest/adapters/looker/local-runtime-store.ts +++ b/packages/context/src/ingest/adapters/looker/local-runtime-store.ts @@ -5,7 +5,7 @@ import type { LookerWarehouseConnectionInfo } from './client.js'; import type { LookerConnectionMapping } from './mapping.js'; import type { LookerRuntimeCursors } from './types.js'; -export type LocalLookerMappingSource = 'klo.yaml' | 'cli' | 'refresh'; +export type LocalLookerMappingSource = 'ktx.yaml' | 'cli' | 'refresh'; interface LocalLookerRuntimeStoreOptions { dbPath: string; @@ -19,7 +19,7 @@ export interface LocalLookerConnectionMappingListRow extends LookerConnectionMap export interface UpsertLocalLookerConnectionMappingInput { lookerConnectionId: string; lookerConnectionName: string; - kloConnectionId: string | null; + ktxConnectionId: string | null; source: LocalLookerMappingSource; } @@ -27,7 +27,7 @@ interface ApplyLocalLookerYamlBootstrapInput { lookerConnectionId: string; mappings: Array<{ lookerConnectionName: string; - kloConnectionId: string | null; + ktxConnectionId: string | null; }>; } @@ -67,7 +67,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { CREATE TABLE IF NOT EXISTS local_looker_connection_mappings ( looker_connection_id TEXT NOT NULL, looker_connection_name TEXT NOT NULL, - klo_connection_id TEXT, + ktx_connection_id TEXT, looker_host TEXT, looker_database TEXT, looker_dialect TEXT, @@ -82,7 +82,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { const timestamp = this.now().toISOString(); const apply = this.db.transaction(() => { const existing = this.db.prepare(` - SELECT klo_connection_id, source + SELECT ktx_connection_id, source FROM local_looker_connection_mappings WHERE looker_connection_id = ? AND looker_connection_name = ? `); @@ -90,36 +90,36 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { INSERT INTO local_looker_connection_mappings ( looker_connection_id, looker_connection_name, - klo_connection_id, + ktx_connection_id, looker_host, looker_database, looker_dialect, source, updated_at ) - VALUES (?, ?, ?, NULL, NULL, NULL, 'klo.yaml', ?) + VALUES (?, ?, ?, NULL, NULL, NULL, 'ktx.yaml', ?) `); const updateRefreshRow = this.db.prepare(` UPDATE local_looker_connection_mappings - SET klo_connection_id = ?, - source = 'klo.yaml', + SET ktx_connection_id = ?, + source = 'ktx.yaml', updated_at = ? WHERE looker_connection_id = ? AND looker_connection_name = ? AND source = 'refresh' - AND klo_connection_id IS NULL + AND ktx_connection_id IS NULL `); for (const mapping of input.mappings) { const row = existing.get(input.lookerConnectionId, mapping.lookerConnectionName) as - | { klo_connection_id: string | null; source: LocalLookerMappingSource } + | { ktx_connection_id: string | null; source: LocalLookerMappingSource } | undefined; if (!row) { - insert.run(input.lookerConnectionId, mapping.lookerConnectionName, mapping.kloConnectionId, timestamp); + insert.run(input.lookerConnectionId, mapping.lookerConnectionName, mapping.ktxConnectionId, timestamp); continue; } - if (row.source === 'refresh' && row.klo_connection_id === null) { - updateRefreshRow.run(mapping.kloConnectionId, timestamp, input.lookerConnectionId, mapping.lookerConnectionName); + if (row.source === 'refresh' && row.ktx_connection_id === null) { + updateRefreshRow.run(mapping.ktxConnectionId, timestamp, input.lookerConnectionId, mapping.lookerConnectionName); } } }); @@ -174,7 +174,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { ` SELECT looker_connection_name, - klo_connection_id, + ktx_connection_id, looker_host, looker_database, looker_dialect, @@ -186,7 +186,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { ) .all(lookerConnectionId) as Array<{ looker_connection_name: string; - klo_connection_id: string | null; + ktx_connection_id: string | null; looker_host: string | null; looker_database: string | null; looker_dialect: string | null; @@ -195,7 +195,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { return rows.map((row) => ({ lookerConnectionName: row.looker_connection_name, - kloConnectionId: row.klo_connection_id, + ktxConnectionId: row.ktx_connection_id, lookerHost: row.looker_host, lookerDatabase: row.looker_database, lookerDialect: row.looker_dialect, @@ -210,7 +210,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { INSERT INTO local_looker_connection_mappings ( looker_connection_id, looker_connection_name, - klo_connection_id, + ktx_connection_id, looker_host, looker_database, looker_dialect, @@ -219,12 +219,12 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { ) VALUES (?, ?, ?, NULL, NULL, NULL, ?, ?) ON CONFLICT(looker_connection_id, looker_connection_name) DO UPDATE SET - klo_connection_id = excluded.klo_connection_id, + ktx_connection_id = excluded.ktx_connection_id, source = excluded.source, updated_at = excluded.updated_at `, ) - .run(input.lookerConnectionId, input.lookerConnectionName, input.kloConnectionId, input.source, this.now().toISOString()); + .run(input.lookerConnectionId, input.lookerConnectionName, input.ktxConnectionId, input.source, this.now().toISOString()); } async refreshDiscoveredConnections(input: RefreshLocalLookerDiscoveredConnectionsInput): Promise { @@ -234,7 +234,7 @@ export class LocalLookerRuntimeStore implements LookerSourceStateReader { INSERT INTO local_looker_connection_mappings ( looker_connection_id, looker_connection_name, - klo_connection_id, + ktx_connection_id, looker_host, looker_database, looker_dialect, diff --git a/packages/context/src/ingest/adapters/looker/mapping.test.ts b/packages/context/src/ingest/adapters/looker/mapping.test.ts index e86f65d0..796a9f05 100644 --- a/packages/context/src/ingest/adapters/looker/mapping.test.ts +++ b/packages/context/src/ingest/adapters/looker/mapping.test.ts @@ -9,7 +9,7 @@ import { projectParsedIdentifier, refreshLookerMappingPlaceholders, sqlglotDialectForConnectionType, - suggestKloConnectionForLookerConnection, + suggestKtxConnectionForLookerConnection, validateLookerMappings, validateLookerWarehouseTarget, } from './mapping.js'; @@ -69,7 +69,7 @@ describe('discoverLookerConnections', () => { }); describe('looker dialect and target validation helpers', () => { - it('maps Looker dialect names to KLO connection types', () => { + it('maps Looker dialect names to KTX connection types', () => { expect(lookerDialectToConnectionType('bigquery_standard_sql')).toBe('BIGQUERY'); expect(lookerDialectToConnectionType('postgres')).toBe('POSTGRESQL'); expect(lookerDialectToConnectionType('mssql')).toBe('SQLSERVER'); @@ -90,10 +90,10 @@ describe('looker dialect and target validation helpers', () => { }); }); -describe('suggestKloConnectionForLookerConnection', () => { +describe('suggestKtxConnectionForLookerConnection', () => { it('returns the single deterministic target with matching type, host, and database', () => { expect( - suggestKloConnectionForLookerConnection({ + suggestKtxConnectionForLookerConnection({ lookerConnection: liveConnections[1], candidateConnections: [ { @@ -113,7 +113,7 @@ describe('suggestKloConnectionForLookerConnection', () => { it('returns null when more than one target matches', () => { expect( - suggestKloConnectionForLookerConnection({ + suggestKtxConnectionForLookerConnection({ lookerConnection: liveConnections[1], candidateConnections: [ { @@ -139,7 +139,7 @@ describe('refreshLookerMappingPlaceholders', () => { stored: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: null, lookerDatabase: null, lookerDialect: null, @@ -152,14 +152,14 @@ describe('refreshLookerMappingPlaceholders', () => { mappings: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: 'warehouse.example.com', lookerDatabase: 'analytics', lookerDialect: 'bigquery_standard_sql', }, { lookerConnectionName: 'pg_runtime', - kloConnectionId: null, + ktxConnectionId: null, lookerHost: 'pg.internal:5432', lookerDatabase: 'app', lookerDialect: 'postgres', @@ -176,14 +176,14 @@ describe('computeLookerMappingDrift and validateLookerMappings', () => { storedMappings: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: null, lookerDatabase: null, lookerDialect: null, }, { lookerConnectionName: 'stale_runtime', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: null, lookerDatabase: null, lookerDialect: null, @@ -194,7 +194,7 @@ describe('computeLookerMappingDrift and validateLookerMappings', () => { ).toEqual({ unmappedDiscovered: [liveConnections[1]], staleMappings: [{ lookerConnectionName: 'stale_runtime', reason: 'looker_connection_not_found' }], - inSync: [{ lookerConnectionName: 'b2b_sandbox_bq', kloConnectionId: 'warehouse' }], + inSync: [{ lookerConnectionName: 'b2b_sandbox_bq', ktxConnectionId: 'warehouse' }], }); }); @@ -204,26 +204,26 @@ describe('computeLookerMappingDrift and validateLookerMappings', () => { mappings: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'missing', + ktxConnectionId: 'missing', lookerHost: null, lookerDatabase: null, lookerDialect: null, }, { lookerConnectionName: 'pg_runtime', - kloConnectionId: 'looker-target', + ktxConnectionId: 'looker-target', lookerHost: null, lookerDatabase: null, lookerDialect: null, }, ], - knownKloConnectionIds: new Set(['looker-target']), + knownKtxConnectionIds: new Set(['looker-target']), knownConnectionTypes: new Map([['looker-target', 'LOOKER']]), }), ).toEqual({ ok: false, errors: [ - { key: 'b2b_sandbox_bq', reason: 'KLO connection missing does not exist' }, + { key: 'b2b_sandbox_bq', reason: 'KTX connection missing does not exist' }, { key: 'pg_runtime', reason: 'Connection type LOOKER cannot be used as a Looker warehouse mapping target', @@ -258,7 +258,7 @@ describe('collectExploreParseItems and projectParsedIdentifier', () => { }); }); - it('projects successful and failed parser rows into KLO parsed target tables', () => { + it('projects successful and failed parser rows into KTX parsed target tables', () => { expect( projectParsedIdentifier({ ok: true, @@ -317,7 +317,7 @@ describe('buildLookerPullConfigFromInputs', () => { refreshedMappings: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: 'warehouse.example.com', lookerDatabase: 'analytics', lookerDialect: 'bigquery_standard_sql', @@ -365,7 +365,7 @@ describe('buildLookerPullConfigFromInputs', () => { refreshedMappings: [ { lookerConnectionName: 'b2b_sandbox_bq', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', lookerHost: null, lookerDatabase: null, lookerDialect: null, diff --git a/packages/context/src/ingest/adapters/looker/mapping.ts b/packages/context/src/ingest/adapters/looker/mapping.ts index d7d1227b..92ba82ce 100644 --- a/packages/context/src/ingest/adapters/looker/mapping.ts +++ b/packages/context/src/ingest/adapters/looker/mapping.ts @@ -26,7 +26,7 @@ export type LookerWarehouseTargetConnectionType = export interface LookerConnectionMapping { lookerConnectionName: string; - kloConnectionId: string | null; + ktxConnectionId: string | null; lookerHost: string | null; lookerDatabase: string | null; lookerDialect: string | null; @@ -43,7 +43,7 @@ export interface LookerMappingCandidateConnection extends LookerTargetConnection export interface LookerMappingDrift { unmappedDiscovered: LookerWarehouseConnectionInfo[]; staleMappings: Array<{ lookerConnectionName: string; reason: 'looker_connection_not_found' }>; - inSync: Array<{ lookerConnectionName: string; kloConnectionId: string }>; + inSync: Array<{ lookerConnectionName: string; ktxConnectionId: string }>; } export type LookerMappingValidationResult = @@ -155,7 +155,7 @@ export function normalizeName(value: string | null): string | null { return value ? value.toLowerCase() : null; } -export function suggestKloConnectionForLookerConnection(args: { +export function suggestKtxConnectionForLookerConnection(args: { lookerConnection: LookerWarehouseConnectionInfo; candidateConnections: LookerMappingCandidateConnection[]; }): string | null { @@ -187,7 +187,7 @@ export function computeLookerMappingDrift(args: { const storedByName = new Map(args.storedMappings.map((mapping) => [mapping.lookerConnectionName, mapping])); return { - unmappedDiscovered: args.discovered.filter((connection) => !storedByName.get(connection.name)?.kloConnectionId), + unmappedDiscovered: args.discovered.filter((connection) => !storedByName.get(connection.name)?.ktxConnectionId), staleMappings: args.storedMappings .filter((mapping) => !discoveredByName.has(mapping.lookerConnectionName)) .map((mapping) => ({ @@ -195,32 +195,32 @@ export function computeLookerMappingDrift(args: { reason: 'looker_connection_not_found' as const, })), inSync: args.storedMappings - .filter((mapping) => discoveredByName.has(mapping.lookerConnectionName) && mapping.kloConnectionId) + .filter((mapping) => discoveredByName.has(mapping.lookerConnectionName) && mapping.ktxConnectionId) .map((mapping) => ({ lookerConnectionName: mapping.lookerConnectionName, - kloConnectionId: mapping.kloConnectionId as string, + ktxConnectionId: mapping.ktxConnectionId as string, })), }; } export function validateLookerMappings(args: { mappings: LookerConnectionMapping[]; - knownKloConnectionIds: Set; + knownKtxConnectionIds: Set; knownConnectionTypes: ReadonlyMap; }): LookerMappingValidationResult { const errors: Array<{ key: string; reason: string }> = []; for (const mapping of args.mappings) { - if (!mapping.kloConnectionId) { + if (!mapping.ktxConnectionId) { continue; } - if (!args.knownKloConnectionIds.has(mapping.kloConnectionId)) { + if (!args.knownKtxConnectionIds.has(mapping.ktxConnectionId)) { errors.push({ key: mapping.lookerConnectionName, - reason: `KLO connection ${mapping.kloConnectionId} does not exist`, + reason: `KTX connection ${mapping.ktxConnectionId} does not exist`, }); continue; } - const connectionType = args.knownConnectionTypes.get(mapping.kloConnectionId); + const connectionType = args.knownConnectionTypes.get(mapping.ktxConnectionId); const validation = validateLookerWarehouseTarget(connectionType ?? 'unknown'); if (!validation.ok) { errors.push({ key: mapping.lookerConnectionName, reason: validation.reason }); @@ -241,7 +241,7 @@ export function refreshLookerMappingPlaceholders(args: { if (!existing) { byName.set(live.name, { lookerConnectionName: live.name, - kloConnectionId: null, + ktxConnectionId: null, lookerHost: live.host, lookerDatabase: live.database, lookerDialect: live.dialect, @@ -346,14 +346,14 @@ export async function buildLookerPullConfigFromInputs(args: { const connectionTypes: Record = {}; for (const mapping of args.refreshedMappings) { - if (!mapping.kloConnectionId) { + if (!mapping.ktxConnectionId) { continue; } - const target = args.targetConnections.get(mapping.kloConnectionId); + const target = args.targetConnections.get(mapping.ktxConnectionId); if (!target || !validateLookerWarehouseTarget(target.connection_type).ok) { continue; } - connectionMappings[mapping.lookerConnectionName] = mapping.kloConnectionId; + connectionMappings[mapping.lookerConnectionName] = mapping.ktxConnectionId; connectionTypes[mapping.lookerConnectionName] = target.connection_type as LookerWarehouseTargetConnectionType; } diff --git a/packages/context/src/ingest/adapters/looker/types.test.ts b/packages/context/src/ingest/adapters/looker/types.test.ts index 998192c3..2d517d50 100644 --- a/packages/context/src/ingest/adapters/looker/types.test.ts +++ b/packages/context/src/ingest/adapters/looker/types.test.ts @@ -255,7 +255,7 @@ describe('Looker staged runtime schemas', () => { }); }); - it('accepts slug-shaped connection ids inside KLO Looker runtime schemas', () => { + it('accepts slug-shaped connection ids inside KTX Looker runtime schemas', () => { const parsedTargetTable = { ok: true as const, catalog: 'proj', @@ -313,7 +313,7 @@ describe('Looker staged runtime schemas', () => { }); }); - it('rejects unsafe KLO Looker connection ids', () => { + it('rejects unsafe KTX Looker connection ids', () => { expect(() => parseLookerPullConfig({ lookerConnectionId: '../prod-looker', diff --git a/packages/context/src/ingest/adapters/metabase/client-boundary.test.ts b/packages/context/src/ingest/adapters/metabase/client-boundary.test.ts index 5c69db8c..7df6691e 100644 --- a/packages/context/src/ingest/adapters/metabase/client-boundary.test.ts +++ b/packages/context/src/ingest/adapters/metabase/client-boundary.test.ts @@ -9,8 +9,8 @@ async function readMetabaseFile(name: string): Promise { return readFile(join(metabaseDir, name), 'utf-8'); } -describe('KLO Metabase client boundary', () => { - it('keeps NestJS, server data-source base classes, and server-relative imports out of the KLO client', async () => { +describe('KTX Metabase client boundary', () => { + it('keeps NestJS, server data-source base classes, and server-relative imports out of the KTX client', async () => { const client = await readMetabaseFile('client.ts'); expect(client).not.toContain(`@${'nestjs'}`); expect(client).not.toContain(`DataSource${'Client'}`); @@ -19,7 +19,7 @@ describe('KLO Metabase client boundary', () => { expect(client).not.toContain('../../types/brand'); }); - it('keeps proxy implementation code out of the KLO v1 client', async () => { + it('keeps proxy implementation code out of the KTX v1 client', async () => { const client = await readMetabaseFile('client.ts'); expect(client).not.toContain(`network-${'proxy'}`); expect(client).not.toContain(`ssh${'2'}`); diff --git a/packages/context/src/ingest/adapters/metabase/client.test.ts b/packages/context/src/ingest/adapters/metabase/client.test.ts index 21973f7c..d6d7a4d9 100644 --- a/packages/context/src/ingest/adapters/metabase/client.test.ts +++ b/packages/context/src/ingest/adapters/metabase/client.test.ts @@ -199,7 +199,7 @@ describe('MetabaseClient admin auth helpers', () => { ); await expect(client.getPermissionGroups()).resolves.toEqual([{ id: 2, name: 'Administrators' }]); - await expect(client.createApiKey({ name: 'KLO CLI test', groupId: 2 })).resolves.toBe(mintedMetabaseCredential); + await expect(client.createApiKey({ name: 'KTX CLI test', groupId: 2 })).resolves.toBe(mintedMetabaseCredential); expect(fetchMock).toHaveBeenNthCalledWith( 1, @@ -214,7 +214,7 @@ describe('MetabaseClient admin auth helpers', () => { 'https://metabase.example.test/api/api-key', expect.objectContaining({ method: 'POST', - body: JSON.stringify({ name: 'KLO CLI test', group_id: 2 }), + body: JSON.stringify({ name: 'KTX CLI test', group_id: 2 }), }), ); }); @@ -343,7 +343,7 @@ describe('MetabaseClient.getResolvedSql', () => { expect(result?.resolutionStatus).toBe('resolved'); const sql = result?.resolvedSql ?? ''; expect(sql.startsWith('--')).toBe(true); - expect(sql).toMatch(/KLO_PLACEHOLDER_WARNING/); + expect(sql).toMatch(/KTX_PLACEHOLDER_WARNING/); expect(sql).toMatch(/\bid\b/); expect(sql).toMatch(/\bn\b/); }); diff --git a/packages/context/src/ingest/adapters/metabase/client.ts b/packages/context/src/ingest/adapters/metabase/client.ts index 3e2a1a66..2ddd970a 100644 --- a/packages/context/src/ingest/adapters/metabase/client.ts +++ b/packages/context/src/ingest/adapters/metabase/client.ts @@ -74,7 +74,7 @@ class MetabaseApiError extends Error { * Strip Metabase `[[ ... {{ var }} ... ]]` optional-clause blocks from native SQL. * * The bracketed blocks are emitted only when the embedded `{{ var }}` is supplied at - * Metabase query time. For KLO semantic-layer ingest there's no such runtime + * Metabase query time. For KTX semantic-layer ingest there's no such runtime * parameter — chat-time filters are composed by the SL query planner — so the optional * block must be removed before the SQL becomes a permanent SL source. Substituting a * dummy value (the alternative) bakes a placeholder filter into the source and silently @@ -425,7 +425,7 @@ export class MetabaseClient implements MetabaseRuntimeClient { private buildPlaceholderWarningComment(tags: MetabaseTemplateTag[]): string { const lines = [ - '-- KLO_PLACEHOLDER_WARNING: this SQL was extracted from a Metabase card with', + '-- KTX_PLACEHOLDER_WARNING: this SQL was extracted from a Metabase card with', '-- unbound template parameters. The placeholders below were substituted with DUMMY', "-- values to satisfy Metabase's parser — they DO NOT represent intended filters.", '-- Drop the corresponding clauses (or expose them as runtime SL filters) before', diff --git a/packages/context/src/ingest/adapters/metabase/fetch.test.ts b/packages/context/src/ingest/adapters/metabase/fetch.test.ts index 8beee21d..a86350ac 100644 --- a/packages/context/src/ingest/adapters/metabase/fetch.test.ts +++ b/packages/context/src/ingest/adapters/metabase/fetch.test.ts @@ -240,7 +240,7 @@ describe('fetchMetabaseBundle', () => { clientFactory, sourceStateReader, }), - ).rejects.toThrow(/unhydrated.*klo connection mapping refresh/); + ).rejects.toThrow(/unhydrated.*ktx connection mapping refresh/); }); it('skips cards whose getResolvedSql returns null and records them in unresolved-cards.json', async () => { diff --git a/packages/context/src/ingest/adapters/metabase/fetch.ts b/packages/context/src/ingest/adapters/metabase/fetch.ts index 81fe6ab3..f8cc1e12 100644 --- a/packages/context/src/ingest/adapters/metabase/fetch.ts +++ b/packages/context/src/ingest/adapters/metabase/fetch.ts @@ -92,7 +92,7 @@ export async function fetchMetabaseBundle(params: FetchMetabaseBundleParams): Pr } if (mapping.metabaseDatabaseName === null) { throw new IngestInputError( - `mapping for database ${pullConfig.metabaseDatabaseId} on Metabase connection ${pullConfig.metabaseConnectionId} is unhydrated; run \`klo connection mapping refresh ${pullConfig.metabaseConnectionId}\` to populate metabaseDatabaseName before ingest.`, + `mapping for database ${pullConfig.metabaseDatabaseId} on Metabase connection ${pullConfig.metabaseConnectionId} is unhydrated; run \`ktx connection mapping refresh ${pullConfig.metabaseConnectionId}\` to populate metabaseDatabaseName before ingest.`, ); } const mappingDatabaseName: string = mapping.metabaseDatabaseName; diff --git a/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.test.ts b/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.test.ts index 71d76007..2e492f07 100644 --- a/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.test.ts +++ b/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; -import type { KloProjectConnectionConfig } from '../../../project/index.js'; +import type { KtxProjectConnectionConfig } from '../../../project/index.js'; import { metabaseRuntimeConfigFromLocalConnection } from './local-metabase.adapter.js'; describe('metabaseRuntimeConfigFromLocalConnection', () => { - it('resolves api_url and env-backed api_key_ref from a flat klo.yaml connection', () => { - const connection: KloProjectConnectionConfig = { + it('resolves api_url and env-backed api_key_ref from a flat ktx.yaml connection', () => { + const connection: KtxProjectConnectionConfig = { driver: 'metabase', api_url: 'https://metabase.example.com', api_key_ref: 'env:METABASE_API_KEY', // pragma: allowlist secret @@ -21,7 +21,7 @@ describe('metabaseRuntimeConfigFromLocalConnection', () => { }); it('accepts url as the local api URL alias', () => { - const connection: KloProjectConnectionConfig = { + const connection: KtxProjectConnectionConfig = { driver: 'metabase', url: 'https://metabase.example.com', api_key: 'literal-test-key', // pragma: allowlist secret @@ -34,7 +34,7 @@ describe('metabaseRuntimeConfigFromLocalConnection', () => { }); it('rejects proxy-bearing local Metabase connections', () => { - const connection: KloProjectConnectionConfig = { + const connection: KtxProjectConnectionConfig = { driver: 'metabase', api_url: 'https://metabase.example.com', api_key: 'literal-test-key', // pragma: allowlist secret @@ -42,12 +42,12 @@ describe('metabaseRuntimeConfigFromLocalConnection', () => { }; expect(() => metabaseRuntimeConfigFromLocalConnection('prod-metabase', connection)).toThrow( - 'Standalone KLO does not support proxy-bearing Metabase connections yet', + 'Standalone KTX does not support proxy-bearing Metabase connections yet', ); }); it('rejects non-Metabase source connections', () => { - const connection: KloProjectConnectionConfig = { + const connection: KtxProjectConnectionConfig = { driver: 'postgres', url: 'postgres://localhost/db', }; diff --git a/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.ts b/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.ts index d9a36e34..a13b3923 100644 --- a/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.ts +++ b/packages/context/src/ingest/adapters/metabase/local-metabase.adapter.ts @@ -1,5 +1,5 @@ -import type { KloLocalProject, KloProjectConnectionConfig } from '../../../project/index.js'; -import { kloLocalStateDbPath } from '../../../project/index.js'; +import type { KtxLocalProject, KtxProjectConnectionConfig } from '../../../project/index.js'; +import { ktxLocalStateDbPath } from '../../../project/index.js'; import { DEFAULT_METABASE_CLIENT_CONFIG, DefaultMetabaseConnectionClientFactory } from './client.js'; import { IngestMetabaseClientFactory, @@ -21,13 +21,13 @@ function resolveEnvReference(ref: string, env: NodeJS.ProcessEnv): string | null return stringField(env[name]); } -function hasNetworkProxy(connection: KloProjectConnectionConfig): boolean { +function hasNetworkProxy(connection: KtxProjectConnectionConfig): boolean { return connection.networkProxy != null || connection.network_proxy != null; } export function metabaseRuntimeConfigFromLocalConnection( connectionId: string, - connection: KloProjectConnectionConfig | undefined, + connection: KtxProjectConnectionConfig | undefined, env: NodeJS.ProcessEnv = process.env, ): MetabaseClientRuntimeConfig { if (!connection || String(connection.driver).toLowerCase() !== 'metabase') { @@ -35,7 +35,7 @@ export function metabaseRuntimeConfigFromLocalConnection( } if (hasNetworkProxy(connection)) { throw new Error( - `Standalone KLO does not support proxy-bearing Metabase connections yet. Use hosted Metabase ingest for "${connectionId}" until the KLO Metabase proxy support spec lands.`, + `Standalone KTX does not support proxy-bearing Metabase connections yet. Use hosted Metabase ingest for "${connectionId}" until the KTX Metabase proxy support spec lands.`, ); } @@ -60,10 +60,10 @@ interface CreateLocalMetabaseSourceAdapterOptions { } export function createLocalMetabaseSourceAdapter( - project: KloLocalProject, + project: KtxLocalProject, options: CreateLocalMetabaseSourceAdapterOptions = {}, ): MetabaseSourceAdapter { - const sourceStateReader = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) }); + const sourceStateReader = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) }); const connectionFactory = new DefaultMetabaseConnectionClientFactory( (metabaseConnectionId) => metabaseRuntimeConfigFromLocalConnection( diff --git a/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts b/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts index 9ab0fbe9..f5aef74c 100644 --- a/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts +++ b/packages/context/src/ingest/adapters/metabase/local-source-state-store.test.ts @@ -9,8 +9,8 @@ describe('LocalMetabaseSourceStateReader', () => { let store: LocalMetabaseSourceStateReader; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-metabase-local-state-')); - store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.klo', 'db.sqlite') }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-metabase-local-state-')); + store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.ktx', 'db.sqlite') }); }); afterEach(async () => { @@ -74,7 +74,7 @@ describe('LocalMetabaseSourceStateReader', () => { metabaseDbName: null, targetConnectionId: 'warehouse', syncEnabled: true, - source: 'klo.yaml', + source: 'ktx.yaml', }, { metabaseDatabaseId: 2, @@ -247,7 +247,7 @@ describe('LocalMetabaseSourceStateReader', () => { await store.applyYamlBootstrap({ connectionId: 'prod-metabase', syncMode: 'ALL', - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], selections: [{ selectionType: 'collection', metabaseObjectId: 12 }], mappings: [{ metabaseDatabaseId: 1, targetConnectionId: 'prod-warehouse', syncEnabled: true }], }); @@ -255,7 +255,7 @@ describe('LocalMetabaseSourceStateReader', () => { await expect(store.getUnhydratedSyncEnabledMappingIds('prod-metabase')).resolves.toEqual([1]); await expect(store.getSourceState('prod-metabase')).resolves.toMatchObject({ syncMode: 'ALL', - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], selections: [{ selectionType: 'collection', metabaseObjectId: 12 }], mappings: [], }); @@ -265,7 +265,7 @@ describe('LocalMetabaseSourceStateReader', () => { metabaseDatabaseName: null, targetConnectionId: 'prod-warehouse', syncEnabled: true, - source: 'klo.yaml', + source: 'ktx.yaml', }, ]); }); @@ -301,7 +301,7 @@ describe('LocalMetabaseSourceStateReader', () => { metabaseEngine: 'postgres', targetConnectionId: 'yaml-warehouse', syncEnabled: true, - source: 'klo.yaml', + source: 'ktx.yaml', }, { metabaseDatabaseId: 2, diff --git a/packages/context/src/ingest/adapters/metabase/local-source-state-store.ts b/packages/context/src/ingest/adapters/metabase/local-source-state-store.ts index bee43298..246d5f33 100644 --- a/packages/context/src/ingest/adapters/metabase/local-source-state-store.ts +++ b/packages/context/src/ingest/adapters/metabase/local-source-state-store.ts @@ -4,7 +4,7 @@ import Database from 'better-sqlite3'; import type { MetabaseSourceState, MetabaseSourceStateReader, MetabaseSourceStateSelection } from './source-state-port.js'; import type { MetabaseSyncMode } from './types.js'; -export type LocalMetabaseMappingSource = 'klo.yaml' | 'cli' | 'refresh'; +export type LocalMetabaseMappingSource = 'ktx.yaml' | 'cli' | 'refresh'; interface LocalMetabaseSourceStateStoreOptions { dbPath: string; @@ -197,13 +197,13 @@ export class LocalMetabaseSourceStateReader implements MetabaseSourceStateReader source, updated_at ) - VALUES (?, ?, NULL, NULL, NULL, NULL, ?, ?, 'klo.yaml', ?) + VALUES (?, ?, NULL, NULL, NULL, NULL, ?, ?, 'ktx.yaml', ?) `); const updateRefreshRow = this.db.prepare(` UPDATE local_metabase_database_mappings SET target_connection_id = ?, sync_enabled = ?, - source = 'klo.yaml', + source = 'ktx.yaml', updated_at = ? WHERE metabase_connection_id = ? AND metabase_database_id = ? diff --git a/packages/context/src/ingest/adapters/metabase/mapping.test.ts b/packages/context/src/ingest/adapters/metabase/mapping.test.ts index 79ccb2df..e347390c 100644 --- a/packages/context/src/ingest/adapters/metabase/mapping.test.ts +++ b/packages/context/src/ingest/adapters/metabase/mapping.test.ts @@ -64,7 +64,7 @@ describe('computeMetabaseMappingDrift', () => { { id: 3, name: 'Warehouse', engine: 'mysql', host: 'mysql.internal', dbName: 'warehouse' }, ], staleMappings: [{ id: '9', reason: 'database_not_found' }], - inSync: [{ id: 2, kloConnectionId: 'target-postgres' }], + inSync: [{ id: 2, ktxConnectionId: 'target-postgres' }], }); }); }); @@ -74,7 +74,7 @@ describe('validateMetabaseMappings', () => { expect( validateMetabaseMappings({ mappings: { '2': 'target-postgres' }, - knownKloConnectionIds: new Set(['target-postgres']), + knownKtxConnectionIds: new Set(['target-postgres']), }), ).toEqual({ ok: true }); }); @@ -83,11 +83,11 @@ describe('validateMetabaseMappings', () => { expect( validateMetabaseMappings({ mappings: { '2': 'missing-target', '3': 'target-mysql' }, - knownKloConnectionIds: new Set(['target-mysql']), + knownKtxConnectionIds: new Set(['target-mysql']), }), ).toEqual({ ok: false, - errors: [{ key: '2', reason: 'KLO connection missing-target does not exist' }], + errors: [{ key: '2', reason: 'KTX connection missing-target does not exist' }], }); }); }); @@ -149,7 +149,7 @@ describe('validateMappingPhysicalMatch', () => { ).toBeNull(); }); - it('returns null for unknown engines because KLO cannot validate them', () => { + it('returns null for unknown engines because KTX cannot validate them', () => { expect( validateMappingPhysicalMatch( { metabaseEngine: 'unknown-engine', metabaseDbName: 'X', metabaseHost: 'host' }, @@ -177,7 +177,7 @@ describe('computeMetabaseMappingPhysicalMismatches', () => { ).toEqual([ { mappingId: 'mapping-bad', - reason: "Metabase database 'app' does not match KLO connection database 'other_app'", + reason: "Metabase database 'app' does not match KTX connection database 'other_app'", }, ]); }); @@ -201,7 +201,7 @@ describe('refreshMetabaseMapping', () => { refreshMetabaseMapping({ client, currentMappings: { '2': 'target-postgres' }, - resolveKloConnectionPhysicalInfo: vi.fn().mockResolvedValue({ + resolveKtxConnectionPhysicalInfo: vi.fn().mockResolvedValue({ connection_type: 'POSTGRESQL', host: 'pg.internal', database: 'wrong_database', @@ -211,12 +211,12 @@ describe('refreshMetabaseMapping', () => { drift: { unmappedDiscovered: [], staleMappings: [], - inSync: [{ id: 2, kloConnectionId: 'target-postgres' }], + inSync: [{ id: 2, ktxConnectionId: 'target-postgres' }], }, physicalMismatches: [ { mappingId: '2', - reason: "Metabase database 'analytics' does not match KLO connection database 'wrong_database'", + reason: "Metabase database 'analytics' does not match KTX connection database 'wrong_database'", }, ], }); @@ -282,7 +282,7 @@ describe('findBestMatch', () => { }); describe('METABASE_ENGINE_TO_CONNECTION_TYPE', () => { - it('keeps the server-supported Metabase engine table in KLO', () => { + it('keeps the server-supported Metabase engine table in KTX', () => { expect(METABASE_ENGINE_TO_CONNECTION_TYPE).toMatchObject({ postgres: 'POSTGRESQL', bigquery: 'BIGQUERY', diff --git a/packages/context/src/ingest/adapters/metabase/mapping.ts b/packages/context/src/ingest/adapters/metabase/mapping.ts index 52007d3e..9dc75006 100644 --- a/packages/context/src/ingest/adapters/metabase/mapping.ts +++ b/packages/context/src/ingest/adapters/metabase/mapping.ts @@ -23,7 +23,7 @@ export interface DiscoveredMetabaseDatabase { export interface MetabaseMappingDrift { unmappedDiscovered: DiscoveredMetabaseDatabase[]; staleMappings: Array<{ id: string; reason: 'database_not_found' }>; - inSync: Array<{ id: number; kloConnectionId: string }>; + inSync: Array<{ id: number; ktxConnectionId: string }>; } export interface MappingPhysicalInfo { @@ -32,7 +32,7 @@ export interface MappingPhysicalInfo { metabaseHost: string | null; } -export interface KloConnectionPhysicalInfo { +export interface KtxConnectionPhysicalInfo { connection_type: string; database?: unknown; host?: unknown; @@ -45,7 +45,7 @@ export interface KloConnectionPhysicalInfo { export interface PhysicalMismatchInput { mappingId: string; metabase: MappingPhysicalInfo; - target: KloConnectionPhysicalInfo; + target: KtxConnectionPhysicalInfo; } export interface PhysicalMismatch { @@ -102,7 +102,7 @@ function displayValue(value: unknown): string { return typeof value === 'string' && value.length > 0 ? value : 'unknown'; } -function getTargetDatabase(target: KloConnectionPhysicalInfo): unknown { +function getTargetDatabase(target: KtxConnectionPhysicalInfo): unknown { if (target.connection_type === 'BIGQUERY') { return target.dataset_id ?? target.project_id ?? target.database; } @@ -164,23 +164,23 @@ export function computeMetabaseMappingDrift(args: { .filter((id) => !discoveredById.has(id)) .map((id) => ({ id, reason: 'database_not_found' as const })); const inSync = Object.entries(args.currentMappings) - .filter(([id, kloConnectionId]) => discoveredById.has(id) && typeof kloConnectionId === 'string') - .map(([id, kloConnectionId]) => ({ id: Number(id), kloConnectionId: kloConnectionId as string })); + .filter(([id, ktxConnectionId]) => discoveredById.has(id) && typeof ktxConnectionId === 'string') + .map(([id, ktxConnectionId]) => ({ id: Number(id), ktxConnectionId: ktxConnectionId as string })); return { unmappedDiscovered, staleMappings, inSync }; } export function validateMetabaseMappings(args: { mappings: Record; - knownKloConnectionIds: Set; + knownKtxConnectionIds: Set; }): MetabaseMappingValidationResult { const errors: Array<{ key: string; reason: string }> = []; for (const [key, connectionId] of Object.entries(args.mappings)) { if (!connectionId) { continue; } - if (!args.knownKloConnectionIds.has(connectionId)) { - errors.push({ key, reason: `KLO connection ${connectionId} does not exist` }); + if (!args.knownKtxConnectionIds.has(connectionId)) { + errors.push({ key, reason: `KTX connection ${connectionId} does not exist` }); } } return errors.length === 0 ? { ok: true } : { ok: false, errors }; @@ -188,7 +188,7 @@ export function validateMetabaseMappings(args: { export function validateMappingPhysicalMatch( mapping: MappingPhysicalInfo, - target: KloConnectionPhysicalInfo, + target: KtxConnectionPhysicalInfo, ): string | null { const engine = mapping.metabaseEngine?.toLowerCase(); if (!engine) { @@ -201,7 +201,7 @@ export function validateMappingPhysicalMatch( } if (target.connection_type !== expectedType) { - return `Metabase database engine '${engine}' does not match KLO connection type '${target.connection_type}'`; + return `Metabase database engine '${engine}' does not match KTX connection type '${target.connection_type}'`; } const metabaseDb = normalizeName(mapping.metabaseDbName); @@ -209,7 +209,7 @@ export function validateMappingPhysicalMatch( if (engine === 'snowflake' || engine === 'bigquery' || engine === 'bigquery-cloud-sdk') { if (metabaseDb && targetDb && metabaseDb !== targetDb) { - return `Metabase database '${mapping.metabaseDbName}' does not match KLO connection database '${displayValue( + return `Metabase database '${mapping.metabaseDbName}' does not match KTX connection database '${displayValue( getTargetDatabase(target), )}'`; } @@ -221,12 +221,12 @@ export function validateMappingPhysicalMatch( const targetHost = normalizeHost(target.host); if (metabaseHost && targetHost && metabaseHost !== targetHost) { - return `Metabase host '${mapping.metabaseHost}' does not match KLO connection host '${displayValue( + return `Metabase host '${mapping.metabaseHost}' does not match KTX connection host '${displayValue( target.host, )}'`; } if (metabaseDb && targetDb && metabaseDb !== targetDb) { - return `Metabase database '${mapping.metabaseDbName}' does not match KLO connection database '${displayValue( + return `Metabase database '${mapping.metabaseDbName}' does not match KTX connection database '${displayValue( getTargetDatabase(target), )}'`; } @@ -250,7 +250,7 @@ export function computeMetabaseMappingPhysicalMismatches(inputs: PhysicalMismatc export async function refreshMetabaseMapping(args: { client: Pick; currentMappings: Record; - resolveKloConnectionPhysicalInfo: (kloConnectionId: string) => Promise; + resolveKtxConnectionPhysicalInfo: (ktxConnectionId: string) => Promise; }): Promise { const discovered = await discoverMetabaseDatabases(args.client); const drift = computeMetabaseMappingDrift({ currentMappings: args.currentMappings, discovered }); @@ -262,11 +262,11 @@ export async function refreshMetabaseMapping(args: { if (!discoveredDatabase) { continue; } - const target = await args.resolveKloConnectionPhysicalInfo(mapping.kloConnectionId); + const target = await args.resolveKtxConnectionPhysicalInfo(mapping.ktxConnectionId); if (!target) { physicalMismatches.push({ mappingId: String(mapping.id), - reason: `KLO connection ${mapping.kloConnectionId} does not exist`, + reason: `KTX connection ${mapping.ktxConnectionId} does not exist`, }); continue; } diff --git a/packages/context/src/ingest/adapters/metricflow/deep-parse.ts b/packages/context/src/ingest/adapters/metricflow/deep-parse.ts index bb80dd20..a76e94f7 100644 --- a/packages/context/src/ingest/adapters/metricflow/deep-parse.ts +++ b/packages/context/src/ingest/adapters/metricflow/deep-parse.ts @@ -1,5 +1,5 @@ import { parse as parseYaml } from 'yaml'; -import { noopLogger, type KloLogger } from '../../../core/index.js'; +import { noopLogger, type KtxLogger } from '../../../core/index.js'; export interface DimensionDefinition { name: string; @@ -42,7 +42,7 @@ export interface ParsedMetricflowRelationship { } export interface MetricflowParseOptions { - logger?: KloLogger; + logger?: KtxLogger; } // ============ MetricFlow YAML Interfaces ============ @@ -191,7 +191,7 @@ export function translateMetricflowJinjaFilter(filter: string): string { } class MetricflowDeepParser { - constructor(private readonly logger: KloLogger) {} + constructor(private readonly logger: KtxLogger) {} parseFiles(files: Array<{ content: string; path: string }>): MetricFlowParseResult { this.logger.log(`Parsing ${files.length} files for MetricFlow definitions`); diff --git a/packages/context/src/ingest/adapters/notion/cluster.test.ts b/packages/context/src/ingest/adapters/notion/cluster.test.ts index 714c6f46..886d4973 100644 --- a/packages/context/src/ingest/adapters/notion/cluster.test.ts +++ b/packages/context/src/ingest/adapters/notion/cluster.test.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, test } from 'vitest'; -import type { KloEmbeddingPort } from '../../../core/embedding.js'; +import type { KtxEmbeddingPort } from '../../../core/embedding.js'; import type { WorkUnit } from '../../types.js'; import { clusterNotionWorkUnits, MIN_PAGES_TO_CLUSTER } from './cluster.js'; @@ -14,7 +14,7 @@ function fakeEmbedding(text: string): number[] { return v; } -const mockEmbed: KloEmbeddingPort = { +const mockEmbed: KtxEmbeddingPort = { maxBatchSize: 100, computeEmbedding: async (t: string) => fakeEmbedding(t), computeEmbeddingsBulk: async (texts: string[]) => texts.map(fakeEmbedding), @@ -104,7 +104,7 @@ describe('clusterNotionWorkUnits', () => { })); const stagedDir = await makeStaged(pages); const wus = makeWorkUnits(pages); - const failingEmbed: KloEmbeddingPort = { + const failingEmbed: KtxEmbeddingPort = { maxBatchSize: 100, computeEmbedding: async () => { throw new Error('embedding down'); diff --git a/packages/context/src/ingest/adapters/notion/cluster.ts b/packages/context/src/ingest/adapters/notion/cluster.ts index 853cd6cb..0d3640dd 100644 --- a/packages/context/src/ingest/adapters/notion/cluster.ts +++ b/packages/context/src/ingest/adapters/notion/cluster.ts @@ -1,6 +1,6 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import type { KloEmbeddingPort } from '../../../core/embedding.js'; +import type { KtxEmbeddingPort } from '../../../core/embedding.js'; import { kmeans, pickK } from '../../clustering/kmeans.js'; import type { WorkUnit } from '../../types.js'; import { notionMetadataSchema } from './types.js'; @@ -12,7 +12,7 @@ const CLUSTER_SEED = 42; interface ClusterNotionWorkUnitsArgs { workUnits: WorkUnit[]; stagedDir: string; - embedding: KloEmbeddingPort; + embedding: KtxEmbeddingPort; } async function buildClusterText(wu: WorkUnit, stagedDir: string): Promise { diff --git a/packages/context/src/ingest/context-candidates/candidate-dedup.service.ts b/packages/context/src/ingest/context-candidates/candidate-dedup.service.ts index 57d4404d..e9c0e878 100644 --- a/packages/context/src/ingest/context-candidates/candidate-dedup.service.ts +++ b/packages/context/src/ingest/context-candidates/candidate-dedup.service.ts @@ -1,4 +1,4 @@ -import { type KloLogger, noopLogger } from '../../core/index.js'; +import { type KtxLogger, noopLogger } from '../../core/index.js'; import type { CandidateDedupResult, ContextCandidateForDedup, JsonValue } from '../ports.js'; import { buildContextCandidateEmbeddingText } from './embedding-text.js'; import type { ContextCandidateStorePort } from './store.js'; @@ -17,11 +17,11 @@ export interface CandidateDedupServiceDeps { store: ContextCandidateStorePort; embeddings: ContextCandidateEmbeddingPort; settings: CandidateDedupSettings; - logger?: KloLogger; + logger?: KtxLogger; } export class CandidateDedupService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; constructor(private readonly deps: CandidateDedupServiceDeps) { this.logger = deps.logger ?? noopLogger; diff --git a/packages/context/src/ingest/context-candidates/context-candidate-carryforward.service.ts b/packages/context/src/ingest/context-candidates/context-candidate-carryforward.service.ts index 5150f517..74151e7e 100644 --- a/packages/context/src/ingest/context-candidates/context-candidate-carryforward.service.ts +++ b/packages/context/src/ingest/context-candidates/context-candidate-carryforward.service.ts @@ -1,5 +1,5 @@ import { createHash } from 'node:crypto'; -import { type KloLogger, noopLogger } from '../../core/index.js'; +import { type KtxLogger, noopLogger } from '../../core/index.js'; import type { JsonValue } from '../ports.js'; import type { ContextCandidateStorePort } from './store.js'; import type { @@ -26,11 +26,11 @@ export interface ContextCandidateCarryforwardResult { export interface ContextCandidateCarryforwardServiceDeps { store: ContextCandidateStorePort; settings: ContextCandidateCarryforwardSettings; - logger?: KloLogger; + logger?: KtxLogger; } export class ContextCandidateCarryforwardService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; constructor(private readonly deps: ContextCandidateCarryforwardServiceDeps) { this.logger = deps.logger ?? noopLogger; diff --git a/packages/context/src/ingest/context-candidates/curator-pagination.service.ts b/packages/context/src/ingest/context-candidates/curator-pagination.service.ts index 189b9670..d40bef9b 100644 --- a/packages/context/src/ingest/context-candidates/curator-pagination.service.ts +++ b/packages/context/src/ingest/context-candidates/curator-pagination.service.ts @@ -1,7 +1,7 @@ -import type { KloModelRole } from '@klo/llm'; +import type { KtxModelRole } from '@ktx/llm'; import type { ToolSet } from 'ai'; import type { AgentRunnerService } from '../../agent/index.js'; -import { type KloLogger, noopLogger } from '../../core/index.js'; +import { type KtxLogger, noopLogger } from '../../core/index.js'; import type { MemoryAction } from '../../memory/index.js'; import type { ContextCandidateForDedup, CuratorPaginationPort, CuratorPaginationReport } from '../ports.js'; import type { @@ -35,7 +35,7 @@ export interface CuratorPaginationInput { evictionUnit: EvictionUnit | undefined; representatives: ContextCandidateForDedup[]; initialBudget: CuratorPaginationBudget; - modelRole: KloModelRole; + modelRole: KtxModelRole; buildSystemPrompt: () => string; buildUserPrompt: (input: CuratorPaginationPromptInput) => string; buildToolSet: (passNumber: number) => ToolSet; @@ -52,11 +52,11 @@ export interface CuratorPaginationServiceDeps { store: ContextCandidateStorePort; agentRunner: AgentRunnerService; settings: CuratorPaginationSettings; - logger?: KloLogger; + logger?: KtxLogger; } export class CuratorPaginationService implements CuratorPaginationPort { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; constructor(private readonly deps: CuratorPaginationServiceDeps) { this.logger = deps.logger ?? noopLogger; diff --git a/packages/context/src/ingest/context-evidence/context-evidence-index.service.ts b/packages/context/src/ingest/context-evidence/context-evidence-index.service.ts index e625ccce..7b54e80f 100644 --- a/packages/context/src/ingest/context-evidence/context-evidence-index.service.ts +++ b/packages/context/src/ingest/context-evidence/context-evidence-index.service.ts @@ -1,7 +1,7 @@ import { createHash } from 'node:crypto'; import { readdir, readFile } from 'node:fs/promises'; import { basename, dirname, join, relative } from 'node:path'; -import { noopLogger, type KloLogger } from '../../core/index.js'; +import { noopLogger, type KtxLogger } from '../../core/index.js'; import type { JsonValue } from '../ports.js'; import type { DiffSet } from '../types.js'; import type { ContextEvidenceIndexStorePort } from './store.js'; @@ -32,7 +32,7 @@ interface PublishSyncArgs { interface ContextEvidenceIndexServiceDeps { store: ContextEvidenceIndexStorePort; embeddings: ContextEvidenceEmbeddingPort; - logger?: Pick; + logger?: Pick; } type JsonObject = { [key: string]: JsonValue | undefined }; @@ -66,7 +66,7 @@ interface MarkdownChunk { export class ContextEvidenceIndexService { private readonly store: ContextEvidenceIndexStorePort; private readonly embeddings: ContextEvidenceEmbeddingPort; - private readonly logger: Pick; + private readonly logger: Pick; constructor(deps: ContextEvidenceIndexServiceDeps) { this.store = deps.store; diff --git a/packages/context/src/ingest/context-evidence/sqlite-context-evidence-store.test.ts b/packages/context/src/ingest/context-evidence/sqlite-context-evidence-store.test.ts index 9e42ff6d..0767a6be 100644 --- a/packages/context/src/ingest/context-evidence/sqlite-context-evidence-store.test.ts +++ b/packages/context/src/ingest/context-evidence/sqlite-context-evidence-store.test.ts @@ -11,8 +11,8 @@ describe('SqliteContextEvidenceStore', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-context-evidence-sqlite-')); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-context-evidence-sqlite-')); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); }); afterEach(async () => { diff --git a/packages/context/src/ingest/index.ts b/packages/context/src/ingest/index.ts index a5a74cee..9991391f 100644 --- a/packages/context/src/ingest/index.ts +++ b/packages/context/src/ingest/index.ts @@ -2,9 +2,9 @@ export { DbtSourceAdapter } from './adapters/dbt/dbt.adapter.js'; export { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; export type { DaemonLiveDatabaseIntrospectionOptions, - KloDaemonDatabaseHttpJsonRunner, - KloDaemonDatabaseIntrospectionCommand, - KloDaemonDatabaseJsonRunner, + KtxDaemonDatabaseHttpJsonRunner, + KtxDaemonDatabaseIntrospectionCommand, + KtxDaemonDatabaseJsonRunner, } from './adapters/live-database/daemon-introspection.js'; export { createDaemonLiveDatabaseIntrospection } from './adapters/live-database/daemon-introspection.js'; export type { @@ -15,7 +15,7 @@ export type { } from './adapters/live-database/extracted-schema.js'; export { buildLiveDatabaseTableNaturalKey, - kloSchemaSnapshotToExtractedSchema, + ktxSchemaSnapshotToExtractedSchema, } from './adapters/live-database/extracted-schema.js'; export { LiveDatabaseSourceAdapter } from './adapters/live-database/live-database.adapter.js'; export type { @@ -68,7 +68,7 @@ export { export { createDaemonLookerTableIdentifierParser, type DaemonLookerTableIdentifierParserOptions, - type KloDaemonTableIdentifierHttpJsonRunner, + type KtxDaemonTableIdentifierHttpJsonRunner, } from './adapters/looker/daemon-table-identifier-parser.js'; export type { LookerConnectionClientFactory, @@ -102,12 +102,12 @@ export { projectParsedIdentifier, refreshLookerMappingPlaceholders, sqlglotDialectForConnectionType, - suggestKloConnectionForLookerConnection, + suggestKtxConnectionForLookerConnection, validateLookerMappings, validateLookerWarehouseTarget, } from './adapters/looker/mapping.js'; export type { - LookerConnectionMapping as KloLookerConnectionMapping, + LookerConnectionMapping as KtxLookerConnectionMapping, LookerMappingCandidateConnection, LookerMappingClient, LookerMappingDrift, @@ -220,7 +220,7 @@ export type { AutoMatchCandidate, AutoMatchResult as MetabaseAutoMatchResult, DiscoveredMetabaseDatabase, - KloConnectionPhysicalInfo, + KtxConnectionPhysicalInfo, MappingPhysicalInfo, MappingRefreshReport, MetabaseMappedConnectionType, @@ -347,7 +347,7 @@ export type { HistoricSqlSourceAdapterDeps, HistoricSqlTimeWindow, HistoricSqlUsage, - KloPostgresQueryClient, + KtxPostgresQueryClient, PostgresPgssAggregateRow, PostgresPgssProbeResult, PostgresPgssReader, @@ -424,7 +424,7 @@ export type { RunLocalMetabaseIngestOptions, } from './local-ingest.js'; export { getLatestLocalIngestStatus, getLocalIngestStatus, runLocalIngest, runLocalMetabaseIngest } from './local-ingest.js'; -export { seedLocalMappingStateFromKloYaml } from './local-mapping-reconcile.js'; +export { seedLocalMappingStateFromKtxYaml } from './local-mapping-reconcile.js'; export type { CreateLocalBundleIngestRuntimeOptions, LocalBundleIngestRuntime, @@ -475,7 +475,7 @@ export type { DbtSchemaFile, DbtSchemaParseResult, } from './adapters/dbt-descriptions/parse-schema.js'; -export { findMatchingKloTable, matchDbtTables } from './adapters/dbt-descriptions/match-tables.js'; +export { findMatchingKtxTable, matchDbtTables } from './adapters/dbt-descriptions/match-tables.js'; export type { DbtHostTableLite, DbtTableMatch } from './adapters/dbt-descriptions/match-tables.js'; export { toDescriptionUpdates } from './adapters/dbt-descriptions/to-description-updates.js'; export type { DbtDescriptionUpdates } from './adapters/dbt-descriptions/to-description-updates.js'; @@ -483,7 +483,7 @@ export { toRelationshipUpdates } from './adapters/dbt-descriptions/to-relationsh export type { DbtRelationshipUpdates } from './adapters/dbt-descriptions/to-relationship-updates.js'; export { toMetadataUpdates } from './adapters/dbt-descriptions/to-metadata-updates.js'; export { mergeSemanticModelTables } from './adapters/dbt-descriptions/merge-semantic-model-tables.js'; -export type { KloJoinUpdate, KloMetadataUpdate } from '../scan/enrichment-types.js'; +export type { KtxJoinUpdate, KtxMetadataUpdate } from '../scan/enrichment-types.js'; export { createInitialMemoryFlowInteractionState, findMemoryFlowSearchMatches, diff --git a/packages/context/src/ingest/ingest-bundle.runner.test.ts b/packages/context/src/ingest/ingest-bundle.runner.test.ts index 56661078..0d1adf4c 100644 --- a/packages/context/src/ingest/ingest-bundle.runner.test.ts +++ b/packages/context/src/ingest/ingest-bundle.runner.test.ts @@ -243,11 +243,11 @@ const buildRunner = (deps: ReturnType = makeDeps(), overrides: gitService: deps.gitService as any, lockingService: deps.lockingService as any, storage: { - homeDir: '/tmp/klo-test', - systemGitAuthor: { name: 'KLO Test', email: 'system@klo.local' }, - resolveUploadDir: (uploadId) => `/tmp/klo-test/ingest-uploads/${uploadId}`, - resolvePullDir: (jobId) => `/tmp/klo-test/ingest-pulls/${jobId}`, - resolveTranscriptDir: (jobId) => `/tmp/klo-test/run/wu-transcripts/${jobId}`, + homeDir: '/tmp/ktx-test', + systemGitAuthor: { name: 'KTX Test', email: 'system@ktx.local' }, + resolveUploadDir: (uploadId) => `/tmp/ktx-test/ingest-uploads/${uploadId}`, + resolvePullDir: (jobId) => `/tmp/ktx-test/ingest-pulls/${jobId}`, + resolveTranscriptDir: (jobId) => `/tmp/ktx-test/run/wu-transcripts/${jobId}`, }, settings: { probeRowCount: 1, memoryIngestionModel: 'test-model' }, skillsRegistry: deps.skillsRegistry as any, @@ -845,7 +845,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { toolTranscripts: [ { unitKey: 'u1', - path: '/tmp/klo-test/run/wu-transcripts/j1/u1.jsonl', + path: '/tmp/ktx-test/run/wu-transcripts/j1/u1.jsonl', toolCallCount: 2, errorCount: 0, toolNames: ['read_raw_span', 'wiki_write'], @@ -1065,7 +1065,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { }); it('runs manual override reconciliation from the prior report snapshot and marks the prior report superseded', async () => { - const tempRoot = await mkdtemp(join(tmpdir(), 'klo-override-')); + const tempRoot = await mkdtemp(join(tmpdir(), 'ktx-override-')); const deps = makeDeps(); deps.reportsRepo.findByJobId.mockResolvedValue({ id: 'report-old', @@ -1134,7 +1134,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { ...(buildRunner(deps) as any).deps, storage: { homeDir: tempRoot, - systemGitAuthor: { name: 'KLO Test', email: 'system@klo.local' }, + systemGitAuthor: { name: 'KTX Test', email: 'system@ktx.local' }, resolveUploadDir: (uploadId: string) => join(tempRoot, 'ingest-uploads', uploadId), resolvePullDir: (jobId: string) => join(tempRoot, 'ingest-pulls', jobId), resolveTranscriptDir: (jobId: string) => join(tempRoot, 'run', 'wu-transcripts', jobId), @@ -1770,8 +1770,8 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { await currentToolSession.gitService.commitFiles( ['semantic-layer/c1/good.yaml'], 'test: add good source', - 'KLO Test', - 'system@klo.local', + 'KTX Test', + 'system@ktx.local', ); } if (unitKey === 'wu-bad') { @@ -1782,8 +1782,8 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { await currentToolSession.gitService.commitFiles( ['semantic-layer/c1/bad.yaml'], 'test: add bad source', - 'KLO Test', - 'system@klo.local', + 'KTX Test', + 'system@ktx.local', ); } return { stopReason: 'natural' }; diff --git a/packages/context/src/ingest/ingest-bundle.runner.ts b/packages/context/src/ingest/ingest-bundle.runner.ts index f25a1782..6ba778e5 100644 --- a/packages/context/src/ingest/ingest-bundle.runner.ts +++ b/packages/context/src/ingest/ingest-bundle.runner.ts @@ -3,7 +3,7 @@ import { dirname, join } from 'node:path'; import { type Tool, tool } from 'ai'; import pLimit from 'p-limit'; import { z } from 'zod'; -import { type KloLogger, noopLogger } from '../core/index.js'; +import { type KtxLogger, noopLogger } from '../core/index.js'; import type { CaptureSession, MemoryAction } from '../memory/index.js'; import type { SlValidationDeps } from '../sl/index.js'; import { createTouchedSlSources, type ToolContext, type ToolSession } from '../tools/index.js'; @@ -88,7 +88,7 @@ function reportIdFromCreateResult(result: unknown): string | undefined { } export class IngestBundleRunner { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private readonly chainByConnection = new Map>(); constructor(private readonly deps: IngestBundleRunnerDeps) { diff --git a/packages/context/src/ingest/ingest-prompts.test.ts b/packages/context/src/ingest/ingest-prompts.test.ts index 7a9eb888..b59ab81f 100644 --- a/packages/context/src/ingest/ingest-prompts.test.ts +++ b/packages/context/src/ingest/ingest-prompts.test.ts @@ -18,14 +18,14 @@ describe('ingest prompt assets', () => { expect(prompt).toContain('Do not create a duplicate contested artifact'); }); - it('uses product-neutral KLO runtime wording', async () => { + it('uses product-neutral KTX runtime wording', async () => { const prompt = await readFile( new URL('../../prompts/memory_agent_bundle_ingest_work_unit.md', import.meta.url), 'utf-8', ); - expect(prompt).toContain('KLO semantic-layer sources and/or knowledge wiki pages'); - expect(prompt).toContain('maps cleanly to KLO'); + expect(prompt).toContain('KTX semantic-layer sources and/or knowledge wiki pages'); + expect(prompt).toContain('maps cleanly to KTX'); expect(prompt).not.toMatch(forbiddenProductPattern()); }); diff --git a/packages/context/src/ingest/ingest-runtime-assets.test.ts b/packages/context/src/ingest/ingest-runtime-assets.test.ts index 8d82db41..9af8fcf6 100644 --- a/packages/context/src/ingest/ingest-runtime-assets.test.ts +++ b/packages/context/src/ingest/ingest-runtime-assets.test.ts @@ -34,7 +34,7 @@ function forbiddenProductPattern() { } describe('ingest runtime assets', () => { - it('resolves every reusable ingest skill from packaged KLO assets without server fallback', async () => { + it('resolves every reusable ingest skill from packaged KTX assets without server fallback', async () => { const registry = new SkillsRegistryService({ skillsDir }); const expected = [...new Set([...adapterSkillNames, ...adapterReconcileSkillNames])].sort(); @@ -48,7 +48,7 @@ describe('ingest runtime assets', () => { } }); - it('loads page-triage and light-extraction prompts from packaged KLO prompt assets', async () => { + it('loads page-triage and light-extraction prompts from packaged KTX prompt assets', async () => { const prompts = new PromptService({ promptsDir, partials: [] }); for (const promptName of pageTriagePromptNames) { @@ -67,7 +67,7 @@ describe('ingest runtime assets', () => { await expect(prompts.loadPrompt('skills/light_extraction')).resolves.toContain('# Light Context Extraction'); }); - it('packages historic-SQL WorkUnit skill guidance from KLO assets', async () => { + it('packages historic-SQL WorkUnit skill guidance from KTX assets', async () => { const registry = new SkillsRegistryService({ skillsDir }); const skills = await registry.listSkills(['historic_sql_ingest'], 'memory_agent'); @@ -100,7 +100,7 @@ describe('ingest runtime assets', () => { expect(body).not.toMatch(forbiddenProductPattern()); }); - it('packages historic-SQL curator reconcile guidance from KLO assets', async () => { + it('packages historic-SQL curator reconcile guidance from KTX assets', async () => { const registry = new SkillsRegistryService({ skillsDir }); const skills = await registry.listSkills(['historic_sql_curator'], 'memory_agent'); diff --git a/packages/context/src/ingest/local-adapters.test.ts b/packages/context/src/ingest/local-adapters.test.ts index 5dfca3e7..009cdda2 100644 --- a/packages/context/src/ingest/local-adapters.test.ts +++ b/packages/context/src/ingest/local-adapters.test.ts @@ -2,27 +2,27 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { initKloProject, type KloLocalProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import type { SqlAnalysisPort } from '../sql-analysis/index.js'; import { LocalLookerRuntimeStore } from './adapters/looker/local-runtime-store.js'; import { createDefaultLocalIngestAdapters, localPullConfigForAdapter } from './local-adapters.js'; describe('local ingest adapters', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-adapters-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-adapters-')); const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); - project = await loadKloProject({ projectDir }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); + project = await loadKtxProject({ projectDir }); }); afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); }); - function projectWithConnections(connections: KloLocalProject['config']['connections']): KloLocalProject { + function projectWithConnections(connections: KtxLocalProject['config']['connections']): KtxLocalProject { return { ...project, config: { @@ -101,7 +101,7 @@ describe('local ingest adapters', () => { return { headers: [], rows: [] }; }, }, - postgresBaselineRootDir: join(project.projectDir, '.klo/cache/historic-sql'), + postgresBaselineRootDir: join(project.projectDir, '.ktx/cache/historic-sql'), }, }); @@ -187,7 +187,7 @@ describe('local ingest adapters', () => { }); it('builds Looker pull config from local mapping state', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-local-looker-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-local-looker-')); const lookerProject = { projectDir, config: { @@ -204,12 +204,12 @@ describe('local ingest adapters', () => { }, }, } as never; - const store = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.klo/db.sqlite') }); + const store = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.ktx/db.sqlite') }); await store.setCursors('prod-looker', { dashboardsLastSyncedAt: null, looksLastSyncedAt: null }); await store.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'analytics', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', source: 'cli', }); const lookerDeps = { @@ -263,7 +263,7 @@ describe('local ingest adapters', () => { }); it('builds Looker pull config from yaml mapping bootstrap when SQLite is empty', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-local-looker-yaml-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-local-looker-yaml-')); const lookerProject = { projectDir, config: { @@ -327,7 +327,7 @@ describe('local ingest adapters', () => { }); }); - it('builds LookML pull config from flat klo.yaml connection fields', async () => { + it('builds LookML pull config from flat ktx.yaml connection fields', async () => { const lookmlProject = { projectDir: tempDir, config: { diff --git a/packages/context/src/ingest/local-adapters.ts b/packages/context/src/ingest/local-adapters.ts index 97c40844..51681774 100644 --- a/packages/context/src/ingest/local-adapters.ts +++ b/packages/context/src/ingest/local-adapters.ts @@ -1,7 +1,7 @@ import { join } from 'node:path'; import { localConnectionToWarehouseDescriptor, notionConnectionToPullConfig, parseNotionConnectionConfig } from '../connections/index.js'; -import { resolveKloConfigReference } from '../core/config-reference.js'; -import type { KloLocalProject } from '../project/index.js'; +import { resolveKtxConfigReference } from '../core/config-reference.js'; +import type { KtxLocalProject } from '../project/index.js'; import type { SqlAnalysisPort } from '../sql-analysis/index.js'; import { DbtSourceAdapter } from './adapters/dbt/dbt.adapter.js'; import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; @@ -11,7 +11,7 @@ import { SnowflakeHistoricSqlQueryHistoryReader } from './adapters/historic-sql/ import { HISTORIC_SQL_SOURCE_KEY, historicSqlPullConfigSchema, - type KloPostgresQueryClient, + type KtxPostgresQueryClient, } from './adapters/historic-sql/types.js'; import { createDaemonLiveDatabaseIntrospection, @@ -35,7 +35,7 @@ import { createLocalMetabaseSourceAdapter } from './adapters/metabase/local-meta import { MetricflowSourceAdapter } from './adapters/metricflow/metricflow.adapter.js'; import { pullConfigFromMetricflowIntegration } from './adapters/metricflow/pull-config.js'; import { NotionSourceAdapter } from './adapters/notion/notion.adapter.js'; -import { seedLocalMappingStateFromKloYaml } from './local-mapping-reconcile.js'; +import { seedLocalMappingStateFromKtxYaml } from './local-mapping-reconcile.js'; import type { SourceAdapter } from './types.js'; export interface DefaultLocalIngestAdaptersOptions { @@ -43,7 +43,7 @@ export interface DefaultLocalIngestAdaptersOptions { databaseIntrospection?: Omit; historicSql?: { sqlAnalysis: SqlAnalysisPort; - postgresQueryClient: KloPostgresQueryClient; + postgresQueryClient: KtxPostgresQueryClient; postgresBaselineRootDir?: string; now?: () => Date; }; @@ -57,7 +57,7 @@ export interface DefaultLocalIngestAdaptersOptions { } export function createDefaultLocalIngestAdapters( - project: KloLocalProject, + project: KtxLocalProject, options: DefaultLocalIngestAdaptersOptions = {}, ): SourceAdapter[] { const lookerConnectionFactory = new DefaultLookerConnectionClientFactory( @@ -73,8 +73,8 @@ export function createDefaultLocalIngestAdapters( ...(options.databaseIntrospectionUrl ? { baseUrl: options.databaseIntrospectionUrl } : {}), }), }), - new LookmlSourceAdapter({ homeDir: join(project.projectDir, '.klo/cache') }), - new DbtSourceAdapter({ homeDir: join(project.projectDir, '.klo/cache') }), + new LookmlSourceAdapter({ homeDir: join(project.projectDir, '.ktx/cache') }), + new DbtSourceAdapter({ homeDir: join(project.projectDir, '.ktx/cache') }), createLocalMetabaseSourceAdapter(project), new LookerSourceAdapter({ clientFactory: { @@ -86,7 +86,7 @@ export function createDefaultLocalIngestAdapters( }, }, }), - new MetricflowSourceAdapter({ homeDir: join(project.projectDir, '.klo/cache') }), + new MetricflowSourceAdapter({ homeDir: join(project.projectDir, '.ktx/cache') }), new NotionSourceAdapter(), ]; @@ -128,7 +128,7 @@ function localLookmlPullConfigFromConnection(connection: Record repoUrl: stringField(connection?.repoUrl) ?? stringField(connection?.repo_url) ?? null, branch: stringField(connection?.branch), path: stringField(connection?.path), - authToken: literalAuthToken ?? resolveKloConfigReference(authTokenRef ?? undefined, env) ?? null, + authToken: literalAuthToken ?? resolveKtxConfigReference(authTokenRef ?? undefined, env) ?? null, expectedLookerConnectionName: stringField(mappings.expectedLookerConnectionName), }); } @@ -151,7 +151,7 @@ function localDbtPullConfigFromConnection(connection: Record | } const authToken = stringField(connection?.authToken) ?? - resolveKloConfigReference( + resolveKtxConfigReference( stringField(connection?.auth_token_ref) ?? stringField(connection?.authTokenRef) ?? undefined, env, ); @@ -164,14 +164,14 @@ function localDbtPullConfigFromConnection(connection: Record | } export async function localPullConfigForAdapter( - project: KloLocalProject, + project: KtxLocalProject, adapter: SourceAdapter, connectionId: string, options: DefaultLocalIngestAdaptersOptions = {}, ): Promise { if (adapter.source === 'metabase') { throw new Error( - 'Metabase scheduled pulls fan out by mapping. Call runLocalMetabaseIngest() or use `klo ingest run --adapter metabase --connection-id ` from the CLI.', + 'Metabase scheduled pulls fan out by mapping. Call runLocalMetabaseIngest() or use `ktx ingest run --adapter metabase --connection-id ` from the CLI.', ); } const connection = project.config.connections[connectionId]; @@ -186,8 +186,8 @@ export async function localPullConfigForAdapter( }); } if (adapter.source === 'looker') { - await seedLocalMappingStateFromKloYaml(project, connectionId); - const store = new LocalLookerRuntimeStore({ dbPath: join(project.projectDir, '.klo', 'db.sqlite') }); + await seedLocalMappingStateFromKtxYaml(project, connectionId); + const store = new LocalLookerRuntimeStore({ dbPath: join(project.projectDir, '.ktx', 'db.sqlite') }); const targetConnections = new Map( Object.entries(project.config.connections).flatMap(([id, config]) => { const descriptor = localConnectionToWarehouseDescriptor(id, config); @@ -197,7 +197,7 @@ export async function localPullConfigForAdapter( const parser = options.looker?.parser ?? createDaemonLookerTableIdentifierParser({ - baseUrl: options.looker?.daemonBaseUrl ?? process.env.KLO_DAEMON_URL ?? 'http://127.0.0.1:8765', + baseUrl: options.looker?.daemonBaseUrl ?? process.env.KTX_DAEMON_URL ?? 'http://127.0.0.1:8765', }); let cleanupClient: Pick | null = null; let client: Pick; @@ -241,7 +241,7 @@ export async function localPullConfigForAdapter( const authToken = typeof metricflowConfig?.authToken === 'string' ? metricflowConfig.authToken - : resolveKloConfigReference( + : resolveKtxConfigReference( typeof metricflowConfig?.auth_token_ref === 'string' ? metricflowConfig.auth_token_ref : undefined, options.looker?.env ?? process.env, ); diff --git a/packages/context/src/ingest/local-bundle-ingest.test.ts b/packages/context/src/ingest/local-bundle-ingest.test.ts index 9c0ce2b3..aa423d9e 100644 --- a/packages/context/src/ingest/local-bundle-ingest.test.ts +++ b/packages/context/src/ingest/local-bundle-ingest.test.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AgentRunnerService } from '../agent/index.js'; -import { initKloProject, type KloLocalProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import { makeLocalGitRepo } from '../test/make-local-git-repo.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; @@ -149,14 +149,14 @@ function makeLookerParser() { describe('canonical local ingest', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-full-ingest-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-full-ingest-')); const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -171,7 +171,7 @@ describe('canonical local ingest', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir }); + project = await loadKtxProject({ projectDir }); }); afterEach(async () => { @@ -254,9 +254,9 @@ describe('canonical local ingest', () => { it('rejects direct Metabase scheduled pulls before requiring a local ingest LLM provider', async () => { const projectDir = join(tempDir, 'metabase-project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -271,7 +271,7 @@ describe('canonical local ingest', () => { ].join('\n'), 'utf-8', ); - const metabaseProject = await loadKloProject({ projectDir }); + const metabaseProject = await loadKtxProject({ projectDir }); await expect( runLocalIngest({ @@ -286,7 +286,7 @@ describe('canonical local ingest', () => { it('runs full MetricFlow local ingest from a dbt repo fixture through the canonical runner', async () => { const projectDir = join(tempDir, 'metricflow-run-project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); const fixtureDir = join(tempDir, 'metricflow-fixture'); await mkdir(join(fixtureDir, 'models'), { recursive: true }); @@ -332,7 +332,7 @@ describe('canonical local ingest', () => { const repo = await makeLocalGitRepo(fixtureDir, join(tempDir, 'metricflow-origin')); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -351,13 +351,13 @@ describe('canonical local ingest', () => { ' search: sqlite-fts5', ' git:', ' auto_commit: false', - ' author: KLO Test ', + ' author: KTX Test ', '', ].join('\n'), 'utf-8', ); - const metricflowProject = await loadKloProject({ projectDir }); + const metricflowProject = await loadKtxProject({ projectDir }); const agentRunner = new TestAgentRunner(); const result = await runLocalIngest({ project: metricflowProject, @@ -403,7 +403,7 @@ describe('canonical local ingest', () => { }); it('local metricflow ingest can fetch from connection metricflow config without sourceDir', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-local-mf-fetch-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-local-mf-fetch-')); const fixtureDir = join(projectDir, 'fixture-src'); await mkdir(join(fixtureDir, 'models'), { recursive: true }); await writeFile(join(fixtureDir, 'dbt_project.yml'), 'name: analytics\n', 'utf-8'); @@ -414,7 +414,7 @@ describe('canonical local ingest', () => { ); const repo = await makeLocalGitRepo(fixtureDir, join(projectDir, 'origin')); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: local-mf', 'connections:', @@ -428,13 +428,13 @@ describe('canonical local ingest', () => { ' search: sqlite-fts5', ' git:', ' auto_commit: false', - ' author: KLO Test ', + ' author: KTX Test ', '', ].join('\n'), 'utf-8', ); - const metricflowProject = await loadKloProject({ projectDir }); + const metricflowProject = await loadKtxProject({ projectDir }); const adapters = createDefaultLocalIngestAdapters(metricflowProject); const metricflow = adapters.find((adapter) => adapter.source === 'metricflow'); @@ -450,9 +450,9 @@ describe('canonical local ingest', () => { it('runs scheduled Looker ingest through the canonical local runner and records SL target evidence', async () => { const projectDir = join(tempDir, 'looker-project'); - await initKloProject({ projectDir, projectName: 'looker-runtime' }); + await initKtxProject({ projectDir, projectName: 'looker-runtime' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: looker-runtime', 'connections:', @@ -473,14 +473,14 @@ describe('canonical local ingest', () => { ' search: sqlite-fts5', ' git:', ' auto_commit: false', - ' author: KLO Test ', + ' author: KTX Test ', '', ].join('\n'), 'utf-8', ); - const lookerProject = await loadKloProject({ projectDir }); - const localStore = new LocalLookerRuntimeStore({ dbPath: join(lookerProject.projectDir, '.klo', 'db.sqlite') }); + const lookerProject = await loadKtxProject({ projectDir }); + const localStore = new LocalLookerRuntimeStore({ dbPath: join(lookerProject.projectDir, '.ktx', 'db.sqlite') }); await localStore.setCursors('prod-looker', { dashboardsLastSyncedAt: null, looksLastSyncedAt: null, @@ -488,7 +488,7 @@ describe('canonical local ingest', () => { await localStore.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'analytics', - kloConnectionId: 'prod-warehouse', + ktxConnectionId: 'prod-warehouse', source: 'cli', }); diff --git a/packages/context/src/ingest/local-bundle-runtime.test.ts b/packages/context/src/ingest/local-bundle-runtime.test.ts index 9c3d402f..779c2cc3 100644 --- a/packages/context/src/ingest/local-bundle-runtime.test.ts +++ b/packages/context/src/ingest/local-bundle-runtime.test.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AgentRunnerService } from '../agent/index.js'; -import { initKloProject, type KloLocalProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; import { createLocalBundleIngestRuntime } from './local-bundle-runtime.js'; @@ -18,14 +18,14 @@ type RuntimeWithConnectionDeps = { describe('createLocalBundleIngestRuntime', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-bundle-runtime-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-bundle-runtime-')); const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -40,7 +40,7 @@ describe('createLocalBundleIngestRuntime', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir }); + project = await loadKtxProject({ projectDir }); }); afterEach(async () => { @@ -53,7 +53,7 @@ describe('createLocalBundleIngestRuntime', () => { project, adapters: [new FakeSourceAdapter()], }), - ).toThrow('klo dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner'); + ).toThrow('ktx dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner'); }); it('builds runner deps with local SQLite stores and context tools enabled', async () => { @@ -67,12 +67,12 @@ describe('createLocalBundleIngestRuntime', () => { }); expect(runtime.nextJobId()).toBe('job-1'); - expect(runtime.storage.resolvePullDir('job-1')).toBe(join(project.projectDir, '.klo/cache/local-ingest/job-1/pull')); + expect(runtime.storage.resolvePullDir('job-1')).toBe(join(project.projectDir, '.ktx/cache/local-ingest/job-1/pull')); expect(runtime.storage.resolveUploadDir('job-1')).toBe( - join(project.projectDir, '.klo/cache/local-ingest/job-1/upload'), + join(project.projectDir, '.ktx/cache/local-ingest/job-1/upload'), ); expect(runtime.storage.resolveTranscriptDir('job-1')).toBe( - join(project.projectDir, '.klo/ingest-transcripts/job-1'), + join(project.projectDir, '.ktx/ingest-transcripts/job-1'), ); await mkdir(runtime.storage.resolveUploadDir('job-1'), { recursive: true }); @@ -109,7 +109,7 @@ describe('createLocalBundleIngestRuntime', () => { it('accepts a debug LLM request file when constructing the default agent runner', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -131,14 +131,14 @@ describe('createLocalBundleIngestRuntime', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const runtime = createLocalBundleIngestRuntime({ project, adapters: [new FakeSourceAdapter()], - llmDebugRequestFile: join(project.projectDir, '.klo', 'llm-debug.jsonl'), + llmDebugRequestFile: join(project.projectDir, '.ktx', 'llm-debug.jsonl'), }); - expect(runtime.storage.resolvePullDir('job-1')).toBe(join(project.projectDir, '.klo/cache/local-ingest/job-1/pull')); + expect(runtime.storage.resolvePullDir('job-1')).toBe(join(project.projectDir, '.ktx/cache/local-ingest/job-1/pull')); }); }); diff --git a/packages/context/src/ingest/local-bundle-runtime.ts b/packages/context/src/ingest/local-bundle-runtime.ts index 78f7e4c3..6665682b 100644 --- a/packages/context/src/ingest/local-bundle-runtime.ts +++ b/packages/context/src/ingest/local-bundle-runtime.ts @@ -1,27 +1,27 @@ import { mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import YAML from 'yaml'; import type { AgentRunnerService } from '../agent/index.js'; import { AgentRunnerService as DefaultAgentRunnerService } from '../agent/index.js'; import { localConnectionInfoFromConfig } from '../connections/index.js'; -import type { KloEmbeddingPort, KloLogger } from '../core/index.js'; +import type { KtxEmbeddingPort, KtxLogger } from '../core/index.js'; import { noopLogger, SessionWorktreeService } from '../core/index.js'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; import { - createJsonlKloLlmDebugRequestRecorder, - createLocalKloEmbeddingProviderFromConfig, - createLocalKloLlmProviderFromConfig, - KloIngestEmbeddingPortAdapter, + createJsonlKtxLlmDebugRequestRecorder, + createLocalKtxEmbeddingProviderFromConfig, + createLocalKtxLlmProviderFromConfig, + KtxIngestEmbeddingPortAdapter, } from '../llm/index.js'; -import type { KloLocalProject } from '../project/index.js'; -import { kloLocalStateDbPath } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { ktxLocalStateDbPath } from '../project/index.js'; import { PromptService } from '../prompts/index.js'; import { SkillsRegistryService } from '../skills/index.js'; import { - type KloConnectionInfo, - type KloQueryResult, + type KtxConnectionInfo, + type KtxQueryResult, SemanticLayerService, type SemanticLayerSource, type SlConnectionCatalogPort, @@ -86,20 +86,20 @@ import type { SourceAdapter } from './types.js'; const promptsDir = fileURLToPath(new URL('../../prompts', import.meta.url)); const skillsDir = fileURLToPath(new URL('../../skills', import.meta.url)); -const LOCAL_AUTHOR = { name: 'KLO Local', email: 'local@klo.local' }; +const LOCAL_AUTHOR = { name: 'KTX Local', email: 'local@ktx.local' }; const LOCAL_SHAPE_WARNING = 'Local ingest validates semantic-layer YAML shape only.'; export interface CreateLocalBundleIngestRuntimeOptions { - project: KloLocalProject; + project: KtxLocalProject; adapters: SourceAdapter[]; agentRunner?: AgentRunnerService; - llmProvider?: KloLlmProvider; + llmProvider?: KtxLlmProvider; llmDebugRequestFile?: string; memoryModel?: string; - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; jobIdFactory?: () => string; - logger?: KloLogger; + logger?: KtxLogger; } export interface LocalBundleIngestRuntime { @@ -111,7 +111,7 @@ export interface LocalBundleIngestRuntime { nextJobId(): string; } -class NoopEmbeddingPort implements KloEmbeddingPort { +class NoopEmbeddingPort implements KtxEmbeddingPort { readonly maxBatchSize = 64; async computeEmbedding(): Promise { @@ -127,20 +127,20 @@ class LocalIngestStorage implements IngestStoragePort { readonly homeDir: string; readonly systemGitAuthor = LOCAL_AUTHOR; - constructor(private readonly project: KloLocalProject) { - this.homeDir = join(project.projectDir, '.klo'); + constructor(private readonly project: KtxLocalProject) { + this.homeDir = join(project.projectDir, '.ktx'); } resolveUploadDir(uploadId: string): string { - return join(this.project.projectDir, '.klo/cache/local-ingest', uploadId, 'upload'); + return join(this.project.projectDir, '.ktx/cache/local-ingest', uploadId, 'upload'); } resolvePullDir(jobId: string): string { - return join(this.project.projectDir, '.klo/cache/local-ingest', jobId, 'pull'); + return join(this.project.projectDir, '.ktx/cache/local-ingest', jobId, 'pull'); } resolveTranscriptDir(jobId: string): string { - return join(this.project.projectDir, '.klo/ingest-transcripts', jobId); + return join(this.project.projectDir, '.ktx/ingest-transcripts', jobId); } } @@ -162,19 +162,19 @@ class LocalAuthorResolver implements GitAuthorResolverPort { class LocalConnectionCatalog implements SlConnectionCatalogPort { constructor( - private readonly project: KloLocalProject, + private readonly project: KtxLocalProject, private readonly queryExecutor?: { - execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise; + execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise; }, ) {} - async listEnabledConnections(ids: string[]): Promise { + async listEnabledConnections(ids: string[]): Promise { return ids .map((id) => localConnectionInfoFromConfig(id, this.project.config.connections[id])) - .filter((connection): connection is KloConnectionInfo => connection !== null); + .filter((connection): connection is KtxConnectionInfo => connection !== null); } - async getConnectionById(connectionId: string): Promise { + async getConnectionById(connectionId: string): Promise { const connection = localConnectionInfoFromConfig(connectionId, this.project.config.connections[connectionId]); if (!connection) { throw new Error(`Connection not found: ${connectionId}`); @@ -182,7 +182,7 @@ class LocalConnectionCatalog implements SlConnectionCatalogPort { return connection; } - async executeQuery(connectionId: string, sql: string): Promise { + async executeQuery(connectionId: string, sql: string): Promise { if (!this.queryExecutor) { throw new Error('Local ingest has no query executor configured'); } @@ -191,7 +191,7 @@ class LocalConnectionCatalog implements SlConnectionCatalogPort { } class LocalSlPythonPort implements SlPythonPort { - constructor(private readonly compute?: KloSemanticLayerComputePort) {} + constructor(private readonly compute?: KtxSemanticLayerComputePort) {} async validateSources(input: Parameters[0]) { if (!this.compute) { @@ -271,7 +271,7 @@ function scoreText(text: string, query: string): number { } class LocalKnowledgeIndex implements KnowledgeIndexPort { - constructor(private readonly project: KloLocalProject) {} + constructor(private readonly project: KtxLocalProject) {} async upsertPage(): Promise {} @@ -363,7 +363,7 @@ class LocalIngestToolsetFactory implements IngestToolsetFactoryPort { private readonly contextTools: BaseTool[]; constructor(deps: { - project: KloLocalProject; + project: KtxLocalProject; wikiService: KnowledgeWikiService; knowledgeIndex: KnowledgeIndexPort; knowledgeEvents: KnowledgeEventPort; @@ -373,7 +373,7 @@ class LocalIngestToolsetFactory implements IngestToolsetFactoryPort { slSourcesRepository: SlSourcesIndexPort; connections: SlConnectionCatalogPort; contextStore: SqliteContextEvidenceStore; - embedding: KloEmbeddingPort; + embedding: KtxEmbeddingPort; }) { const slDeps = { semanticLayerService: deps.semanticLayerService, @@ -443,10 +443,10 @@ function nextLocalJobId(): string { function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): { agentRunner: AgentRunnerService; - llmProvider?: KloLlmProvider; + llmProvider?: KtxLlmProvider; } { const llmProvider = - options.llmProvider ?? createLocalKloLlmProviderFromConfig(options.project.config.llm) ?? undefined; + options.llmProvider ?? createLocalKtxLlmProviderFromConfig(options.project.config.llm) ?? undefined; if (options.agentRunner) { return { agentRunner: options.agentRunner, ...(llmProvider ? { llmProvider } : {}) }; @@ -454,7 +454,7 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): { if (!llmProvider) { throw new Error( - 'klo dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', + 'ktx dev ingest run requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner', ); } @@ -463,7 +463,7 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): { llmProvider, logger: options.logger ?? noopLogger, ...(options.llmDebugRequestFile - ? { debugRequestRecorder: createJsonlKloLlmDebugRequestRecorder(options.llmDebugRequestFile) } + ? { debugRequestRecorder: createJsonlKtxLlmDebugRequestRecorder(options.llmDebugRequestFile) } : {}), }), llmProvider, @@ -474,12 +474,12 @@ export function createLocalBundleIngestRuntime( options: CreateLocalBundleIngestRuntimeOptions, ): LocalBundleIngestRuntime { const logger = options.logger ?? noopLogger; - const dbPath = kloLocalStateDbPath(options.project); - mkdirSync(join(options.project.projectDir, '.klo/cache/local-ingest'), { recursive: true }); + const dbPath = ktxLocalStateDbPath(options.project); + mkdirSync(join(options.project.projectDir, '.ktx/cache/local-ingest'), { recursive: true }); const store = new SqliteBundleIngestStore({ dbPath }); const contextStore = new SqliteContextEvidenceStore({ dbPath }); - const embeddingProvider = createLocalKloEmbeddingProviderFromConfig(options.project.config.ingest.embeddings); - const embedding = embeddingProvider ? new KloIngestEmbeddingPortAdapter(embeddingProvider) : new NoopEmbeddingPort(); + const embeddingProvider = createLocalKtxEmbeddingProviderFromConfig(options.project.config.ingest.embeddings); + const embedding = embeddingProvider ? new KtxIngestEmbeddingPortAdapter(embeddingProvider) : new NoopEmbeddingPort(); const connections = new LocalConnectionCatalog(options.project, options.queryExecutor); const rootFileStore = options.project.fileStore; const semanticLayerService = new SemanticLayerService( diff --git a/packages/context/src/ingest/local-embedding-provider.integration.test.ts b/packages/context/src/ingest/local-embedding-provider.integration.test.ts index f1e35e93..af14fea9 100644 --- a/packages/context/src/ingest/local-embedding-provider.integration.test.ts +++ b/packages/context/src/ingest/local-embedding-provider.integration.test.ts @@ -2,7 +2,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { createLocalKloEmbeddingProviderFromConfig, KloIngestEmbeddingPortAdapter } from '../llm/index.js'; +import { createLocalKtxEmbeddingProviderFromConfig, KtxIngestEmbeddingPortAdapter } from '../llm/index.js'; import { CandidateDedupService } from './context-candidates/candidate-dedup.service.js'; import { ContextEvidenceIndexService } from './context-evidence/context-evidence-index.service.js'; import { SqliteContextEvidenceStore } from './context-evidence/sqlite-context-evidence-store.js'; @@ -14,8 +14,8 @@ describe('local ingest embedding providers with SQLite ingest stores', () => { let stagedDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-ingest-embedding-')); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-ingest-embedding-')); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); stagedDir = join(tempDir, 'staged'); await mkdir(join(stagedDir, 'pages', 'revenue'), { recursive: true }); await writeFile( @@ -44,7 +44,7 @@ describe('local ingest embedding providers with SQLite ingest stores', () => { }); function embeddings() { - const provider = createLocalKloEmbeddingProviderFromConfig({ + const provider = createLocalKtxEmbeddingProviderFromConfig({ backend: 'deterministic', dimensions: 8, batchSize: 4, @@ -52,7 +52,7 @@ describe('local ingest embedding providers with SQLite ingest stores', () => { if (!provider) { throw new Error('deterministic local embedding provider was not created'); } - return new KloIngestEmbeddingPortAdapter(provider); + return new KtxIngestEmbeddingPortAdapter(provider); } it('indexes and searches context evidence using a package-owned local embedding provider', async () => { diff --git a/packages/context/src/ingest/local-ingest.ts b/packages/context/src/ingest/local-ingest.ts index ca7f4ace..c4086f4c 100644 --- a/packages/context/src/ingest/local-ingest.ts +++ b/packages/context/src/ingest/local-ingest.ts @@ -1,18 +1,18 @@ import { randomUUID } from 'node:crypto'; import { cp, mkdir, rm } from 'node:fs/promises'; import { isAbsolute, resolve } from 'node:path'; -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import type { AgentRunnerService } from '../agent/index.js'; -import type { KloLogger } from '../core/index.js'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; -import type { KloLocalProject } from '../project/index.js'; -import { kloLocalStateDbPath } from '../project/index.js'; -import type { KloQueryResult } from '../sl/index.js'; +import type { KtxLogger } from '../core/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { ktxLocalStateDbPath } from '../project/index.js'; +import type { KtxQueryResult } from '../sl/index.js'; import { planMetabaseFanoutChildren } from './adapters/metabase/fanout-planner.js'; import { LocalMetabaseSourceStateReader } from './adapters/metabase/local-source-state-store.js'; import { localPullConfigForAdapter, type DefaultLocalIngestAdaptersOptions } from './local-adapters.js'; import { createLocalBundleIngestRuntime } from './local-bundle-runtime.js'; -import { seedLocalMappingStateFromKloYaml } from './local-mapping-reconcile.js'; +import { seedLocalMappingStateFromKtxYaml } from './local-mapping-reconcile.js'; import type { MemoryFlowEventSink } from './memory-flow/types.js'; import { buildSyncId } from './raw-sources-paths.js'; import type { IngestReportBody, IngestReportSnapshot } from './reports.js'; @@ -20,7 +20,7 @@ import { SqliteBundleIngestStore } from './sqlite-bundle-ingest-store.js'; import type { IngestBundleResult, IngestJobContext, IngestJobPhase, IngestTrigger, SourceAdapter } from './types.js'; export interface RunLocalIngestOptions { - project: KloLocalProject; + project: KtxLocalProject; adapters: SourceAdapter[]; adapter: string; connectionId: string; @@ -30,12 +30,12 @@ export interface RunLocalIngestOptions { jobId?: string; memoryFlow?: MemoryFlowEventSink; agentRunner?: AgentRunnerService; - llmProvider?: KloLlmProvider; + llmProvider?: KtxLlmProvider; llmDebugRequestFile?: string; memoryModel?: string; - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; - logger?: KloLogger; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; + logger?: KtxLogger; } export interface LocalIngestMcpOptions @@ -116,12 +116,12 @@ function safeSegment(kind: string, value: string): string { return value; } -function assertConfigured(project: KloLocalProject, adapter: string, connectionId: string): void { +function assertConfigured(project: KtxLocalProject, adapter: string, connectionId: string): void { if (!project.config.connections[connectionId]) { - throw new Error(`Connection "${connectionId}" is not configured in klo.yaml`); + throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`); } if (!project.config.ingest.adapters.includes(adapter)) { - throw new Error(`Adapter "${adapter}" is not enabled in klo.yaml`); + throw new Error(`Adapter "${adapter}" is not enabled in ktx.yaml`); } } @@ -153,7 +153,7 @@ async function copySourceDirToUpload(sourceDir: string, uploadDir: string): Prom } async function runScheduledPullJob(options: { - project: KloLocalProject; + project: KtxLocalProject; adapters: SourceAdapter[]; adapter: SourceAdapter; connectionId: string; @@ -162,11 +162,11 @@ async function runScheduledPullJob(options: { jobId?: string; memoryFlow?: MemoryFlowEventSink; agentRunner?: AgentRunnerService; - llmProvider?: KloLlmProvider; + llmProvider?: KtxLlmProvider; memoryModel?: string; - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; - logger?: KloLogger; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; + logger?: KtxLogger; }): Promise { const runtime = createLocalBundleIngestRuntime(options); const jobId = options.jobId ?? runtime.nextJobId(); @@ -269,14 +269,14 @@ function metabaseChildJobId(metabaseDatabaseId: number): string { } async function recordLocalMetabaseChildFailure(options: { - project: KloLocalProject; + project: KtxLocalProject; jobId: string; targetConnectionId: string; metabaseDatabaseId: number; trigger?: IngestTrigger; error: unknown; }): Promise { - const store = new SqliteBundleIngestStore({ dbPath: kloLocalStateDbPath(options.project) }); + const store = new SqliteBundleIngestStore({ dbPath: ktxLocalStateDbPath(options.project) }); const syncId = buildSyncId(new Date(), options.jobId); const diffSummary = { added: 0, modified: 0, deleted: 0, unchanged: 0 }; const reason = errorMessage(options.error); @@ -357,14 +357,14 @@ export async function runLocalMetabaseIngest( const metabaseConnectionId = safeSegment('metabase connection id', options.metabaseConnectionId); assertConfigured(options.project, 'metabase', metabaseConnectionId); - await seedLocalMappingStateFromKloYaml(options.project, metabaseConnectionId); + await seedLocalMappingStateFromKtxYaml(options.project, metabaseConnectionId); const adapter = findAdapter(options.adapters, 'metabase'); - const sourceStateReader = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(options.project) }); + const sourceStateReader = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(options.project) }); const unhydrated = await sourceStateReader.getUnhydratedSyncEnabledMappingIds(metabaseConnectionId); if (unhydrated.length > 0) { throw new Error( - `Metabase mappings ${unhydrated.join(', ')} are not hydrated; run \`klo connection mapping refresh ${metabaseConnectionId}\` before local Metabase ingest.`, + `Metabase mappings ${unhydrated.join(', ')} are not hydrated; run \`ktx connection mapping refresh ${metabaseConnectionId}\` before local Metabase ingest.`, ); } @@ -385,7 +385,7 @@ export async function runLocalMetabaseIngest( for (const childPlan of childPlans) { const targetConnectionId = safeSegment('target connection id', childPlan.targetConnectionId); if (!options.project.config.connections[targetConnectionId]) { - throw new Error(`Target connection "${targetConnectionId}" is not configured in klo.yaml`); + throw new Error(`Target connection "${targetConnectionId}" is not configured in ktx.yaml`); } const childJobId = options.jobIdFactory?.() ?? metabaseChildJobId(childPlan.metabaseDatabaseId); options.progress?.onMetabaseChildStarted?.({ @@ -448,12 +448,12 @@ export async function runLocalMetabaseIngest( } export async function getLocalIngestStatus( - project: KloLocalProject, + project: KtxLocalProject, id: string, ): Promise { - return new SqliteBundleIngestStore({ dbPath: kloLocalStateDbPath(project) }).findReportByAnyId(id); + return new SqliteBundleIngestStore({ dbPath: ktxLocalStateDbPath(project) }).findReportByAnyId(id); } -export async function getLatestLocalIngestStatus(project: KloLocalProject): Promise { - return new SqliteBundleIngestStore({ dbPath: kloLocalStateDbPath(project) }).findLatestReport(); +export async function getLatestLocalIngestStatus(project: KtxLocalProject): Promise { + return new SqliteBundleIngestStore({ dbPath: ktxLocalStateDbPath(project) }).findLatestReport(); } diff --git a/packages/context/src/ingest/local-mapping-reconcile.test.ts b/packages/context/src/ingest/local-mapping-reconcile.test.ts index 4880fab1..c0f8dcac 100644 --- a/packages/context/src/ingest/local-mapping-reconcile.test.ts +++ b/packages/context/src/ingest/local-mapping-reconcile.test.ts @@ -2,10 +2,10 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; -import { kloLocalStateDbPath, type KloLocalProject } from '../project/index.js'; +import { ktxLocalStateDbPath, type KtxLocalProject } from '../project/index.js'; import { LocalLookerRuntimeStore } from './adapters/looker/local-runtime-store.js'; import { LocalMetabaseSourceStateReader } from './adapters/metabase/local-source-state-store.js'; -import { seedLocalMappingStateFromKloYaml } from './local-mapping-reconcile.js'; +import { seedLocalMappingStateFromKtxYaml } from './local-mapping-reconcile.js'; describe('local mapping yaml reconciliation bridge', () => { let tempDir: string; @@ -16,15 +16,15 @@ describe('local mapping yaml reconciliation bridge', () => { } }); - function projectWithConnections(connections: KloLocalProject['config']['connections']): KloLocalProject { + function projectWithConnections(connections: KtxLocalProject['config']['connections']): KtxLocalProject { return { projectDir: tempDir, config: { connections }, - } as KloLocalProject; + } as KtxLocalProject; } - it('seeds Metabase local state from klo.yaml mapping intent', async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-metabase-yaml-seed-')); + it('seeds Metabase local state from ktx.yaml mapping intent', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'ktx-metabase-yaml-seed-')); const project = projectWithConnections({ 'prod-metabase': { driver: 'metabase', @@ -33,27 +33,27 @@ describe('local mapping yaml reconciliation bridge', () => { syncEnabled: { '1': true }, syncMode: 'ONLY', selections: { collections: [12] }, - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], }, }, 'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' }, }); - await seedLocalMappingStateFromKloYaml(project, 'prod-metabase'); + await seedLocalMappingStateFromKtxYaml(project, 'prod-metabase'); - const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) }); + const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) }); await expect(store.listDatabaseMappings('prod-metabase')).resolves.toMatchObject([ - { metabaseDatabaseId: 1, targetConnectionId: 'prod-warehouse', syncEnabled: true, source: 'klo.yaml' }, + { metabaseDatabaseId: 1, targetConnectionId: 'prod-warehouse', syncEnabled: true, source: 'ktx.yaml' }, ]); await expect(store.getSourceState('prod-metabase')).resolves.toMatchObject({ syncMode: 'ONLY', selections: [{ selectionType: 'collection', metabaseObjectId: 12 }], - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], }); }); - it('seeds Looker local mappings from klo.yaml mapping intent', async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-looker-yaml-seed-')); + it('seeds Looker local mappings from ktx.yaml mapping intent', async () => { + tempDir = await mkdtemp(join(tmpdir(), 'ktx-looker-yaml-seed-')); const project = projectWithConnections({ 'prod-looker': { driver: 'looker', @@ -62,18 +62,18 @@ describe('local mapping yaml reconciliation bridge', () => { 'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' }, }); - await seedLocalMappingStateFromKloYaml(project, 'prod-looker'); + await seedLocalMappingStateFromKtxYaml(project, 'prod-looker'); - const store = new LocalLookerRuntimeStore({ dbPath: kloLocalStateDbPath(project) }); + const store = new LocalLookerRuntimeStore({ dbPath: ktxLocalStateDbPath(project) }); await expect(store.listConnectionMappings('prod-looker')).resolves.toMatchObject([ - { lookerConnectionName: 'analytics', kloConnectionId: 'prod-warehouse', source: 'klo.yaml' }, + { lookerConnectionName: 'analytics', ktxConnectionId: 'prod-warehouse', source: 'ktx.yaml' }, ]); }); it('does nothing for connections without mapping bootstrap intent', async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-no-yaml-seed-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-no-yaml-seed-')); const project = projectWithConnections({ warehouse: { driver: 'postgres', url: 'env:DATABASE_URL' } }); - await expect(seedLocalMappingStateFromKloYaml(project, 'warehouse')).resolves.toBeUndefined(); + await expect(seedLocalMappingStateFromKtxYaml(project, 'warehouse')).resolves.toBeUndefined(); }); }); diff --git a/packages/context/src/ingest/local-mapping-reconcile.ts b/packages/context/src/ingest/local-mapping-reconcile.ts index 58b81e15..a1bae2fc 100644 --- a/packages/context/src/ingest/local-mapping-reconcile.ts +++ b/packages/context/src/ingest/local-mapping-reconcile.ts @@ -1,7 +1,7 @@ import { - kloLocalStateDbPath, + ktxLocalStateDbPath, parseConnectionMappingBootstrap, - type KloLocalProject, + type KtxLocalProject, type LookerMappingBootstrap, type MetabaseMappingBootstrap, } from '../project/index.js'; @@ -30,10 +30,10 @@ function metabaseMappings(bootstrap: MetabaseMappingBootstrap) { function lookerMappings(bootstrap: LookerMappingBootstrap) { return Object.entries(bootstrap.connectionMappings) .sort(([a], [b]) => a.localeCompare(b)) - .map(([lookerConnectionName, kloConnectionId]) => ({ lookerConnectionName, kloConnectionId })); + .map(([lookerConnectionName, ktxConnectionId]) => ({ lookerConnectionName, ktxConnectionId })); } -export async function seedLocalMappingStateFromKloYaml(project: KloLocalProject, connectionId: string): Promise { +export async function seedLocalMappingStateFromKtxYaml(project: KtxLocalProject, connectionId: string): Promise { const connection = project.config.connections[connectionId]; if (!connection) { return; @@ -44,7 +44,7 @@ export async function seedLocalMappingStateFromKloYaml(project: KloLocalProject, return; } - const dbPath = kloLocalStateDbPath(project); + const dbPath = ktxLocalStateDbPath(project); if (bootstrap.adapter === 'metabase') { await new LocalMetabaseSourceStateReader({ dbPath }).applyYamlBootstrap({ connectionId, diff --git a/packages/context/src/ingest/local-metabase-ingest.test.ts b/packages/context/src/ingest/local-metabase-ingest.test.ts index 86dcb7fa..da00c7ec 100644 --- a/packages/context/src/ingest/local-metabase-ingest.test.ts +++ b/packages/context/src/ingest/local-metabase-ingest.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AgentRunnerService } from '../agent/index.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { LocalMetabaseSourceStateReader } from './adapters/metabase/local-source-state-store.js'; import { getLocalIngestStatus, runLocalMetabaseIngest } from './local-ingest.js'; import type { ChunkResult, FetchContext, SourceAdapter } from './types.js'; @@ -72,11 +72,11 @@ class ThrowingFetchMetabaseSourceAdapter extends FakeMetabaseSourceAdapter { describe('runLocalMetabaseIngest', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-metabase-fanout-')); - project = await initKloProject({ projectDir: tempDir, force: true }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-metabase-fanout-')); + project = await initKtxProject({ projectDir: tempDir, force: true }); project.config.connections = { 'prod-metabase': { driver: 'metabase', @@ -94,11 +94,11 @@ describe('runLocalMetabaseIngest', () => { }); async function seedMetabaseState(): Promise { - const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.klo', 'db.sqlite') }); + const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.ktx', 'db.sqlite') }); await store.replaceSourceState({ connectionId: 'prod-metabase', syncMode: 'ALL', - defaultTagNames: ['klo'], + defaultTagNames: ['ktx'], selections: [], mappings: [ { @@ -151,7 +151,7 @@ describe('runLocalMetabaseIngest', () => { }); it('throws before runner work when there are no sync-enabled mapped rows', async () => { - const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.klo', 'db.sqlite') }); + const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.ktx', 'db.sqlite') }); await store.replaceSourceState({ connectionId: 'prod-metabase', mappings: [ @@ -179,7 +179,7 @@ describe('runLocalMetabaseIngest', () => { }); it('throws with refresh guidance for unhydrated sync-enabled rows', async () => { - const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.klo', 'db.sqlite') }); + const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.ktx', 'db.sqlite') }); await store.replaceSourceState({ connectionId: 'prod-metabase', mappings: [ @@ -191,7 +191,7 @@ describe('runLocalMetabaseIngest', () => { metabaseDbName: null, targetConnectionId: 'warehouse_a', syncEnabled: true, - source: 'klo.yaml', + source: 'ktx.yaml', }, ], }); @@ -203,7 +203,7 @@ describe('runLocalMetabaseIngest', () => { metabaseConnectionId: 'prod-metabase', agentRunner: new TestAgentRunner(), }), - ).rejects.toThrow('run `klo connection mapping refresh prod-metabase`'); + ).rejects.toThrow('run `ktx connection mapping refresh prod-metabase`'); }); it('seeds yaml-only Metabase mappings before the unhydrated fan-out preflight', async () => { @@ -230,7 +230,7 @@ describe('runLocalMetabaseIngest', () => { adapters: [new FakeMetabaseSourceAdapter()], metabaseConnectionId: 'prod-metabase', }), - ).rejects.toThrow('run `klo connection mapping refresh prod-metabase`'); + ).rejects.toThrow('run `ktx connection mapping refresh prod-metabase`'); }); it('rejects source-dir uploads through the Metabase fan-out runner', async () => { @@ -266,7 +266,7 @@ describe('runLocalMetabaseIngest', () => { it('captures fetch-time child failures and continues later mappings', async () => { await seedMetabaseState(); project.config.connections.warehouse_c = { driver: 'postgres', url: 'postgres://localhost/c' }; - const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.klo', 'db.sqlite') }); + const store = new LocalMetabaseSourceStateReader({ dbPath: join(tempDir, '.ktx', 'db.sqlite') }); await store.upsertDatabaseMapping({ connectionId: 'prod-metabase', metabaseDatabaseId: 3, diff --git a/packages/context/src/ingest/local-stage-ingest.test.ts b/packages/context/src/ingest/local-stage-ingest.test.ts index 77615558..e24174fb 100644 --- a/packages/context/src/ingest/local-stage-ingest.test.ts +++ b/packages/context/src/ingest/local-stage-ingest.test.ts @@ -2,7 +2,7 @@ import { access, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promise import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { initKloProject, type KloLocalProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; import { createDefaultLocalIngestAdapters } from './local-adapters.js'; import { @@ -15,7 +15,7 @@ import type { SourceAdapter } from './types.js'; async function writeWarehouseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -32,7 +32,7 @@ async function writeWarehouseConfig(projectDir: string): Promise { async function writeLiveDatabaseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -84,14 +84,14 @@ function fetchOnlyAdapter(): SourceAdapter { describe('local ingest', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-ingest-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-ingest-')); const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); - project = await loadKloProject({ projectDir }); + project = await loadKtxProject({ projectDir }); }); afterEach(async () => { @@ -158,12 +158,12 @@ describe('local ingest', () => { const status = await getLocalStageOnlyIngestStatus(project, 'local-job-1'); expect(status).toEqual(result); - await expect(access(join(project.projectDir, '.klo', 'db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx', 'db.sqlite'))).resolves.toBeUndefined(); await expect( - readFile(join(project.projectDir, '.klo', 'ingest-runs', 'local-job-1.json'), 'utf-8'), + readFile(join(project.projectDir, '.ktx', 'ingest-runs', 'local-job-1.json'), 'utf-8'), ).rejects.toThrow(); await expect( - readFile(join(project.projectDir, '.klo', 'ingest-reports', 'local-job-1.json'), 'utf-8'), + readFile(join(project.projectDir, '.ktx', 'ingest-reports', 'local-job-1.json'), 'utf-8'), ).rejects.toThrow(); }); @@ -345,12 +345,12 @@ describe('local ingest', () => { const status = await getLocalStageOnlyIngestStatus(project, 'local-job-3'); expect(status).toEqual(changed); - await expect(access(join(project.projectDir, '.klo', 'db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx', 'db.sqlite'))).resolves.toBeUndefined(); await expect( - readFile(join(project.projectDir, '.klo', 'ingest-runs', 'local-job-3.json'), 'utf-8'), + readFile(join(project.projectDir, '.ktx', 'ingest-runs', 'local-job-3.json'), 'utf-8'), ).rejects.toThrow(); await expect( - readFile(join(project.projectDir, '.klo', 'ingest-reports', 'local-job-3.json'), 'utf-8'), + readFile(join(project.projectDir, '.ktx', 'ingest-reports', 'local-job-3.json'), 'utf-8'), ).rejects.toThrow(); }); @@ -430,7 +430,7 @@ describe('local ingest', () => { it('runs fetch-capable adapters without a source directory', async () => { await writeLiveDatabaseConfig(project.projectDir); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalStageOnlyIngest({ project, @@ -470,7 +470,7 @@ describe('local ingest', () => { it('supports dry-run planning without writing raw files, status, or commits', async () => { await writeLiveDatabaseConfig(project.projectDir); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalStageOnlyIngest({ project, @@ -516,7 +516,7 @@ describe('local ingest', () => { it('uses daemon-backed live-database introspection in default local adapters', async () => { await writeLiveDatabaseConfig(project.projectDir); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const runJson = vi.fn(async () => ({ connection_id: 'warehouse', extracted_at: '2026-04-28T10:00:00+00:00', @@ -562,7 +562,7 @@ describe('local ingest', () => { }); }); - it('includes upload-capable KLO adapters in default local ingest adapters', () => { + it('includes upload-capable KTX adapters in default local ingest adapters', () => { expect(createDefaultLocalIngestAdapters(project).map((adapter) => adapter.source)).toEqual( expect.arrayContaining(['dbt', 'metricflow', 'notion']), ); @@ -573,7 +573,7 @@ describe('local ingest', () => { process.env.NOTION_AUTH_TOKEN = 'ntn_local_test_token'; try { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -590,7 +590,7 @@ describe('local ingest', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const fetch = vi.fn(async (_pullConfig: unknown, stagedDir: string) => { await mkdir(join(stagedDir, 'pages', 'page-1'), { recursive: true }); @@ -686,7 +686,7 @@ describe('local ingest', () => { ).rejects.toThrow('Local ingest adapter "fake" requires sourceDir because it does not implement fetch().'); }); - it('rejects adapters that are not enabled in klo.yaml', async () => { + it('rejects adapters that are not enabled in ktx.yaml', async () => { const sourceDir = join(tempDir, 'source'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); await writeFile(join(sourceDir, 'orders', 'orders.json'), '{"name":"orders"}\n', 'utf-8'); @@ -701,6 +701,6 @@ describe('local ingest', () => { jobId: 'local-job-2', now: () => new Date('2026-04-27T12:00:00.000Z'), }), - ).rejects.toThrow('Adapter "metricflow" is not enabled in klo.yaml'); + ).rejects.toThrow('Adapter "metricflow" is not enabled in ktx.yaml'); }); }); diff --git a/packages/context/src/ingest/local-stage-ingest.ts b/packages/context/src/ingest/local-stage-ingest.ts index eb2a2f7e..001c321e 100644 --- a/packages/context/src/ingest/local-stage-ingest.ts +++ b/packages/context/src/ingest/local-stage-ingest.ts @@ -1,8 +1,8 @@ import { createHash } from 'node:crypto'; import { cp, mkdir, readdir, readFile, rm } from 'node:fs/promises'; import { isAbsolute, join, relative, resolve, sep } from 'node:path'; -import type { KloLocalProject } from '../project/index.js'; -import { kloLocalStateDbPath } from '../project/local-state-db.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { ktxLocalStateDbPath } from '../project/local-state-db.js'; import { computeDiffSetFromHashes } from './diff-set.service.js'; import { localPullConfigForAdapter } from './local-adapters.js'; import { sanitizeMemoryFlowError } from './memory-flow/live-buffer.js'; @@ -52,7 +52,7 @@ export type LocalIngestReport = LocalIngestRunRecord & { }; export interface RunLocalStageOnlyIngestOptions { - project: KloLocalProject; + project: KtxLocalProject; adapters: SourceAdapter[]; adapter: string; connectionId: string; @@ -64,8 +64,8 @@ export interface RunLocalStageOnlyIngestOptions { memoryFlow?: MemoryFlowEventSink; } -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; function safeSegment(kind: string, value: string): string { if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) { @@ -143,17 +143,17 @@ function findAdapter(adapters: SourceAdapter[], source: string): SourceAdapter { return adapter; } -function assertConfigured(project: KloLocalProject, adapter: string, connectionId: string): void { +function assertConfigured(project: KtxLocalProject, adapter: string, connectionId: string): void { if (!project.config.connections[connectionId]) { - throw new Error(`Connection "${connectionId}" is not configured in klo.yaml`); + throw new Error(`Connection "${connectionId}" is not configured in ktx.yaml`); } if (!project.config.ingest.adapters.includes(adapter)) { - throw new Error(`Adapter "${adapter}" is not enabled in klo.yaml`); + throw new Error(`Adapter "${adapter}" is not enabled in ktx.yaml`); } } -function createLocalIngestStore(project: KloLocalProject): SqliteLocalIngestStore { - return new SqliteLocalIngestStore({ dbPath: kloLocalStateDbPath(project) }); +function createLocalIngestStore(project: KtxLocalProject): SqliteLocalIngestStore { + return new SqliteLocalIngestStore({ dbPath: ktxLocalStateDbPath(project) }); } function buildLocalJobId(now: Date): string { @@ -189,7 +189,7 @@ function memoryFlowPlannedWorkUnits( } async function pruneStaleRawFiles(input: { - project: KloLocalProject; + project: KtxLocalProject; rawPrefix: string; nextRawPaths: string[]; adapter: string; @@ -210,7 +210,7 @@ async function pruneStaleRawFiles(input: { } async function prepareLocalStagedDir( - project: KloLocalProject, + project: KtxLocalProject, adapter: SourceAdapter, stagedDir: string, sourceDir: string | undefined, @@ -263,7 +263,7 @@ async function runLocalStageOnlyIngestInner(options: RunLocalStageOnlyIngestOpti const existingRun = options.dryRun ? null : store.findRunById(runId); assertCompatibleExistingRun(existingRun, runId, adapter.source, connectionId); - const stagedDir = join(options.project.projectDir, '.klo/cache/local-ingest', runId, 'staged'); + const stagedDir = join(options.project.projectDir, '.ktx/cache/local-ingest', runId, 'staged'); const sourceDir = await prepareLocalStagedDir(options.project, adapter, stagedDir, options.sourceDir, connectionId); const detected = await adapter.detect(stagedDir); @@ -404,7 +404,7 @@ async function runLocalStageOnlyIngestInner(options: RunLocalStageOnlyIngestOpti } export async function getLocalStageOnlyIngestStatus( - project: KloLocalProject, + project: KtxLocalProject, runId: string, ): Promise { return createLocalIngestStore(project).findRunById(runId); diff --git a/packages/context/src/ingest/memory-flow/acceptance.test.ts b/packages/context/src/ingest/memory-flow/acceptance.test.ts index 7376c5db..42bff8a0 100644 --- a/packages/context/src/ingest/memory-flow/acceptance.test.ts +++ b/packages/context/src/ingest/memory-flow/acceptance.test.ts @@ -17,7 +17,7 @@ describe('memory-flow acceptance scenarios', () => { it('renders a completed replay with a clear saved-memory completion line', () => { const output = renderScenario(successfulReplayScenario()); - expect(output).toContain('KLO memory flow warehouse/metricflow done'); + expect(output).toContain('KTX memory flow warehouse/metricflow done'); expect(output).toContain('Saved 3 memories from 4 raw files: 2 wiki pages, 1 SL updates.'); expect(output).toContain('Commit: abc12345 Run: run-success Report: ingest-report.json'); }); @@ -48,7 +48,7 @@ describe('memory-flow acceptance scenarios', () => { it('renders no ANSI color codes in the text fallback for terminals without color support', () => { const output = renderScenario(successfulReplayScenario(), 80); - expect(output).toContain('KLO memory flow warehouse/metricflow done'); + expect(output).toContain('KTX memory flow warehouse/metricflow done'); expect(output).not.toMatch(/\u001b\[[0-9;]*m/); }); diff --git a/packages/context/src/ingest/memory-flow/events.test.ts b/packages/context/src/ingest/memory-flow/events.test.ts index 46f4eaf4..e65cfc83 100644 --- a/packages/context/src/ingest/memory-flow/events.test.ts +++ b/packages/context/src/ingest/memory-flow/events.test.ts @@ -96,14 +96,14 @@ function reportSnapshot(): IngestReportSnapshot { toolTranscripts: [ { unitKey: 'orders', - path: '/tmp/klo/run/wu-transcripts/job-1/orders.jsonl', + path: '/tmp/ktx/run/wu-transcripts/job-1/orders.jsonl', toolCallCount: 3, errorCount: 0, toolNames: ['read_raw_span', 'wiki_write', 'sl_write_source'], }, { unitKey: 'customers', - path: '/tmp/klo/run/wu-transcripts/job-1/customers.jsonl', + path: '/tmp/ktx/run/wu-transcripts/job-1/customers.jsonl', toolCallCount: 2, errorCount: 1, toolNames: ['read_raw_span', 'sl_write_source'], @@ -244,14 +244,14 @@ describe('memory-flow event mapping', () => { expect(replay.details.transcripts).toEqual([ { unitKey: 'orders', - path: '/tmp/klo/run/wu-transcripts/job-1/orders.jsonl', + path: '/tmp/ktx/run/wu-transcripts/job-1/orders.jsonl', toolCallCount: 3, errorCount: 0, toolNames: ['read_raw_span', 'wiki_write', 'sl_write_source'], }, { unitKey: 'customers', - path: '/tmp/klo/run/wu-transcripts/job-1/customers.jsonl', + path: '/tmp/ktx/run/wu-transcripts/job-1/customers.jsonl', toolCallCount: 2, errorCount: 1, toolNames: ['read_raw_span', 'sl_write_source'], diff --git a/packages/context/src/ingest/memory-flow/interaction.test.ts b/packages/context/src/ingest/memory-flow/interaction.test.ts index 138878ed..d997b236 100644 --- a/packages/context/src/ingest/memory-flow/interaction.test.ts +++ b/packages/context/src/ingest/memory-flow/interaction.test.ts @@ -13,7 +13,7 @@ import type { MemoryFlowInteractionState, MemoryFlowViewModel } from './types.js function view(): MemoryFlowViewModel { return { - title: 'KLO memory flow warehouse/metricflow running', + title: 'KTX memory flow warehouse/metricflow running', subtitle: 'Run run-1 Sync sync-1', status: 'running', activeLine: 'active: WorkUnit orders step 2/4', diff --git a/packages/context/src/ingest/memory-flow/interactive-render.test.ts b/packages/context/src/ingest/memory-flow/interactive-render.test.ts index 50e00127..a3ff0d5c 100644 --- a/packages/context/src/ingest/memory-flow/interactive-render.test.ts +++ b/packages/context/src/ingest/memory-flow/interactive-render.test.ts @@ -5,7 +5,7 @@ import type { MemoryFlowViewModel } from './types.js'; function view(): MemoryFlowViewModel { return { - title: 'KLO memory flow warehouse/metricflow done', + title: 'KTX memory flow warehouse/metricflow done', subtitle: 'Run run-1 Sync sync-1', status: 'done', activeLine: 'active: complete', @@ -128,7 +128,7 @@ describe('renderMemoryFlowInteractive', () => { const output = renderMemoryFlowInteractive(view(), state, { terminalWidth: 140 }); - expect(output).toContain('KLO memory flow warehouse/metricflow done'); + expect(output).toContain('KTX memory flow warehouse/metricflow done'); expect(output).toContain('OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED'); expect(output).toContain('[WORKUNITS]'); expect(output).toContain('> orders'); diff --git a/packages/context/src/ingest/memory-flow/package-export.test.ts b/packages/context/src/ingest/memory-flow/package-export.test.ts index 3ce54fdd..5ddd9b91 100644 --- a/packages/context/src/ingest/memory-flow/package-export.test.ts +++ b/packages/context/src/ingest/memory-flow/package-export.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/context/ingest/memory-flow lightweight export', () => { +describe('@ktx/context/ingest/memory-flow lightweight export', () => { it('exports replay parsing and text rendering without the full ingest entry point', async () => { const memoryFlow = await import('./index.js'); diff --git a/packages/context/src/ingest/memory-flow/render.test.ts b/packages/context/src/ingest/memory-flow/render.test.ts index d7900bf8..e1bf425a 100644 --- a/packages/context/src/ingest/memory-flow/render.test.ts +++ b/packages/context/src/ingest/memory-flow/render.test.ts @@ -4,7 +4,7 @@ import { renderMemoryFlowReplay } from './render.js'; function view(): MemoryFlowViewModel { return { - title: 'KLO memory flow warehouse/metricflow done', + title: 'KTX memory flow warehouse/metricflow done', subtitle: 'Run run-1 Sync sync-1', status: 'done', activeLine: 'active: complete', @@ -79,7 +79,7 @@ describe('renderMemoryFlowReplay', () => { 'OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED', ); expect(renderMemoryFlowReplay(view(), { terminalWidth: 140 })).toMatchInlineSnapshot(` - "KLO memory flow warehouse/metricflow done + "KTX memory flow warehouse/metricflow done active: complete Run run-1 Sync sync-1 OK SOURCE -> OK CHUNKS -> !! WORKUNITS -> OK ACTIONS -> !! GATES -> OK SAVED diff --git a/packages/context/src/ingest/memory-flow/view-model.test.ts b/packages/context/src/ingest/memory-flow/view-model.test.ts index 527efe0a..27322c69 100644 --- a/packages/context/src/ingest/memory-flow/view-model.test.ts +++ b/packages/context/src/ingest/memory-flow/view-model.test.ts @@ -93,7 +93,7 @@ describe('buildMemoryFlowViewModel', () => { it('builds six readable columns from replay events', () => { const view = buildMemoryFlowViewModel(replayInput()); - expect(view.title).toBe('KLO memory flow warehouse/metricflow done'); + expect(view.title).toBe('KTX memory flow warehouse/metricflow done'); expect(view.activeLine).toBe('active: complete'); expect(view.columns.map((column) => column.id)).toEqual([ 'source', @@ -179,7 +179,7 @@ describe('buildMemoryFlowViewModel', () => { details: { actions: [], provenance: [], transcripts: [] }, }); - expect(view.title).toBe('KLO memory flow Warehouse + dbt + BI + Docs done'); + expect(view.title).toBe('KTX memory flow Warehouse + dbt + BI + Docs done'); expect(view.columns.find((column) => column.id === 'source')?.counters[0]).toBe('Warehouse, dbt, BI, Docs'); expect(view.completionLine).toContain('Saved 16 memories from 29 raw files'); }); diff --git a/packages/context/src/ingest/memory-flow/view-model.ts b/packages/context/src/ingest/memory-flow/view-model.ts index 64d0b433..7c908fbc 100644 --- a/packages/context/src/ingest/memory-flow/view-model.ts +++ b/packages/context/src/ingest/memory-flow/view-model.ts @@ -509,7 +509,7 @@ export function buildMemoryFlowViewModel(input: MemoryFlowReplayInput): MemoryFl : `${input.connectionId}/${input.adapter}`; return { - title: `KLO memory flow ${titleSources} ${input.status}`, + title: `KTX memory flow ${titleSources} ${input.status}`, subtitle: `Run ${input.runId} Sync ${input.syncId}`, status: input.status, activeLine: activeLine(input), diff --git a/packages/context/src/ingest/memory-flow/visuals.test.ts b/packages/context/src/ingest/memory-flow/visuals.test.ts index 75bb16a1..7144c897 100644 --- a/packages/context/src/ingest/memory-flow/visuals.test.ts +++ b/packages/context/src/ingest/memory-flow/visuals.test.ts @@ -11,7 +11,7 @@ function viewWithStatuses(statuses: Array<'waiting' | 'active' | 'complete' | 'w const ids = ['source', 'chunks', 'workUnits', 'actions', 'gates', 'saved'] as const; return { - title: 'KLO memory flow warehouse/metricflow running', + title: 'KTX memory flow warehouse/metricflow running', subtitle: 'Run run-1 Sync sync-1', status: 'running', activeLine: 'active: WorkUnit orders', diff --git a/packages/context/src/ingest/metabase-mapping.ts b/packages/context/src/ingest/metabase-mapping.ts index 471d01d9..5ce66a12 100644 --- a/packages/context/src/ingest/metabase-mapping.ts +++ b/packages/context/src/ingest/metabase-mapping.ts @@ -12,7 +12,7 @@ export type { AutoMatchCandidate, AutoMatchResult as MetabaseAutoMatchResult, DiscoveredMetabaseDatabase, - KloConnectionPhysicalInfo, + KtxConnectionPhysicalInfo, MappingPhysicalInfo, MappingRefreshReport, MetabaseMappedConnectionType, diff --git a/packages/context/src/ingest/page-triage/page-triage.service.test.ts b/packages/context/src/ingest/page-triage/page-triage.service.test.ts index 1586727f..5e53d233 100644 --- a/packages/context/src/ingest/page-triage/page-triage.service.test.ts +++ b/packages/context/src/ingest/page-triage/page-triage.service.test.ts @@ -216,7 +216,7 @@ describe('PageTriageService', () => { 'Reusable outbound sequence:', '', '- Ask about current customer success expansion workflow.', - '- Position KLO as AI search visibility for CS teams.', + '- Position KTX as AI search visibility for CS teams.', '- Close with a discovery call request.', ].join('\n'), 'utf-8', @@ -247,7 +247,7 @@ describe('PageTriageService', () => { { candidateKey: 'cold-call-script', topic: 'Cold Call Script', - assertion: 'Cold call outreach should position KLO around AI search visibility for CS teams.', + assertion: 'Cold call outreach should position KTX around AI search visibility for CS teams.', rationale: 'The script gives a reusable outbound call sequence and positioning language.', evidenceChunkIds: ['00000000-0000-0000-0000-000000000101'], suggestedPageKey: 'cold-call-script', diff --git a/packages/context/src/ingest/page-triage/page-triage.service.ts b/packages/context/src/ingest/page-triage/page-triage.service.ts index fc4bc389..d362ef53 100644 --- a/packages/context/src/ingest/page-triage/page-triage.service.ts +++ b/packages/context/src/ingest/page-triage/page-triage.service.ts @@ -1,11 +1,11 @@ import { createHash } from 'node:crypto'; import { readdir, readFile } from 'node:fs/promises'; import { dirname, join, relative } from 'node:path'; -import { KloMessageBuilder, type KloLlmProvider } from '@klo/llm'; +import { KtxMessageBuilder, type KtxLlmProvider } from '@ktx/llm'; import { generateText, type ToolSet } from 'ai'; import pLimit from 'p-limit'; import { z } from 'zod'; -import { type KloLogger, noopLogger } from '../../core/index.js'; +import { type KtxLogger, noopLogger } from '../../core/index.js'; import type { PromptService } from '../../prompts/index.js'; import type { InsertContextCandidateInput } from '../context-candidates/index.js'; import type { JsonValue } from '../ports.js'; @@ -100,15 +100,15 @@ export interface PageTriageSettings { export interface PageTriageServiceDeps { store: PageTriageStorePort; - llmProvider: KloLlmProvider; + llmProvider: KtxLlmProvider; settings: PageTriageSettings; promptService: PromptService; - logger?: KloLogger; + logger?: KtxLogger; generateText?: typeof generateText; } export class PageTriageService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private readonly runGenerateText: typeof generateText; constructor(private readonly deps: PageTriageServiceDeps) { @@ -335,7 +335,7 @@ export class PageTriageService { unitKey: string; }): Promise { const model = this.deps.llmProvider.getModel('triage'); - const built = new KloMessageBuilder(this.deps.llmProvider).wrapSimple({ + const built = new KtxMessageBuilder(this.deps.llmProvider).wrapSimple({ messages: [{ role: 'user', content: params.prompt }], tools: {}, model, diff --git a/packages/context/src/ingest/ports.ts b/packages/context/src/ingest/ports.ts index 0ec51939..fbb2451e 100644 --- a/packages/context/src/ingest/ports.ts +++ b/packages/context/src/ingest/ports.ts @@ -1,8 +1,8 @@ import type { ToolSet } from 'ai'; -import type { KloModelRole } from '@klo/llm'; +import type { KtxModelRole } from '@ktx/llm'; import type { AgentRunnerService } from '../agent/index.js'; -import type { KloEmbeddingPort } from '../core/embedding.js'; -import type { GitService, KloFileStorePort, KloLogger, SessionOutcome } from '../core/index.js'; +import type { KtxEmbeddingPort } from '../core/embedding.js'; +import type { GitService, KtxFileStorePort, KtxLogger, SessionOutcome } from '../core/index.js'; import type { CaptureSession, MemoryAction, MemoryKnowledgeSlRefsPort } from '../memory/index.js'; import type { PromptService } from '../prompts/index.js'; import type { SkillsRegistryService } from '../skills/index.js'; @@ -120,7 +120,7 @@ export interface IngestLockPort { withLock(key: string, fn: () => Promise): Promise; } -export interface IngestFileStorePort extends KloFileStorePort {} +export interface IngestFileStorePort extends KtxFileStorePort {} export interface IngestSessionWorktree { chatId: string; @@ -308,7 +308,7 @@ export interface CuratorPaginationPort { evictionUnit: EvictionUnit | undefined; representatives: ContextCandidateForDedup[]; initialBudget: { creates: number; updates: number }; - modelRole: KloModelRole; + modelRole: KtxModelRole; buildSystemPrompt: () => string; buildUserPrompt: (input: { summary: ReconcileCandidateSummary; @@ -367,7 +367,7 @@ export interface IngestBundleRunnerDeps { slValidator: SlValidatorPort; toolsetFactory: IngestToolsetFactoryPort; commitMessages: IngestCommitMessagePort; - embedding: KloEmbeddingPort; + embedding: KtxEmbeddingPort; contextEvidenceIndex?: ContextEvidenceIndexPort; pageTriage?: PageTriagePort; contextEvidenceCandidates?: ContextEvidenceCandidatesPort; @@ -375,7 +375,7 @@ export interface IngestBundleRunnerDeps { contextCandidateCarryforward?: ContextCandidateCarryforwardPort; curatorPagination?: CuratorPaginationPort; postProcessors?: Record; - logger?: KloLogger; + logger?: KtxLogger; } export interface IngestCaptureState { diff --git a/packages/context/src/ingest/repo-fetch.test.ts b/packages/context/src/ingest/repo-fetch.test.ts index 0c8e2343..dcefd6ca 100644 --- a/packages/context/src/ingest/repo-fetch.test.ts +++ b/packages/context/src/ingest/repo-fetch.test.ts @@ -105,8 +105,8 @@ describe('repo-fetch', () => { await cloneOrPull({ repoUrl: repo.repoUrl, cacheDir, branch: 'main' }); const cacheGit = createSimpleGit(cacheDir); - await cacheGit.addConfig('user.email', 'test@klo.local'); - await cacheGit.addConfig('user.name', 'KLO Test'); + await cacheGit.addConfig('user.email', 'test@ktx.local'); + await cacheGit.addConfig('user.name', 'KTX Test'); await writeFile(join(cacheDir, 'local-only.txt'), 'local commit\n', 'utf-8'); await cacheGit.add('.'); await cacheGit.commit('local-only divergent commit'); diff --git a/packages/context/src/ingest/sqlite-bundle-ingest-store.test.ts b/packages/context/src/ingest/sqlite-bundle-ingest-store.test.ts index d9afa467..3c27ab2d 100644 --- a/packages/context/src/ingest/sqlite-bundle-ingest-store.test.ts +++ b/packages/context/src/ingest/sqlite-bundle-ingest-store.test.ts @@ -71,8 +71,8 @@ describe('SqliteBundleIngestStore', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-bundle-ingest-store-')); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-bundle-ingest-store-')); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); }); afterEach(async () => { diff --git a/packages/context/src/ingest/sqlite-local-ingest-store.test.ts b/packages/context/src/ingest/sqlite-local-ingest-store.test.ts index f311bf31..67fad006 100644 --- a/packages/context/src/ingest/sqlite-local-ingest-store.test.ts +++ b/packages/context/src/ingest/sqlite-local-ingest-store.test.ts @@ -52,8 +52,8 @@ describe('SqliteLocalIngestStore', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-sqlite-local-ingest-')); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-sqlite-local-ingest-')); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); }); afterEach(async () => { diff --git a/packages/context/src/ingest/stages/stage-3-work-units.ts b/packages/context/src/ingest/stages/stage-3-work-units.ts index 511ada14..bbf23079 100644 --- a/packages/context/src/ingest/stages/stage-3-work-units.ts +++ b/packages/context/src/ingest/stages/stage-3-work-units.ts @@ -1,5 +1,5 @@ -import type { AgentRunnerService } from '@klo/context/agent'; -import type { KloModelRole } from '@klo/llm'; +import type { AgentRunnerService } from '@ktx/context/agent'; +import type { KtxModelRole } from '@ktx/llm'; import type { Tool } from 'ai'; import type { CaptureSession, MemoryAction } from '../../memory/index.js'; import { listTouchedSlSources, type TouchedSlSource } from '../../tools/index.js'; @@ -22,7 +22,7 @@ export interface WorkUnitExecutionDeps { buildToolSet: (wu: WorkUnit) => Record; captureSession: CaptureSession; sessionActions: MemoryAction[]; - modelRole: KloModelRole; + modelRole: KtxModelRole; stepBudget: number; sourceKey: string; connectionId: string; diff --git a/packages/context/src/ingest/stages/stage-4-reconciliation.ts b/packages/context/src/ingest/stages/stage-4-reconciliation.ts index 9252130d..00bb2d2c 100644 --- a/packages/context/src/ingest/stages/stage-4-reconciliation.ts +++ b/packages/context/src/ingest/stages/stage-4-reconciliation.ts @@ -1,5 +1,5 @@ -import type { AgentRunnerService } from '@klo/context/agent'; -import type { KloModelRole } from '@klo/llm'; +import type { AgentRunnerService } from '@ktx/context/agent'; +import type { KtxModelRole } from '@ktx/llm'; import type { ToolSet } from 'ai'; import type { EvictionUnit } from '../types.js'; import type { StageIndex } from './stage-index.types.js'; @@ -11,7 +11,7 @@ export interface ReconciliationContext { buildSystemPrompt: (idx: StageIndex, ev: EvictionUnit | undefined) => string; buildUserPrompt: (idx: StageIndex, ev: EvictionUnit | undefined) => string; buildToolSet: () => ToolSet; - modelRole: KloModelRole; + modelRole: KtxModelRole; stepBudget: number; sourceKey: string; jobId: string; diff --git a/packages/context/src/ingest/stages/validate-wu-sources.ts b/packages/context/src/ingest/stages/validate-wu-sources.ts index a6394c50..dd72c8d0 100644 --- a/packages/context/src/ingest/stages/validate-wu-sources.ts +++ b/packages/context/src/ingest/stages/validate-wu-sources.ts @@ -1,4 +1,4 @@ -import type { SlValidationDeps, SlValidatorPort } from '@klo/context/sl'; +import type { SlValidationDeps, SlValidatorPort } from '@ktx/context/sl'; import type { TouchedSlSource } from '../../tools/index.js'; export interface WuValidationResult { diff --git a/packages/context/src/ingest/types.ts b/packages/context/src/ingest/types.ts index 460db0d3..27472523 100644 --- a/packages/context/src/ingest/types.ts +++ b/packages/context/src/ingest/types.ts @@ -1,4 +1,4 @@ -import type { KloEmbeddingPort } from '../core/embedding.js'; +import type { KtxEmbeddingPort } from '../core/embedding.js'; import type { MemoryFlowEventSink } from './memory-flow/types.js'; export type IngestTrigger = 'upload' | 'scheduled_pull' | 'manual_resync' | 'manual_override'; @@ -93,7 +93,7 @@ export interface TriageSignals { export interface ClusterWorkUnitsContext { workUnits: WorkUnit[]; stagedDir: string; - embedding: KloEmbeddingPort; + embedding: KtxEmbeddingPort; } export interface SourceAdapter { diff --git a/packages/context/src/llm/debug-request-recorder.test.ts b/packages/context/src/llm/debug-request-recorder.test.ts index b2b2f4d2..4a00400f 100644 --- a/packages/context/src/llm/debug-request-recorder.test.ts +++ b/packages/context/src/llm/debug-request-recorder.test.ts @@ -3,13 +3,13 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { - createJsonlKloLlmDebugRequestRecorder, - summarizeKloLlmDebugRequest, + createJsonlKtxLlmDebugRequestRecorder, + summarizeKtxLlmDebugRequest, } from './debug-request-recorder.js'; -describe('summarizeKloLlmDebugRequest', () => { +describe('summarizeKtxLlmDebugRequest', () => { it('records providerOptions positions without message text or tool schemas', () => { - const summary = summarizeKloLlmDebugRequest({ + const summary = summarizeKtxLlmDebugRequest({ operationName: 'ingest-bundle-wu', source: 'metabase', jobId: 'job-1', @@ -81,7 +81,7 @@ describe('summarizeKloLlmDebugRequest', () => { }); }); -describe('createJsonlKloLlmDebugRequestRecorder', () => { +describe('createJsonlKtxLlmDebugRequestRecorder', () => { let tempDir: string | undefined; afterEach(async () => { @@ -92,9 +92,9 @@ describe('createJsonlKloLlmDebugRequestRecorder', () => { }); it('appends one JSON object per recorded request', async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-llm-debug-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-llm-debug-')); const filePath = join(tempDir, 'nested', 'llm-debug.jsonl'); - const recorder = createJsonlKloLlmDebugRequestRecorder(filePath); + const recorder = createJsonlKtxLlmDebugRequestRecorder(filePath); await recorder.record({ timestamp: '2026-05-04T00:00:00.000Z', diff --git a/packages/context/src/llm/debug-request-recorder.ts b/packages/context/src/llm/debug-request-recorder.ts index 187311bc..143b6890 100644 --- a/packages/context/src/llm/debug-request-recorder.ts +++ b/packages/context/src/llm/debug-request-recorder.ts @@ -1,12 +1,12 @@ import { appendFile, mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; import type { ModelMessage } from 'ai'; -import type { KloModelRole } from '@klo/llm'; +import type { KtxModelRole } from '@ktx/llm'; type ProviderOptionsCarrier = { providerOptions?: unknown; [key: string]: unknown }; type ToolMap = Record; -export interface KloLlmDebugProviderOptionsEntry { +export interface KtxLlmDebugProviderOptionsEntry { target: 'message' | 'message-part' | 'tool'; index?: number; role?: string; @@ -15,29 +15,29 @@ export interface KloLlmDebugProviderOptionsEntry { providerOptions: unknown; } -export interface KloLlmDebugRequest { +export interface KtxLlmDebugRequest { timestamp: string; operationName: string; source?: string; jobId?: string; unitKey?: string; - modelRole: KloModelRole; + modelRole: KtxModelRole; modelId: string; messageCount: number; toolNames: string[]; - providerOptions: KloLlmDebugProviderOptionsEntry[]; + providerOptions: KtxLlmDebugProviderOptionsEntry[]; } -export interface KloLlmDebugRequestRecorder { - record(request: KloLlmDebugRequest): Promise | void; +export interface KtxLlmDebugRequestRecorder { + record(request: KtxLlmDebugRequest): Promise | void; } -export interface SummarizeKloLlmDebugRequestInput { +export interface SummarizeKtxLlmDebugRequestInput { operationName: string; source?: string; jobId?: string; unitKey?: string; - modelRole: KloModelRole; + modelRole: KtxModelRole; modelId: string; messages: ModelMessage[]; tools: ToolMap; @@ -52,7 +52,7 @@ function isProviderOptionsCarrier(value: unknown): value is ProviderOptionsCarri return typeof value === 'object' && value !== null && !Array.isArray(value); } -function contentPartProviderOptions(message: ModelMessage, index: number): KloLlmDebugProviderOptionsEntry[] { +function contentPartProviderOptions(message: ModelMessage, index: number): KtxLlmDebugProviderOptionsEntry[] { if (!Array.isArray(message.content)) { return []; } @@ -74,9 +74,9 @@ function contentPartProviderOptions(message: ModelMessage, index: number): KloLl }); } -function messageProviderOptions(messages: ModelMessage[]): KloLlmDebugProviderOptionsEntry[] { +function messageProviderOptions(messages: ModelMessage[]): KtxLlmDebugProviderOptionsEntry[] { return messages.flatMap((message, index) => { - const entries: KloLlmDebugProviderOptionsEntry[] = []; + const entries: KtxLlmDebugProviderOptionsEntry[] = []; const providerOptions = (message as ProviderOptionsCarrier).providerOptions; if (providerOptions) { entries.push({ @@ -91,7 +91,7 @@ function messageProviderOptions(messages: ModelMessage[]): KloLlmDebugProviderOp }); } -function toolProviderOptions(tools: ToolMap): KloLlmDebugProviderOptionsEntry[] { +function toolProviderOptions(tools: ToolMap): KtxLlmDebugProviderOptionsEntry[] { return Object.entries(tools).flatMap(([name, tool]) => { return tool.providerOptions ? [ @@ -105,7 +105,7 @@ function toolProviderOptions(tools: ToolMap): KloLlmDebugProviderOptionsEntry[] }); } -export function summarizeKloLlmDebugRequest(input: SummarizeKloLlmDebugRequestInput): KloLlmDebugRequest { +export function summarizeKtxLlmDebugRequest(input: SummarizeKtxLlmDebugRequestInput): KtxLlmDebugRequest { const toolNames = Object.keys(input.tools).sort(); return { timestamp: input.timestamp ?? new Date().toISOString(), @@ -121,7 +121,7 @@ export function summarizeKloLlmDebugRequest(input: SummarizeKloLlmDebugRequestIn }; } -export function createJsonlKloLlmDebugRequestRecorder(filePath: string): KloLlmDebugRequestRecorder { +export function createJsonlKtxLlmDebugRequestRecorder(filePath: string): KtxLlmDebugRequestRecorder { return { async record(request) { await mkdir(dirname(filePath), { recursive: true }); diff --git a/packages/context/src/llm/embedding-port.test.ts b/packages/context/src/llm/embedding-port.test.ts index 7bbe40b4..129ef4d9 100644 --- a/packages/context/src/llm/embedding-port.test.ts +++ b/packages/context/src/llm/embedding-port.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it, vi } from 'vitest'; -import { KloIngestEmbeddingPortAdapter, KloScanEmbeddingPortAdapter } from './embedding-port.js'; +import { KtxIngestEmbeddingPortAdapter, KtxScanEmbeddingPortAdapter } from './embedding-port.js'; -describe('KLO embedding port adapters', () => { - it('adapts @klo/llm embeddings to ingest embedding port shape', async () => { +describe('KTX embedding port adapters', () => { + it('adapts @ktx/llm embeddings to ingest embedding port shape', async () => { const provider = { dimensions: 3, maxBatchSize: 2, @@ -12,7 +12,7 @@ describe('KLO embedding port adapters', () => { [4, 5, 6], ]), }; - const adapter = new KloIngestEmbeddingPortAdapter(provider as never); + const adapter = new KtxIngestEmbeddingPortAdapter(provider as never); await expect(adapter.computeEmbedding('alpha')).resolves.toEqual([1, 2, 3]); await expect(adapter.computeEmbeddingsBulk(['alpha', 'beta'])).resolves.toEqual([ @@ -22,14 +22,14 @@ describe('KLO embedding port adapters', () => { expect(adapter.maxBatchSize).toBe(2); }); - it('adapts @klo/llm embeddings to scan embedding port shape', async () => { + it('adapts @ktx/llm embeddings to scan embedding port shape', async () => { const provider = { dimensions: 3, maxBatchSize: 2, embed: vi.fn(), [['embed', 'Many'].join('')]: vi.fn(async () => [[1, 2, 3]]), }; - const adapter = new KloScanEmbeddingPortAdapter(provider as never); + const adapter = new KtxScanEmbeddingPortAdapter(provider as never); await expect(adapter.embedBatch(['alpha'])).resolves.toEqual([[1, 2, 3]]); expect(adapter.dimensions).toBe(3); diff --git a/packages/context/src/llm/embedding-port.ts b/packages/context/src/llm/embedding-port.ts index c42dac52..6eb0ad80 100644 --- a/packages/context/src/llm/embedding-port.ts +++ b/packages/context/src/llm/embedding-port.ts @@ -1,17 +1,17 @@ -import type { KloEmbeddingProvider } from '@klo/llm'; -import type { KloEmbeddingPort as KloIngestEmbeddingPort } from '../core/embedding.js'; -import type { KloEmbeddingPort as KloScanEmbeddingPort } from '../scan/types.js'; +import type { KtxEmbeddingProvider } from '@ktx/llm'; +import type { KtxEmbeddingPort as KtxIngestEmbeddingPort } from '../core/embedding.js'; +import type { KtxEmbeddingPort as KtxScanEmbeddingPort } from '../scan/types.js'; -const bulkEmbeddingMethod = ['embed', 'Many'].join('') as keyof KloEmbeddingProvider; +const bulkEmbeddingMethod = ['embed', 'Many'].join('') as keyof KtxEmbeddingProvider; -function computeBulkEmbeddings(provider: KloEmbeddingProvider, texts: string[]): Promise { +function computeBulkEmbeddings(provider: KtxEmbeddingProvider, texts: string[]): Promise { return (provider[bulkEmbeddingMethod] as (items: string[]) => Promise)(texts); } -export class KloIngestEmbeddingPortAdapter implements KloIngestEmbeddingPort { +export class KtxIngestEmbeddingPortAdapter implements KtxIngestEmbeddingPort { readonly maxBatchSize: number; - constructor(private readonly provider: KloEmbeddingProvider) { + constructor(private readonly provider: KtxEmbeddingProvider) { this.maxBatchSize = provider.maxBatchSize; } @@ -24,11 +24,11 @@ export class KloIngestEmbeddingPortAdapter implements KloIngestEmbeddingPort { } } -export class KloScanEmbeddingPortAdapter implements KloScanEmbeddingPort { +export class KtxScanEmbeddingPortAdapter implements KtxScanEmbeddingPort { readonly dimensions: number; readonly maxBatchSize: number; - constructor(private readonly provider: KloEmbeddingProvider) { + constructor(private readonly provider: KtxEmbeddingProvider) { this.dimensions = provider.dimensions; this.maxBatchSize = provider.maxBatchSize; } diff --git a/packages/context/src/llm/generation.ts b/packages/context/src/llm/generation.ts index 714330f5..a7afd5b9 100644 --- a/packages/context/src/llm/generation.ts +++ b/packages/context/src/llm/generation.ts @@ -1,12 +1,12 @@ -import { KloMessageBuilder, type KloLlmProvider, type KloModelRole } from '@klo/llm'; +import { KtxMessageBuilder, type KtxLlmProvider, type KtxModelRole } from '@ktx/llm'; import { generateText, Output, type FlexibleSchema, type ToolSet } from 'ai'; type GenerateTextInput = Parameters[0]; type GenerateTextFn = (input: GenerateTextInput) => Promise<{ text?: string; output?: unknown }>; -interface GenerateKloTextInput { - llmProvider: KloLlmProvider; - role: KloModelRole; +interface GenerateKtxTextInput { + llmProvider: KtxLlmProvider; + role: KtxModelRole; prompt: string; system?: string; tools?: ToolSet; @@ -14,12 +14,12 @@ interface GenerateKloTextInput { generateText?: GenerateTextFn; } -export async function generateKloText(input: GenerateKloTextInput): Promise { +export async function generateKtxText(input: GenerateKtxTextInput): Promise { const model = input.llmProvider.getModel(input.role); if ((model as { provider?: string }).provider === 'deterministic') { return `Deterministic description for ${input.prompt.slice(0, 64).trim() || 'data source'}`; } - const built = new KloMessageBuilder(input.llmProvider).wrapSimple({ + const built = new KtxMessageBuilder(input.llmProvider).wrapSimple({ system: input.system, messages: [{ role: 'user', content: input.prompt }], tools: input.tools ?? {}, @@ -32,16 +32,16 @@ export async function generateKloText(input: GenerateKloTextInput): Promise( - input: GenerateKloTextInput & { schema: TSchema }, +export async function generateKtxObject( + input: GenerateKtxTextInput & { schema: TSchema }, ): Promise { const model = input.llmProvider.getModel(input.role); - const built = new KloMessageBuilder(input.llmProvider).wrapSimple({ + const built = new KtxMessageBuilder(input.llmProvider).wrapSimple({ system: input.system, messages: [{ role: 'user', content: input.prompt }], tools: input.tools ?? {}, @@ -57,7 +57,7 @@ export async function generateKloObject( }), }); if (result.output == null) { - throw new Error('KLO LLM object generation returned no output'); + throw new Error('KTX LLM object generation returned no output'); } return result.output as TOutput; } diff --git a/packages/context/src/llm/index.ts b/packages/context/src/llm/index.ts index fcbf104a..67e94b93 100644 --- a/packages/context/src/llm/index.ts +++ b/packages/context/src/llm/index.ts @@ -1,18 +1,18 @@ -export { KloIngestEmbeddingPortAdapter, KloScanEmbeddingPortAdapter } from './embedding-port.js'; -export { generateKloObject, generateKloText } from './generation.js'; +export { KtxIngestEmbeddingPortAdapter, KtxScanEmbeddingPortAdapter } from './embedding-port.js'; +export { generateKtxObject, generateKtxText } from './generation.js'; export type { - KloLlmDebugProviderOptionsEntry, - KloLlmDebugRequest, - KloLlmDebugRequestRecorder, - SummarizeKloLlmDebugRequestInput, + KtxLlmDebugProviderOptionsEntry, + KtxLlmDebugRequest, + KtxLlmDebugRequestRecorder, + SummarizeKtxLlmDebugRequestInput, } from './debug-request-recorder.js'; export { - createJsonlKloLlmDebugRequestRecorder, - summarizeKloLlmDebugRequest, + createJsonlKtxLlmDebugRequestRecorder, + summarizeKtxLlmDebugRequest, } from './debug-request-recorder.js'; export { - createLocalKloEmbeddingProviderFromConfig, - createLocalKloLlmProviderFromConfig, - resolveLocalKloEmbeddingConfig, - resolveLocalKloLlmConfig, + createLocalKtxEmbeddingProviderFromConfig, + createLocalKtxLlmProviderFromConfig, + resolveLocalKtxEmbeddingConfig, + resolveLocalKtxLlmConfig, } from './local-config.js'; diff --git a/packages/context/src/llm/local-config.test.ts b/packages/context/src/llm/local-config.test.ts index e63b7d24..96292b56 100644 --- a/packages/context/src/llm/local-config.test.ts +++ b/packages/context/src/llm/local-config.test.ts @@ -1,31 +1,31 @@ import { describe, expect, it, vi } from 'vitest'; import { - buildDefaultKloProjectConfig, - type KloProjectEmbeddingConfig, - type KloProjectLlmConfig, + buildDefaultKtxProjectConfig, + type KtxProjectEmbeddingConfig, + type KtxProjectLlmConfig, } from '../project/config.js'; import { - createLocalKloEmbeddingProviderFromConfig, - createLocalKloLlmProviderFromConfig, - resolveLocalKloEmbeddingConfig, - resolveLocalKloLlmConfig, + createLocalKtxEmbeddingProviderFromConfig, + createLocalKtxLlmProviderFromConfig, + resolveLocalKtxEmbeddingConfig, + resolveLocalKtxLlmConfig, } from './local-config.js'; -describe('local KLO LLM config', () => { - it('resolves env and file references into a KloLlmConfig', () => { - const config: KloProjectLlmConfig = { +describe('local KTX LLM config', () => { + it('resolves env and file references into a KtxLlmConfig', () => { + const config: KtxProjectLlmConfig = { provider: { backend: 'gateway', gateway: { api_key: 'env:AI_GATEWAY_API_KEY', base_url: 'https://gateway.example/v1' }, // pragma: allowlist secret }, - models: { default: 'env:KLO_MODEL', triage: 'anthropic/claude-haiku-4-5' }, + models: { default: 'env:KTX_MODEL', triage: 'anthropic/claude-haiku-4-5' }, promptCaching: { enabled: false }, }; expect( - resolveLocalKloLlmConfig(config, { + resolveLocalKtxLlmConfig(config, { AI_GATEWAY_API_KEY: 'gateway-key', // pragma: allowlist secret - KLO_MODEL: 'anthropic/claude-sonnet-4-6', + KTX_MODEL: 'anthropic/claude-sonnet-4-6', }), ).toEqual({ backend: 'gateway', @@ -37,16 +37,16 @@ describe('local KLO LLM config', () => { it('returns null when the local LLM backend is disabled', () => { expect( - createLocalKloLlmProviderFromConfig({ + createLocalKtxLlmProviderFromConfig({ provider: { backend: 'none' }, models: {}, }), ).toBeNull(); }); - it('constructs providers through @klo/llm', () => { - const createKloLlmProvider = vi.fn(() => ({ getModel: vi.fn() }) as never); - const result = createLocalKloLlmProviderFromConfig( + it('constructs providers through @ktx/llm', () => { + const createKtxLlmProvider = vi.fn(() => ({ getModel: vi.fn() }) as never); + const result = createLocalKtxLlmProviderFromConfig( { provider: { backend: 'anthropic', @@ -54,11 +54,11 @@ describe('local KLO LLM config', () => { }, models: { default: 'claude-sonnet-4-6' }, }, - { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, createKloLlmProvider }, // pragma: allowlist secret + { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, createKtxLlmProvider }, // pragma: allowlist secret ); expect(result).not.toBeNull(); - expect(createKloLlmProvider).toHaveBeenCalledWith({ + expect(createKtxLlmProvider).toHaveBeenCalledWith({ backend: 'anthropic', anthropic: { apiKey: 'sk-ant-test' }, // pragma: allowlist secret modelSlots: { default: 'claude-sonnet-4-6' }, @@ -66,8 +66,8 @@ describe('local KLO LLM config', () => { }); }); - it('inherits enabled prompt caching from @klo/llm when local config omits promptCaching', () => { - const provider = createLocalKloLlmProviderFromConfig({ + it('inherits enabled prompt caching from @ktx/llm when local config omits promptCaching', () => { + const provider = createLocalKtxLlmProviderFromConfig({ provider: { backend: 'gateway', gateway: { base_url: 'https://gateway.example/v1' }, @@ -85,9 +85,9 @@ describe('local KLO LLM config', () => { }); }); -describe('local KLO embedding config', () => { +describe('local KTX embedding config', () => { it('resolves sentence-transformers config', () => { - const config: KloProjectEmbeddingConfig = { + const config: KtxProjectEmbeddingConfig = { backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', dimensions: 384, @@ -95,7 +95,7 @@ describe('local KLO embedding config', () => { batchSize: 16, }; - expect(resolveLocalKloEmbeddingConfig(config, {})).toEqual({ + expect(resolveLocalKtxEmbeddingConfig(config, {})).toEqual({ backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', dimensions: 384, @@ -105,14 +105,14 @@ describe('local KLO embedding config', () => { }); it('constructs deterministic embeddings from the default project config', () => { - const createKloEmbeddingProvider = vi.fn(() => ({}) as never); - const provider = createLocalKloEmbeddingProviderFromConfig( - buildDefaultKloProjectConfig('warehouse').ingest.embeddings, - { createKloEmbeddingProvider }, + const createKtxEmbeddingProvider = vi.fn(() => ({}) as never); + const provider = createLocalKtxEmbeddingProviderFromConfig( + buildDefaultKtxProjectConfig('warehouse').ingest.embeddings, + { createKtxEmbeddingProvider }, ); expect(provider).not.toBeNull(); - expect(createKloEmbeddingProvider).toHaveBeenCalledWith( + expect(createKtxEmbeddingProvider).toHaveBeenCalledWith( expect.objectContaining({ backend: 'deterministic', model: 'deterministic', @@ -122,6 +122,6 @@ describe('local KLO embedding config', () => { }); it('returns null when embeddings are disabled', () => { - expect(createLocalKloEmbeddingProviderFromConfig({ backend: 'none', dimensions: 8 })).toBeNull(); + expect(createLocalKtxEmbeddingProviderFromConfig({ backend: 'none', dimensions: 8 })).toBeNull(); }); }); diff --git a/packages/context/src/llm/local-config.ts b/packages/context/src/llm/local-config.ts index a765467d..f0654642 100644 --- a/packages/context/src/llm/local-config.ts +++ b/packages/context/src/llm/local-config.ts @@ -1,23 +1,23 @@ import { - createKloEmbeddingProvider, - createKloLlmProvider, - type KloEmbeddingConfig, - type KloEmbeddingProvider, - type KloLlmConfig, - type KloLlmProvider, - type KloModelRole, -} from '@klo/llm'; -import { resolveKloConfigReference } from '../core/config-reference.js'; -import type { KloProjectEmbeddingConfig, KloProjectLlmConfig } from '../project/config.js'; + createKtxEmbeddingProvider, + createKtxLlmProvider, + type KtxEmbeddingConfig, + type KtxEmbeddingProvider, + type KtxLlmConfig, + type KtxLlmProvider, + type KtxModelRole, +} from '@ktx/llm'; +import { resolveKtxConfigReference } from '../core/config-reference.js'; +import type { KtxProjectEmbeddingConfig, KtxProjectLlmConfig } from '../project/config.js'; interface LocalConfigDeps { env?: NodeJS.ProcessEnv; - createKloLlmProvider?: typeof createKloLlmProvider; - createKloEmbeddingProvider?: typeof createKloEmbeddingProvider; + createKtxLlmProvider?: typeof createKtxLlmProvider; + createKtxEmbeddingProvider?: typeof createKtxEmbeddingProvider; } function resolveOptional(value: string | undefined, env: NodeJS.ProcessEnv): string | undefined { - return resolveKloConfigReference(value, env) || undefined; + return resolveKtxConfigReference(value, env) || undefined; } function resolveRequired(value: string | undefined, env: NodeJS.ProcessEnv, message: string): string { @@ -29,19 +29,19 @@ function resolveRequired(value: string | undefined, env: NodeJS.ProcessEnv, mess } function resolveModelSlots( - models: KloProjectLlmConfig['models'], + models: KtxProjectLlmConfig['models'], env: NodeJS.ProcessEnv, -): KloLlmConfig['modelSlots'] { - const resolved: Partial> & { default?: string } = {}; +): KtxLlmConfig['modelSlots'] { + const resolved: Partial> & { default?: string } = {}; for (const [role, value] of Object.entries(models)) { if (value) { - resolved[role as KloModelRole] = resolveRequired(value, env, `llm.models.${role} is required`); + resolved[role as KtxModelRole] = resolveRequired(value, env, `llm.models.${role} is required`); } } if (!resolved.default) { throw new Error('llm.models.default is required when llm.provider.backend is not none'); } - return resolved as KloLlmConfig['modelSlots']; + return resolved as KtxLlmConfig['modelSlots']; } function resolvedProviderConfig( @@ -64,7 +64,7 @@ function resolvedProviderConfig( }; } -export function resolveLocalKloLlmConfig(config: KloProjectLlmConfig, env: NodeJS.ProcessEnv): KloLlmConfig | null { +export function resolveLocalKtxLlmConfig(config: KtxProjectLlmConfig, env: NodeJS.ProcessEnv): KtxLlmConfig | null { if (config.provider.backend === 'none') { return null; } @@ -81,18 +81,18 @@ export function resolveLocalKloLlmConfig(config: KloProjectLlmConfig, env: NodeJ }; } -export function createLocalKloLlmProviderFromConfig( - config: KloProjectLlmConfig, +export function createLocalKtxLlmProviderFromConfig( + config: KtxProjectLlmConfig, deps: LocalConfigDeps = {}, -): KloLlmProvider | null { - const resolved = resolveLocalKloLlmConfig(config, deps.env ?? process.env); - return resolved ? (deps.createKloLlmProvider ?? createKloLlmProvider)(resolved) : null; +): KtxLlmProvider | null { + const resolved = resolveLocalKtxLlmConfig(config, deps.env ?? process.env); + return resolved ? (deps.createKtxLlmProvider ?? createKtxLlmProvider)(resolved) : null; } -export function resolveLocalKloEmbeddingConfig( - config: KloProjectEmbeddingConfig, +export function resolveLocalKtxEmbeddingConfig( + config: KtxProjectEmbeddingConfig, env: NodeJS.ProcessEnv, -): KloEmbeddingConfig | null { +): KtxEmbeddingConfig | null { if (config.backend === 'none') { return null; } @@ -113,10 +113,10 @@ export function resolveLocalKloEmbeddingConfig( }; } -export function createLocalKloEmbeddingProviderFromConfig( - config: KloProjectEmbeddingConfig, +export function createLocalKtxEmbeddingProviderFromConfig( + config: KtxProjectEmbeddingConfig, deps: LocalConfigDeps = {}, -): KloEmbeddingProvider | null { - const resolved = resolveLocalKloEmbeddingConfig(config, deps.env ?? process.env); - return resolved ? (deps.createKloEmbeddingProvider ?? createKloEmbeddingProvider)(resolved) : null; +): KtxEmbeddingProvider | null { + const resolved = resolveLocalKtxEmbeddingConfig(config, deps.env ?? process.env); + return resolved ? (deps.createKtxEmbeddingProvider ?? createKtxEmbeddingProvider)(resolved) : null; } diff --git a/packages/context/src/mcp/context-tools.ts b/packages/context/src/mcp/context-tools.ts index e5eb67a2..48830d44 100644 --- a/packages/context/src/mcp/context-tools.ts +++ b/packages/context/src/mcp/context-tools.ts @@ -1,10 +1,10 @@ import { z } from 'zod'; -import type { KloMcpContextPorts, KloMcpServerLike, KloMcpToolResult, KloMcpUserContext } from './types.js'; +import type { KtxMcpContextPorts, KtxMcpServerLike, KtxMcpToolResult, KtxMcpUserContext } from './types.js'; -export interface RegisterKloContextToolsDeps { - server: KloMcpServerLike; - ports: KloMcpContextPorts; - userContext: KloMcpUserContext; +export interface RegisterKtxContextToolsDeps { + server: KtxMcpServerLike; + ports: KtxMcpContextPorts; + userContext: KtxMcpUserContext; } const connectionIdSchema = z.string().min(1); @@ -143,14 +143,14 @@ const scanArtifactReadSchema = z.object({ path: z.string().min(1), }); -export function jsonToolResult(structuredContent: T): KloMcpToolResult { +export function jsonToolResult(structuredContent: T): KtxMcpToolResult { return { content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }], structuredContent, }; } -export function jsonErrorToolResult(text: string): KloMcpToolResult> { +export function jsonErrorToolResult(text: string): KtxMcpToolResult> { return { content: [{ type: 'text', text }], isError: true, @@ -158,16 +158,16 @@ export function jsonErrorToolResult(text: string): KloMcpToolResult( - server: KloMcpServerLike, + server: KtxMcpServerLike, name: string, config: { title: string; description: string; inputSchema: unknown }, schema: TSchema, - handler: (input: z.infer) => Promise, + handler: (input: z.infer) => Promise, ): void { server.registerTool(name, config, async (input) => handler(schema.parse(input))); } -export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void { +export function registerKtxContextTools(deps: RegisterKtxContextToolsDeps): void { const { ports, server, userContext } = deps; if (ports.connections) { @@ -177,7 +177,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'connection_list', { title: 'Connection List', - description: 'List configured read-only data connections available to the KLO project.', + description: 'List configured read-only data connections available to the KTX project.', inputSchema: connectionListSchema.shape, }, connectionListSchema, @@ -190,7 +190,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'connection_test', { title: 'Connection Test', - description: 'Test a configured standalone KLO connection through the host-provided scan connector.', + description: 'Test a configured standalone KTX connection through the host-provided scan connector.', inputSchema: connectionTestSchema.shape, }, connectionTestSchema, @@ -211,7 +211,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'knowledge_search', { title: 'Knowledge Search', - description: 'Search KLO knowledge pages and return ranked summaries.', + description: 'Search KTX knowledge pages and return ranked summaries.', inputSchema: knowledgeSearchSchema.shape, }, knowledgeSearchSchema, @@ -230,7 +230,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'knowledge_read', { title: 'Knowledge Read', - description: 'Read a KLO knowledge page by key.', + description: 'Read a KTX knowledge page by key.', inputSchema: knowledgeReadSchema.shape, }, knowledgeReadSchema, @@ -245,7 +245,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'knowledge_write', { title: 'Knowledge Write', - description: 'Create or replace a KLO knowledge page and its SL references.', + description: 'Create or replace a KTX knowledge page and its SL references.', inputSchema: knowledgeWriteSchema.shape, }, knowledgeWriteSchema, @@ -368,7 +368,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'ingest_trigger', { title: 'Ingest Trigger', - description: 'Trigger a KLO ingest run for an adapter and connection.', + description: 'Trigger a KTX ingest run for an adapter and connection.', inputSchema: ingestTriggerSchema.shape, }, ingestTriggerSchema, @@ -397,7 +397,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'ingest_report', { title: 'Ingest Report', - description: 'Read the stored canonical KLO ingest report for a local run id, job id, or report id.', + description: 'Read the stored canonical KTX ingest report for a local run id, job id, or report id.', inputSchema: ingestReportSchema.shape, }, ingestReportSchema, @@ -414,7 +414,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'ingest_replay', { title: 'Ingest Replay', - description: 'Read the memory-flow replay snapshot for a stored canonical KLO ingest run.', + description: 'Read the memory-flow replay snapshot for a stored canonical KTX ingest run.', inputSchema: ingestReplaySchema.shape, }, ingestReplaySchema, @@ -433,7 +433,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'scan_trigger', { title: 'Scan Trigger', - description: 'Run a standalone KLO structural connection scan and return its report summary.', + description: 'Run a standalone KTX structural connection scan and return its report summary.', inputSchema: scanTriggerSchema.shape, }, scanTriggerSchema, @@ -445,7 +445,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'scan_status', { title: 'Scan Status', - description: 'Read the current or final status for a standalone KLO scan run.', + description: 'Read the current or final status for a standalone KTX scan run.', inputSchema: scanStatusSchema.shape, }, scanStatusSchema, @@ -460,7 +460,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'scan_report', { title: 'Scan Report', - description: 'Read a standalone KLO scan report by run id.', + description: 'Read a standalone KTX scan report by run id.', inputSchema: scanStatusSchema.shape, }, scanStatusSchema, @@ -476,7 +476,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'scan_list_artifacts', { title: 'Scan List Artifacts', - description: 'List report, raw-source, manifest, and enrichment artifact paths for a standalone KLO scan run.', + description: 'List report, raw-source, manifest, and enrichment artifact paths for a standalone KTX scan run.', inputSchema: scanStatusSchema.shape, }, scanStatusSchema, @@ -493,7 +493,7 @@ export function registerKloContextTools(deps: RegisterKloContextToolsDeps): void 'scan_read_artifact', { title: 'Scan Read Artifact', - description: 'Read one artifact that belongs to a standalone KLO scan run.', + description: 'Read one artifact that belongs to a standalone KTX scan run.', inputSchema: scanArtifactReadSchema.shape, }, scanArtifactReadSchema, diff --git a/packages/context/src/mcp/index.ts b/packages/context/src/mcp/index.ts index 0fde3f55..c3f02a66 100644 --- a/packages/context/src/mcp/index.ts +++ b/packages/context/src/mcp/index.ts @@ -1,33 +1,33 @@ -export type { RegisterKloContextToolsDeps } from './context-tools.js'; -export { jsonErrorToolResult, jsonToolResult, registerKloContextTools } from './context-tools.js'; +export type { RegisterKtxContextToolsDeps } from './context-tools.js'; +export { jsonErrorToolResult, jsonToolResult, registerKtxContextTools } from './context-tools.js'; export { createLocalProjectMcpContextPorts } from './local-project-ports.js'; -export { createDefaultKloMcpServer, createKloMcpServer } from './server.js'; +export { createDefaultKtxMcpServer, createKtxMcpServer } from './server.js'; export type { - KloConnectionSummary, - KloConnectionsMcpPort, - KloIngestDiffSummary, - KloIngestMcpPort, - KloIngestStatusResponse, - KloIngestTriggerKind, - KloIngestTriggerResponse, - KloIngestWorkUnitSummary, - KloKnowledgeMcpPort, - KloKnowledgePage, - KloKnowledgeSearchResponse, - KloKnowledgeSearchResult, - KloKnowledgeWriteResponse, - KloMcpContextPorts, - KloMcpServerDeps, - KloMcpServerLike, - KloMcpTextContent, - KloMcpToolResult, - KloMcpUserContext, - KloSemanticLayerListResponse, - KloSemanticLayerMcpPort, - KloSemanticLayerQueryResponse, - KloSemanticLayerReadResponse, - KloSemanticLayerSourceSummary, - KloSemanticLayerValidationResponse, - KloSemanticLayerWriteResponse, + KtxConnectionSummary, + KtxConnectionsMcpPort, + KtxIngestDiffSummary, + KtxIngestMcpPort, + KtxIngestStatusResponse, + KtxIngestTriggerKind, + KtxIngestTriggerResponse, + KtxIngestWorkUnitSummary, + KtxKnowledgeMcpPort, + KtxKnowledgePage, + KtxKnowledgeSearchResponse, + KtxKnowledgeSearchResult, + KtxKnowledgeWriteResponse, + KtxMcpContextPorts, + KtxMcpServerDeps, + KtxMcpServerLike, + KtxMcpTextContent, + KtxMcpToolResult, + KtxMcpUserContext, + KtxSemanticLayerListResponse, + KtxSemanticLayerMcpPort, + KtxSemanticLayerQueryResponse, + KtxSemanticLayerReadResponse, + KtxSemanticLayerSourceSummary, + KtxSemanticLayerValidationResponse, + KtxSemanticLayerWriteResponse, MemoryCapturePort, } from './types.js'; diff --git a/packages/context/src/mcp/local-project-ports.test.ts b/packages/context/src/mcp/local-project-ports.test.ts index 215bdaf9..b4534f56 100644 --- a/packages/context/src/mcp/local-project-ports.test.ts +++ b/packages/context/src/mcp/local-project-ports.test.ts @@ -4,8 +4,8 @@ import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { AgentRunnerService } from '../agent/index.js'; import { FakeSourceAdapter, type MemoryFlowReplayInput } from '../ingest/index.js'; -import { initKloProject } from '../project/index.js'; -import { createKloConnectorCapabilities, type KloScanConnector, type KloSchemaSnapshot } from '../scan/index.js'; +import { initKtxProject } from '../project/index.js'; +import { createKtxConnectorCapabilities, type KtxScanConnector, type KtxSchemaSnapshot } from '../scan/index.js'; import { writeLocalSlSource } from '../sl/index.js'; import { createLocalProjectMcpContextPorts } from './local-project-ports.js'; @@ -21,14 +21,14 @@ describe('createLocalProjectMcpContextPorts', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-mcp-local-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-mcp-local-')); }); afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); }); - function testSnapshot(connectionId = 'warehouse'): KloSchemaSnapshot { + function testSnapshot(connectionId = 'warehouse'): KtxSchemaSnapshot { return { connectionId, driver: 'postgres', @@ -60,18 +60,18 @@ describe('createLocalProjectMcpContextPorts', () => { }; } - function testConnector(snapshot = testSnapshot()): KloScanConnector { + function testConnector(snapshot = testSnapshot()): KtxScanConnector { return { id: `test:${snapshot.connectionId}`, driver: snapshot.driver, - capabilities: createKloConnectorCapabilities(), + capabilities: createKtxConnectorCapabilities(), introspect: vi.fn(async () => snapshot), cleanup: vi.fn(async () => {}), }; } - it('lists local project connections from klo.yaml', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + it('lists local project connections from ktx.yaml', async () => { + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'env:DATABASE_URL', @@ -85,7 +85,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('tests a local project connection through the native scan connector factory', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'env:DATABASE_URL', @@ -122,7 +122,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('triggers canonical bundle ingest and reads status, report, and replay through MCP ports', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', readonly: true, @@ -219,7 +219,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('returns child run metadata for local Metabase fan-out triggers', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections = { 'prod-metabase': { driver: 'metabase', @@ -342,7 +342,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('writes, reads, and searches global knowledge pages', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const ports = createLocalProjectMcpContextPorts(project); await expect( @@ -382,11 +382,11 @@ describe('createLocalProjectMcpContextPorts', () => { totalFound: 1, }); expect(search?.results[0]?.score).toBeGreaterThan(0); - await expect(access(join(project.projectDir, '.klo', 'db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx', 'db.sqlite'))).resolves.toBeUndefined(); }); it('writes, lists, reads, and validates semantic-layer sources', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const ports = createLocalProjectMcpContextPorts(project); await expect( @@ -435,7 +435,7 @@ describe('createLocalProjectMcpContextPorts', () => { ], totalSources: 1, }); - await expect(access(join(project.projectDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); await expect( ports.semanticLayer?.readSource({ connectionId: 'warehouse', sourceName: 'orders' }), @@ -452,7 +452,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('returns semantic-layer hybrid search metadata through local project ports', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); await writeLocalSlSource(project, { connectionId: 'warehouse', sourceName: 'orders', @@ -499,8 +499,8 @@ describe('createLocalProjectMcpContextPorts', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed dictionary profile', ); @@ -521,7 +521,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('uses configured local embeddings for semantic-layer search when available', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.ingest.embeddings = { backend: 'none', dimensions: 2 }; await writeLocalSlSource(project, { connectionId: 'warehouse', @@ -561,7 +561,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('rejects path traversal keys before touching the project directory', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const ports = createLocalProjectMcpContextPorts(project); await expect( @@ -580,7 +580,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('uses semantic compute for validation and compile-only sl_query when supplied', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'env:DATABASE_URL', @@ -667,7 +667,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('executes local MCP sl_query when a query executor is configured', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'env:DATABASE_URL', @@ -726,7 +726,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('exposes detailed local ingest trigger and status ports when local ingest is enabled', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres' }; project.config.ingest.adapters = ['fake']; project.config.ingest.embeddings = { @@ -846,7 +846,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('triggers fetch-capable local ingest without sourceDir config', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'postgres://localhost:5432/warehouse', @@ -922,7 +922,7 @@ describe('createLocalProjectMcpContextPorts', () => { }); it('lists and reads only artifacts that belong to a local scan report', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', url: 'env:DATABASE_URL', @@ -1035,10 +1035,10 @@ describe('createLocalProjectMcpContextPorts', () => { await expect( ports.scan?.readArtifact?.({ runId: 'local-scan-artifacts', - path: 'klo.yaml', + path: 'ktx.yaml', }), ).resolves.toBeNull(); await expect(ports.scan?.listArtifacts?.({ runId: 'missing' })).resolves.toBeNull(); - await expect(readFile(join(project.projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); + await expect(readFile(join(project.projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); }); }); diff --git a/packages/context/src/mcp/local-project-ports.ts b/packages/context/src/mcp/local-project-ports.ts index faf7ceb2..60808426 100644 --- a/packages/context/src/mcp/local-project-ports.ts +++ b/packages/context/src/mcp/local-project-ports.ts @@ -1,11 +1,11 @@ import YAML from 'yaml'; import { - type KloSqlQueryExecutorPort, + type KtxSqlQueryExecutorPort, localConnectionInfoFromConfig, localConnectionTypeForConfig, } from '../connections/index.js'; -import type { KloEmbeddingPort } from '../core/index.js'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; +import type { KtxEmbeddingPort } from '../core/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; import { createDefaultLocalIngestAdapters, getLocalIngestStatus, @@ -15,14 +15,14 @@ import { runLocalIngest, runLocalMetabaseIngest, } from '../ingest/index.js'; -import { createLocalKloEmbeddingProviderFromConfig, KloIngestEmbeddingPortAdapter } from '../llm/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import { createLocalKtxEmbeddingProviderFromConfig, KtxIngestEmbeddingPortAdapter } from '../llm/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { getLocalScanReport, getLocalScanStatus, - type KloConnectionDriver, - type KloScanConnector, - type KloScanReport, + type KtxConnectionDriver, + type KtxScanConnector, + type KtxScanReport, type LocalScanMcpOptions, runLocalScan, } from '../scan/index.js'; @@ -37,25 +37,25 @@ import { } from '../sl/index.js'; import { readLocalKnowledgePage, searchLocalKnowledgePages, writeLocalKnowledgePage } from '../wiki/local-knowledge.js'; import type { - KloConnectionTestResponse, - KloIngestStatusResponse, - KloMcpContextPorts, - KloScanArtifactListResponse, - KloScanArtifactReadResponse, - KloScanArtifactSummary, - KloScanArtifactType, + KtxConnectionTestResponse, + KtxIngestStatusResponse, + KtxMcpContextPorts, + KtxScanArtifactListResponse, + KtxScanArtifactReadResponse, + KtxScanArtifactSummary, + KtxScanArtifactType, } from './types.js'; -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; const SL_SHAPE_WARNING = 'Local stdio validation checks YAML shape only; Python semantic validation is not configured.'; interface CreateLocalProjectMcpContextPortsOptions { - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: KloSqlQueryExecutorPort; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: KtxSqlQueryExecutorPort; localIngest?: LocalIngestMcpOptions; localScan?: LocalScanMcpOptions; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; } function dialectForDriver(driver: string | undefined): string { @@ -105,7 +105,7 @@ function assertSafeSourceName(sourceName: string): string { return assertSafePathToken('semantic-layer source name', sourceName); } -function normalizeScanDriver(driver: string | undefined): KloConnectionDriver { +function normalizeScanDriver(driver: string | undefined): KtxConnectionDriver { const normalized = (driver ?? '').toLowerCase(); if ( normalized === 'postgres' || @@ -124,17 +124,17 @@ function normalizeScanDriver(driver: string | undefined): KloConnectionDriver { return 'postgres'; } -async function cleanupConnector(connector: KloScanConnector | null): Promise { +async function cleanupConnector(connector: KtxScanConnector | null): Promise { if (connector?.cleanup) { await connector.cleanup(); } } async function testLocalConnection( - project: KloLocalProject, + project: KtxLocalProject, options: CreateLocalProjectMcpContextPortsOptions, connectionId: string, -): Promise { +): Promise { const safeConnectionId = assertSafeConnectionId(connectionId); const connection = project.config.connections[safeConnectionId]; if (!connection) { @@ -149,11 +149,11 @@ async function testLocalConnection( ok: true, tableCount: null, message: 'Connection is configured; no native scan connector is available for live testing.', - warnings: ['klo serve was not configured with a local scan connector factory.'], + warnings: ['ktx serve was not configured with a local scan connector factory.'], }; } - let connector: KloScanConnector | null = null; + let connector: KtxScanConnector | null = null; try { connector = await createConnector(safeConnectionId); const snapshot = await connector.introspect( @@ -188,7 +188,7 @@ async function testLocalConnection( } } -function scanArtifactType(path: string, report: KloScanReport): KloScanArtifactType { +function scanArtifactType(path: string, report: KtxScanReport): KtxScanArtifactType { if (path === report.artifactPaths.reportPath) { return 'report'; } @@ -201,7 +201,7 @@ function scanArtifactType(path: string, report: KloScanReport): KloScanArtifactT return 'raw_source'; } -async function artifactSize(project: KloLocalProject, path: string): Promise { +async function artifactSize(project: KtxLocalProject, path: string): Promise { try { const result = await project.fileStore.readFile(path); return typeof result.size === 'number' ? result.size : undefined; @@ -211,10 +211,10 @@ async function artifactSize(project: KloLocalProject, path: string): Promise { + report: KtxScanReport, +): Promise { const paths = new Set(); if (report.artifactPaths.rawSourcesDir) { const listed = await project.fileStore.listFiles(report.artifactPaths.rawSourcesDir); @@ -232,7 +232,7 @@ async function listArtifactsForReport( paths.add(path); } - const artifacts: KloScanArtifactSummary[] = []; + const artifacts: KtxScanArtifactSummary[] = []; for (const path of [...paths].sort()) { const size = await artifactSize(project, path); artifacts.push({ @@ -245,10 +245,10 @@ async function listArtifactsForReport( } async function readScanArtifact( - project: KloLocalProject, + project: KtxLocalProject, runId: string, path: string, -): Promise { +): Promise { const report = await getLocalScanReport(project, runId); if (!report) { return null; @@ -293,14 +293,14 @@ function parseYamlRecord(raw: string): Record { return parsed; } -async function listSlPaths(project: KloLocalProject, connectionId?: string): Promise { +async function listSlPaths(project: KtxLocalProject, connectionId?: string): Promise { const root = connectionId ? `semantic-layer/${assertSafeConnectionId(connectionId)}` : 'semantic-layer'; const listed = await project.fileStore.listFiles(root); return listed.files.filter((file) => file.endsWith('.yaml') || file.endsWith('.yml')).sort(); } async function loadComputableSources( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ): Promise[]> { const paths = await listSlPaths(project, connectionId); @@ -348,7 +348,7 @@ function hasSlSearchMetadata( return 'score' in source; } -function statusFromIngestReport(report: IngestReportSnapshot): KloIngestStatusResponse { +function statusFromIngestReport(report: IngestReportSnapshot): KtxIngestStatusResponse { const failedWorkUnits = report.body.failedWorkUnits; return { runId: report.runId, @@ -380,14 +380,14 @@ function statusFromIngestReport(report: IngestReportSnapshot): KloIngestStatusRe } export function createLocalProjectMcpContextPorts( - project: KloLocalProject, + project: KtxLocalProject, options: CreateLocalProjectMcpContextPortsOptions = {}, -): KloMcpContextPorts { - const configuredEmbeddingProvider = createLocalKloEmbeddingProviderFromConfig(project.config.ingest.embeddings); +): KtxMcpContextPorts { + const configuredEmbeddingProvider = createLocalKtxEmbeddingProviderFromConfig(project.config.ingest.embeddings); const embeddingService = options.embeddingService ?? - (configuredEmbeddingProvider ? new KloIngestEmbeddingPortAdapter(configuredEmbeddingProvider) : null); - const ports: KloMcpContextPorts = { + (configuredEmbeddingProvider ? new KtxIngestEmbeddingPortAdapter(configuredEmbeddingProvider) : null); + const ports: KtxMcpContextPorts = { connections: { async list() { return Object.entries(project.config.connections) diff --git a/packages/context/src/mcp/server.test.ts b/packages/context/src/mcp/server.test.ts index 067e4230..4430f6f7 100644 --- a/packages/context/src/mcp/server.test.ts +++ b/packages/context/src/mcp/server.test.ts @@ -3,14 +3,14 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; import { createLocalProjectMemoryCapture } from '../memory/index.js'; -import { initKloProject } from '../project/index.js'; -import { createKloMcpServer } from './server.js'; +import { initKtxProject } from '../project/index.js'; +import { createKtxMcpServer } from './server.js'; import type { - KloIngestMcpPort, - KloKnowledgeMcpPort, - KloMcpContextPorts, - KloScanMcpPort, - KloSemanticLayerMcpPort, + KtxIngestMcpPort, + KtxKnowledgeMcpPort, + KtxMcpContextPorts, + KtxScanMcpPort, + KtxSemanticLayerMcpPort, MemoryCapturePort, } from './types.js'; @@ -40,11 +40,11 @@ function getTool(tools: RegisteredTool[], name: string): RegisteredTool { return found; } -describe('createKloMcpServer', () => { +describe('createKtxMcpServer', () => { it('registers context tools without memory capture tools when memory capture is omitted', async () => { const fake = makeFakeServer(); - createKloMcpServer({ + createKtxMcpServer({ server: fake.server, userContext: { userId: 'local-user' }, contextTools: { @@ -81,7 +81,7 @@ describe('createKloMcpServer', () => { }), }; - createKloMcpServer({ + createKtxMcpServer({ server: fake.server, memoryCapture: capture, userContext: { userId: 'mcp-user' }, @@ -152,7 +152,7 @@ describe('createKloMcpServer', () => { status: vi.fn().mockResolvedValue(null), }; - createKloMcpServer({ + createKtxMcpServer({ server: fake.server, memoryCapture: capture, userContext: { userId: 'mcp-user' }, @@ -166,9 +166,9 @@ describe('createKloMcpServer', () => { }); it('runs MCP memory_capture against a local project memory port', async () => { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-mcp-local-memory-')); + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-mcp-local-memory-')); try { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const agentRunner = { runLoop: async ({ toolSet, @@ -193,7 +193,7 @@ describe('createKloMcpServer', () => { }); const fake = makeFakeServer(); - createKloMcpServer({ + createKtxMcpServer({ server: fake.server, memoryCapture, userContext: { userId: 'mcp-user' }, @@ -218,8 +218,8 @@ describe('createKloMcpServer', () => { captured: { wiki: ['arr'], sl: [], xrefs: [] }, }, }); - await expect(access(join(project.projectDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); - await expect(access(join(project.projectDir, '.klo/memory-runs/memory-run-mcp.json'))).rejects.toThrow(); + await expect(access(join(project.projectDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx/memory-runs/memory-run-mcp.json'))).rejects.toThrow(); await expect(readFile(join(project.projectDir, 'knowledge/global/arr.md'), 'utf-8')).resolves.toContain( 'ARR means annual recurring revenue.', ); @@ -228,13 +228,13 @@ describe('createKloMcpServer', () => { } }); - it('registers KLO context MCP tools when context ports are supplied', async () => { + it('registers KTX context MCP tools when context ports are supplied', async () => { const fake = makeFakeServer(); const capture: MemoryCapturePort = { capture: vi.fn().mockResolvedValue({ runId: 'run-1' }), status: vi.fn().mockResolvedValue(null), }; - const contextTools: KloMcpContextPorts = { + const contextTools: KtxMcpContextPorts = { connections: { list: vi.fn().mockResolvedValue([ { @@ -253,7 +253,7 @@ describe('createKloMcpServer', () => { }), }, knowledge: { - search: vi.fn().mockResolvedValue({ + search: vi.fn().mockResolvedValue({ results: [ { key: 'revenue', @@ -266,7 +266,7 @@ describe('createKloMcpServer', () => { ], totalFound: 1, }), - read: vi.fn().mockResolvedValue({ + read: vi.fn().mockResolvedValue({ key: 'revenue', summary: 'Paid order value', content: '# Revenue', @@ -275,14 +275,14 @@ describe('createKloMcpServer', () => { refs: [], slRefs: ['orders'], }), - write: vi.fn().mockResolvedValue({ + write: vi.fn().mockResolvedValue({ success: true, key: 'revenue', action: 'updated', }), }, semanticLayer: { - listSources: vi.fn().mockResolvedValue({ + listSources: vi.fn().mockResolvedValue({ sources: [ { connectionId: '00000000-0000-4000-8000-000000000001', @@ -296,22 +296,22 @@ describe('createKloMcpServer', () => { ], totalSources: 1, }), - readSource: vi.fn().mockResolvedValue({ + readSource: vi.fn().mockResolvedValue({ sourceName: 'orders', yaml: 'name: orders\n', }), - writeSource: vi.fn().mockResolvedValue({ + writeSource: vi.fn().mockResolvedValue({ success: true, sourceName: 'orders', yaml: 'name: orders\n', commitHash: 'abc123', }), - validate: vi.fn().mockResolvedValue({ + validate: vi.fn().mockResolvedValue({ success: true, errors: [], warnings: [], }), - query: vi.fn().mockResolvedValue({ + query: vi.fn().mockResolvedValue({ sql: 'select 1', headers: ['count'], rows: [[1]], @@ -320,12 +320,12 @@ describe('createKloMcpServer', () => { }), }, ingest: { - trigger: vi.fn().mockResolvedValue({ + trigger: vi.fn().mockResolvedValue({ runId: 'run-42', jobId: 'job-42', reportId: 'report-42', }), - status: vi.fn().mockResolvedValue({ + status: vi.fn().mockResolvedValue({ runId: 'run-42', jobId: 'job-42', reportId: 'report-42', @@ -359,7 +359,7 @@ describe('createKloMcpServer', () => { evictionDeletedRawPaths: [], errors: [], }), - report: vi.fn>().mockResolvedValue({ + report: vi.fn>().mockResolvedValue({ id: 'report-42', runId: 'run-42', jobId: 'job-42', @@ -384,7 +384,7 @@ describe('createKloMcpServer', () => { toolTranscripts: [], }, }), - replay: vi.fn>().mockResolvedValue({ + replay: vi.fn>().mockResolvedValue({ runId: 'run-42', reportId: 'report-42', reportPath: 'report-42', @@ -400,7 +400,7 @@ describe('createKloMcpServer', () => { }), }, scan: { - trigger: vi.fn().mockResolvedValue({ + trigger: vi.fn().mockResolvedValue({ runId: 'scan-run-1', status: 'done', done: true, @@ -460,7 +460,7 @@ describe('createKloMcpServer', () => { createdAt: '2026-04-29T09:00:00.000Z', }, }), - status: vi.fn().mockResolvedValue({ + status: vi.fn().mockResolvedValue({ runId: 'scan-run-1', status: 'done', done: true, @@ -474,8 +474,8 @@ describe('createKloMcpServer', () => { reportPath: 'raw-sources/warehouse/live-database/sync-1/scan-report.json', warnings: [], }), - report: vi.fn().mockResolvedValue(null), - listArtifacts: vi.fn>().mockResolvedValue({ + report: vi.fn().mockResolvedValue(null), + listArtifacts: vi.fn>().mockResolvedValue({ runId: 'scan-run-1', artifacts: [ { @@ -490,7 +490,7 @@ describe('createKloMcpServer', () => { }, ], }), - readArtifact: vi.fn>().mockImplementation(async (input) => { + readArtifact: vi.fn>().mockImplementation(async (input) => { if (input.path !== 'raw-sources/warehouse/live-database/sync-1/tables/orders.json') { return null; } @@ -505,7 +505,7 @@ describe('createKloMcpServer', () => { }, }; - createKloMcpServer({ + createKtxMcpServer({ server: fake.server, memoryCapture: capture, userContext: { userId: 'mcp-user' }, @@ -859,10 +859,10 @@ describe('createKloMcpServer', () => { await expect( getTool(fake.tools, 'scan_read_artifact').handler({ runId: 'scan-run-1', - path: 'klo.yaml', + path: 'ktx.yaml', }), ).resolves.toEqual({ - content: [{ type: 'text', text: 'Scan artifact "klo.yaml" was not found for run "scan-run-1".' }], + content: [{ type: 'text', text: 'Scan artifact "ktx.yaml" was not found for run "scan-run-1".' }], isError: true, }); }); diff --git a/packages/context/src/mcp/server.ts b/packages/context/src/mcp/server.ts index 85342826..ba11c086 100644 --- a/packages/context/src/mcp/server.ts +++ b/packages/context/src/mcp/server.ts @@ -2,8 +2,8 @@ import { randomUUID } from 'node:crypto'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import type { MemoryAgentInput } from '../memory/index.js'; -import { jsonErrorToolResult, jsonToolResult, registerKloContextTools } from './context-tools.js'; -import type { KloMcpServerDeps, KloMcpServerLike, MemoryCapturePort } from './types.js'; +import { jsonErrorToolResult, jsonToolResult, registerKtxContextTools } from './context-tools.js'; +import type { KtxMcpServerDeps, KtxMcpServerLike, MemoryCapturePort } from './types.js'; const memoryCaptureInputSchema = { userMessage: z.string().min(1).describe('The user message that may contain durable knowledge.'), @@ -16,9 +16,9 @@ const memoryCaptureStatusInputSchema = { }; function registerMemoryCaptureTools(deps: { - server: KloMcpServerLike; + server: KtxMcpServerLike; memoryCapture: MemoryCapturePort; - userContext: KloMcpServerDeps['userContext']; + userContext: KtxMcpServerDeps['userContext']; }): void { deps.server.registerTool( 'memory_capture', @@ -57,7 +57,7 @@ function registerMemoryCaptureTools(deps: { ); } -export function createKloMcpServer(deps: KloMcpServerDeps): KloMcpServerDeps['server'] { +export function createKtxMcpServer(deps: KtxMcpServerDeps): KtxMcpServerDeps['server'] { if (deps.memoryCapture) { registerMemoryCaptureTools({ server: deps.server, @@ -67,7 +67,7 @@ export function createKloMcpServer(deps: KloMcpServerDeps): KloMcpServerDeps['se } if (deps.contextTools) { - registerKloContextTools({ + registerKtxContextTools({ server: deps.server, ports: deps.contextTools, userContext: deps.userContext, @@ -77,15 +77,15 @@ export function createKloMcpServer(deps: KloMcpServerDeps): KloMcpServerDeps['se return deps.server; } -export function createDefaultKloMcpServer( - deps: Omit & { name?: string; version?: string }, +export function createDefaultKtxMcpServer( + deps: Omit & { name?: string; version?: string }, ): McpServer { const server = new McpServer({ - name: deps.name ?? 'klo', + name: deps.name ?? 'ktx', version: deps.version ?? '0.0.0-private', }); - createKloMcpServer({ - server: server as KloMcpServerLike, + createKtxMcpServer({ + server: server as KtxMcpServerLike, memoryCapture: deps.memoryCapture, userContext: deps.userContext, contextTools: deps.contextTools, diff --git a/packages/context/src/mcp/types.ts b/packages/context/src/mcp/types.ts index 899c04fb..58f8e22b 100644 --- a/packages/context/src/mcp/types.ts +++ b/packages/context/src/mcp/types.ts @@ -1,6 +1,6 @@ import type { IngestReportSnapshot, MemoryFlowReplayInput } from '../ingest/index.js'; import type { MemoryCaptureService } from '../memory/index.js'; -import type { KloScanMode, KloScanReport } from '../scan/index.js'; +import type { KtxScanMode, KtxScanReport } from '../scan/index.js'; import type { SemanticLayerQueryInput, SlDictionaryMatch, @@ -9,13 +9,13 @@ import type { } from '../sl/index.js'; import type { WikiSearchLaneSummary, WikiSearchMatchReason } from '../wiki/index.js'; -export interface KloMcpTextContent { +export interface KtxMcpTextContent { type: 'text'; text: string; } -export interface KloMcpToolResult { - content: KloMcpTextContent[]; +export interface KtxMcpToolResult { + content: KtxMcpTextContent[]; structuredContent?: T; isError?: true; } @@ -25,11 +25,11 @@ export interface MemoryCapturePort { status: MemoryCaptureService['status']; } -export interface KloMcpUserContext { +export interface KtxMcpUserContext { userId: string; } -export interface KloMcpServerLike { +export interface KtxMcpServerLike { registerTool( name: string, config: { @@ -41,13 +41,13 @@ export interface KloMcpServerLike { ): void; } -export interface KloConnectionSummary { +export interface KtxConnectionSummary { id: string; name: string; connectionType: string; } -export interface KloConnectionTestResponse { +export interface KtxConnectionTestResponse { id: string; connectionType: string; ok: boolean; @@ -56,12 +56,12 @@ export interface KloConnectionTestResponse { warnings: string[]; } -export interface KloConnectionsMcpPort { - list(): Promise; - test?(input: { connectionId: string }): Promise; +export interface KtxConnectionsMcpPort { + list(): Promise; + test?(input: { connectionId: string }): Promise; } -export interface KloKnowledgeSearchResult { +export interface KtxKnowledgeSearchResult { key: string; path: string; scope: 'GLOBAL' | 'USER'; @@ -71,12 +71,12 @@ export interface KloKnowledgeSearchResult { lanes?: WikiSearchLaneSummary[]; } -export interface KloKnowledgeSearchResponse { - results: KloKnowledgeSearchResult[]; +export interface KtxKnowledgeSearchResponse { + results: KtxKnowledgeSearchResult[]; totalFound: number; } -export interface KloKnowledgePage { +export interface KtxKnowledgePage { key: string; summary: string; content: string; @@ -86,7 +86,7 @@ export interface KloKnowledgePage { slRefs?: string[]; } -interface KloHistoricSqlKnowledgeUsage { +interface KtxHistoricSqlKnowledgeUsage { executions: number; distinct_users: number; first_seen: string; @@ -97,15 +97,15 @@ interface KloHistoricSqlKnowledgeUsage { rows_produced?: number; } -export interface KloKnowledgeWriteResponse { +export interface KtxKnowledgeWriteResponse { success: boolean; key: string; action: 'created' | 'updated'; } -export interface KloKnowledgeMcpPort { - search(input: { userId: string; query: string; limit: number }): Promise; - read(input: { userId: string; key: string }): Promise; +export interface KtxKnowledgeMcpPort { + search(input: { userId: string; query: string; limit: number }): Promise; + read(input: { userId: string; key: string }): Promise; write(input: { userId: string; key: string; @@ -118,12 +118,12 @@ export interface KloKnowledgeMcpPort { intent?: string; tables?: string[]; representativeSql?: string; - usage?: KloHistoricSqlKnowledgeUsage; + usage?: KtxHistoricSqlKnowledgeUsage; fingerprints?: string[]; - }): Promise; + }): Promise; } -export interface KloSemanticLayerSourceSummary { +export interface KtxSemanticLayerSourceSummary { connectionId: string; connectionName: string; name: string; @@ -137,17 +137,17 @@ export interface KloSemanticLayerSourceSummary { lanes?: SlSearchLaneSummary[]; } -export interface KloSemanticLayerListResponse { - sources: KloSemanticLayerSourceSummary[]; +export interface KtxSemanticLayerListResponse { + sources: KtxSemanticLayerSourceSummary[]; totalSources: number; } -export interface KloSemanticLayerReadResponse { +export interface KtxSemanticLayerReadResponse { sourceName: string; yaml: string; } -export interface KloSemanticLayerWriteResponse { +export interface KtxSemanticLayerWriteResponse { success: boolean; sourceName: string; yaml?: string; @@ -156,13 +156,13 @@ export interface KloSemanticLayerWriteResponse { commitHash?: string; } -export interface KloSemanticLayerValidationResponse { +export interface KtxSemanticLayerValidationResponse { success: boolean; errors: string[]; warnings: string[]; } -export interface KloSemanticLayerQueryResponse { +export interface KtxSemanticLayerQueryResponse { sql: string; headers: string[]; rows: unknown[][]; @@ -170,23 +170,23 @@ export interface KloSemanticLayerQueryResponse { plan?: Record; } -export interface KloSemanticLayerMcpPort { - listSources(input: { connectionId?: string; query?: string }): Promise; - readSource(input: { connectionId: string; sourceName: string }): Promise; +export interface KtxSemanticLayerMcpPort { + listSources(input: { connectionId?: string; query?: string }): Promise; + readSource(input: { connectionId: string; sourceName: string }): Promise; writeSource(input: { connectionId: string; sourceName: string; yaml?: string; source?: Record; delete?: boolean; - }): Promise; - validate(input: { connectionId: string; names?: string[] }): Promise; - query(input: { connectionId?: string; query: SemanticLayerQueryInput }): Promise; + }): Promise; + validate(input: { connectionId: string; names?: string[] }): Promise; + query(input: { connectionId?: string; query: SemanticLayerQueryInput }): Promise; } -export type KloIngestTriggerKind = 'upload' | 'scheduled_pull' | 'manual_resync'; +export type KtxIngestTriggerKind = 'upload' | 'scheduled_pull' | 'manual_resync'; -interface KloIngestTriggerFanoutChild { +interface KtxIngestTriggerFanoutChild { runId: string; jobId: string; reportId: string; @@ -194,31 +194,31 @@ interface KloIngestTriggerFanoutChild { metabaseDatabaseId: number; } -export interface KloIngestTriggerResponse { +export interface KtxIngestTriggerResponse { runId: string; jobId?: string; reportId?: string; fanout?: { status: 'all_succeeded' | 'partial_failure' | 'all_failed'; - children: KloIngestTriggerFanoutChild[]; + children: KtxIngestTriggerFanoutChild[]; }; } -export interface KloIngestDiffSummary { +export interface KtxIngestDiffSummary { added: number; modified: number; deleted: number; unchanged: number; } -export interface KloIngestWorkUnitSummary { +export interface KtxIngestWorkUnitSummary { unitKey: string; rawFiles: string[]; peerFileIndex: string[]; dependencyPaths: string[]; } -export interface KloIngestStatusResponse { +export interface KtxIngestStatusResponse { runId: string; jobId?: string; reportId?: string; @@ -234,93 +234,93 @@ export interface KloIngestStatusResponse { startedAt?: string; completedAt?: string; previousRunId?: string | null; - diffSummary?: KloIngestDiffSummary; + diffSummary?: KtxIngestDiffSummary; workUnitCount?: number; rawFileCount?: number; - workUnits?: KloIngestWorkUnitSummary[]; + workUnits?: KtxIngestWorkUnitSummary[]; evictionDeletedRawPaths?: string[]; } -export interface KloIngestMcpPort { +export interface KtxIngestMcpPort { trigger(input: { adapter: string; connectionId: string; config?: unknown; - trigger: KloIngestTriggerKind; - }): Promise; - status(input: { runId: string }): Promise; + trigger: KtxIngestTriggerKind; + }): Promise; + status(input: { runId: string }): Promise; report?(input: { runId: string }): Promise; replay?(input: { runId: string }): Promise; } -interface KloScanTriggerResponse { +interface KtxScanTriggerResponse { runId: string; status: 'done'; done: true; connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; dryRun: boolean; syncId: string; - report: KloScanReport; + report: KtxScanReport; } -interface KloScanStatusResponse { +interface KtxScanStatusResponse { runId: string; status: string; done: boolean; connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; dryRun: boolean; syncId: string; progress: number; startedAt: string; completedAt: string; reportPath: string | null; - warnings: KloScanReport['warnings']; + warnings: KtxScanReport['warnings']; } -export type KloScanArtifactType = 'report' | 'raw_source' | 'manifest_shard' | 'enrichment_artifact'; +export type KtxScanArtifactType = 'report' | 'raw_source' | 'manifest_shard' | 'enrichment_artifact'; -export interface KloScanArtifactSummary { +export interface KtxScanArtifactSummary { path: string; - type: KloScanArtifactType; + type: KtxScanArtifactType; size?: number; } -export interface KloScanArtifactListResponse { +export interface KtxScanArtifactListResponse { runId: string; - artifacts: KloScanArtifactSummary[]; + artifacts: KtxScanArtifactSummary[]; } -export interface KloScanArtifactReadResponse extends KloScanArtifactSummary { +export interface KtxScanArtifactReadResponse extends KtxScanArtifactSummary { runId: string; content: string; } -export interface KloScanMcpPort { +export interface KtxScanMcpPort { trigger(input: { connectionId: string; - mode?: KloScanMode; + mode?: KtxScanMode; detectRelationships: boolean; dryRun: boolean; - }): Promise; - status(input: { runId: string }): Promise; - report(input: { runId: string }): Promise; - listArtifacts?(input: { runId: string }): Promise; - readArtifact?(input: { runId: string; path: string }): Promise; + }): Promise; + status(input: { runId: string }): Promise; + report(input: { runId: string }): Promise; + listArtifacts?(input: { runId: string }): Promise; + readArtifact?(input: { runId: string; path: string }): Promise; } -export interface KloMcpContextPorts { - connections?: KloConnectionsMcpPort; - knowledge?: KloKnowledgeMcpPort; - semanticLayer?: KloSemanticLayerMcpPort; - ingest?: KloIngestMcpPort; - scan?: KloScanMcpPort; +export interface KtxMcpContextPorts { + connections?: KtxConnectionsMcpPort; + knowledge?: KtxKnowledgeMcpPort; + semanticLayer?: KtxSemanticLayerMcpPort; + ingest?: KtxIngestMcpPort; + scan?: KtxScanMcpPort; } -export interface KloMcpServerDeps { - server: KloMcpServerLike; +export interface KtxMcpServerDeps { + server: KtxMcpServerLike; memoryCapture?: MemoryCapturePort; - userContext: KloMcpUserContext; - contextTools?: KloMcpContextPorts; + userContext: KtxMcpUserContext; + contextTools?: KtxMcpContextPorts; } diff --git a/packages/context/src/memory/local-memory-runs.ts b/packages/context/src/memory/local-memory-runs.ts index 29daa728..566f979f 100644 --- a/packages/context/src/memory/local-memory-runs.ts +++ b/packages/context/src/memory/local-memory-runs.ts @@ -21,7 +21,7 @@ type MemoryRunRow = { }; function localMemoryDbPath(projectDir: string): string { - return join(projectDir, '.klo', 'db.sqlite'); + return join(projectDir, '.ktx', 'db.sqlite'); } function isSafeRunId(runId: string): boolean { diff --git a/packages/context/src/memory/local-memory.test.ts b/packages/context/src/memory/local-memory.test.ts index bea67c26..1284f76d 100644 --- a/packages/context/src/memory/local-memory.test.ts +++ b/packages/context/src/memory/local-memory.test.ts @@ -2,7 +2,7 @@ import { access, mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { initKloProject } from '../project/index.js'; +import { initKtxProject } from '../project/index.js'; import { createLocalProjectMemoryCapture } from './local-memory.js'; import { LocalMemoryRunStore } from './local-memory-runs.js'; @@ -20,7 +20,7 @@ describe('LocalMemoryRunStore', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-memory-runs-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-memory-runs-')); }); afterEach(async () => { @@ -44,8 +44,8 @@ describe('LocalMemoryRunStore', () => { commitHash: 'abc123', }); - await expect(access(join(tempDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); - await expectPathMissing(join(tempDir, '.klo/memory-runs/memory-run-1.json')); + await expect(access(join(tempDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); + await expectPathMissing(join(tempDir, '.ktx/memory-runs/memory-run-1.json')); await expect(store.findById('memory-run-1')).resolves.toMatchObject({ id: 'memory-run-1', @@ -81,7 +81,7 @@ describe('createLocalProjectMemoryCapture', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-memory-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-memory-')); }); afterEach(async () => { @@ -89,7 +89,7 @@ describe('createLocalProjectMemoryCapture', () => { }); it('captures a wiki page through the local memory agent and persists pollable status', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); const agentRunner = { runLoop: async ({ toolSet, @@ -126,8 +126,8 @@ describe('createLocalProjectMemoryCapture', () => { ).resolves.toEqual({ runId: 'memory-run-1' }); await capture.waitForRun('memory-run-1'); - await expect(access(join(project.projectDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); - await expectPathMissing(join(project.projectDir, '.klo/memory-runs/memory-run-1.json')); + await expect(access(join(project.projectDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); + await expectPathMissing(join(project.projectDir, '.ktx/memory-runs/memory-run-1.json')); await expect(capture.status('memory-run-1')).resolves.toMatchObject({ runId: 'memory-run-1', @@ -144,7 +144,7 @@ describe('createLocalProjectMemoryCapture', () => { }); it('captures a semantic-layer source for a named local connection id', async () => { - const project = await initKloProject({ projectDir: tempDir, projectName: 'warehouse' }); + const project = await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', readonly: true }; const agentRunner = { runLoop: async ({ @@ -187,8 +187,8 @@ describe('createLocalProjectMemoryCapture', () => { }); await capture.waitForRun('memory-run-2'); - await expect(access(join(project.projectDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); - await expectPathMissing(join(project.projectDir, '.klo/memory-runs/memory-run-2.json')); + await expect(access(join(project.projectDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); + await expectPathMissing(join(project.projectDir, '.ktx/memory-runs/memory-run-2.json')); await expect(capture.status('memory-run-2')).resolves.toMatchObject({ runId: 'memory-run-2', diff --git a/packages/context/src/memory/local-memory.ts b/packages/context/src/memory/local-memory.ts index cbad2fc8..25ddb2c1 100644 --- a/packages/context/src/memory/local-memory.ts +++ b/packages/context/src/memory/local-memory.ts @@ -1,19 +1,19 @@ import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import YAML from 'yaml'; import { AgentRunnerService } from '../agent/index.js'; import { localConnectionInfoFromConfig } from '../connections/index.js'; -import type { KloEmbeddingPort, KloFileStorePort, KloFileWriteResult } from '../core/index.js'; -import { type KloLogger, noopLogger, SessionWorktreeService } from '../core/index.js'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; -import { createLocalKloLlmProviderFromConfig } from '../llm/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import type { KtxEmbeddingPort, KtxFileStorePort, KtxFileWriteResult } from '../core/index.js'; +import { type KtxLogger, noopLogger, SessionWorktreeService } from '../core/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; +import { createLocalKtxLlmProviderFromConfig } from '../llm/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { PromptService } from '../prompts/index.js'; import { SkillsRegistryService } from '../skills/index.js'; import { - type KloConnectionInfo, - type KloQueryResult, + type KtxConnectionInfo, + type KtxQueryResult, SemanticLayerService, type SemanticLayerSource, type SlConnectionCatalogPort, @@ -58,21 +58,21 @@ import type { const promptsDir = fileURLToPath(new URL('../../prompts', import.meta.url)); const skillsDir = fileURLToPath(new URL('../../skills', import.meta.url)); -const LOCAL_AUTHOR = { name: 'KLO Local', email: 'local@klo.local' }; +const LOCAL_AUTHOR = { name: 'KTX Local', email: 'local@ktx.local' }; const LOCAL_SHAPE_WARNING = 'Local memory capture validates semantic-layer YAML shape only.'; export interface CreateLocalProjectMemoryCaptureOptions { - llmProvider?: KloLlmProvider; + llmProvider?: KtxLlmProvider; agentRunner?: AgentRunnerService; memoryModel?: string; - semanticLayerCompute?: KloSemanticLayerComputePort; - queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; + semanticLayerCompute?: KtxSemanticLayerComputePort; + queryExecutor?: { execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise }; runIdFactory?: () => string; - logger?: KloLogger; + logger?: KtxLogger; } export function createLocalProjectMemoryCapture( - project: KloLocalProject, + project: KtxLocalProject, options: CreateLocalProjectMemoryCaptureOptions = {}, ): MemoryCaptureService { const logger = options.logger ?? noopLogger; @@ -84,11 +84,11 @@ export function createLocalProjectMemoryCapture( const connections = new LocalMemoryConnections(project, options.queryExecutor); const slPython = new LocalSlPythonPort(options.semanticLayerCompute); const semanticLayerService = new SemanticLayerService(rootFileStore, connections, slPython, logger); - const slSourcesRepository = new SqliteSlSourcesIndex({ dbPath: join(project.projectDir, '.klo', 'db.sqlite') }); + const slSourcesRepository = new SqliteSlSourcesIndex({ dbPath: join(project.projectDir, '.ktx', 'db.sqlite') }); const slSearchService = new SlSearchService(embedding, slSourcesRepository, logger); const wikiService = new KnowledgeWikiService(rootFileStore, embedding, knowledgeIndex, project.git, logger); const authorResolver = new LocalAuthorResolver(); - const llmProvider = options.llmProvider ?? createLocalKloLlmProviderFromConfig(project.config.llm); + const llmProvider = options.llmProvider ?? createLocalKtxLlmProviderFromConfig(project.config.llm); const toolsetFactory = new LocalMemoryToolsetFactory({ project, embedding, @@ -142,7 +142,7 @@ export function createLocalProjectMemoryCapture( }); } -function requireLlmProvider(provider: KloLlmProvider | null | undefined): KloLlmProvider { +function requireLlmProvider(provider: KtxLlmProvider | null | undefined): KtxLlmProvider { if (!provider) { throw new Error('createLocalProjectMemoryCapture requires llm.provider.backend or an injected agentRunner'); } @@ -150,36 +150,36 @@ function requireLlmProvider(provider: KloLlmProvider | null | undefined): KloLlm } class LocalMemoryFileStore implements MemoryFileStorePort { - constructor(private readonly fileStore: MemoryFileStorePort | KloFileStorePort) {} + constructor(private readonly fileStore: MemoryFileStorePort | KtxFileStorePort) {} forWorktree(workdir: string): LocalMemoryFileStore { - return new LocalMemoryFileStore(this.fileStore.forWorktree(workdir) as KloFileStorePort); + return new LocalMemoryFileStore(this.fileStore.forWorktree(workdir) as KtxFileStorePort); } - writeFile(...args: Parameters): Promise { + writeFile(...args: Parameters): Promise { return this.fileStore.writeFile(...args); } - readFile(...args: Parameters) { + readFile(...args: Parameters) { return this.fileStore.readFile(...args); } - deleteFile(...args: Parameters) { + deleteFile(...args: Parameters) { return this.fileStore.deleteFile(...args); } - listFiles(...args: Parameters) { + listFiles(...args: Parameters) { return this.fileStore.listFiles(...args); } - getFileHistory(...args: Parameters) { + getFileHistory(...args: Parameters) { return this.fileStore.getFileHistory(...args); } async enqueueCommitMessageJobForExternalCommit(): Promise {} } -class NoopEmbeddingPort implements KloEmbeddingPort { +class NoopEmbeddingPort implements KtxEmbeddingPort { readonly maxBatchSize = 64; async computeEmbedding(): Promise { @@ -192,7 +192,7 @@ class NoopEmbeddingPort implements KloEmbeddingPort { } class LocalKnowledgeIndex implements KnowledgeIndexPort { - constructor(private readonly project: KloLocalProject) {} + constructor(private readonly project: KtxLocalProject) {} async upsertPage(): Promise {} @@ -276,19 +276,19 @@ class NoopKnowledgeSlRefsPort implements MemoryKnowledgeSlRefsPort { class LocalMemoryConnections implements MemoryConnectionPort, SlConnectionCatalogPort { constructor( - private readonly project: KloLocalProject, + private readonly project: KtxLocalProject, private readonly queryExecutor?: { - execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise; + execute(input: { connectionId: string; sql: string; maxRows?: number }): Promise; }, ) {} - async listEnabledConnections(ids: string[]): Promise { + async listEnabledConnections(ids: string[]): Promise { return ids .map((id) => localConnectionInfoFromConfig(id, this.project.config.connections[id])) - .filter((connection): connection is KloConnectionInfo => connection !== null); + .filter((connection): connection is KtxConnectionInfo => connection !== null); } - async getConnectionById(connectionId: string): Promise { + async getConnectionById(connectionId: string): Promise { const connection = localConnectionInfoFromConfig(connectionId, this.project.config.connections[connectionId]); if (!connection) { throw new Error(`Connection not found: ${connectionId}`); @@ -296,7 +296,7 @@ class LocalMemoryConnections implements MemoryConnectionPort, SlConnectionCatalo return connection; } - async executeQuery(connectionId: string, sql: string): Promise { + async executeQuery(connectionId: string, sql: string): Promise { if (!this.queryExecutor) { throw new Error('Local memory capture has no query executor configured'); } @@ -305,7 +305,7 @@ class LocalMemoryConnections implements MemoryConnectionPort, SlConnectionCatalo } class LocalSlPythonPort implements SlPythonPort { - constructor(private readonly compute?: KloSemanticLayerComputePort) {} + constructor(private readonly compute?: KtxSemanticLayerComputePort) {} async validateSources(input: Parameters[0]) { if (!this.compute) { @@ -394,8 +394,8 @@ class LocalMemoryToolsetFactory implements MemoryToolsetFactoryPort { private readonly slTools: BaseTool[]; constructor(deps: { - project: KloLocalProject; - embedding: KloEmbeddingPort; + project: KtxLocalProject; + embedding: KtxEmbeddingPort; wikiService: KnowledgeWikiService; knowledgeIndex: KnowledgeIndexPort; knowledgeEvents: KnowledgeEventPort; diff --git a/packages/context/src/memory/memory-agent.service.ts b/packages/context/src/memory/memory-agent.service.ts index e2ae9d42..fd1f0a6c 100644 --- a/packages/context/src/memory/memory-agent.service.ts +++ b/packages/context/src/memory/memory-agent.service.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { tool } from 'ai'; import * as YAML from 'yaml'; import { z } from 'zod'; -import { type KloLogger, noopLogger } from '../core/index.js'; +import { type KtxLogger, noopLogger } from '../core/index.js'; import { revertSourceToPreHead, type SemanticLayerSource, @@ -41,7 +41,7 @@ import type { type GateDeps = SlValidationDeps & { slValidator: SlValidatorPort }; export class MemoryAgentService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; constructor(private readonly deps: MemoryAgentServiceDeps) { this.logger = deps.logger ?? noopLogger; diff --git a/packages/context/src/memory/memory-runtime-assets.test.ts b/packages/context/src/memory/memory-runtime-assets.test.ts index 882b267b..204461ec 100644 --- a/packages/context/src/memory/memory-runtime-assets.test.ts +++ b/packages/context/src/memory/memory-runtime-assets.test.ts @@ -18,9 +18,9 @@ const expectedAdapterSkillHeadings: Record = { historic_sql_ingest: '# Historic SQL Ingest', live_database_ingest: '# Live Database Ingest', looker_ingest: '# Looker Runtime Ingest', - lookml_ingest: '# LookML to KLO Semantic Layer', - metabase_ingest: '# Metabase to KLO Semantic Layer', - metricflow_ingest: '# MetricFlow to KLO Semantic Layer', + lookml_ingest: '# LookML to KTX Semantic Layer', + metabase_ingest: '# Metabase to KTX Semantic Layer', + metricflow_ingest: '# MetricFlow to KTX Semantic Layer', }; function forbiddenProductPattern() { diff --git a/packages/context/src/memory/types.ts b/packages/context/src/memory/types.ts index 559431be..e1e03b41 100644 --- a/packages/context/src/memory/types.ts +++ b/packages/context/src/memory/types.ts @@ -1,11 +1,11 @@ import type { Tool } from 'ai'; import type { AgentRunnerService } from '../agent/index.js'; -import type { GitService, KloFileStorePort, KloLogger, SessionWorktreeService } from '../core/index.js'; +import type { GitService, KtxFileStorePort, KtxLogger, SessionWorktreeService } from '../core/index.js'; import type { PromptService } from '../prompts/index.js'; import type { SkillsRegistryService } from '../skills/index.js'; import type { - KloConnectionInfo, - KloQueryResult, + KtxConnectionInfo, + KtxQueryResult, SemanticLayerService, SemanticLayerSource, SlSearchService, @@ -101,9 +101,9 @@ export interface MemoryKnowledgeSlRefsPort { } export interface MemoryConnectionPort { - listEnabledConnections(ids: string[]): Promise; - getConnectionById(connectionId: string): Promise; - executeQuery(connectionId: string, sql: string): Promise; + listEnabledConnections(ids: string[]): Promise; + getConnectionById(connectionId: string): Promise; + executeQuery(connectionId: string, sql: string): Promise; } export interface MemoryCommitMessagePort { @@ -114,7 +114,7 @@ export interface MemoryCommitMessagePort { ): Promise; } -export interface MemoryFileStorePort extends KloFileStorePort, MemoryCommitMessagePort {} +export interface MemoryFileStorePort extends KtxFileStorePort, MemoryCommitMessagePort {} export interface MemoryToolSetLike { toAiSdkTools(context: ToolContext): Record; @@ -153,5 +153,5 @@ export interface MemoryAgentServiceDeps { slValidator: SlValidatorPort; toolsetFactory: MemoryToolsetFactoryPort; telemetry?: MemoryTelemetryPort; - logger?: KloLogger; + logger?: KtxLogger; } diff --git a/packages/context/src/package-exports.test.ts b/packages/context/src/package-exports.test.ts index 402da0e7..fd84c9c5 100644 --- a/packages/context/src/package-exports.test.ts +++ b/packages/context/src/package-exports.test.ts @@ -9,7 +9,7 @@ const scanTypeExportCoverage: Partial<{ result: ApplyLocalScanRelationshipReviewDecisionsResult; }> = {}; -describe('@klo/context package exports', () => { +describe('@ktx/context package exports', () => { it('exports package entry points used by host adapters', async () => { const core = await import('./core/index.js'); const connections = await import('./connections/index.js'); @@ -41,75 +41,75 @@ describe('@klo/context package exports', () => { expect(connections.notionConnectionToPullConfig).toBeTypeOf('function'); expect(scan).toBeDefined(); expect(scanTypeExportCoverage).toEqual({}); - expect(scan.createKloConnectorCapabilities).toBeTypeOf('function'); - expect(`liveDatabaseSnapshotToKlo${'SchemaSnapshot'}` in scan).toBe(false); - expect(scan.normalizeKloNativeType).toBeTypeOf('function'); - expect(scan.inferKloDimensionType).toBeTypeOf('function'); - expect(scan.redactKloCredentialEnvelope).toBeTypeOf('function'); - expect(scan.redactKloScanReport).toBeTypeOf('function'); - expect(scan.redactKloScanWarning).toBeTypeOf('function'); - expect(core.redactKloSensitiveMetadata).toBeTypeOf('function'); - expect(core.redactKloSensitiveText).toBeTypeOf('function'); - expect(scan.isKloDataDictionaryCandidate).toBeTypeOf('function'); - expect(scan.buildKloColumnEmbeddingText).toBeTypeOf('function'); - expect(scan.KloDescriptionGenerator).toBeTypeOf('function'); - expect(scan.KloScanOrchestrator).toBeTypeOf('function'); + expect(scan.createKtxConnectorCapabilities).toBeTypeOf('function'); + expect(`liveDatabaseSnapshotToKtx${'SchemaSnapshot'}` in scan).toBe(false); + expect(scan.normalizeKtxNativeType).toBeTypeOf('function'); + expect(scan.inferKtxDimensionType).toBeTypeOf('function'); + expect(scan.redactKtxCredentialEnvelope).toBeTypeOf('function'); + expect(scan.redactKtxScanReport).toBeTypeOf('function'); + expect(scan.redactKtxScanWarning).toBeTypeOf('function'); + expect(core.redactKtxSensitiveMetadata).toBeTypeOf('function'); + expect(core.redactKtxSensitiveText).toBeTypeOf('function'); + expect(scan.isKtxDataDictionaryCandidate).toBeTypeOf('function'); + expect(scan.buildKtxColumnEmbeddingText).toBeTypeOf('function'); + expect(scan.KtxDescriptionGenerator).toBeTypeOf('function'); + expect(scan.KtxScanOrchestrator).toBeTypeOf('function'); expect(scan.runLocalScan).toBeTypeOf('function'); expect(scan.writeLocalScanEnrichmentArtifacts).toBeTypeOf('function'); expect(scan.readLocalScanStructuralSnapshot).toBeTypeOf('function'); expect(scan.writeLocalScanManifestShards).toBeTypeOf('function'); - expect(scan.appendKloWordLimitInstruction).toBeTypeOf('function'); - expect(scan.buildKloColumnDescriptionPrompt).toBeTypeOf('function'); - expect(scan.buildKloTableDescriptionPrompt).toBeTypeOf('function'); - expect(scan.buildKloDataSourceDescriptionPrompt).toBeTypeOf('function'); - expect(scan.currentKloRelationshipBenchmarkDetector).toBeTypeOf('function'); - expect(scan.generateKloRelationshipDiscoveryCandidates).toBeTypeOf('function'); - expect(scan.inferKloRelationshipTargetPks).toBeTypeOf('function'); - expect(scan.mergeKloRelationshipDiscoveryCandidates).toBeTypeOf('function'); - expect(scan.normalizeKloRelationshipName).toBeTypeOf('function'); - expect(scan.tokenizeKloRelationshipName).toBeTypeOf('function'); + expect(scan.appendKtxWordLimitInstruction).toBeTypeOf('function'); + expect(scan.buildKtxColumnDescriptionPrompt).toBeTypeOf('function'); + expect(scan.buildKtxTableDescriptionPrompt).toBeTypeOf('function'); + expect(scan.buildKtxDataSourceDescriptionPrompt).toBeTypeOf('function'); + expect(scan.currentKtxRelationshipBenchmarkDetector).toBeTypeOf('function'); + expect(scan.generateKtxRelationshipDiscoveryCandidates).toBeTypeOf('function'); + expect(scan.inferKtxRelationshipTargetPks).toBeTypeOf('function'); + expect(scan.mergeKtxRelationshipDiscoveryCandidates).toBeTypeOf('function'); + expect(scan.normalizeKtxRelationshipName).toBeTypeOf('function'); + expect(scan.tokenizeKtxRelationshipName).toBeTypeOf('function'); expect(scan.tokenSimilarity).toBeTypeOf('function'); expect(scan.localCandidateTables).toBeTypeOf('function'); - expect(scan.scoreKloRelationshipCandidate).toBeTypeOf('function'); - expect(scan.defaultKloRelationshipScoreWeights).toBeTypeOf('function'); - expect(scan.normalizeKloRelationshipScoreWeights).toBeTypeOf('function'); + expect(scan.scoreKtxRelationshipCandidate).toBeTypeOf('function'); + expect(scan.defaultKtxRelationshipScoreWeights).toBeTypeOf('function'); + expect(scan.normalizeKtxRelationshipScoreWeights).toBeTypeOf('function'); expect(scan.calibrateWeightsFromSyntheticFixtures).toBeTypeOf('function'); - expect(scan.singularizeKloRelationshipToken).toBeTypeOf('function'); - expect(scan.pluralizeKloRelationshipToken).toBeTypeOf('function'); - expect(scan.collectKloFormalMetadataRelationships).toBeTypeOf('function'); - expect(scan.discoverKloCompositeRelationships).toBeTypeOf('function'); - expect(scan.proposeKloRelationshipCandidatesWithLlm).toBeTypeOf('function'); - expect(scan.profileKloRelationshipSchema).toBeTypeOf('function'); - expect(scan.quoteKloRelationshipIdentifier).toBeTypeOf('function'); - expect(scan.formatKloRelationshipTableRef).toBeTypeOf('function'); - expect(scan.validateKloRelationshipDiscoveryCandidates).toBeTypeOf('function'); - expect(scan.applyKloRelationshipValidationBudget).toBeTypeOf('function'); - expect(scan.defaultKloRelationshipValidationBudget).toBeTypeOf('function'); - expect(scan.resolveKloRelationshipGraph).toBeTypeOf('function'); - expect(scan.discoverKloRelationships).toBeTypeOf('function'); - expect('KloRelationshipDetector' in scan).toBe(false); - expect('defaultKloRelationshipDetectionSettings' in scan).toBe(false); - expect('KLO_RELATIONSHIP_DETECTION_CONFIDENCE' in scan).toBe(false); - expect(scan.buildKloRelationshipArtifacts).toBeTypeOf('function'); - expect(scan.buildKloRelationshipDiagnostics).toBeTypeOf('function'); + expect(scan.singularizeKtxRelationshipToken).toBeTypeOf('function'); + expect(scan.pluralizeKtxRelationshipToken).toBeTypeOf('function'); + expect(scan.collectKtxFormalMetadataRelationships).toBeTypeOf('function'); + expect(scan.discoverKtxCompositeRelationships).toBeTypeOf('function'); + expect(scan.proposeKtxRelationshipCandidatesWithLlm).toBeTypeOf('function'); + expect(scan.profileKtxRelationshipSchema).toBeTypeOf('function'); + expect(scan.quoteKtxRelationshipIdentifier).toBeTypeOf('function'); + expect(scan.formatKtxRelationshipTableRef).toBeTypeOf('function'); + expect(scan.validateKtxRelationshipDiscoveryCandidates).toBeTypeOf('function'); + expect(scan.applyKtxRelationshipValidationBudget).toBeTypeOf('function'); + expect(scan.defaultKtxRelationshipValidationBudget).toBeTypeOf('function'); + expect(scan.resolveKtxRelationshipGraph).toBeTypeOf('function'); + expect(scan.discoverKtxRelationships).toBeTypeOf('function'); + expect('KtxRelationshipDetector' in scan).toBe(false); + expect('defaultKtxRelationshipDetectionSettings' in scan).toBe(false); + expect('KTX_RELATIONSHIP_DETECTION_CONFIDENCE' in scan).toBe(false); + expect(scan.buildKtxRelationshipArtifacts).toBeTypeOf('function'); + expect(scan.buildKtxRelationshipDiagnostics).toBeTypeOf('function'); expect(scan.readLocalScanRelationshipArtifacts).toBeTypeOf('function'); expect(scan.writeLocalScanRelationshipReviewDecision).toBeTypeOf('function'); expect(scan.applyLocalScanRelationshipReviewDecisions).toBeTypeOf('function'); expect(scan.exportLocalRelationshipFeedbackLabels).toBeTypeOf('function'); - expect(scan.formatKloRelationshipFeedbackLabelsJsonl).toBeTypeOf('function'); - expect(scan.buildKloRelationshipFeedbackCalibrationReport).toBeTypeOf('function'); + expect(scan.formatKtxRelationshipFeedbackLabelsJsonl).toBeTypeOf('function'); + expect(scan.buildKtxRelationshipFeedbackCalibrationReport).toBeTypeOf('function'); expect(scan.calibrateLocalRelationshipFeedbackLabels).toBeTypeOf('function'); - expect(scan.formatKloRelationshipFeedbackCalibrationMarkdown).toBeTypeOf('function'); - expect(scan.buildKloRelationshipThresholdAdviceReport).toBeTypeOf('function'); + expect(scan.formatKtxRelationshipFeedbackCalibrationMarkdown).toBeTypeOf('function'); + expect(scan.buildKtxRelationshipThresholdAdviceReport).toBeTypeOf('function'); expect(scan.adviseLocalRelationshipFeedbackThresholds).toBeTypeOf('function'); - expect(scan.formatKloRelationshipThresholdAdviceMarkdown).toBeTypeOf('function'); - expect(scan.emptyKloRelationshipProfileArtifact).toBeTypeOf('function'); - expect(scan.loadKloRelationshipBenchmarkFixture).toBeTypeOf('function'); - expect(scan.loadKloRelationshipBenchmarkFixtures).toBeTypeOf('function'); - expect(scan.maskKloRelationshipBenchmarkSnapshot).toBeTypeOf('function'); - expect(scan.runKloRelationshipBenchmarkCase).toBeTypeOf('function'); - expect(scan.runKloRelationshipBenchmarkSuite).toBeTypeOf('function'); - expect(scan.KLO_RELATIONSHIP_BENCHMARK_MODES).toEqual([ + expect(scan.formatKtxRelationshipThresholdAdviceMarkdown).toBeTypeOf('function'); + expect(scan.emptyKtxRelationshipProfileArtifact).toBeTypeOf('function'); + expect(scan.loadKtxRelationshipBenchmarkFixture).toBeTypeOf('function'); + expect(scan.loadKtxRelationshipBenchmarkFixtures).toBeTypeOf('function'); + expect(scan.maskKtxRelationshipBenchmarkSnapshot).toBeTypeOf('function'); + expect(scan.runKtxRelationshipBenchmarkCase).toBeTypeOf('function'); + expect(scan.runKtxRelationshipBenchmarkSuite).toBeTypeOf('function'); + expect(scan.KTX_RELATIONSHIP_BENCHMARK_MODES).toEqual([ 'metadata_present', 'declared_fks_removed', 'declared_pks_removed', @@ -119,23 +119,23 @@ describe('@klo/context package exports', () => { 'validation_disabled', 'embeddings_disabled', ]); - expect(scan.buildKloRelationshipBenchmarkReport).toBeTypeOf('function'); - expect(scan.formatKloRelationshipBenchmarkReportMarkdown).toBeTypeOf('function'); + expect(scan.buildKtxRelationshipBenchmarkReport).toBeTypeOf('function'); + expect(scan.formatKtxRelationshipBenchmarkReportMarkdown).toBeTypeOf('function'); expect(search).toBeDefined(); expect(search.HybridSearchCore).toBeTypeOf('function'); expect(search.normalizeSearchQuery).toBeTypeOf('function'); expect(search.rrfContribution).toBeTypeOf('function'); expect(search.assertSearchBackendConformanceCase).toBeTypeOf('function'); expect(search.assertSearchBackendCapabilities).toBeTypeOf('function'); - expect(core.resolveKloConfigReference).toBeTypeOf('function'); + expect(core.resolveKtxConfigReference).toBeTypeOf('function'); expect(root.HybridSearchCore).toBeTypeOf('function'); expect(root.assertSearchBackendConformanceCase).toBeTypeOf('function'); expect(root.assertSearchBackendCapabilities).toBeTypeOf('function'); - expect(root.createLocalKloEmbeddingProviderFromConfig).toBeTypeOf('function'); + expect(root.createLocalKtxEmbeddingProviderFromConfig).toBeTypeOf('function'); expect(agent).toBeDefined(); expect(agent.AgentRunnerService).toBeTypeOf('function'); expect(root.AgentRunnerService).toBeTypeOf('function'); - expect(root.createLocalKloLlmProviderFromConfig).toBeTypeOf('function'); + expect(root.createLocalKtxLlmProviderFromConfig).toBeTypeOf('function'); expect(prompts).toBeDefined(); expect(skills).toBeDefined(); expect(sl).toBeDefined(); @@ -186,7 +186,7 @@ describe('@klo/context package exports', () => { expect(ingest.computeLookerMappingDrift).toBeTypeOf('function'); expect(ingest.validateLookerMappings).toBeTypeOf('function'); expect(ingest.refreshLookerMappingPlaceholders).toBeTypeOf('function'); - expect(ingest.suggestKloConnectionForLookerConnection).toBeTypeOf('function'); + expect(ingest.suggestKtxConnectionForLookerConnection).toBeTypeOf('function'); expect(ingest.buildLookerPullConfigFromInputs).toBeTypeOf('function'); expect(ingest.validateLookerWarehouseTarget).toBeTypeOf('function'); expect(ingest.sqlglotDialectForConnectionType).toBeTypeOf('function'); @@ -242,9 +242,9 @@ describe('@klo/context package exports', () => { expect(mcp).toBeDefined(); expect(project).toBeDefined(); expect(daemon).toBeDefined(); - expect(mcp.registerKloContextTools).toBeTypeOf('function'); + expect(mcp.registerKtxContextTools).toBeTypeOf('function'); expect(mcp.createLocalProjectMcpContextPorts).toBeTypeOf('function'); - expect(project.buildDefaultKloProjectConfig).toBeTypeOf('function'); + expect(project.buildDefaultKtxProjectConfig).toBeTypeOf('function'); expect(daemon.createHttpSemanticLayerComputePort).toBeTypeOf('function'); expect(daemon.createPythonSemanticLayerComputePort).toBeTypeOf('function'); expect(sqlAnalysis.createHttpSqlAnalysisPort).toBeTypeOf('function'); diff --git a/packages/context/src/project/config.test.ts b/packages/context/src/project/config.test.ts index 4d5c851d..1be70322 100644 --- a/packages/context/src/project/config.test.ts +++ b/packages/context/src/project/config.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; -import { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js'; +import { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js'; -describe('KLO project config', () => { +describe('KTX project config', () => { it('builds the default standalone project config', () => { - expect(buildDefaultKloProjectConfig('warehouse')).toEqual({ + expect(buildDefaultKtxProjectConfig('warehouse')).toEqual({ project: 'warehouse', connections: {}, storage: { @@ -11,7 +11,7 @@ describe('KLO project config', () => { search: 'sqlite-fts5', git: { auto_commit: true, - author: 'klo ', + author: 'ktx ', }, }, llm: { @@ -63,8 +63,8 @@ describe('KLO project config', () => { }); it('round-trips through YAML with stable defaults', () => { - const serialized = serializeKloProjectConfig(buildDefaultKloProjectConfig('warehouse')); - const parsed = parseKloProjectConfig(serialized); + const serialized = serializeKtxProjectConfig(buildDefaultKtxProjectConfig('warehouse')); + const parsed = parseKtxProjectConfig(serialized); expect(serialized).toContain('project: warehouse'); expect(serialized).toContain('live-database'); @@ -82,7 +82,7 @@ describe('KLO project config', () => { }); it('parses and serializes setup wizard metadata', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: revenue setup: database_connection_ids: @@ -102,14 +102,14 @@ connections: completed_steps: ['project', 'llm'], }); - const serialized = serializeKloProjectConfig(config); + const serialized = serializeKtxProjectConfig(config); expect(serialized).toContain('setup:'); expect(serialized).toContain('database_connection_ids:'); expect(serialized).toContain('completed_steps:'); }); it('parses global direct Anthropic LLM config', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo llm: provider: @@ -149,7 +149,7 @@ ingest: }); it('parses global Vertex LLM config', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo llm: provider: @@ -171,7 +171,7 @@ llm: }); it('parses gateway LLM, OpenAI scan embeddings, and sentence-transformers ingest embeddings', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo llm: provider: @@ -215,7 +215,7 @@ scan: }); it('parses scan relationship settings', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo scan: relationships: @@ -243,20 +243,20 @@ scan: validationConcurrency: 2, validationBudget: 0, }); - expect(serializeKloProjectConfig(config)).toContain('enabled: false'); - expect(serializeKloProjectConfig(config)).toContain('llmProposals: false'); - expect(serializeKloProjectConfig(config)).toContain('validationRequiredForManifest: true'); - expect(serializeKloProjectConfig(config)).toContain('acceptThreshold: 0.91'); - expect(serializeKloProjectConfig(config)).toContain('reviewThreshold: 0.61'); - expect(serializeKloProjectConfig(config)).toContain('maxLlmTablesPerBatch: 12'); - expect(serializeKloProjectConfig(config)).toContain('maxCandidatesPerColumn: 7'); - expect(serializeKloProjectConfig(config)).toContain('profileSampleRows: 500'); - expect(serializeKloProjectConfig(config)).toContain('validationConcurrency: 2'); - expect(serializeKloProjectConfig(config)).toContain('validationBudget: 0'); + expect(serializeKtxProjectConfig(config)).toContain('enabled: false'); + expect(serializeKtxProjectConfig(config)).toContain('llmProposals: false'); + expect(serializeKtxProjectConfig(config)).toContain('validationRequiredForManifest: true'); + expect(serializeKtxProjectConfig(config)).toContain('acceptThreshold: 0.91'); + expect(serializeKtxProjectConfig(config)).toContain('reviewThreshold: 0.61'); + expect(serializeKtxProjectConfig(config)).toContain('maxLlmTablesPerBatch: 12'); + expect(serializeKtxProjectConfig(config)).toContain('maxCandidatesPerColumn: 7'); + expect(serializeKtxProjectConfig(config)).toContain('profileSampleRows: 500'); + expect(serializeKtxProjectConfig(config)).toContain('validationConcurrency: 2'); + expect(serializeKtxProjectConfig(config)).toContain('validationBudget: 0'); }); it('parses the scan relationship validation budget sentinel', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo scan: relationships: @@ -264,11 +264,11 @@ scan: `); expect(config.scan.relationships.validationBudget).toBe('all'); - expect(serializeKloProjectConfig(config)).toContain('validationBudget: all'); + expect(serializeKtxProjectConfig(config)).toContain('validationBudget: all'); }); it('falls back to safe scan relationship defaults for invalid numeric settings', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo scan: relationships: @@ -293,7 +293,7 @@ scan: }); it('falls back for invalid scan relationship validation budget strings', () => { - const config = parseKloProjectConfig(` + const config = parseKtxProjectConfig(` project: demo scan: relationships: @@ -305,7 +305,7 @@ scan: it('rejects legacy local LLM and embedding fields', () => { expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo ingest: llm: @@ -314,7 +314,7 @@ ingest: ).toThrow('Unsupported ingest.llm: use top-level llm.provider, llm.models, and ingest.workUnits'); expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo scan: enrichment: @@ -323,7 +323,7 @@ scan: ).toThrow('Unsupported scan.enrichment.backend: use scan.enrichment.mode'); expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo scan: enrichment: @@ -334,7 +334,7 @@ scan: ).toThrow('Unsupported scan.enrichment.llm: use top-level llm.provider and llm.models'); expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo ingest: embeddings: @@ -346,7 +346,7 @@ ingest: it('rejects gateway embedding configs', () => { expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo ingest: embeddings: @@ -357,7 +357,7 @@ ingest: ).toThrow('Unsupported ingest.embeddings.backend: gateway'); expect(() => - parseKloProjectConfig(` + parseKtxProjectConfig(` project: demo scan: enrichment: @@ -371,9 +371,9 @@ scan: }); it('fills optional sections when a minimal config is loaded', () => { - const config = parseKloProjectConfig('project: local\n'); + const config = parseKtxProjectConfig('project: local\n'); - expect(config).toEqual(buildDefaultKloProjectConfig('local')); + expect(config).toEqual(buildDefaultKtxProjectConfig('local')); expect(config.ingest.embeddings).toEqual({ backend: 'deterministic', model: 'deterministic', @@ -382,10 +382,10 @@ scan: }); it('rejects configs without an object root', () => { - expect(() => parseKloProjectConfig('- nope\n')).toThrow('klo.yaml must contain a YAML object'); + expect(() => parseKtxProjectConfig('- nope\n')).toThrow('ktx.yaml must contain a YAML object'); }); it('rejects configs with a missing project name', () => { - expect(() => parseKloProjectConfig('connections: {}\n')).toThrow('klo.yaml field "project" is required'); + expect(() => parseKtxProjectConfig('connections: {}\n')).toThrow('ktx.yaml field "project" is required'); }); }); diff --git a/packages/context/src/project/config.ts b/packages/context/src/project/config.ts index 5151da52..6d2e8941 100644 --- a/packages/context/src/project/config.ts +++ b/packages/context/src/project/config.ts @@ -1,35 +1,35 @@ -import type { KloEmbeddingBackend, KloLlmBackend, KloModelRole, KloPromptCacheTtl } from '@klo/llm'; +import type { KtxEmbeddingBackend, KtxLlmBackend, KtxModelRole, KtxPromptCacheTtl } from '@ktx/llm'; import YAML from 'yaml'; -export type KloStorageState = 'postgres' | 'sqlite'; -export type KloSearchBackend = 'postgres-hybrid' | 'sqlite-fts5'; -type KloLocalLlmBackend = KloLlmBackend | 'none'; -type KloLocalEmbeddingBackend = KloEmbeddingBackend | 'none'; -type KloScanEnrichmentMode = 'none' | 'deterministic' | 'llm'; +export type KtxStorageState = 'postgres' | 'sqlite'; +export type KtxSearchBackend = 'postgres-hybrid' | 'sqlite-fts5'; +type KtxLocalLlmBackend = KtxLlmBackend | 'none'; +type KtxLocalEmbeddingBackend = KtxEmbeddingBackend | 'none'; +type KtxScanEnrichmentMode = 'none' | 'deterministic' | 'llm'; -interface KloProjectPromptCachingConfig { +interface KtxProjectPromptCachingConfig { enabled?: boolean; - systemTtl?: KloPromptCacheTtl; - toolsTtl?: KloPromptCacheTtl; - historyTtl?: KloPromptCacheTtl; + systemTtl?: KtxPromptCacheTtl; + toolsTtl?: KtxPromptCacheTtl; + historyTtl?: KtxPromptCacheTtl; vertexFallbackTo5m?: boolean; } -export interface KloProjectLlmProviderConfig { - backend: KloLocalLlmBackend; +export interface KtxProjectLlmProviderConfig { + backend: KtxLocalLlmBackend; vertex?: { project?: string; location: string }; anthropic?: { api_key?: string; base_url?: string }; gateway?: { api_key?: string; base_url?: string }; } -export interface KloProjectLlmConfig { - provider: KloProjectLlmProviderConfig; - models: Partial> & { default?: string }; - promptCaching?: KloProjectPromptCachingConfig; +export interface KtxProjectLlmConfig { + provider: KtxProjectLlmProviderConfig; + models: Partial> & { default?: string }; + promptCaching?: KtxProjectPromptCachingConfig; } -export interface KloProjectEmbeddingConfig { - backend: KloLocalEmbeddingBackend; +export interface KtxProjectEmbeddingConfig { + backend: KtxLocalEmbeddingBackend; model?: string; dimensions: number; openai?: { api_key?: string; base_url?: string }; @@ -37,18 +37,18 @@ export interface KloProjectEmbeddingConfig { batchSize?: number; } -export interface KloScanEnrichmentConfig { - mode: KloScanEnrichmentMode; - embeddings?: KloProjectEmbeddingConfig; +export interface KtxScanEnrichmentConfig { + mode: KtxScanEnrichmentMode; + embeddings?: KtxProjectEmbeddingConfig; } -export interface KloIngestWorkUnitsConfig { +export interface KtxIngestWorkUnitsConfig { stepBudget: number; maxConcurrency: number; failureMode: 'abort' | 'continue'; } -export interface KloScanRelationshipConfig { +export interface KtxScanRelationshipConfig { enabled: boolean; llmProposals: boolean; validationRequiredForManifest: boolean; @@ -61,40 +61,40 @@ export interface KloScanRelationshipConfig { validationBudget?: number | 'all'; } -export interface KloProjectScanConfig { - enrichment: KloScanEnrichmentConfig; - relationships: KloScanRelationshipConfig; +export interface KtxProjectScanConfig { + enrichment: KtxScanEnrichmentConfig; + relationships: KtxScanRelationshipConfig; } -export interface KloProjectConnectionConfig { +export interface KtxProjectConnectionConfig { driver: string; url?: string; readonly?: boolean; [key: string]: unknown; } -export interface KloProjectSetupConfig { +export interface KtxProjectSetupConfig { database_connection_ids: string[]; completed_steps: string[]; } -export interface KloProjectConfig { +export interface KtxProjectConfig { project: string; - setup?: KloProjectSetupConfig; - connections: Record; + setup?: KtxProjectSetupConfig; + connections: Record; storage: { - state: KloStorageState; - search: KloSearchBackend; + state: KtxStorageState; + search: KtxSearchBackend; git: { auto_commit: boolean; author: string; }; }; - llm: KloProjectLlmConfig; + llm: KtxProjectLlmConfig; ingest: { adapters: string[]; - embeddings: KloProjectEmbeddingConfig; - workUnits: KloIngestWorkUnitsConfig; + embeddings: KtxProjectEmbeddingConfig; + workUnits: KtxIngestWorkUnitsConfig; }; agent: { run_research: { @@ -106,7 +106,7 @@ export interface KloProjectConfig { memory: { auto_commit: boolean; }; - scan: KloProjectScanConfig; + scan: KtxProjectScanConfig; } function isRecord(value: unknown): value is Record { @@ -167,7 +167,7 @@ function ratioConfigValue(value: unknown, fallback: number): number { return value; } -function localLlmBackend(value: unknown, fallback: KloLocalLlmBackend, section = 'llm.provider'): KloLocalLlmBackend { +function localLlmBackend(value: unknown, fallback: KtxLocalLlmBackend, section = 'llm.provider'): KtxLocalLlmBackend { if (value == null) { return fallback; } @@ -181,9 +181,9 @@ function localLlmBackend(value: unknown, fallback: KloLocalLlmBackend, section = function localEmbeddingBackend( value: unknown, - fallback: KloLocalEmbeddingBackend, + fallback: KtxLocalEmbeddingBackend, section = 'ingest.embeddings', -): KloLocalEmbeddingBackend { +): KtxLocalEmbeddingBackend { if (value == null) { return fallback; } @@ -200,7 +200,7 @@ function localEmbeddingBackend( throw new Error(`Unsupported ${section}.backend: ${String(value)}`); } -function scanEnrichmentMode(value: unknown, fallback: KloScanEnrichmentMode): KloScanEnrichmentMode { +function scanEnrichmentMode(value: unknown, fallback: KtxScanEnrichmentMode): KtxScanEnrichmentMode { if (value == null) { return fallback; } @@ -239,26 +239,26 @@ function optionalProviderConfig(value: unknown): { api_key?: string; base_url?: }; } -function parseModels(value: unknown): KloProjectLlmConfig['models'] { +function parseModels(value: unknown): KtxProjectLlmConfig['models'] { if (!isRecord(value)) { return {}; } - const models: KloProjectLlmConfig['models'] = {}; + const models: KtxProjectLlmConfig['models'] = {}; for (const [role, model] of Object.entries(value)) { const modelName = optionalNonEmptyString(model); if (modelName) { - models[role as KloModelRole] = modelName; + models[role as KtxModelRole] = modelName; } } return models; } -function promptCacheTtl(value: unknown): KloPromptCacheTtl | undefined { +function promptCacheTtl(value: unknown): KtxPromptCacheTtl | undefined { return value === '5m' || value === '1h' ? value : undefined; } -function parsePromptCaching(value: unknown): KloProjectPromptCachingConfig | undefined { +function parsePromptCaching(value: unknown): KtxProjectPromptCachingConfig | undefined { if (!isRecord(value)) { return undefined; } @@ -274,9 +274,9 @@ function parsePromptCaching(value: unknown): KloProjectPromptCachingConfig | und function parseProjectLlmProviderConfig( raw: Record, - defaults: KloProjectLlmProviderConfig, + defaults: KtxProjectLlmProviderConfig, section: string, -): KloProjectLlmProviderConfig { +): KtxProjectLlmProviderConfig { rejectLegacyProvider(section, raw.provider); const vertex = isRecord(raw.vertex) @@ -296,7 +296,7 @@ function parseProjectLlmProviderConfig( }; } -function parseProjectLlmConfig(raw: Record, defaults: KloProjectLlmConfig): KloProjectLlmConfig { +function parseProjectLlmConfig(raw: Record, defaults: KtxProjectLlmConfig): KtxProjectLlmConfig { const provider = isRecord(raw.provider) ? raw.provider : {}; return { provider: parseProjectLlmProviderConfig(provider, defaults.provider, 'llm.provider'), @@ -307,9 +307,9 @@ function parseProjectLlmConfig(raw: Record, defaults: KloProjec function parseProjectEmbeddingConfig( raw: Record, - defaults: KloProjectEmbeddingConfig, + defaults: KtxProjectEmbeddingConfig, section: string, -): KloProjectEmbeddingConfig { +): KtxProjectEmbeddingConfig { rejectLegacyProvider(section, raw.provider); const openai = optionalProviderConfig(raw.openai); @@ -338,8 +338,8 @@ function parseProjectEmbeddingConfig( function parseScanRelationshipConfig( raw: Record, - defaults: KloScanRelationshipConfig, -): KloScanRelationshipConfig { + defaults: KtxScanRelationshipConfig, +): KtxScanRelationshipConfig { const validationBudget = validationBudgetConfigValue( raw.validation_budget ?? raw.validationBudget, defaults.validationBudget, @@ -380,8 +380,8 @@ function workUnitFailureMode(value: unknown, fallback: 'abort' | 'continue'): 'a function parseIngestWorkUnitsConfig( raw: Record, - defaults: KloIngestWorkUnitsConfig, -): KloIngestWorkUnitsConfig { + defaults: KtxIngestWorkUnitsConfig, +): KtxIngestWorkUnitsConfig { return { stepBudget: positiveIntegerConfigValue(raw.stepBudget, defaults.stepBudget), maxConcurrency: positiveIntegerConfigValue(raw.maxConcurrency, defaults.maxConcurrency), @@ -389,7 +389,7 @@ function parseIngestWorkUnitsConfig( }; } -export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloProjectConfig { +export function buildDefaultKtxProjectConfig(projectName = 'ktx-project'): KtxProjectConfig { return { project: projectName, connections: {}, @@ -398,7 +398,7 @@ export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloPr search: 'sqlite-fts5', git: { auto_commit: true, - author: 'klo ', + author: 'ktx ', }, }, llm: { @@ -449,18 +449,18 @@ export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloPr }; } -export function parseKloProjectConfig(raw: string): KloProjectConfig { +export function parseKtxProjectConfig(raw: string): KtxProjectConfig { const parsed = YAML.parse(raw) as unknown; if (!isRecord(parsed)) { - throw new Error('klo.yaml must contain a YAML object'); + throw new Error('ktx.yaml must contain a YAML object'); } const project = parsed.project; if (typeof project !== 'string' || project.trim().length === 0) { - throw new Error('klo.yaml field "project" is required'); + throw new Error('ktx.yaml field "project" is required'); } - const defaults = buildDefaultKloProjectConfig(project.trim()); + const defaults = buildDefaultKtxProjectConfig(project.trim()); const llm = isRecord(parsed.llm) ? parsed.llm : {}; const storage = isRecord(parsed.storage) ? parsed.storage : {}; const storageGit = isRecord(storage.git) ? storage.git : {}; @@ -496,7 +496,7 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig { defaults.ingest.embeddings, 'scan.enrichment.embeddings', ); - const parsedScanEnrichment: KloScanEnrichmentConfig = { + const parsedScanEnrichment: KtxScanEnrichmentConfig = { mode: scanEnrichmentMode(scanEnrichment.mode, defaults.scan.enrichment.mode), ...(isRecord(scanEnrichment.embeddings) ? { embeddings: scanEmbeddings } : {}), }; @@ -513,7 +513,7 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig { } : {}), connections: isRecord(parsed.connections) - ? (parsed.connections as Record) + ? (parsed.connections as Record) : defaults.connections, storage: { state: storage.state === 'sqlite' ? 'sqlite' : defaults.storage.state, @@ -546,6 +546,6 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig { }; } -export function serializeKloProjectConfig(config: KloProjectConfig): string { +export function serializeKtxProjectConfig(config: KtxProjectConfig): string { return `${YAML.stringify(config, { indent: 2, lineWidth: 0 }).trimEnd()}\n`; } diff --git a/packages/context/src/project/index.ts b/packages/context/src/project/index.ts index 71750f02..651e9fb8 100644 --- a/packages/context/src/project/index.ts +++ b/packages/context/src/project/index.ts @@ -1,15 +1,15 @@ export type { - KloProjectConfig, - KloProjectConnectionConfig, - KloProjectEmbeddingConfig, - KloProjectLlmConfig, - KloSearchBackend, - KloStorageState, + KtxProjectConfig, + KtxProjectConnectionConfig, + KtxProjectEmbeddingConfig, + KtxProjectLlmConfig, + KtxSearchBackend, + KtxStorageState, } from './config.js'; -export { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js'; +export { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js'; export type { LocalGitFileStoreDeps } from './local-git-file-store.js'; export { LocalGitFileStore } from './local-git-file-store.js'; -export { kloLocalStateDbPath } from './local-state-db.js'; +export { ktxLocalStateDbPath } from './local-state-db.js'; export type { ConnectionMappingBootstrap, LookerMappingBootstrap, @@ -22,12 +22,12 @@ export { parseLookmlMappingBootstrap, parseMetabaseMappingBootstrap, } from './mappings-yaml-schema.js'; -export type { InitKloProjectOptions, InitKloProjectResult, KloLocalProject, LoadKloProjectOptions } from './project.js'; -export { initKloProject, loadKloProject } from './project.js'; -export type { KloSetupStep } from './setup-config.js'; +export type { InitKtxProjectOptions, InitKtxProjectResult, KtxLocalProject, LoadKtxProjectOptions } from './project.js'; +export { initKtxProject, loadKtxProject } from './project.js'; +export type { KtxSetupStep } from './setup-config.js'; export { - KLO_SETUP_STEPS, - markKloSetupStepComplete, - mergeKloSetupGitignoreEntries, - setKloSetupDatabaseConnectionIds, + KTX_SETUP_STEPS, + markKtxSetupStepComplete, + mergeKtxSetupGitignoreEntries, + setKtxSetupDatabaseConnectionIds, } from './setup-config.js'; diff --git a/packages/context/src/project/local-git-file-store.test.ts b/packages/context/src/project/local-git-file-store.test.ts index 58bdff10..94085488 100644 --- a/packages/context/src/project/local-git-file-store.test.ts +++ b/packages/context/src/project/local-git-file-store.test.ts @@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { GitService, type KloCoreConfig } from '../core/index.js'; +import { GitService, type KtxCoreConfig } from '../core/index.js'; import { LocalGitFileStore } from './local-git-file-store.js'; describe('LocalGitFileStore', () => { @@ -10,15 +10,15 @@ describe('LocalGitFileStore', () => { let store: LocalGitFileStore; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-store-')); - const coreConfig: KloCoreConfig = { + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-store-')); + const coreConfig: KtxCoreConfig = { storage: { configDir: tempDir, homeDir: tempDir }, git: { - userName: 'klo', - userEmail: 'klo@example.com', + userName: 'ktx', + userEmail: 'ktx@example.com', bootstrapMessage: 'Initialize test project', - bootstrapAuthor: 'klo', - bootstrapAuthorEmail: 'klo@example.com', + bootstrapAuthor: 'ktx', + bootstrapAuthorEmail: 'ktx@example.com', }, }; const git = new GitService(coreConfig); diff --git a/packages/context/src/project/local-git-file-store.ts b/packages/context/src/project/local-git-file-store.ts index 969b079b..8d1bd065 100644 --- a/packages/context/src/project/local-git-file-store.ts +++ b/packages/context/src/project/local-git-file-store.ts @@ -3,11 +3,11 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'; import type { GitCommitInfo, GitService, - KloFileHistoryEntry, - KloFileListResult, - KloFileReadResult, - KloFileStorePort, - KloFileWriteResult, + KtxFileHistoryEntry, + KtxFileListResult, + KtxFileReadResult, + KtxFileStorePort, + KtxFileWriteResult, } from '../core/index.js'; export interface LocalGitFileStoreDeps { @@ -19,7 +19,7 @@ function normalizeRelativePath(filePath: string): string { return filePath.replaceAll('\\', '/').replace(/^\.\/+/, ''); } -function gitInfoToWriteResult(info: GitCommitInfo): KloFileWriteResult { +function gitInfoToWriteResult(info: GitCommitInfo): KtxFileWriteResult { return { success: true, commitHash: info.commitHash, @@ -31,7 +31,7 @@ function gitInfoToWriteResult(info: GitCommitInfo): KloFileWriteResult { }; } -export class LocalGitFileStore implements KloFileStorePort { +export class LocalGitFileStore implements KtxFileStorePort { private readonly rootDir: string; private readonly git: GitService; @@ -51,7 +51,7 @@ export class LocalGitFileStore implements KloFileStorePort { authorEmail: string, commitMessage: string, options?: { skipLock?: boolean }, - ): Promise { + ): Promise { const relativePath = this.safeRelativePath(path); const absolutePath = this.absolutePath(relativePath); await fs.mkdir(dirname(absolutePath), { recursive: true }); @@ -65,7 +65,7 @@ export class LocalGitFileStore implements KloFileStorePort { return { ...gitInfoToWriteResult(info), path: relativePath, operation: 'write' }; } - async readFile(path: string): Promise { + async readFile(path: string): Promise { const relativePath = this.safeRelativePath(path); const absolutePath = this.absolutePath(relativePath); const content = await fs.readFile(absolutePath, 'utf-8'); @@ -84,7 +84,7 @@ export class LocalGitFileStore implements KloFileStorePort { authorEmail: string, commitMessage: string, options?: { skipLock?: boolean }, - ): Promise { + ): Promise { const relativePath = this.safeRelativePath(path); const absolutePath = this.absolutePath(relativePath); try { @@ -103,7 +103,7 @@ export class LocalGitFileStore implements KloFileStorePort { return { ...gitInfoToWriteResult(info), path: relativePath, operation: 'delete' }; } - async listFiles(path = '', stripPrefix = false): Promise { + async listFiles(path = '', stripPrefix = false): Promise { const relativePath = path ? this.safeRelativePath(path) : ''; const searchRoot = relativePath ? this.absolutePath(relativePath) : this.rootDir; let files: string[]; @@ -121,14 +121,14 @@ export class LocalGitFileStore implements KloFileStorePort { const relativeFiles = files .map((file) => normalizeRelativePath(relative(this.rootDir, file))) .filter((file) => !file.startsWith('.git/') && !file.includes('/.git/')) - .filter((file) => !file.startsWith('.klo/cache/')) + .filter((file) => !file.startsWith('.ktx/cache/')) .map((file) => (stripPrefix && prefix && file.startsWith(prefix) ? file.slice(prefix.length) : file)) .sort(); return { files: relativeFiles }; } - async getFileHistory(path: string): Promise { + async getFileHistory(path: string): Promise { const relativePath = this.safeRelativePath(path); const history = await this.git.getFileHistory(relativePath); return history.map((entry) => ({ diff --git a/packages/context/src/project/local-state-db.ts b/packages/context/src/project/local-state-db.ts index f2155780..24f829f9 100644 --- a/packages/context/src/project/local-state-db.ts +++ b/packages/context/src/project/local-state-db.ts @@ -1,6 +1,6 @@ import { join } from 'node:path'; -import type { KloLocalProject } from './project.js'; +import type { KtxLocalProject } from './project.js'; -export function kloLocalStateDbPath(project: Pick): string { - return join(project.projectDir, '.klo', 'db.sqlite'); +export function ktxLocalStateDbPath(project: Pick): string { + return join(project.projectDir, '.ktx', 'db.sqlite'); } diff --git a/packages/context/src/project/mappings-yaml-schema.test.ts b/packages/context/src/project/mappings-yaml-schema.test.ts index 6ba31822..5497bba9 100644 --- a/packages/context/src/project/mappings-yaml-schema.test.ts +++ b/packages/context/src/project/mappings-yaml-schema.test.ts @@ -6,7 +6,7 @@ import { parseMetabaseMappingBootstrap, } from './mappings-yaml-schema.js'; -describe('klo.yaml mapping bootstrap schema', () => { +describe('ktx.yaml mapping bootstrap schema', () => { it('parses Metabase mapping intent with CLI syncMode default ALL', () => { const bootstrap = parseMetabaseMappingBootstrap('prod-metabase', { driver: 'metabase', @@ -14,7 +14,7 @@ describe('klo.yaml mapping bootstrap schema', () => { databaseMappings: { '1': 'prod-warehouse', '2': null }, syncEnabled: { '1': true, '2': false }, selections: { collections: [12], items: [345] }, - defaultTagNames: ['klo', 'prod'], + defaultTagNames: ['ktx', 'prod'], }, }); @@ -25,7 +25,7 @@ describe('klo.yaml mapping bootstrap schema', () => { syncEnabled: { '1': true, '2': false }, syncMode: 'ALL', selections: { collections: [12], items: [345] }, - defaultTagNames: ['klo', 'prod'], + defaultTagNames: ['ktx', 'prod'], }); }); diff --git a/packages/context/src/project/mappings-yaml-schema.ts b/packages/context/src/project/mappings-yaml-schema.ts index 47e53f53..67f14627 100644 --- a/packages/context/src/project/mappings-yaml-schema.ts +++ b/packages/context/src/project/mappings-yaml-schema.ts @@ -1,5 +1,5 @@ import * as z from 'zod'; -import type { KloProjectConnectionConfig } from './config.js'; +import type { KtxProjectConnectionConfig } from './config.js'; const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']); const positiveIntegerValueSchema = z.number().int().positive(); @@ -66,13 +66,13 @@ function assertPositiveIntegerKeys(field: string, record: Record { +describe('KTX local project runtime', () => { let tempDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-project-runtime-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-project-runtime-')); }); afterEach(async () => { @@ -18,7 +18,7 @@ describe('KLO local project runtime', () => { it('initializes the standalone project layout and commits it', async () => { const projectDir = join(tempDir, 'warehouse'); - const result = await initKloProject({ + const result = await initKtxProject({ projectDir, projectName: 'warehouse', authorName: 'Agent', @@ -28,8 +28,8 @@ describe('KLO local project runtime', () => { expect(result.projectDir).toBe(projectDir); expect(result.config.project).toBe('warehouse'); expect(result.commitHash).toMatch(/^[0-9a-f]{40}$/); - await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); - const gitignore = await readFile(join(projectDir, '.klo/.gitignore'), 'utf-8'); + await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse'); + const gitignore = await readFile(join(projectDir, '.ktx/.gitignore'), 'utf-8'); expect(gitignore).toContain('cache/'); expect(gitignore).toContain('db.sqlite'); expect(gitignore).toContain('secrets/'); @@ -44,9 +44,9 @@ describe('KLO local project runtime', () => { it('loads an initialized project with a working file store', async () => { const projectDir = join(tempDir, 'warehouse'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); - const loaded = await loadKloProject({ projectDir }); + const loaded = await loadKtxProject({ projectDir }); await loaded.fileStore.writeFile( 'knowledge/global/revenue.md', '# Revenue\n', @@ -63,13 +63,13 @@ describe('KLO local project runtime', () => { it('rejects reinitializing an existing project unless force is set', async () => { const projectDir = join(tempDir, 'warehouse'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); - await expect(initKloProject({ projectDir, projectName: 'warehouse' })).rejects.toThrow( - 'Project already contains klo.yaml', + await expect(initKtxProject({ projectDir, projectName: 'warehouse' })).rejects.toThrow( + 'Project already contains ktx.yaml', ); - await expect(initKloProject({ projectDir, projectName: 'warehouse-v2', force: true })).resolves.toMatchObject({ + await expect(initKtxProject({ projectDir, projectName: 'warehouse-v2', force: true })).resolves.toMatchObject({ config: { project: 'warehouse-v2', }, diff --git a/packages/context/src/project/project.ts b/packages/context/src/project/project.ts index bec915c1..cbe522a5 100644 --- a/packages/context/src/project/project.ts +++ b/packages/context/src/project/project.ts @@ -1,59 +1,59 @@ import { promises as fs } from 'node:fs'; import { basename, dirname, join, resolve } from 'node:path'; -import { GitService, type KloCoreConfig, type KloLogger, noopLogger } from '../core/index.js'; -import type { KloProjectConfig } from './config.js'; -import { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js'; +import { GitService, type KtxCoreConfig, type KtxLogger, noopLogger } from '../core/index.js'; +import type { KtxProjectConfig } from './config.js'; +import { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js'; import { LocalGitFileStore } from './local-git-file-store.js'; -export interface InitKloProjectOptions { +export interface InitKtxProjectOptions { projectDir: string; projectName?: string; force?: boolean; authorName?: string; authorEmail?: string; - logger?: KloLogger; + logger?: KtxLogger; } -export interface LoadKloProjectOptions { +export interface LoadKtxProjectOptions { projectDir: string; authorName?: string; authorEmail?: string; - logger?: KloLogger; + logger?: KtxLogger; } -export interface KloLocalProject { +export interface KtxLocalProject { projectDir: string; configPath: string; - config: KloProjectConfig; - coreConfig: KloCoreConfig; + config: KtxProjectConfig; + coreConfig: KtxCoreConfig; git: GitService; fileStore: LocalGitFileStore; } -export interface InitKloProjectResult extends KloLocalProject { +export interface InitKtxProjectResult extends KtxLocalProject { commitHash: string | null; } const TRACKED_SCAFFOLD_FILES: Array<{ path: string; content: string }> = [ - { path: '.klo/.gitignore', content: 'cache/\ndb.sqlite\nsecrets/\nsetup/\nagents/\n' }, - { path: '.klo/prompts/.gitkeep', content: '' }, - { path: '.klo/skills/.gitkeep', content: '' }, + { path: '.ktx/.gitignore', content: 'cache/\ndb.sqlite\nsecrets/\nsetup/\nagents/\n' }, + { path: '.ktx/prompts/.gitkeep', content: '' }, + { path: '.ktx/skills/.gitkeep', content: '' }, { path: 'knowledge/global/.gitkeep', content: '' }, { path: 'semantic-layer/.gitkeep', content: '' }, { path: 'raw-sources/.gitkeep', content: '' }, ]; -function createCoreConfig(projectDir: string, authorName: string, authorEmail: string): KloCoreConfig { +function createCoreConfig(projectDir: string, authorName: string, authorEmail: string): KtxCoreConfig { return { storage: { configDir: projectDir, homeDir: dirname(projectDir), - worktreesDir: join(projectDir, '.klo/worktrees'), + worktreesDir: join(projectDir, '.ktx/worktrees'), }, git: { userName: authorName, userEmail: authorEmail, - bootstrapMessage: 'Initialize klo project repository', + bootstrapMessage: 'Initialize ktx project repository', bootstrapAuthor: authorName, bootstrapAuthorEmail: authorEmail, }, @@ -77,18 +77,18 @@ async function writeProjectFile(projectDir: string, relativePath: string, conten async function createRuntime( projectDir: string, - config: KloProjectConfig, + config: KtxProjectConfig, authorName: string, authorEmail: string, - logger: KloLogger, -): Promise { + logger: KtxLogger, +): Promise { const coreConfig = createCoreConfig(projectDir, authorName, authorEmail); const git = new GitService(coreConfig, logger); await git.onModuleInit(); return { projectDir, - configPath: join(projectDir, 'klo.yaml'), + configPath: join(projectDir, 'ktx.yaml'), config, coreConfig, git, @@ -96,31 +96,31 @@ async function createRuntime( }; } -export async function initKloProject(options: InitKloProjectOptions): Promise { +export async function initKtxProject(options: InitKtxProjectOptions): Promise { const projectDir = resolve(options.projectDir); - const projectName = options.projectName?.trim() || basename(projectDir) || 'klo-project'; - const authorName = options.authorName ?? 'klo'; - const authorEmail = options.authorEmail ?? 'klo@example.com'; + const projectName = options.projectName?.trim() || basename(projectDir) || 'ktx-project'; + const authorName = options.authorName ?? 'ktx'; + const authorEmail = options.authorEmail ?? 'ktx@example.com'; const logger = options.logger ?? noopLogger; - const configPath = join(projectDir, 'klo.yaml'); + const configPath = join(projectDir, 'ktx.yaml'); await fs.mkdir(projectDir, { recursive: true }); if (!options.force && (await fileExists(configPath))) { - throw new Error(`Project already contains klo.yaml: ${configPath}`); + throw new Error(`Project already contains ktx.yaml: ${configPath}`); } - const config = buildDefaultKloProjectConfig(projectName); + const config = buildDefaultKtxProjectConfig(projectName); const runtime = await createRuntime(projectDir, config, authorName, authorEmail, logger); - await writeProjectFile(projectDir, 'klo.yaml', serializeKloProjectConfig(config)); - await fs.mkdir(join(projectDir, '.klo/cache'), { recursive: true }); + await writeProjectFile(projectDir, 'ktx.yaml', serializeKtxProjectConfig(config)); + await fs.mkdir(join(projectDir, '.ktx/cache'), { recursive: true }); for (const file of TRACKED_SCAFFOLD_FILES) { await writeProjectFile(projectDir, file.path, file.content); } const commit = await runtime.git.commitFiles( - ['klo.yaml', ...TRACKED_SCAFFOLD_FILES.map((file) => file.path)], - `Initialize KLO project: ${projectName}`, + ['ktx.yaml', ...TRACKED_SCAFFOLD_FILES.map((file) => file.path)], + `Initialize KTX project: ${projectName}`, authorName, authorEmail, ); @@ -131,13 +131,13 @@ export async function initKloProject(options: InitKloProjectOptions): Promise { +export async function loadKtxProject(options: LoadKtxProjectOptions): Promise { const projectDir = resolve(options.projectDir); - const authorName = options.authorName ?? 'klo'; - const authorEmail = options.authorEmail ?? 'klo@example.com'; + const authorName = options.authorName ?? 'ktx'; + const authorEmail = options.authorEmail ?? 'ktx@example.com'; const logger = options.logger ?? noopLogger; - const configPath = join(projectDir, 'klo.yaml'); + const configPath = join(projectDir, 'ktx.yaml'); const raw = await fs.readFile(configPath, 'utf-8'); - const config = parseKloProjectConfig(raw); + const config = parseKtxProjectConfig(raw); return createRuntime(projectDir, config, authorName, authorEmail, logger); } diff --git a/packages/context/src/project/setup-config.test.ts b/packages/context/src/project/setup-config.test.ts index f5f9e016..3fc8726b 100644 --- a/packages/context/src/project/setup-config.test.ts +++ b/packages/context/src/project/setup-config.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest'; -import { buildDefaultKloProjectConfig } from './config.js'; +import { buildDefaultKtxProjectConfig } from './config.js'; import { - markKloSetupStepComplete, - mergeKloSetupGitignoreEntries, - setKloSetupDatabaseConnectionIds, + markKtxSetupStepComplete, + mergeKtxSetupGitignoreEntries, + setKtxSetupDatabaseConnectionIds, } from './setup-config.js'; -describe('KLO setup config helpers', () => { +describe('KTX setup config helpers', () => { it('marks setup steps complete without duplicating existing state', () => { - const config = buildDefaultKloProjectConfig('warehouse'); + const config = buildDefaultKtxProjectConfig('warehouse'); - const withProject = markKloSetupStepComplete(config, 'project'); - const withProjectAgain = markKloSetupStepComplete(withProject, 'project'); - const withLlm = markKloSetupStepComplete(withProjectAgain, 'llm'); - const withContext = markKloSetupStepComplete(withLlm, 'context'); + const withProject = markKtxSetupStepComplete(config, 'project'); + const withProjectAgain = markKtxSetupStepComplete(withProject, 'project'); + const withLlm = markKtxSetupStepComplete(withProjectAgain, 'llm'); + const withContext = markKtxSetupStepComplete(withLlm, 'context'); expect(withProject.setup).toEqual({ database_connection_ids: [], @@ -27,23 +27,23 @@ describe('KLO setup config helpers', () => { it('preserves database connection ids while marking a step complete', () => { const config = { - ...buildDefaultKloProjectConfig('warehouse'), + ...buildDefaultKtxProjectConfig('warehouse'), setup: { database_connection_ids: ['warehouse'], completed_steps: ['databases'], }, }; - expect(markKloSetupStepComplete(config, 'project').setup).toEqual({ + expect(markKtxSetupStepComplete(config, 'project').setup).toEqual({ database_connection_ids: ['warehouse'], completed_steps: ['databases', 'project'], }); }); it('sets setup database connection ids without duplicates', () => { - const config = buildDefaultKloProjectConfig('warehouse'); + const config = buildDefaultKtxProjectConfig('warehouse'); - const withDatabases = setKloSetupDatabaseConnectionIds(config, ['warehouse', 'analytics', 'warehouse']); + const withDatabases = setKtxSetupDatabaseConnectionIds(config, ['warehouse', 'analytics', 'warehouse']); expect(withDatabases.setup).toEqual({ database_connection_ids: ['warehouse', 'analytics'], @@ -53,10 +53,10 @@ describe('KLO setup config helpers', () => { }); it('marks databases complete only when requested', () => { - const config = markKloSetupStepComplete(buildDefaultKloProjectConfig('warehouse'), 'project'); + const config = markKtxSetupStepComplete(buildDefaultKtxProjectConfig('warehouse'), 'project'); - const withDatabases = setKloSetupDatabaseConnectionIds(config, ['warehouse'], { complete: true }); - const withDatabasesAgain = setKloSetupDatabaseConnectionIds(withDatabases, ['warehouse'], { complete: true }); + const withDatabases = setKtxSetupDatabaseConnectionIds(config, ['warehouse'], { complete: true }); + const withDatabasesAgain = setKtxSetupDatabaseConnectionIds(withDatabases, ['warehouse'], { complete: true }); expect(withDatabases.setup).toEqual({ database_connection_ids: ['warehouse'], @@ -66,10 +66,10 @@ describe('KLO setup config helpers', () => { }); it('merges setup-local gitignore entries without removing existing lines', () => { - expect(mergeKloSetupGitignoreEntries('cache/\ndb.sqlite\n')).toBe( + expect(mergeKtxSetupGitignoreEntries('cache/\ndb.sqlite\n')).toBe( ['cache/', 'db.sqlite', 'secrets/', 'setup/', 'agents/', ''].join('\n'), ); - expect(mergeKloSetupGitignoreEntries('cache/\nsecrets/\n')).toBe( + expect(mergeKtxSetupGitignoreEntries('cache/\nsecrets/\n')).toBe( ['cache/', 'secrets/', 'setup/', 'agents/', ''].join('\n'), ); }); diff --git a/packages/context/src/project/setup-config.ts b/packages/context/src/project/setup-config.ts index c916a9b7..d0f46cf0 100644 --- a/packages/context/src/project/setup-config.ts +++ b/packages/context/src/project/setup-config.ts @@ -1,12 +1,12 @@ -import type { KloProjectConfig } from './config.js'; +import type { KtxProjectConfig } from './config.js'; -export const KLO_SETUP_STEPS = ['project', 'llm', 'embeddings', 'databases', 'sources', 'context', 'agents'] as const; +export const KTX_SETUP_STEPS = ['project', 'llm', 'embeddings', 'databases', 'sources', 'context', 'agents'] as const; -export type KloSetupStep = (typeof KLO_SETUP_STEPS)[number]; +export type KtxSetupStep = (typeof KTX_SETUP_STEPS)[number]; const SETUP_GITIGNORE_ENTRIES = ['secrets/', 'setup/', 'agents/'] as const; -export function markKloSetupStepComplete(config: KloProjectConfig, step: KloSetupStep): KloProjectConfig { +export function markKtxSetupStepComplete(config: KtxProjectConfig, step: KtxSetupStep): KtxProjectConfig { const databaseConnectionIds = config.setup?.database_connection_ids ?? []; const completedSteps = config.setup?.completed_steps ?? []; return { @@ -18,11 +18,11 @@ export function markKloSetupStepComplete(config: KloProjectConfig, step: KloSetu }; } -export function setKloSetupDatabaseConnectionIds( - config: KloProjectConfig, +export function setKtxSetupDatabaseConnectionIds( + config: KtxProjectConfig, connectionIds: string[], options: { complete?: boolean } = {}, -): KloProjectConfig { +): KtxProjectConfig { const uniqueConnectionIds = [...new Set(connectionIds.filter((connectionId) => connectionId.trim().length > 0))]; const completedSteps = config.setup?.completed_steps ?? []; const nextCompletedSteps = @@ -39,7 +39,7 @@ export function setKloSetupDatabaseConnectionIds( }; } -export function mergeKloSetupGitignoreEntries(content: string): string { +export function mergeKtxSetupGitignoreEntries(content: string): string { const lines = content .split(/\r?\n/) .map((line) => line.trimEnd()) diff --git a/packages/context/src/prompts/prompt.service.test.ts b/packages/context/src/prompts/prompt.service.test.ts index 78ec979b..046b777b 100644 --- a/packages/context/src/prompts/prompt.service.test.ts +++ b/packages/context/src/prompts/prompt.service.test.ts @@ -8,7 +8,7 @@ describe('PromptService', () => { let dir: string; beforeEach(async () => { - dir = await mkdtemp(join(tmpdir(), 'klo-prompts-')); + dir = await mkdtemp(join(tmpdir(), 'ktx-prompts-')); }); afterEach(async () => { @@ -23,7 +23,7 @@ describe('PromptService', () => { }); it('loads prompts from additional directories when the primary directory misses', async () => { - const extraDir = await mkdtemp(join(tmpdir(), 'klo-prompts-extra-')); + const extraDir = await mkdtemp(join(tmpdir(), 'ktx-prompts-extra-')); try { await writeFile(join(extraDir, 'memory_agent_research.md'), 'Packaged memory prompt', 'utf-8'); const service = new PromptService({ promptsDir: dir, additionalPromptDirs: [extraDir], partials: [] }); diff --git a/packages/context/src/prompts/prompt.service.ts b/packages/context/src/prompts/prompt.service.ts index f9939b97..f351a70d 100644 --- a/packages/context/src/prompts/prompt.service.ts +++ b/packages/context/src/prompts/prompt.service.ts @@ -1,7 +1,7 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import Handlebars from 'handlebars'; -import { type KloLogger, noopLogger } from '../core/index.js'; +import { type KtxLogger, noopLogger } from '../core/index.js'; export interface PromptContext { current_date?: string; @@ -18,11 +18,11 @@ export interface PromptServiceOptions { additionalPromptDirs?: string[]; defaultSettings?: Record; partials?: string[]; - logger?: KloLogger; + logger?: KtxLogger; } export class PromptService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private readonly partials: string[]; private partialsRegistered = false; diff --git a/packages/context/src/scan/credentials.test.ts b/packages/context/src/scan/credentials.test.ts index 1a8ae1a7..60b6c2e9 100644 --- a/packages/context/src/scan/credentials.test.ts +++ b/packages/context/src/scan/credentials.test.ts @@ -1,26 +1,26 @@ import { describe, expect, it } from 'vitest'; import { - REDACTED_KLO_CREDENTIAL_VALUE, - redactKloCredentialEnvelope, - redactKloCredentialValue, - redactKloScanMetadata, - redactKloScanReport, - redactKloScanWarning, + REDACTED_KTX_CREDENTIAL_VALUE, + redactKtxCredentialEnvelope, + redactKtxCredentialValue, + redactKtxScanMetadata, + redactKtxScanReport, + redactKtxScanWarning, } from './credentials.js'; -import type { KloCredentialEnvelope, KloScanReport, KloScanWarning } from './types.js'; +import type { KtxCredentialEnvelope, KtxScanReport, KtxScanWarning } from './types.js'; -describe('KLO scan credential redaction', () => { +describe('KTX scan credential redaction', () => { it('keeps credential references inspectable', () => { - const envReference: KloCredentialEnvelope = { kind: 'env', name: 'DATABASE_URL' }; - const fileReference: KloCredentialEnvelope = { kind: 'file', path: '~/.config/klo/warehouse' }; + const envReference: KtxCredentialEnvelope = { kind: 'env', name: 'DATABASE_URL' }; + const fileReference: KtxCredentialEnvelope = { kind: 'file', path: '~/.config/ktx/warehouse' }; - expect(redactKloCredentialEnvelope(envReference)).toEqual(envReference); - expect(redactKloCredentialEnvelope(fileReference)).toEqual(fileReference); + expect(redactKtxCredentialEnvelope(envReference)).toEqual(envReference); + expect(redactKtxCredentialEnvelope(fileReference)).toEqual(fileReference); }); it('redacts resolved credential envelope values recursively', () => { expect( - redactKloCredentialEnvelope({ + redactKtxCredentialEnvelope({ kind: 'resolved', source: 'host', values: { @@ -39,19 +39,19 @@ describe('KLO scan credential redaction', () => { redacted: true, values: { username: 'readonly', - password: REDACTED_KLO_CREDENTIAL_VALUE, + password: REDACTED_KTX_CREDENTIAL_VALUE, nested: { - api_key: REDACTED_KLO_CREDENTIAL_VALUE, + api_key: REDACTED_KTX_CREDENTIAL_VALUE, warehouse: 'compute_wh', }, - headers: [{ authorizationToken: REDACTED_KLO_CREDENTIAL_VALUE }, { label: 'safe' }], + headers: [{ authorizationToken: REDACTED_KTX_CREDENTIAL_VALUE }, { label: 'safe' }], }, }); }); it('redacts scan metadata fields that commonly contain secrets', () => { expect( - redactKloScanMetadata({ + redactKtxScanMetadata({ driver: 'postgres', url: 'postgres://user:pass@example.test/db', // pragma: allowlist secret serviceAccountJson: { @@ -62,17 +62,17 @@ describe('KLO scan credential redaction', () => { }), ).toEqual({ driver: 'postgres', - url: REDACTED_KLO_CREDENTIAL_VALUE, + url: REDACTED_KTX_CREDENTIAL_VALUE, serviceAccountJson: { client_email: 'reader@example.test', - private_key: REDACTED_KLO_CREDENTIAL_VALUE, + private_key: REDACTED_KTX_CREDENTIAL_VALUE, }, safeCount: 3, }); }); it('redacts scan warning messages and metadata without hiding safe context', () => { - const warning: KloScanWarning = { + const warning: KtxScanWarning = { code: 'sampling_failed', message: 'sample failed for postgres://reader:secret@example.test/db', // pragma: allowlist secret recoverable: true, @@ -86,15 +86,15 @@ describe('KLO scan credential redaction', () => { }, }; - expect(redactKloScanWarning(warning)).toEqual({ + expect(redactKtxScanWarning(warning)).toEqual({ code: 'sampling_failed', message: 'sample failed for postgres://reader:@example.test/db', recoverable: true, metadata: { table: 'orders', - url: REDACTED_KLO_CREDENTIAL_VALUE, + url: REDACTED_KTX_CREDENTIAL_VALUE, nested: { - api_key: REDACTED_KLO_CREDENTIAL_VALUE, + api_key: REDACTED_KTX_CREDENTIAL_VALUE, schema: 'public', }, }, @@ -102,7 +102,7 @@ describe('KLO scan credential redaction', () => { }); it('redacts scan report warning metadata recursively', () => { - const report: KloScanReport = { + const report: KtxScanReport = { connectionId: 'warehouse', driver: 'postgres', syncId: 'sync-1', @@ -164,10 +164,10 @@ describe('KLO scan credential redaction', () => { createdAt: '2026-04-29T00:00:00.000Z', }; - const redacted = redactKloScanReport(report); + const redacted = redactKtxScanReport(report); expect(redacted.warnings[0]?.metadata).toEqual({ - credentials_json: REDACTED_KLO_CREDENTIAL_VALUE, + credentials_json: REDACTED_KTX_CREDENTIAL_VALUE, safeCount: 2, }); expect(report.warnings[0]?.metadata).toEqual({ @@ -177,7 +177,7 @@ describe('KLO scan credential redaction', () => { }); it('redacts standalone primitive credential values only when the field key is sensitive', () => { - expect(redactKloCredentialValue('password', 'abc')).toBe(REDACTED_KLO_CREDENTIAL_VALUE); - expect(redactKloCredentialValue('schema', 'public')).toBe('public'); + expect(redactKtxCredentialValue('password', 'abc')).toBe(REDACTED_KTX_CREDENTIAL_VALUE); + expect(redactKtxCredentialValue('schema', 'public')).toBe('public'); }); }); diff --git a/packages/context/src/scan/credentials.ts b/packages/context/src/scan/credentials.ts index bd75332a..23fd634e 100644 --- a/packages/context/src/scan/credentials.ts +++ b/packages/context/src/scan/credentials.ts @@ -1,22 +1,22 @@ import { - redactKloSensitiveMetadata, - redactKloSensitiveText, - redactKloSensitiveValue, - REDACTED_KLO_CREDENTIAL_VALUE, + redactKtxSensitiveMetadata, + redactKtxSensitiveText, + redactKtxSensitiveValue, + REDACTED_KTX_CREDENTIAL_VALUE, } from '../core/redaction.js'; -import type { KloCredentialEnvelope, KloScanReport, KloScanWarning } from './types.js'; +import type { KtxCredentialEnvelope, KtxScanReport, KtxScanWarning } from './types.js'; -export { REDACTED_KLO_CREDENTIAL_VALUE }; +export { REDACTED_KTX_CREDENTIAL_VALUE }; -export function redactKloCredentialValue(key: string, value: unknown): unknown { - return redactKloSensitiveValue(key, value); +export function redactKtxCredentialValue(key: string, value: unknown): unknown { + return redactKtxSensitiveValue(key, value); } -export function redactKloScanMetadata(metadata: Record): Record { - return redactKloSensitiveMetadata(metadata); +export function redactKtxScanMetadata(metadata: Record): Record { + return redactKtxSensitiveMetadata(metadata); } -export function redactKloCredentialEnvelope(envelope: KloCredentialEnvelope): KloCredentialEnvelope { +export function redactKtxCredentialEnvelope(envelope: KtxCredentialEnvelope): KtxCredentialEnvelope { if (envelope.kind !== 'resolved') { return envelope; } @@ -24,27 +24,27 @@ export function redactKloCredentialEnvelope(envelope: KloCredentialEnvelope): Kl kind: 'resolved', source: envelope.source, redacted: true, - values: redactKloScanMetadata(envelope.values), + values: redactKtxScanMetadata(envelope.values), }; } -export function redactKloScanWarning(warning: KloScanWarning): KloScanWarning { +export function redactKtxScanWarning(warning: KtxScanWarning): KtxScanWarning { if (!warning.metadata) { return { ...warning, - message: redactKloSensitiveText(warning.message), + message: redactKtxSensitiveText(warning.message), }; } return { ...warning, - message: redactKloSensitiveText(warning.message), - metadata: redactKloScanMetadata(warning.metadata), + message: redactKtxSensitiveText(warning.message), + metadata: redactKtxScanMetadata(warning.metadata), }; } -export function redactKloScanReport(report: KloScanReport): KloScanReport { +export function redactKtxScanReport(report: KtxScanReport): KtxScanReport { return { ...report, - warnings: report.warnings.map((warning) => redactKloScanWarning(warning)), + warnings: report.warnings.map((warning) => redactKtxScanWarning(warning)), }; } diff --git a/packages/context/src/scan/data-dictionary.test.ts b/packages/context/src/scan/data-dictionary.test.ts index 0f5d6d5e..b8b39376 100644 --- a/packages/context/src/scan/data-dictionary.test.ts +++ b/packages/context/src/scan/data-dictionary.test.ts @@ -1,113 +1,113 @@ import { describe, expect, it } from 'vitest'; import { - defaultKloDataDictionarySettings, - isKloDataDictionaryCandidate, - shouldKloSampleColumnForDictionary, + defaultKtxDataDictionarySettings, + isKtxDataDictionaryCandidate, + shouldKtxSampleColumnForDictionary, } from './data-dictionary.js'; -const defaultPatterns = defaultKloDataDictionarySettings.excludePatterns; +const defaultPatterns = defaultKtxDataDictionarySettings.excludePatterns; -describe('KLO scan data dictionary policy', () => { +describe('KTX scan data dictionary policy', () => { it('includes text-like and boolean categorical types', () => { - expect(isKloDataDictionaryCandidate('varchar(50)', 'status', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('VARCHAR', 'category', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('text', 'region', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('string', 'payment_method', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('nvarchar(100)', 'tier', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('enum', 'status', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('boolean', 'active', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('bool', 'verified', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('character varying(50)', 'region', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('character(1)', 'flag', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('ntext', 'category', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar(50)', 'status', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('VARCHAR', 'category', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('text', 'region', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('string', 'payment_method', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('nvarchar(100)', 'tier', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('enum', 'status', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('boolean', 'active', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('bool', 'verified', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('character varying(50)', 'region', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('character(1)', 'flag', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('ntext', 'category', defaultPatterns)).toBe(true); }); it('excludes non-categorical primitive types', () => { - expect(isKloDataDictionaryCandidate('integer', 'count', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('bigint', 'total', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('timestamp', 'created', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('date', 'birth', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('numeric', 'amount', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('decimal(10,2)', 'price', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('float', 'rate', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('integer', 'count', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('bigint', 'total', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('timestamp', 'created', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('date', 'birth', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('numeric', 'amount', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('decimal(10,2)', 'price', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('float', 'rate', defaultPatterns)).toBe(false); }); it('excludes configured high-cardinality or sensitive name patterns', () => { - expect(isKloDataDictionaryCandidate('varchar', 'user_id', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'session_uuid', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'api_key', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'password_hash', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'auth_token', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'id', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'created_at', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'birth_date', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('text', 'description', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('text', 'email_body', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'image_url', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'email', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'phone_number', defaultPatterns)).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'street_address', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'user_id', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'session_uuid', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'api_key', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'password_hash', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'auth_token', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'id', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'created_at', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'birth_date', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('text', 'description', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('text', 'email_body', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'image_url', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'email', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'phone_number', defaultPatterns)).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'street_address', defaultPatterns)).toBe(false); }); it('keeps business categorical names eligible', () => { - expect(isKloDataDictionaryCandidate('varchar', 'status', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'region', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'country', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'payment_method', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'currency', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'plan', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'category', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'tier', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'gender', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'language', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'order_type', defaultPatterns)).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'order_status', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'status', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'region', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'country', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'payment_method', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'currency', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'plan', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'category', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'tier', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'gender', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'language', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'order_type', defaultPatterns)).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'order_status', defaultPatterns)).toBe(true); }); it('respects host-provided exclusion patterns and skips invalid regex patterns', () => { - expect(isKloDataDictionaryCandidate('varchar', 'company_size', ['company'])).toBe(false); - expect(isKloDataDictionaryCandidate('varchar', 'status', ['company'])).toBe(true); - expect(isKloDataDictionaryCandidate('varchar', 'status', ['[invalid', '(unclosed'])).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'company_size', ['company'])).toBe(false); + expect(isKtxDataDictionaryCandidate('varchar', 'status', ['company'])).toBe(true); + expect(isKtxDataDictionaryCandidate('varchar', 'status', ['[invalid', '(unclosed'])).toBe(true); }); it('skips columns that already have persisted dictionary state', () => { expect( - shouldKloSampleColumnForDictionary({ + shouldKtxSampleColumnForDictionary({ columnType: 'varchar', columnName: 'status', sampleValues: ['paid'], cardinality: null, - settings: defaultKloDataDictionarySettings, + settings: defaultKtxDataDictionarySettings, }), ).toEqual({ sample: false, reason: 'already_populated' }); expect( - shouldKloSampleColumnForDictionary({ + shouldKtxSampleColumnForDictionary({ columnType: 'varchar', columnName: 'empty_status', sampleValues: null, cardinality: 0, - settings: defaultKloDataDictionarySettings, + settings: defaultKtxDataDictionarySettings, }), ).toEqual({ sample: false, reason: 'empty_column' }); expect( - shouldKloSampleColumnForDictionary({ + shouldKtxSampleColumnForDictionary({ columnType: 'varchar', columnName: 'customer_name', sampleValues: null, cardinality: 300, - settings: defaultKloDataDictionarySettings, + settings: defaultKtxDataDictionarySettings, }), ).toEqual({ sample: false, reason: 'high_cardinality' }); expect( - shouldKloSampleColumnForDictionary({ + shouldKtxSampleColumnForDictionary({ columnType: 'varchar', columnName: 'status', sampleValues: null, cardinality: null, - settings: defaultKloDataDictionarySettings, + settings: defaultKtxDataDictionarySettings, }), ).toEqual({ sample: true }); }); diff --git a/packages/context/src/scan/data-dictionary.ts b/packages/context/src/scan/data-dictionary.ts index e8aff474..6c1acab7 100644 --- a/packages/context/src/scan/data-dictionary.ts +++ b/packages/context/src/scan/data-dictionary.ts @@ -1,4 +1,4 @@ -export interface KloDataDictionarySettings { +export interface KtxDataDictionarySettings { cardinalityThreshold: number; maxValuesToStore: number; sampleSize: number; @@ -6,7 +6,7 @@ export interface KloDataDictionarySettings { excludePatterns: string[]; } -export const defaultKloDataDictionarySettings: KloDataDictionarySettings = { +export const defaultKtxDataDictionarySettings: KtxDataDictionarySettings = { cardinalityThreshold: 200, maxValuesToStore: 100, sampleSize: 10000, @@ -36,31 +36,31 @@ export const defaultKloDataDictionarySettings: KloDataDictionarySettings = { ], }; -export type KloDataDictionarySkipReason = +export type KtxDataDictionarySkipReason = | 'not_candidate' | 'already_populated' | 'empty_column' | 'high_cardinality'; -export interface KloDataDictionarySampleDecision { +export interface KtxDataDictionarySampleDecision { sample: boolean; - reason?: KloDataDictionarySkipReason; + reason?: KtxDataDictionarySkipReason; } -export interface KloDataDictionaryColumnState { +export interface KtxDataDictionaryColumnState { columnType: string; columnName: string; sampleValues?: readonly string[] | null; cardinality?: number | null; - settings: KloDataDictionarySettings; + settings: KtxDataDictionarySettings; } const categoricalCandidateTypes = /^(n?varchar|n?char|n?text|string|character|enum|bool(ean)?)/i; -export function isKloDataDictionaryCandidate( +export function isKtxDataDictionaryCandidate( columnType: string, columnName: string, - excludePatterns: readonly string[] = defaultKloDataDictionarySettings.excludePatterns, + excludePatterns: readonly string[] = defaultKtxDataDictionarySettings.excludePatterns, ): boolean { const typeLower = columnType.toLowerCase(); const nameLower = columnName.toLowerCase(); @@ -83,9 +83,9 @@ export function isKloDataDictionaryCandidate( return true; } -export function shouldKloSampleColumnForDictionary( - input: KloDataDictionaryColumnState, -): KloDataDictionarySampleDecision { +export function shouldKtxSampleColumnForDictionary( + input: KtxDataDictionaryColumnState, +): KtxDataDictionarySampleDecision { const sampleValues = input.sampleValues ?? null; const cardinality = input.cardinality ?? null; @@ -101,7 +101,7 @@ export function shouldKloSampleColumnForDictionary( return { sample: false, reason: 'high_cardinality' }; } - if (!isKloDataDictionaryCandidate(input.columnType, input.columnName, input.settings.excludePatterns)) { + if (!isKtxDataDictionaryCandidate(input.columnType, input.columnName, input.settings.excludePatterns)) { return { sample: false, reason: 'not_candidate' }; } diff --git a/packages/context/src/scan/description-generation.test.ts b/packages/context/src/scan/description-generation.test.ts index 5eaefc34..de69fb27 100644 --- a/packages/context/src/scan/description-generation.test.ts +++ b/packages/context/src/scan/description-generation.test.ts @@ -7,15 +7,15 @@ vi.mock('ai', async (importOriginal) => { import { generateText } from 'ai'; import { - buildKloColumnDescriptionPrompt, - buildKloDataSourceDescriptionPrompt, - buildKloTableDescriptionPrompt, - type KloDescriptionCachePort, - KloDescriptionGenerator, + buildKtxColumnDescriptionPrompt, + buildKtxDataSourceDescriptionPrompt, + buildKtxTableDescriptionPrompt, + type KtxDescriptionCachePort, + KtxDescriptionGenerator, } from './description-generation.js'; -import { createKloConnectorCapabilities, type KloScanConnector } from './types.js'; +import { createKtxConnectorCapabilities, type KtxScanConnector } from './types.js'; -function createCache(initial: Record = {}): KloDescriptionCachePort { +function createCache(initial: Record = {}): KtxDescriptionCachePort { const data = new Map(Object.entries(initial)); return { buildTableKey: (table) => [table.catalog, table.db, table.name].filter(Boolean).join('.'), @@ -51,11 +51,11 @@ function createLlmProvider(text = 'generated description') { } as any; } -function createConnector(): KloScanConnector { +function createConnector(): KtxScanConnector { return { id: 'test-connector', driver: 'postgres', - capabilities: createKloConnectorCapabilities({ + capabilities: createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, nestedAnalysis: true, @@ -79,9 +79,9 @@ function createConnector(): KloScanConnector { }; } -describe('KLO description prompt builders', () => { +describe('KTX description prompt builders', () => { it('builds column prompts with sample values, source descriptions, and nested BigQuery guidance', () => { - const prompt = buildKloColumnDescriptionPrompt({ + const prompt = buildKtxColumnDescriptionPrompt({ columnName: 'payload', columnValues: [{ nested: true }, '[1,2]'], tableContext: 'Table: events | Columns: payload | Data source: BIGQUERY', @@ -112,7 +112,7 @@ describe('KLO description prompt builders', () => { }; expect( - buildKloTableDescriptionPrompt({ + buildKtxTableDescriptionPrompt({ tableName: 'orders', sampleData: sample, dataSourceType: 'POSTGRESQL', @@ -121,7 +121,7 @@ describe('KLO description prompt builders', () => { ).toContain('status: paid, refunded'); expect( - buildKloDataSourceDescriptionPrompt({ + buildKtxDataSourceDescriptionPrompt({ tableSamples: [['orders', sample]], dataSourceType: 'POSTGRESQL', }), @@ -129,12 +129,12 @@ describe('KLO description prompt builders', () => { }); }); -describe('KloDescriptionGenerator', () => { +describe('KtxDescriptionGenerator', () => { it('generates column descriptions with pre-fetched values, cache hits, and word-limit metadata', async () => { const cache = createCache({ 'warehouse.public.orders.cached_status': 'Cached status description' }); const llmProvider = createLlmProvider('Payment state'); const connector = createConnector(); - const generator = new KloDescriptionGenerator({ + const generator = new KtxDescriptionGenerator({ llmProvider, cache, settings: { @@ -189,7 +189,7 @@ describe('KloDescriptionGenerator', () => { it('samples through the connector when column values are not pre-fetched', async () => { const connector = createConnector(); - const generator = new KloDescriptionGenerator({ + const generator = new KtxDescriptionGenerator({ llmProvider: createLlmProvider('Current order state'), settings: { columnMaxWords: 12, @@ -238,7 +238,7 @@ describe('KloDescriptionGenerator', () => { totalRows: 1, })), }; - const generator = new KloDescriptionGenerator({ + const generator = new KtxDescriptionGenerator({ llmProvider: createLlmProvider('Generated through sampler'), settings: { columnMaxWords: 12, @@ -277,7 +277,7 @@ describe('KloDescriptionGenerator', () => { it('generates and caches table and data-source descriptions', async () => { const cache = createCache(); const connector = createConnector(); - const generator = new KloDescriptionGenerator({ + const generator = new KtxDescriptionGenerator({ llmProvider: createLlmProvider('Commerce orders'), cache, settings: { diff --git a/packages/context/src/scan/description-generation.ts b/packages/context/src/scan/description-generation.ts index 193544cb..dc30af04 100644 --- a/packages/context/src/scan/description-generation.ts +++ b/packages/context/src/scan/description-generation.ts @@ -1,30 +1,30 @@ -import type { KloLlmProvider } from '@klo/llm'; -import { generateKloText } from '../llm/index.js'; +import type { KtxLlmProvider } from '@ktx/llm'; +import { generateKtxText } from '../llm/index.js'; import type { - KloColumnSampleInput, - KloColumnSampleResult, - KloScanContext, - KloScanLoggerPort, - KloTableRef, - KloTableSampleInput, - KloTableSampleResult, + KtxColumnSampleInput, + KtxColumnSampleResult, + KtxScanContext, + KtxScanLoggerPort, + KtxTableRef, + KtxTableSampleInput, + KtxTableSampleResult, } from './types.js'; -export interface KloDescriptionCachePort { - buildTableKey(table: KloTableRef): string; - buildColumnKey(table: KloTableRef, columnName: string): string; +export interface KtxDescriptionCachePort { + buildTableKey(table: KtxTableRef): string; + buildColumnKey(table: KtxTableRef, columnName: string): string; buildConnectionKey(connectionName: string): string; get(key: string): Promise; set(key: string, value: string): Promise; } -export interface KloDescriptionSamplingPort { +export interface KtxDescriptionSamplingPort { id: string; - sampleColumn?(input: KloColumnSampleInput, ctx: KloScanContext): Promise; - sampleTable?(input: KloTableSampleInput, ctx: KloScanContext): Promise; + sampleColumn?(input: KtxColumnSampleInput, ctx: KtxScanContext): Promise; + sampleTable?(input: KtxTableSampleInput, ctx: KtxScanContext): Promise; } -export interface KloDescriptionGenerationSettings { +export interface KtxDescriptionGenerationSettings { columnMaxWords: number; tableMaxWords: number; dataSourceMaxWords: number; @@ -32,7 +32,7 @@ export interface KloDescriptionGenerationSettings { concurrencyLimit?: number; } -interface ResolvedKloDescriptionGenerationSettings { +interface ResolvedKtxDescriptionGenerationSettings { columnMaxWords: number; tableMaxWords: number; dataSourceMaxWords: number; @@ -40,28 +40,28 @@ interface ResolvedKloDescriptionGenerationSettings { concurrencyLimit: number; } -export interface KloDescriptionColumn { +export interface KtxDescriptionColumn { name: string; type?: string; rawDescriptions?: Record; sampleValues?: unknown[]; } -export interface KloDescriptionColumnTable extends KloTableRef { - columns: KloDescriptionColumn[]; +export interface KtxDescriptionColumnTable extends KtxTableRef { + columns: KtxDescriptionColumn[]; } -export interface KloDescriptionTableInput extends KloTableRef { +export interface KtxDescriptionTableInput extends KtxTableRef { rawDescriptions?: Record; } -export interface KloColumnAnalysisResult { +export interface KtxColumnAnalysisResult { columnDescriptions: Array<[string, string | null]>; processedColumns: string[]; skippedColumns: string[]; } -export interface KloColumnDescriptionPromptInput { +export interface KtxColumnDescriptionPromptInput { columnName: string; columnValues: unknown[]; tableContext: string; @@ -70,51 +70,51 @@ export interface KloColumnDescriptionPromptInput { rawDescriptions?: Record; } -export interface KloTableDescriptionPromptInput { +export interface KtxTableDescriptionPromptInput { tableName: string; - sampleData: KloTableSampleResult; + sampleData: KtxTableSampleResult; dataSourceType: string; rawDescriptions?: Record; } -export interface KloDataSourceDescriptionPromptInput { - tableSamples: Array<[string, KloTableSampleResult]>; +export interface KtxDataSourceDescriptionPromptInput { + tableSamples: Array<[string, KtxTableSampleResult]>; dataSourceType: string; } -export interface KloGenerateColumnDescriptionsInput { +export interface KtxGenerateColumnDescriptionsInput { connectionId: string; - connector: KloDescriptionSamplingPort; - context: KloScanContext; + connector: KtxDescriptionSamplingPort; + context: KtxScanContext; dataSourceType: string; supportsNestedAnalysis: boolean; - table: KloDescriptionColumnTable; + table: KtxDescriptionColumnTable; skipExisting?: boolean; existingDescriptions?: Record; } -export interface KloGenerateTableDescriptionInput { +export interface KtxGenerateTableDescriptionInput { connectionId: string; - connector: KloDescriptionSamplingPort; - context: KloScanContext; + connector: KtxDescriptionSamplingPort; + context: KtxScanContext; dataSourceType: string; - table: KloDescriptionTableInput; + table: KtxDescriptionTableInput; } -export interface KloGenerateDataSourceDescriptionInput { +export interface KtxGenerateDataSourceDescriptionInput { connectionId: string; - connector: KloDescriptionSamplingPort; - context: KloScanContext; + connector: KtxDescriptionSamplingPort; + context: KtxScanContext; dataSourceType: string; - tables: KloTableRef[]; + tables: KtxTableRef[]; connectionName?: string; } -export interface KloDescriptionGeneratorOptions { - llmProvider: KloLlmProvider; - cache?: KloDescriptionCachePort; - logger?: KloScanLoggerPort; - settings: KloDescriptionGenerationSettings; +export interface KtxDescriptionGeneratorOptions { + llmProvider: KtxLlmProvider; + cache?: KtxDescriptionCachePort; + logger?: KtxScanLoggerPort; + settings: KtxDescriptionGenerationSettings; } interface ColumnTaskResult { @@ -136,7 +136,7 @@ function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } -function toTableRef(table: KloTableRef): KloTableRef { +function toTableRef(table: KtxTableRef): KtxTableRef { return { catalog: table.catalog, db: table.db, @@ -169,11 +169,11 @@ async function runWithConcurrency( return results; } -export function appendKloWordLimitInstruction(prompt: string, maxWords: number): string { +export function appendKtxWordLimitInstruction(prompt: string, maxWords: number): string { return `${prompt}\n\nPlease provide a concise description in ${maxWords} words or less.`; } -export function buildKloColumnDescriptionPrompt(input: KloColumnDescriptionPromptInput): string { +export function buildKtxColumnDescriptionPrompt(input: KtxColumnDescriptionPromptInput): string { const sampleValues = input.columnValues.slice(0, 5); const valuesStr = sampleValues .filter((value) => value !== null && value !== undefined) @@ -221,7 +221,7 @@ Example: return prompt.trim(); } -export function buildKloTableDescriptionPrompt(input: KloTableDescriptionPromptInput): string { +export function buildKtxTableDescriptionPrompt(input: KtxTableDescriptionPromptInput): string { const columnInfo: string[] = []; for (let index = 0; index < Math.min(input.sampleData.headers.length, 10); index += 1) { const header = input.sampleData.headers[index]; @@ -268,7 +268,7 @@ export function buildKloTableDescriptionPrompt(input: KloTableDescriptionPromptI return prompt.trim(); } -export function buildKloDataSourceDescriptionPrompt(input: KloDataSourceDescriptionPromptInput): string { +export function buildKtxDataSourceDescriptionPrompt(input: KtxDataSourceDescriptionPromptInput): string { const tablesText = input.tableSamples .map( ([tableName, sampleData]) => @@ -301,13 +301,13 @@ export function buildKloDataSourceDescriptionPrompt(input: KloDataSourceDescript return prompt.trim(); } -export class KloDescriptionGenerator { - private readonly llmProvider: KloLlmProvider; - private readonly cache?: KloDescriptionCachePort; - private readonly logger?: KloScanLoggerPort; - private readonly settings: ResolvedKloDescriptionGenerationSettings; +export class KtxDescriptionGenerator { + private readonly llmProvider: KtxLlmProvider; + private readonly cache?: KtxDescriptionCachePort; + private readonly logger?: KtxScanLoggerPort; + private readonly settings: ResolvedKtxDescriptionGenerationSettings; - constructor(options: KloDescriptionGeneratorOptions) { + constructor(options: KtxDescriptionGeneratorOptions) { this.llmProvider = options.llmProvider; this.cache = options.cache; this.logger = options.logger; @@ -320,7 +320,7 @@ export class KloDescriptionGenerator { }; } - async generateColumnDescriptions(input: KloGenerateColumnDescriptionsInput): Promise { + async generateColumnDescriptions(input: KtxGenerateColumnDescriptionsInput): Promise { const columnsToProcess = input.table.columns; const tableContext = `Table: ${input.table.name} | Columns: ${columnsToProcess.map((column) => column.name).join(', ')} | Data source: ${input.dataSourceType}`; @@ -348,7 +348,7 @@ export class KloDescriptionGenerator { }; } - async generateTableDescription(input: KloGenerateTableDescriptionInput): Promise { + async generateTableDescription(input: KtxGenerateTableDescriptionInput): Promise { const tableRef = toTableRef(input.table); const cacheKey = this.cache?.buildTableKey(tableRef); if (cacheKey) { @@ -359,7 +359,7 @@ export class KloDescriptionGenerator { } if (!input.connector.sampleTable) { - this.logger?.warn('KLO scan connector does not support table sampling for table description generation', { + this.logger?.warn('KTX scan connector does not support table sampling for table description generation', { connectorId: input.connector.id, table: input.table.name, }); @@ -375,7 +375,7 @@ export class KloDescriptionGenerator { }, input.context, ); - const prompt = buildKloTableDescriptionPrompt({ + const prompt = buildKtxTableDescriptionPrompt({ tableName: input.table.name, sampleData, dataSourceType: input.dataSourceType, @@ -384,7 +384,7 @@ export class KloDescriptionGenerator { const description = await this.generateAiDescription( prompt, this.settings.tableMaxWords, - 'klo-table-description', + 'ktx-table-description', ); if (cacheKey) { await this.cache?.set(cacheKey, description); @@ -396,7 +396,7 @@ export class KloDescriptionGenerator { } } - async generateDataSourceDescription(input: KloGenerateDataSourceDescriptionInput): Promise { + async generateDataSourceDescription(input: KtxGenerateDataSourceDescriptionInput): Promise { if (input.tables.length === 0) { return 'No tables found in database'; } @@ -410,7 +410,7 @@ export class KloDescriptionGenerator { } if (!input.connector.sampleTable) { - this.logger?.warn('KLO scan connector does not support table sampling for data-source description generation', { + this.logger?.warn('KTX scan connector does not support table sampling for data-source description generation', { connectorId: input.connector.id, }); return 'No accessible tables found in database'; @@ -427,7 +427,7 @@ export class KloDescriptionGenerator { }, input.context, ); - return [table.name, sampleData] as [string, KloTableSampleResult]; + return [table.name, sampleData] as [string, KtxTableSampleResult]; } catch (error) { this.logger?.warn(`Failed to sample table '${table.name}' for data source analysis - ${errorMessage(error)}`); return null; @@ -435,21 +435,21 @@ export class KloDescriptionGenerator { }); const accessibleSamples = tableSamples.filter( - (sample): sample is [string, KloTableSampleResult] => sample !== null, + (sample): sample is [string, KtxTableSampleResult] => sample !== null, ); if (accessibleSamples.length === 0) { return 'No accessible tables found in database'; } try { - const prompt = buildKloDataSourceDescriptionPrompt({ + const prompt = buildKtxDataSourceDescriptionPrompt({ tableSamples: accessibleSamples, dataSourceType: input.dataSourceType, }); const description = await this.generateAiDescription( prompt, this.settings.dataSourceMaxWords, - 'klo-data-source-description', + 'ktx-data-source-description', ); if (cacheKey) { await this.cache?.set(cacheKey, description); @@ -462,8 +462,8 @@ export class KloDescriptionGenerator { } private async generateOneColumnDescription( - input: KloGenerateColumnDescriptionsInput, - column: KloDescriptionColumn, + input: KtxGenerateColumnDescriptionsInput, + column: KtxDescriptionColumn, tableContext: string, ): Promise { const existingDescription = input.existingDescriptions?.[column.name]; @@ -494,7 +494,7 @@ export class KloDescriptionGenerator { let columnValues = column.sampleValues; if (!columnValues || columnValues.length === 0) { if (!input.connector.sampleColumn) { - this.logger?.warn('KLO scan connector does not support column sampling for column description generation', { + this.logger?.warn('KTX scan connector does not support column sampling for column description generation', { connectorId: input.connector.id, table: input.table.name, column: column.name, @@ -529,7 +529,7 @@ export class KloDescriptionGenerator { }; } - const prompt = buildKloColumnDescriptionPrompt({ + const prompt = buildKtxColumnDescriptionPrompt({ columnName: column.name, columnValues: nonNullValues, tableContext, @@ -540,7 +540,7 @@ export class KloDescriptionGenerator { const description = await this.generateAiDescription( prompt, this.settings.columnMaxWords, - 'klo-column-description', + 'ktx-column-description', ); if (cacheKey) { @@ -566,10 +566,10 @@ export class KloDescriptionGenerator { private async generateAiDescription(prompt: string, maxWords: number, _operationName: string): Promise { try { - const text = await generateKloText({ + const text = await generateKtxText({ llmProvider: this.llmProvider, role: 'candidateExtraction', - prompt: appendKloWordLimitInstruction(prompt, maxWords), + prompt: appendKtxWordLimitInstruction(prompt, maxWords), temperature: this.settings.temperature, }); const description = text.trim(); diff --git a/packages/context/src/scan/embedding-text.test.ts b/packages/context/src/scan/embedding-text.test.ts index ce60f95a..ee019bce 100644 --- a/packages/context/src/scan/embedding-text.test.ts +++ b/packages/context/src/scan/embedding-text.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; -import { buildKloColumnEmbeddingText } from './embedding-text.js'; +import { buildKtxColumnEmbeddingText } from './embedding-text.js'; -describe('KLO scan embedding text', () => { +describe('KTX scan embedding text', () => { it('builds column embedding text with table, description, FK, and sample-value context', () => { expect( - buildKloColumnEmbeddingText({ + buildKtxColumnEmbeddingText({ tableName: 'orders', columnName: 'status', columnType: 'varchar', @@ -24,7 +24,7 @@ describe('KLO scan embedding text', () => { it('omits optional sections when the scan has no enrichment context yet', () => { expect( - buildKloColumnEmbeddingText({ + buildKtxColumnEmbeddingText({ tableName: 'orders', columnName: 'id', columnType: 'integer', @@ -35,7 +35,7 @@ describe('KLO scan embedding text', () => { it('keeps all available sample values when no explicit max is supplied', () => { expect( - buildKloColumnEmbeddingText({ + buildKtxColumnEmbeddingText({ tableName: 'orders', columnName: 'status', columnType: 'varchar', diff --git a/packages/context/src/scan/embedding-text.ts b/packages/context/src/scan/embedding-text.ts index cf385354..927f5779 100644 --- a/packages/context/src/scan/embedding-text.ts +++ b/packages/context/src/scan/embedding-text.ts @@ -1,20 +1,20 @@ -export interface KloColumnEmbeddingForeignKeys { +export interface KtxColumnEmbeddingForeignKeys { outgoing: Array<{ toTable: string; toColumn: string }>; incoming: Array<{ fromTable: string; fromColumn: string }>; } -export interface KloColumnEmbeddingTextInput { +export interface KtxColumnEmbeddingTextInput { tableName: string; columnName: string; columnType: string; resolvedDescription: string | null; sampleValues?: readonly string[] | null; resolvedTableDescription?: string | null; - foreignKeys?: KloColumnEmbeddingForeignKeys | null; + foreignKeys?: KtxColumnEmbeddingForeignKeys | null; maxSampleValues?: number; } -export function buildKloColumnEmbeddingText(input: KloColumnEmbeddingTextInput): string { +export function buildKtxColumnEmbeddingText(input: KtxColumnEmbeddingTextInput): string { const parts: string[] = []; parts.push(`${input.tableName}.${input.columnName} (${input.columnType})`); diff --git a/packages/context/src/scan/enrichment-state.test.ts b/packages/context/src/scan/enrichment-state.test.ts index 7f41254d..036db607 100644 --- a/packages/context/src/scan/enrichment-state.test.ts +++ b/packages/context/src/scan/enrichment-state.test.ts @@ -3,14 +3,14 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { - completedKloScanEnrichmentStateSummary, - computeKloScanEnrichmentInputHash, - summarizeKloScanEnrichmentState, + completedKtxScanEnrichmentStateSummary, + computeKtxScanEnrichmentInputHash, + summarizeKtxScanEnrichmentState, } from './enrichment-state.js'; import { SqliteLocalScanEnrichmentStateStore } from './sqlite-local-enrichment-state-store.js'; -import type { KloSchemaSnapshot } from './types.js'; +import type { KtxSchemaSnapshot } from './types.js'; -const snapshot: KloSchemaSnapshot = { +const snapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-04-29T12:00:00.000Z', @@ -45,7 +45,7 @@ describe('scan enrichment state', () => { let store: SqliteLocalScanEnrichmentStateStore; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-scan-enrichment-state-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-scan-enrichment-state-')); store = new SqliteLocalScanEnrichmentStateStore({ dbPath: join(tempDir, 'db.sqlite') }); }); @@ -54,13 +54,13 @@ describe('scan enrichment state', () => { }); it('computes stable input hashes without depending on object key order', () => { - const first = computeKloScanEnrichmentInputHash({ + const first = computeKtxScanEnrichmentInputHash({ snapshot, mode: 'enriched', detectRelationships: true, providerIdentity: { provider: 'deterministic', embeddingDimensions: 8, llmModel: 'a' }, }); - const second = computeKloScanEnrichmentInputHash({ + const second = computeKtxScanEnrichmentInputHash({ snapshot: { ...snapshot, metadata: {} }, mode: 'enriched', detectRelationships: true, @@ -70,7 +70,7 @@ describe('scan enrichment state', () => { if (!firstTable) { throw new Error('Expected test snapshot table'); } - const changed = computeKloScanEnrichmentInputHash({ + const changed = computeKtxScanEnrichmentInputHash({ snapshot: { ...snapshot, tables: [{ ...firstTable, name: 'orders_v2' }] }, mode: 'enriched', detectRelationships: true, @@ -83,7 +83,7 @@ describe('scan enrichment state', () => { }); it('persists completed stages and ignores stale hashes', async () => { - const inputHash = computeKloScanEnrichmentInputHash({ + const inputHash = computeKtxScanEnrichmentInputHash({ snapshot, mode: 'enriched', detectRelationships: true, @@ -155,7 +155,7 @@ describe('scan enrichment state', () => { it('summarizes resumed, completed, and failed stages for reports', () => { expect( - summarizeKloScanEnrichmentState({ + summarizeKtxScanEnrichmentState({ resumedStages: ['descriptions'], completedStages: ['descriptions', 'embeddings'], failedStages: ['relationships'], @@ -166,7 +166,7 @@ describe('scan enrichment state', () => { failedStages: ['relationships'], }); - expect(completedKloScanEnrichmentStateSummary()).toEqual({ + expect(completedKtxScanEnrichmentStateSummary()).toEqual({ resumedStages: [], completedStages: [], failedStages: [], diff --git a/packages/context/src/scan/enrichment-state.ts b/packages/context/src/scan/enrichment-state.ts index 9fcf5a63..8eb5f421 100644 --- a/packages/context/src/scan/enrichment-state.ts +++ b/packages/context/src/scan/enrichment-state.ts @@ -1,24 +1,24 @@ import { createHash } from 'node:crypto'; -import type { KloScanEnrichmentStage, KloScanEnrichmentStateSummary, KloScanMode, KloSchemaSnapshot } from './types.js'; +import type { KtxScanEnrichmentStage, KtxScanEnrichmentStateSummary, KtxScanMode, KtxSchemaSnapshot } from './types.js'; -export const KLO_SCAN_ENRICHMENT_STAGES: readonly KloScanEnrichmentStage[] = [ +export const KTX_SCAN_ENRICHMENT_STAGES: readonly KtxScanEnrichmentStage[] = [ 'descriptions', 'embeddings', 'relationships', ] as const; -export interface KloScanEnrichmentStageLookup { +export interface KtxScanEnrichmentStageLookup { runId: string; - stage: KloScanEnrichmentStage; + stage: KtxScanEnrichmentStage; inputHash: string; } -export interface KloScanEnrichmentCompletedStage { +export interface KtxScanEnrichmentCompletedStage { runId: string; connectionId: string; syncId: string; - mode: KloScanMode; - stage: KloScanEnrichmentStage; + mode: KtxScanMode; + stage: KtxScanEnrichmentStage; inputHash: string; status: 'completed'; output: TOutput; @@ -26,12 +26,12 @@ export interface KloScanEnrichmentCompletedStage { updatedAt: string; } -export interface KloScanEnrichmentFailedStage { +export interface KtxScanEnrichmentFailedStage { runId: string; connectionId: string; syncId: string; - mode: KloScanMode; - stage: KloScanEnrichmentStage; + mode: KtxScanMode; + stage: KtxScanEnrichmentStage; inputHash: string; status: 'failed'; output: null; @@ -39,24 +39,24 @@ export interface KloScanEnrichmentFailedStage { updatedAt: string; } -export type KloScanEnrichmentStageRecord = - | KloScanEnrichmentCompletedStage - | KloScanEnrichmentFailedStage; +export type KtxScanEnrichmentStageRecord = + | KtxScanEnrichmentCompletedStage + | KtxScanEnrichmentFailedStage; -export interface KloScanEnrichmentStateStore { +export interface KtxScanEnrichmentStateStore { findCompletedStage( - input: KloScanEnrichmentStageLookup, - ): Promise | null>; + input: KtxScanEnrichmentStageLookup, + ): Promise | null>; saveCompletedStage( - input: Omit, 'status' | 'errorMessage'>, + input: Omit, 'status' | 'errorMessage'>, ): Promise; - saveFailedStage(input: Omit): Promise; - listRunStages(runId: string): Promise; + saveFailedStage(input: Omit): Promise; + listRunStages(runId: string): Promise; } -export interface ComputeKloScanEnrichmentInputHashInput { - snapshot: KloSchemaSnapshot; - mode: KloScanMode; +export interface ComputeKtxScanEnrichmentInputHashInput { + snapshot: KtxSchemaSnapshot; + mode: KtxScanMode; detectRelationships: boolean; providerIdentity: Record; relationshipSettings?: unknown; @@ -75,14 +75,14 @@ function stableJson(value: unknown): string { return JSON.stringify(value); } -export function computeKloScanEnrichmentInputHash(input: ComputeKloScanEnrichmentInputHashInput): string { +export function computeKtxScanEnrichmentInputHash(input: ComputeKtxScanEnrichmentInputHashInput): string { return createHash('sha256').update(stableJson(input)).digest('hex'); } -function uniqueStages(stages: KloScanEnrichmentStage[]): KloScanEnrichmentStage[] { - const seen = new Set(); - const ordered: KloScanEnrichmentStage[] = []; - for (const stage of KLO_SCAN_ENRICHMENT_STAGES) { +function uniqueStages(stages: KtxScanEnrichmentStage[]): KtxScanEnrichmentStage[] { + const seen = new Set(); + const ordered: KtxScanEnrichmentStage[] = []; + for (const stage of KTX_SCAN_ENRICHMENT_STAGES) { if (stages.includes(stage) && !seen.has(stage)) { seen.add(stage); ordered.push(stage); @@ -91,7 +91,7 @@ function uniqueStages(stages: KloScanEnrichmentStage[]): KloScanEnrichmentStage[ return ordered; } -export function completedKloScanEnrichmentStateSummary(): KloScanEnrichmentStateSummary { +export function completedKtxScanEnrichmentStateSummary(): KtxScanEnrichmentStateSummary { return { resumedStages: [], completedStages: [], @@ -99,7 +99,7 @@ export function completedKloScanEnrichmentStateSummary(): KloScanEnrichmentState }; } -export function summarizeKloScanEnrichmentState(input: KloScanEnrichmentStateSummary): KloScanEnrichmentStateSummary { +export function summarizeKtxScanEnrichmentState(input: KtxScanEnrichmentStateSummary): KtxScanEnrichmentStateSummary { return { resumedStages: uniqueStages(input.resumedStages), completedStages: uniqueStages(input.completedStages), diff --git a/packages/context/src/scan/enrichment-summary.test.ts b/packages/context/src/scan/enrichment-summary.test.ts index e2876ccc..f320046b 100644 --- a/packages/context/src/scan/enrichment-summary.test.ts +++ b/packages/context/src/scan/enrichment-summary.test.ts @@ -1,17 +1,17 @@ import { describe, expect, it } from 'vitest'; import { - failedKloScanEnrichmentSummary, - kloScanErrorMessage, - skippedKloScanEnrichmentSummary, + failedKtxScanEnrichmentSummary, + ktxScanErrorMessage, + skippedKtxScanEnrichmentSummary, } from './enrichment-summary.js'; -describe('KLO scan enrichment summaries', () => { +describe('KTX scan enrichment summaries', () => { it('keeps structural scans skipped when no enrichment was requested', () => { - expect(failedKloScanEnrichmentSummary('structural', false)).toEqual(skippedKloScanEnrichmentSummary); + expect(failedKtxScanEnrichmentSummary('structural', false)).toEqual(skippedKtxScanEnrichmentSummary); }); it('marks relationship stages failed when relationship detection fails', () => { - expect(failedKloScanEnrichmentSummary('relationships', true)).toEqual({ + expect(failedKtxScanEnrichmentSummary('relationships', true)).toEqual({ dataDictionary: 'skipped', tableDescriptions: 'skipped', columnDescriptions: 'skipped', @@ -23,7 +23,7 @@ describe('KLO scan enrichment summaries', () => { }); it('marks every enriched-only stage failed when full enrichment fails', () => { - expect(failedKloScanEnrichmentSummary('enriched', true)).toEqual({ + expect(failedKtxScanEnrichmentSummary('enriched', true)).toEqual({ dataDictionary: 'failed', tableDescriptions: 'failed', columnDescriptions: 'failed', @@ -35,8 +35,8 @@ describe('KLO scan enrichment summaries', () => { }); it('formats unknown thrown values for scan warnings', () => { - expect(kloScanErrorMessage(new Error('gateway timeout'))).toBe('gateway timeout'); - expect(kloScanErrorMessage('plain failure')).toBe('plain failure'); - expect(kloScanErrorMessage({ code: 'E_SCAN' })).toBe('{"code":"E_SCAN"}'); + expect(ktxScanErrorMessage(new Error('gateway timeout'))).toBe('gateway timeout'); + expect(ktxScanErrorMessage('plain failure')).toBe('plain failure'); + expect(ktxScanErrorMessage({ code: 'E_SCAN' })).toBe('{"code":"E_SCAN"}'); }); }); diff --git a/packages/context/src/scan/enrichment-summary.ts b/packages/context/src/scan/enrichment-summary.ts index e4798423..6d723e4e 100644 --- a/packages/context/src/scan/enrichment-summary.ts +++ b/packages/context/src/scan/enrichment-summary.ts @@ -1,6 +1,6 @@ -import type { KloScanEnrichmentSummary, KloScanMode } from './types.js'; +import type { KtxScanEnrichmentSummary, KtxScanMode } from './types.js'; -export const skippedKloScanEnrichmentSummary: KloScanEnrichmentSummary = { +export const skippedKtxScanEnrichmentSummary: KtxScanEnrichmentSummary = { dataDictionary: 'skipped', tableDescriptions: 'skipped', columnDescriptions: 'skipped', @@ -10,10 +10,10 @@ export const skippedKloScanEnrichmentSummary: KloScanEnrichmentSummary = { statisticalValidation: 'skipped', }; -export function failedKloScanEnrichmentSummary( - mode: KloScanMode, +export function failedKtxScanEnrichmentSummary( + mode: KtxScanMode, detectRelationships = false, -): KloScanEnrichmentSummary { +): KtxScanEnrichmentSummary { if (mode === 'enriched') { return { dataDictionary: 'failed', @@ -28,16 +28,16 @@ export function failedKloScanEnrichmentSummary( if (mode === 'relationships' || detectRelationships) { return { - ...skippedKloScanEnrichmentSummary, + ...skippedKtxScanEnrichmentSummary, deterministicRelationships: 'failed', statisticalValidation: 'failed', }; } - return skippedKloScanEnrichmentSummary; + return skippedKtxScanEnrichmentSummary; } -export function kloScanErrorMessage(error: unknown): string { +export function ktxScanErrorMessage(error: unknown): string { if (error instanceof Error) { return error.message; } diff --git a/packages/context/src/scan/enrichment-types.test.ts b/packages/context/src/scan/enrichment-types.test.ts index 14480107..3f7828dc 100644 --- a/packages/context/src/scan/enrichment-types.test.ts +++ b/packages/context/src/scan/enrichment-types.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest'; import type { - KloColumnSampleUpdate, - KloDescriptionUpdate, - KloEmbeddingUpdate, - KloEnrichedSchema, - KloJoinUpdate, - KloRelationshipEndpoint, - KloRelationshipUpdate, - KloScanMetadataStore, - KloStructuralSyncPlan, + KtxColumnSampleUpdate, + KtxDescriptionUpdate, + KtxEmbeddingUpdate, + KtxEnrichedSchema, + KtxJoinUpdate, + KtxRelationshipEndpoint, + KtxRelationshipUpdate, + KtxScanMetadataStore, + KtxStructuralSyncPlan, } from './enrichment-types.js'; -describe('KLO scan enrichment contracts', () => { +describe('KTX scan enrichment contracts', () => { it('models an enriched schema with reusable table, column, and relationship metadata', () => { - const schema: KloEnrichedSchema = { + const schema: KtxEnrichedSchema = { connectionId: 'warehouse', tables: [ { @@ -69,36 +69,36 @@ describe('KLO scan enrichment contracts', () => { }); it('models metadata-store updates without requiring a concrete store implementation', async () => { - const structuralPlan: KloStructuralSyncPlan = { + const structuralPlan: KtxStructuralSyncPlan = { connectionId: 'warehouse', snapshotId: 'snapshot-1', operations: [{ kind: 'create_table', table: 'orders' }], }; - const descriptionUpdate: KloDescriptionUpdate = { + const descriptionUpdate: KtxDescriptionUpdate = { connectionId: 'warehouse', table: { catalog: 'analytics', db: 'public', name: 'orders' }, source: 'ai', tableDescription: 'Customer orders', columnDescriptions: { status: 'Payment lifecycle state' }, }; - const sampleUpdate: KloColumnSampleUpdate = { + const sampleUpdate: KtxColumnSampleUpdate = { columnId: 'column-orders-status', sampleValues: ['paid', 'refunded'], cardinality: 2, }; - const embeddingUpdate: KloEmbeddingUpdate = { + const embeddingUpdate: KtxEmbeddingUpdate = { columnId: 'column-orders-status', text: 'orders.status (varchar). Values: paid, refunded', embedding: [0.25, 0.75], }; - const relationshipUpdate: KloRelationshipUpdate = { + const relationshipUpdate: KtxRelationshipUpdate = { connectionId: 'warehouse', accepted: [], rejected: [], skipped: [{ reason: 'missing parent table', relationshipId: 'candidate-1' }], }; - const store: KloScanMetadataStore = { + const store: KtxScanMetadataStore = { loadSchema: async () => null, applyStructuralPlan: async (plan) => ({ connectionId: plan.connectionId, @@ -134,21 +134,21 @@ describe('KLO scan enrichment contracts', () => { describe('relationship tuple contracts', () => { it('represents relationship endpoints and join updates as ordered column tuples', () => { - const endpoint: KloRelationshipEndpoint = { + const endpoint: KtxRelationshipEndpoint = { tableId: 'public.order_lines', columnIds: ['public.order_lines.order_id', 'public.order_lines.line_number'], table: { catalog: null, db: 'public', name: 'order_lines' }, columns: ['order_id', 'line_number'], }; - const update: KloJoinUpdate = { + const update: KtxJoinUpdate = { connectionId: 'warehouse', fromTable: 'order_line_allocations', fromColumns: ['order_id', 'line_number'], toTable: 'order_lines', toColumns: ['order_id', 'line_number'], relationship: 'many_to_one', - author: 'klo', - authorEmail: 'klo@example.com', + author: 'ktx', + authorEmail: 'ktx@example.com', }; expect(endpoint.columns).toEqual(['order_id', 'line_number']); diff --git a/packages/context/src/scan/enrichment-types.ts b/packages/context/src/scan/enrichment-types.ts index c3226126..2b186976 100644 --- a/packages/context/src/scan/enrichment-types.ts +++ b/packages/context/src/scan/enrichment-types.ts @@ -1,69 +1,69 @@ -import type { KloSchemaDimensionType, KloTableRef } from './types.js'; +import type { KtxSchemaDimensionType, KtxTableRef } from './types.js'; -export type KloDescriptionSource = 'ai' | 'db' | 'dbt' | 'user' | (string & {}); +export type KtxDescriptionSource = 'ai' | 'db' | 'dbt' | 'user' | (string & {}); -export type KloRelationshipSource = 'formal' | 'inferred' | 'manual'; +export type KtxRelationshipSource = 'formal' | 'inferred' | 'manual'; -export type KloRelationshipType = 'many_to_one' | 'one_to_many' | 'one_to_one'; +export type KtxRelationshipType = 'many_to_one' | 'one_to_many' | 'one_to_one'; -export interface KloEnrichedColumn { +export interface KtxEnrichedColumn { id: string; tableId: string; - tableRef: KloTableRef; + tableRef: KtxTableRef; name: string; nativeType: string; normalizedType: string; - dimensionType: KloSchemaDimensionType; + dimensionType: KtxSchemaDimensionType; nullable: boolean; primaryKey: boolean; parentColumnId: string | null; - descriptions: Partial>; + descriptions: Partial>; embedding: number[] | null; sampleValues: string[] | null; cardinality: number | null; } -export interface KloEnrichedTable { +export interface KtxEnrichedTable { id: string; - ref: KloTableRef; + ref: KtxTableRef; enabled: boolean; - descriptions: Partial>; - columns: KloEnrichedColumn[]; + descriptions: Partial>; + columns: KtxEnrichedColumn[]; } -export interface KloRelationshipEndpoint { +export interface KtxRelationshipEndpoint { tableId: string; columnIds: string[]; - table: KloTableRef; + table: KtxTableRef; columns: string[]; } -export interface KloEnrichedRelationship { +export interface KtxEnrichedRelationship { id: string; - source: KloRelationshipSource; - from: KloRelationshipEndpoint; - to: KloRelationshipEndpoint; - relationshipType: KloRelationshipType; + source: KtxRelationshipSource; + from: KtxRelationshipEndpoint; + to: KtxRelationshipEndpoint; + relationshipType: KtxRelationshipType; confidence: number; isPrimaryKeyReference: boolean; } -export interface KloEnrichedSchema { +export interface KtxEnrichedSchema { connectionId: string; - tables: KloEnrichedTable[]; - relationships: KloEnrichedRelationship[]; + tables: KtxEnrichedTable[]; + relationships: KtxEnrichedRelationship[]; } -export interface KloStructuralSyncPlan { +export interface KtxStructuralSyncPlan { connectionId: string; snapshotId: string; operations: Array>; } -export interface KloDescriptionUpdate { +export interface KtxDescriptionUpdate { connectionId: string; - table: KloTableRef; - source: KloDescriptionSource; + table: KtxTableRef; + source: KtxDescriptionSource; tableDescription?: string; columnDescriptions?: Record; } @@ -77,54 +77,54 @@ const PREFERRED_METADATA_FIELD_NAMES = [ 'lineage', ] as const; -export interface KloMetadataUpdate { +export interface KtxMetadataUpdate { connectionId: string; - table: KloTableRef; - source: KloDescriptionSource; + table: KtxTableRef; + source: KtxDescriptionSource; tableFields?: Record; columnFields?: Record>; } -export interface KloJoinUpdate { +export interface KtxJoinUpdate { connectionId: string; fromTable: string; fromColumns: string[]; toTable: string; toColumns: string[]; - relationship: KloRelationshipType; + relationship: KtxRelationshipType; author: string; authorEmail: string; } -export interface KloColumnSampleUpdate { +export interface KtxColumnSampleUpdate { columnId: string; sampleValues: string[] | null; cardinality: number | null; } -export interface KloEmbeddingUpdate { +export interface KtxEmbeddingUpdate { columnId: string; text: string; embedding: number[]; } -export interface KloSkippedRelationship { +export interface KtxSkippedRelationship { relationshipId: string; reason: string; } -export interface KloRelationshipUpdate { +export interface KtxRelationshipUpdate { connectionId: string; - accepted: KloEnrichedRelationship[]; - rejected: KloEnrichedRelationship[]; - skipped: KloSkippedRelationship[]; + accepted: KtxEnrichedRelationship[]; + rejected: KtxEnrichedRelationship[]; + skipped: KtxSkippedRelationship[]; } -export interface KloScanMetadataStore { - loadSchema(connectionId: string): Promise; - applyStructuralPlan(plan: KloStructuralSyncPlan): Promise; - updateDescriptions(input: KloDescriptionUpdate): Promise; - updateColumnSamples(input: KloColumnSampleUpdate[]): Promise; - updateColumnEmbeddings(input: KloEmbeddingUpdate[]): Promise; - updateInferredRelationships(input: KloRelationshipUpdate): Promise; +export interface KtxScanMetadataStore { + loadSchema(connectionId: string): Promise; + applyStructuralPlan(plan: KtxStructuralSyncPlan): Promise; + updateDescriptions(input: KtxDescriptionUpdate): Promise; + updateColumnSamples(input: KtxColumnSampleUpdate[]): Promise; + updateColumnEmbeddings(input: KtxEmbeddingUpdate[]): Promise; + updateInferredRelationships(input: KtxRelationshipUpdate): Promise; } diff --git a/packages/context/src/scan/index.ts b/packages/context/src/scan/index.ts index 4f45a6a1..c1478993 100644 --- a/packages/context/src/scan/index.ts +++ b/packages/context/src/scan/index.ts @@ -1,93 +1,93 @@ export { - REDACTED_KLO_CREDENTIAL_VALUE, - redactKloCredentialEnvelope, - redactKloCredentialValue, - redactKloScanMetadata, - redactKloScanReport, - redactKloScanWarning, + REDACTED_KTX_CREDENTIAL_VALUE, + redactKtxCredentialEnvelope, + redactKtxCredentialValue, + redactKtxScanMetadata, + redactKtxScanReport, + redactKtxScanWarning, } from './credentials.js'; export type { - KloDataDictionaryColumnState, - KloDataDictionarySampleDecision, - KloDataDictionarySettings, - KloDataDictionarySkipReason, + KtxDataDictionaryColumnState, + KtxDataDictionarySampleDecision, + KtxDataDictionarySettings, + KtxDataDictionarySkipReason, } from './data-dictionary.js'; export { - defaultKloDataDictionarySettings, - isKloDataDictionaryCandidate, - shouldKloSampleColumnForDictionary, + defaultKtxDataDictionarySettings, + isKtxDataDictionaryCandidate, + shouldKtxSampleColumnForDictionary, } from './data-dictionary.js'; export type { - KloColumnAnalysisResult, - KloColumnDescriptionPromptInput, - KloDataSourceDescriptionPromptInput, - KloDescriptionCachePort, - KloDescriptionColumn, - KloDescriptionColumnTable, - KloDescriptionGenerationSettings, - KloDescriptionGeneratorOptions, - KloDescriptionSamplingPort, - KloDescriptionTableInput, - KloGenerateColumnDescriptionsInput, - KloGenerateDataSourceDescriptionInput, - KloGenerateTableDescriptionInput, - KloTableDescriptionPromptInput, + KtxColumnAnalysisResult, + KtxColumnDescriptionPromptInput, + KtxDataSourceDescriptionPromptInput, + KtxDescriptionCachePort, + KtxDescriptionColumn, + KtxDescriptionColumnTable, + KtxDescriptionGenerationSettings, + KtxDescriptionGeneratorOptions, + KtxDescriptionSamplingPort, + KtxDescriptionTableInput, + KtxGenerateColumnDescriptionsInput, + KtxGenerateDataSourceDescriptionInput, + KtxGenerateTableDescriptionInput, + KtxTableDescriptionPromptInput, } from './description-generation.js'; export { - appendKloWordLimitInstruction, - buildKloColumnDescriptionPrompt, - buildKloDataSourceDescriptionPrompt, - buildKloTableDescriptionPrompt, - KloDescriptionGenerator, + appendKtxWordLimitInstruction, + buildKtxColumnDescriptionPrompt, + buildKtxDataSourceDescriptionPrompt, + buildKtxTableDescriptionPrompt, + KtxDescriptionGenerator, } from './description-generation.js'; -export type { KloColumnEmbeddingForeignKeys, KloColumnEmbeddingTextInput } from './embedding-text.js'; -export { buildKloColumnEmbeddingText } from './embedding-text.js'; +export type { KtxColumnEmbeddingForeignKeys, KtxColumnEmbeddingTextInput } from './embedding-text.js'; +export { buildKtxColumnEmbeddingText } from './embedding-text.js'; export type { - ComputeKloScanEnrichmentInputHashInput, - KloScanEnrichmentCompletedStage, - KloScanEnrichmentFailedStage, - KloScanEnrichmentStageLookup, - KloScanEnrichmentStageRecord, - KloScanEnrichmentStateStore, + ComputeKtxScanEnrichmentInputHashInput, + KtxScanEnrichmentCompletedStage, + KtxScanEnrichmentFailedStage, + KtxScanEnrichmentStageLookup, + KtxScanEnrichmentStageRecord, + KtxScanEnrichmentStateStore, } from './enrichment-state.js'; export { - completedKloScanEnrichmentStateSummary, - computeKloScanEnrichmentInputHash, - KLO_SCAN_ENRICHMENT_STAGES, - summarizeKloScanEnrichmentState, + completedKtxScanEnrichmentStateSummary, + computeKtxScanEnrichmentInputHash, + KTX_SCAN_ENRICHMENT_STAGES, + summarizeKtxScanEnrichmentState, } from './enrichment-state.js'; export { - failedKloScanEnrichmentSummary, - kloScanErrorMessage, - skippedKloScanEnrichmentSummary, + failedKtxScanEnrichmentSummary, + ktxScanErrorMessage, + skippedKtxScanEnrichmentSummary, } from './enrichment-summary.js'; export type { - KloColumnSampleUpdate, - KloDescriptionSource, - KloDescriptionUpdate, - KloEmbeddingUpdate, - KloEnrichedColumn, - KloEnrichedRelationship, - KloEnrichedSchema, - KloEnrichedTable, - KloRelationshipEndpoint, - KloRelationshipSource, - KloRelationshipType, - KloRelationshipUpdate, - KloScanMetadataStore, - KloSkippedRelationship, - KloStructuralSyncPlan, + KtxColumnSampleUpdate, + KtxDescriptionSource, + KtxDescriptionUpdate, + KtxEmbeddingUpdate, + KtxEnrichedColumn, + KtxEnrichedRelationship, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxRelationshipEndpoint, + KtxRelationshipSource, + KtxRelationshipType, + KtxRelationshipUpdate, + KtxScanMetadataStore, + KtxSkippedRelationship, + KtxStructuralSyncPlan, } from './enrichment-types.js'; export type { DeterministicLocalScanEnrichmentProviderOptions, - KloLocalScanEnrichmentInput, - KloLocalScanEnrichmentProviders, - KloLocalScanEnrichmentResult, + KtxLocalScanEnrichmentInput, + KtxLocalScanEnrichmentProviders, + KtxLocalScanEnrichmentResult, } from './local-enrichment.js'; export { createDeterministicLocalScanEnrichmentProviders, runLocalScanEnrichment, - snapshotToKloEnrichedSchema, + snapshotToKtxEnrichedSchema, } from './local-enrichment.js'; export type { WriteLocalScanEnrichmentArtifactsInput, @@ -109,182 +109,182 @@ export { getLocalScanReport, getLocalScanStatus, runLocalScan } from './local-sc export type { ReadLocalScanStructuralSnapshotInput } from './local-structural-artifacts.js'; export { readLocalScanStructuralSnapshot } from './local-structural-artifacts.js'; export type { - KloEnrichmentScanPhaseResult, - KloScanOrchestratorOptions, - KloScanOrchestratorRunInput, - KloScanOrchestratorRunResult, - KloStructuralScanPhaseResult, + KtxEnrichmentScanPhaseResult, + KtxScanOrchestratorOptions, + KtxScanOrchestratorRunInput, + KtxScanOrchestratorRunResult, + KtxStructuralScanPhaseResult, } from './orchestrator.js'; -export { KloScanOrchestrator } from './orchestrator.js'; +export { KtxScanOrchestrator } from './orchestrator.js'; export type { - KloRelationshipArtifactStatus, + KtxRelationshipArtifactStatus, ReadLocalScanRelationshipArtifactsResult, } from './relationship-artifacts.js'; export { readLocalScanRelationshipArtifacts } from './relationship-artifacts.js'; export type { - KloRelationshipBenchmarkReport, - KloRelationshipBenchmarkReportCase, - KloRelationshipBenchmarkReportCaseStatus, + KtxRelationshipBenchmarkReport, + KtxRelationshipBenchmarkReportCase, + KtxRelationshipBenchmarkReportCaseStatus, } from './relationship-benchmark-report.js'; export { - buildKloRelationshipBenchmarkReport, - formatKloRelationshipBenchmarkReportMarkdown, + buildKtxRelationshipBenchmarkReport, + formatKtxRelationshipBenchmarkReportMarkdown, } from './relationship-benchmark-report.js'; export type { - KloRelationshipBenchmarkCaseResult, - KloRelationshipBenchmarkDetectedLink, - KloRelationshipBenchmarkDetectedPk, - KloRelationshipBenchmarkDetector, - KloRelationshipBenchmarkDetectorInput, - KloRelationshipBenchmarkDetectorResult, - KloRelationshipBenchmarkExpectedLink, - KloRelationshipBenchmarkExpectedLinks, - KloRelationshipBenchmarkExpectedPk, - KloRelationshipBenchmarkFixture, - KloRelationshipBenchmarkMetrics, - KloRelationshipBenchmarkMode, - KloRelationshipBenchmarkStatus, - KloRelationshipBenchmarkSuiteResult, - KloRelationshipBenchmarkTier, + KtxRelationshipBenchmarkCaseResult, + KtxRelationshipBenchmarkDetectedLink, + KtxRelationshipBenchmarkDetectedPk, + KtxRelationshipBenchmarkDetector, + KtxRelationshipBenchmarkDetectorInput, + KtxRelationshipBenchmarkDetectorResult, + KtxRelationshipBenchmarkExpectedLink, + KtxRelationshipBenchmarkExpectedLinks, + KtxRelationshipBenchmarkExpectedPk, + KtxRelationshipBenchmarkFixture, + KtxRelationshipBenchmarkMetrics, + KtxRelationshipBenchmarkMode, + KtxRelationshipBenchmarkStatus, + KtxRelationshipBenchmarkSuiteResult, + KtxRelationshipBenchmarkTier, } from './relationship-benchmarks.js'; export { - currentKloRelationshipBenchmarkDetector, - kloRelationshipBenchmarkDetectorWithLlm, - KLO_RELATIONSHIP_BENCHMARK_MODES, - KLO_RELATIONSHIP_BENCHMARK_TIERS, - loadKloRelationshipBenchmarkFixture, - loadKloRelationshipBenchmarkFixtures, - maskKloRelationshipBenchmarkSnapshot, - runKloRelationshipBenchmarkCase, - runKloRelationshipBenchmarkSuite, + currentKtxRelationshipBenchmarkDetector, + ktxRelationshipBenchmarkDetectorWithLlm, + KTX_RELATIONSHIP_BENCHMARK_MODES, + KTX_RELATIONSHIP_BENCHMARK_TIERS, + loadKtxRelationshipBenchmarkFixture, + loadKtxRelationshipBenchmarkFixtures, + maskKtxRelationshipBenchmarkSnapshot, + runKtxRelationshipBenchmarkCase, + runKtxRelationshipBenchmarkSuite, } from './relationship-benchmarks.js'; export type { - ApplyKloRelationshipValidationBudgetInput, - KloRelationshipBudgetedCandidate, - KloRelationshipValidationBudget, - KloRelationshipValidationBudgetResult, + ApplyKtxRelationshipValidationBudgetInput, + KtxRelationshipBudgetedCandidate, + KtxRelationshipValidationBudget, + KtxRelationshipValidationBudgetResult, } from './relationship-budget.js'; export { - applyKloRelationshipValidationBudget, - defaultKloRelationshipValidationBudget, + applyKtxRelationshipValidationBudget, + defaultKtxRelationshipValidationBudget, } from './relationship-budget.js'; export type { - KloRelationshipDiscoveryCandidate, - KloRelationshipDiscoveryCandidateEvidence, - KloRelationshipDiscoveryCandidateOptions, - KloRelationshipDiscoveryCandidateSource, - KloRelationshipDiscoveryCandidateStatus, - KloRelationshipInferredTargetPk, + KtxRelationshipDiscoveryCandidate, + KtxRelationshipDiscoveryCandidateEvidence, + KtxRelationshipDiscoveryCandidateOptions, + KtxRelationshipDiscoveryCandidateSource, + KtxRelationshipDiscoveryCandidateStatus, + KtxRelationshipInferredTargetPk, } from './relationship-candidates.js'; export { - generateKloRelationshipDiscoveryCandidates, - inferKloRelationshipTargetPks, - mergeKloRelationshipDiscoveryCandidates, + generateKtxRelationshipDiscoveryCandidates, + inferKtxRelationshipTargetPks, + mergeKtxRelationshipDiscoveryCandidates, } from './relationship-candidates.js'; export type { - DiscoverKloCompositeRelationshipsInput, - DiscoverKloCompositeRelationshipsResult, - KloCompositePrimaryKeyCandidate, - KloCompositeRelationshipCandidate, - KloCompositeRelationshipStatus, - KloCompositeRelationshipTupleEndpoint, - KloCompositeRelationshipValidationEvidence, + DiscoverKtxCompositeRelationshipsInput, + DiscoverKtxCompositeRelationshipsResult, + KtxCompositePrimaryKeyCandidate, + KtxCompositeRelationshipCandidate, + KtxCompositeRelationshipStatus, + KtxCompositeRelationshipTupleEndpoint, + KtxCompositeRelationshipValidationEvidence, } from './relationship-composite-candidates.js'; -export { discoverKloCompositeRelationships } from './relationship-composite-candidates.js'; +export { discoverKtxCompositeRelationships } from './relationship-composite-candidates.js'; export type { - BuildKloRelationshipArtifactsInput, - BuildKloRelationshipDiagnosticsInput, - EmptyKloRelationshipProfileArtifactInput, - KloRelationshipArtifact, - KloRelationshipArtifactEdge, - KloRelationshipArtifactEndpoint, - KloRelationshipDiagnosticsArtifact, - KloRelationshipDiagnosticsSummary, - KloRelationshipDiagnosticsThresholds, - KloRelationshipDiagnosticsValidation, + BuildKtxRelationshipArtifactsInput, + BuildKtxRelationshipDiagnosticsInput, + EmptyKtxRelationshipProfileArtifactInput, + KtxRelationshipArtifact, + KtxRelationshipArtifactEdge, + KtxRelationshipArtifactEndpoint, + KtxRelationshipDiagnosticsArtifact, + KtxRelationshipDiagnosticsSummary, + KtxRelationshipDiagnosticsThresholds, + KtxRelationshipDiagnosticsValidation, } from './relationship-diagnostics.js'; export { - buildKloRelationshipArtifacts, - buildKloRelationshipDiagnostics, - emptyKloRelationshipProfileArtifact, + buildKtxRelationshipArtifacts, + buildKtxRelationshipDiagnostics, + emptyKtxRelationshipProfileArtifact, } from './relationship-diagnostics.js'; export type { - BuildKloRelationshipFeedbackCalibrationReportInput, + BuildKtxRelationshipFeedbackCalibrationReportInput, CalibrateLocalRelationshipFeedbackLabelsInput, - KloRelationshipFeedbackCalibrationBucket, - KloRelationshipFeedbackCalibrationLabel, - KloRelationshipFeedbackCalibrationReport, + KtxRelationshipFeedbackCalibrationBucket, + KtxRelationshipFeedbackCalibrationLabel, + KtxRelationshipFeedbackCalibrationReport, } from './relationship-feedback-calibration.js'; export { - buildKloRelationshipFeedbackCalibrationReport, + buildKtxRelationshipFeedbackCalibrationReport, calibrateLocalRelationshipFeedbackLabels, - formatKloRelationshipFeedbackCalibrationMarkdown, + formatKtxRelationshipFeedbackCalibrationMarkdown, } from './relationship-feedback-calibration.js'; export type { ExportLocalRelationshipFeedbackLabelsInput, ExportLocalRelationshipFeedbackLabelsResult, - KloRelationshipFeedbackDecisionFilter, - KloRelationshipFeedbackExportWarning, - KloRelationshipFeedbackLabel, + KtxRelationshipFeedbackDecisionFilter, + KtxRelationshipFeedbackExportWarning, + KtxRelationshipFeedbackLabel, } from './relationship-feedback-export.js'; export { exportLocalRelationshipFeedbackLabels, - formatKloRelationshipFeedbackLabelsJsonl, + formatKtxRelationshipFeedbackLabelsJsonl, } from './relationship-feedback-export.js'; export { - collectKloFormalMetadataRelationships, - type KloFormalMetadataRelationshipCollection, + collectKtxFormalMetadataRelationships, + type KtxFormalMetadataRelationshipCollection, } from './relationship-formal-metadata.js'; export type { - KloRelationshipGraphResolutionResult, - KloRelationshipGraphResolverSettings, - KloResolvedRelationshipDiscoveryCandidate, - KloResolvedRelationshipGraphEvidence, - KloResolvedRelationshipPk, - KloResolvedRelationshipPkEvidence, - KloResolvedRelationshipStatus, - ResolveKloRelationshipGraphInput, + KtxRelationshipGraphResolutionResult, + KtxRelationshipGraphResolverSettings, + KtxResolvedRelationshipDiscoveryCandidate, + KtxResolvedRelationshipGraphEvidence, + KtxResolvedRelationshipPk, + KtxResolvedRelationshipPkEvidence, + KtxResolvedRelationshipStatus, + ResolveKtxRelationshipGraphInput, } from './relationship-graph-resolver.js'; -export { resolveKloRelationshipGraph } from './relationship-graph-resolver.js'; +export { resolveKtxRelationshipGraph } from './relationship-graph-resolver.js'; export type { - KloRelationshipLlmProposalGenerateText, - KloRelationshipLlmProposalResult, - KloRelationshipLlmProposalSettings, - ProposeKloRelationshipCandidatesWithLlmInput, + KtxRelationshipLlmProposalGenerateText, + KtxRelationshipLlmProposalResult, + KtxRelationshipLlmProposalSettings, + ProposeKtxRelationshipCandidatesWithLlmInput, } from './relationship-llm-proposal.js'; -export { proposeKloRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; +export { proposeKtxRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; export type { - KloRelationshipLocalityCandidateTable, - LocalKloRelationshipCandidateTablesInput, + KtxRelationshipLocalityCandidateTable, + LocalKtxRelationshipCandidateTablesInput, } from './relationship-locality.js'; export { localCandidateTables } from './relationship-locality.js'; export type { - KloRelationshipNormalizedName, - KloRelationshipTokenInput, + KtxRelationshipNormalizedName, + KtxRelationshipTokenInput, } from './relationship-name-similarity.js'; export { - normalizeKloRelationshipName, - pluralizeKloRelationshipToken, - singularizeKloRelationshipToken, - tokenizeKloRelationshipName, + normalizeKtxRelationshipName, + pluralizeKtxRelationshipToken, + singularizeKtxRelationshipToken, + tokenizeKtxRelationshipName, tokenSimilarity, } from './relationship-name-similarity.js'; export type { - DiscoverKloRelationshipsInput, - DiscoverKloRelationshipsResult, + DiscoverKtxRelationshipsInput, + DiscoverKtxRelationshipsResult, } from './relationship-discovery.js'; -export { discoverKloRelationships } from './relationship-discovery.js'; +export { discoverKtxRelationships } from './relationship-discovery.js'; export type { - KloRelationshipColumnProfile, - KloRelationshipProfileArtifact, - KloRelationshipReadOnlyExecutor, - KloRelationshipTableProfile, - ProfileKloRelationshipSchemaInput, + KtxRelationshipColumnProfile, + KtxRelationshipProfileArtifact, + KtxRelationshipReadOnlyExecutor, + KtxRelationshipTableProfile, + ProfileKtxRelationshipSchemaInput, } from './relationship-profiling.js'; export { - formatKloRelationshipTableRef, - profileKloRelationshipSchema, - quoteKloRelationshipIdentifier, + formatKtxRelationshipTableRef, + profileKtxRelationshipSchema, + quoteKtxRelationshipIdentifier, } from './relationship-profiling.js'; export type { AppliedRelationshipReviewDecision, @@ -293,108 +293,108 @@ export type { } from './relationship-review-apply.js'; export { applyLocalScanRelationshipReviewDecisions } from './relationship-review-apply.js'; export type { - KloRelationshipReviewDecisionArtifact, - KloRelationshipReviewDecisionEntry, - KloRelationshipReviewDecisionValue, + KtxRelationshipReviewDecisionArtifact, + KtxRelationshipReviewDecisionEntry, + KtxRelationshipReviewDecisionValue, WriteLocalScanRelationshipReviewDecisionInput, WriteLocalScanRelationshipReviewDecisionResult, } from './relationship-review-decisions.js'; export { writeLocalScanRelationshipReviewDecision } from './relationship-review-decisions.js'; export type { - KloRelationshipFixtureOrigin, - KloRelationshipScoreBreakdown, - KloRelationshipScoreSignal, - KloRelationshipScoreWeights, - KloRelationshipScoringCalibrationObservation, - KloRelationshipSignalVector, + KtxRelationshipFixtureOrigin, + KtxRelationshipScoreBreakdown, + KtxRelationshipScoreSignal, + KtxRelationshipScoreWeights, + KtxRelationshipScoringCalibrationObservation, + KtxRelationshipSignalVector, } from './relationship-scoring.js'; export { calibrateWeightsFromSyntheticFixtures, - defaultKloRelationshipScoreWeights, - KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS, - normalizeKloRelationshipScoreWeights, - scoreKloRelationshipCandidate, + defaultKtxRelationshipScoreWeights, + KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS, + normalizeKtxRelationshipScoreWeights, + scoreKtxRelationshipCandidate, } from './relationship-scoring.js'; export type { AdviseLocalRelationshipFeedbackThresholdsInput, - BuildKloRelationshipThresholdAdviceReportInput, - KloRelationshipThresholdAdviceCandidate, - KloRelationshipThresholdAdviceReport, - KloRelationshipThresholdAdviceStatus, + BuildKtxRelationshipThresholdAdviceReportInput, + KtxRelationshipThresholdAdviceCandidate, + KtxRelationshipThresholdAdviceReport, + KtxRelationshipThresholdAdviceStatus, } from './relationship-threshold-advice.js'; export { adviseLocalRelationshipFeedbackThresholds, - buildKloRelationshipThresholdAdviceReport, - formatKloRelationshipThresholdAdviceMarkdown, + buildKtxRelationshipThresholdAdviceReport, + formatKtxRelationshipThresholdAdviceMarkdown, } from './relationship-threshold-advice.js'; export type { - KloRelationshipValidationEvidence, - KloRelationshipValidationSettings, - KloValidatedRelationshipDiscoveryCandidate, - KloValidatedRelationshipStatus, - ValidateKloRelationshipDiscoveryCandidatesInput, + KtxRelationshipValidationEvidence, + KtxRelationshipValidationSettings, + KtxValidatedRelationshipDiscoveryCandidate, + KtxValidatedRelationshipStatus, + ValidateKtxRelationshipDiscoveryCandidatesInput, } from './relationship-validation.js'; -export { validateKloRelationshipDiscoveryCandidates } from './relationship-validation.js'; +export { validateKtxRelationshipDiscoveryCandidates } from './relationship-validation.js'; export type { SqliteLocalScanEnrichmentStateStoreOptions } from './sqlite-local-enrichment-state-store.js'; export { SqliteLocalScanEnrichmentStateStore } from './sqlite-local-enrichment-state-store.js'; -export type { KloColumnTypeMapping } from './type-normalization.js'; +export type { KtxColumnTypeMapping } from './type-normalization.js'; export { - inferKloDimensionType, - kloColumnTypeMappingFromNative, - normalizeKloNativeType, + inferKtxDimensionType, + ktxColumnTypeMappingFromNative, + normalizeKtxNativeType, } from './type-normalization.js'; export type { - KloColumnSampleInput, - KloColumnSampleResult, - KloColumnStatsInput, - KloColumnStatsResult, - KloConnectionDriver, - KloConnectorCapabilities, - KloCredentialEnvelope, - KloCredentialEnvReference, - KloCredentialFileReference, - KloEmbeddingPort, - KloEventPropertyDiscovery, - KloEventPropertyDiscoveryInput, - KloEventPropertyValuesInput, - KloEventPropertyValuesResult, - KloEventStreamDiscoveryPort, - KloEventTypeDiscovery, - KloEventTypeDiscoveryInput, - KloNetworkEndpoint, - KloNetworkTunnelPort, - KloNetworkTunnelRequest, - KloOptionalConnectorCapabilities, - KloProgressPort, - KloProgressUpdateOptions, - KloQueryResult, - KloReadOnlyQueryInput, - KloResolvedCredentialEnvelope, - KloScanArtifactPaths, - KloScanConnector, - KloScanContext, - KloScanDiffSummary, - KloScanEnrichmentStage, - KloScanEnrichmentStateSummary, - KloScanEnrichmentSummary, - KloScanInput, - KloScanLoggerPort, - KloScanMode, - KloScanRelationshipSummary, - KloScanReport, - KloScanTrigger, - KloScanWarning, - KloScanWarningCode, - KloSchemaColumn, - KloSchemaDimensionType, - KloSchemaForeignKey, - KloSchemaScope, - KloSchemaSnapshot, - KloSchemaTable, - KloSchemaTableKind, - KloStructuralSyncStats, - KloTableRef, - KloTableSampleInput, - KloTableSampleResult, + KtxColumnSampleInput, + KtxColumnSampleResult, + KtxColumnStatsInput, + KtxColumnStatsResult, + KtxConnectionDriver, + KtxConnectorCapabilities, + KtxCredentialEnvelope, + KtxCredentialEnvReference, + KtxCredentialFileReference, + KtxEmbeddingPort, + KtxEventPropertyDiscovery, + KtxEventPropertyDiscoveryInput, + KtxEventPropertyValuesInput, + KtxEventPropertyValuesResult, + KtxEventStreamDiscoveryPort, + KtxEventTypeDiscovery, + KtxEventTypeDiscoveryInput, + KtxNetworkEndpoint, + KtxNetworkTunnelPort, + KtxNetworkTunnelRequest, + KtxOptionalConnectorCapabilities, + KtxProgressPort, + KtxProgressUpdateOptions, + KtxQueryResult, + KtxReadOnlyQueryInput, + KtxResolvedCredentialEnvelope, + KtxScanArtifactPaths, + KtxScanConnector, + KtxScanContext, + KtxScanDiffSummary, + KtxScanEnrichmentStage, + KtxScanEnrichmentStateSummary, + KtxScanEnrichmentSummary, + KtxScanInput, + KtxScanLoggerPort, + KtxScanMode, + KtxScanRelationshipSummary, + KtxScanReport, + KtxScanTrigger, + KtxScanWarning, + KtxScanWarningCode, + KtxSchemaColumn, + KtxSchemaDimensionType, + KtxSchemaForeignKey, + KtxSchemaScope, + KtxSchemaSnapshot, + KtxSchemaTable, + KtxSchemaTableKind, + KtxStructuralSyncStats, + KtxTableRef, + KtxTableSampleInput, + KtxTableSampleResult, } from './types.js'; -export { createKloConnectorCapabilities } from './types.js'; +export { createKtxConnectorCapabilities } from './types.js'; diff --git a/packages/context/src/scan/local-enrichment-artifacts.test.ts b/packages/context/src/scan/local-enrichment-artifacts.test.ts index 41a3418a..d34da036 100644 --- a/packages/context/src/scan/local-enrichment-artifacts.test.ts +++ b/packages/context/src/scan/local-enrichment-artifacts.test.ts @@ -3,12 +3,12 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import YAML from 'yaml'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; -import type { KloLocalScanEnrichmentResult } from './local-enrichment.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; +import type { KtxLocalScanEnrichmentResult } from './local-enrichment.js'; import { writeLocalScanEnrichmentArtifacts, writeLocalScanManifestShards } from './local-enrichment-artifacts.js'; -import type { KloSchemaSnapshot } from './types.js'; +import type { KtxSchemaSnapshot } from './types.js'; -const snapshot: KloSchemaSnapshot = { +const snapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-04-29T12:00:00.000Z', @@ -76,7 +76,7 @@ const snapshot: KloSchemaSnapshot = { ], }; -function enrichment(): KloLocalScanEnrichmentResult { +function enrichment(): KtxLocalScanEnrichmentResult { return { snapshot, summary: { @@ -225,11 +225,11 @@ function enrichment(): KloLocalScanEnrichmentResult { describe('writeLocalScanEnrichmentArtifacts', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-enrichment-artifacts-')); - project = await initKloProject({ + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-enrichment-artifacts-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse', }); @@ -269,8 +269,8 @@ describe('writeLocalScanEnrichmentArtifacts', () => { }, { indent: 2, lineWidth: 0 }, ), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed manifest shard', ); @@ -426,7 +426,7 @@ describe('writeLocalScanEnrichmentArtifacts', () => { it('writes formal accepted relationships into relationship artifacts and manifest shards', async () => { const source = enrichment(); - const formalEnrichment: KloLocalScanEnrichmentResult = { + const formalEnrichment: KtxLocalScanEnrichmentResult = { ...source, relationshipUpdate: { connectionId: 'warehouse', @@ -554,7 +554,7 @@ describe('writeLocalScanEnrichmentArtifacts', () => { }); it('writes accepted composite relationships to relationship artifacts and manifest shards', async () => { - const compositeSnapshot: KloSchemaSnapshot = { + const compositeSnapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-05-07T12:00:00.000Z', @@ -621,7 +621,7 @@ describe('writeLocalScanEnrichmentArtifacts', () => { }, ], }; - const compositeEnrichment: KloLocalScanEnrichmentResult = Object.assign(enrichment(), { + const compositeEnrichment: KtxLocalScanEnrichmentResult = Object.assign(enrichment(), { snapshot: compositeSnapshot, relationships: { accepted: 1, review: 0, rejected: 0, skipped: 0 }, descriptionUpdates: [], @@ -763,8 +763,8 @@ describe('writeLocalScanEnrichmentArtifacts', () => { }, { indent: 2, lineWidth: 0 }, ), - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed structural manifest shard', ); diff --git a/packages/context/src/scan/local-enrichment-artifacts.ts b/packages/context/src/scan/local-enrichment-artifacts.ts index 0cd06220..b8186b0e 100644 --- a/packages/context/src/scan/local-enrichment-artifacts.ts +++ b/packages/context/src/scan/local-enrichment-artifacts.ts @@ -7,31 +7,31 @@ import { type LiveDatabaseManifestShard, type LiveDatabaseManifestTableData, } from '../ingest/index.js'; -import type { KloScanRelationshipConfig } from '../project/config.js'; -import type { KloLocalProject } from '../project/index.js'; -import type { KloLocalScanEnrichmentResult } from './local-enrichment.js'; +import type { KtxScanRelationshipConfig } from '../project/config.js'; +import type { KtxLocalProject } from '../project/index.js'; +import type { KtxLocalScanEnrichmentResult } from './local-enrichment.js'; import { - buildKloRelationshipArtifacts, - buildKloRelationshipDiagnostics, - emptyKloRelationshipProfileArtifact, + buildKtxRelationshipArtifacts, + buildKtxRelationshipDiagnostics, + emptyKtxRelationshipProfileArtifact, } from './relationship-diagnostics.js'; -import type { KloConnectionDriver, KloSchemaColumn, KloSchemaSnapshot, KloSchemaTable } from './types.js'; +import type { KtxConnectionDriver, KtxSchemaColumn, KtxSchemaSnapshot, KtxSchemaTable } from './types.js'; const LIVE_DATABASE_ADAPTER = 'live-database'; -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; const SCHEMA_DIR = '_schema'; const SL_DIR_PREFIX = 'semantic-layer'; export interface WriteLocalScanManifestShardsInput { - project: KloLocalProject; + project: KtxLocalProject; connectionId: string; syncId: string; - driver: KloConnectionDriver; - snapshot: KloSchemaSnapshot; + driver: KtxConnectionDriver; + snapshot: KtxSchemaSnapshot; dryRun: boolean; - descriptionUpdates?: KloLocalScanEnrichmentResult['descriptionUpdates']; - relationshipUpdate?: KloLocalScanEnrichmentResult['relationshipUpdate']; + descriptionUpdates?: KtxLocalScanEnrichmentResult['descriptionUpdates']; + relationshipUpdate?: KtxLocalScanEnrichmentResult['relationshipUpdate']; } export interface WriteLocalScanManifestShardsResult { @@ -40,13 +40,13 @@ export interface WriteLocalScanManifestShardsResult { } export interface WriteLocalScanEnrichmentArtifactsInput { - project: KloLocalProject; + project: KtxLocalProject; connectionId: string; syncId: string; - driver: KloConnectionDriver; - enrichment: KloLocalScanEnrichmentResult; + driver: KtxConnectionDriver; + enrichment: KtxLocalScanEnrichmentResult; dryRun: boolean; - relationshipSettings?: KloScanRelationshipConfig; + relationshipSettings?: KtxScanRelationshipConfig; } export interface WriteLocalScanEnrichmentArtifactsResult extends WriteLocalScanManifestShardsResult { @@ -58,7 +58,7 @@ interface ExistingManifestState { preservedJoins: Map; } -type LocalDescriptionUpdates = KloLocalScanEnrichmentResult['descriptionUpdates']; +type LocalDescriptionUpdates = KtxLocalScanEnrichmentResult['descriptionUpdates']; function artifactDir(connectionId: string, syncId: string): string { return `raw-sources/${connectionId}/${LIVE_DATABASE_ADAPTER}/${syncId}/enrichment`; @@ -69,7 +69,7 @@ function schemaDir(connectionId: string): string { } function tableDescription( - table: KloSchemaTable, + table: KtxSchemaTable, descriptionUpdates: LocalDescriptionUpdates = [], ): Record | undefined { const update = descriptionUpdates.find((candidate) => candidate.table.name === table.name); @@ -84,8 +84,8 @@ function tableDescription( } function columnDescription( - table: KloSchemaTable, - column: KloSchemaColumn, + table: KtxSchemaTable, + column: KtxSchemaColumn, descriptionUpdates: LocalDescriptionUpdates = [], ): Record | undefined { const update = descriptionUpdates.find((candidate) => candidate.table.name === table.name); @@ -101,7 +101,7 @@ function columnDescription( } function snapshotTablesToManifestData( - snapshot: KloSchemaSnapshot, + snapshot: KtxSchemaSnapshot, descriptionUpdates: LocalDescriptionUpdates = [], ): LiveDatabaseManifestTableData[] { return snapshot.tables.map((table) => ({ @@ -119,7 +119,7 @@ function snapshotTablesToManifestData( })); } -function formalJoins(snapshot: KloSchemaSnapshot): LiveDatabaseManifestJoinData[] { +function formalJoins(snapshot: KtxSchemaSnapshot): LiveDatabaseManifestJoinData[] { const joins: LiveDatabaseManifestJoinData[] = []; for (const table of snapshot.tables) { for (const foreignKey of table.foreignKeys) { @@ -137,7 +137,7 @@ function formalJoins(snapshot: KloSchemaSnapshot): LiveDatabaseManifestJoinData[ } function acceptedRelationshipJoins( - relationshipUpdate: KloLocalScanEnrichmentResult['relationshipUpdate'] | undefined, + relationshipUpdate: KtxLocalScanEnrichmentResult['relationshipUpdate'] | undefined, ): LiveDatabaseManifestJoinData[] { return (relationshipUpdate?.accepted ?? []).map((relationship) => ({ fromTable: relationship.from.table.name, @@ -150,8 +150,8 @@ function acceptedRelationshipJoins( } function relationshipJoins( - snapshot: KloSchemaSnapshot, - relationshipUpdate: KloLocalScanEnrichmentResult['relationshipUpdate'] | undefined, + snapshot: KtxSchemaSnapshot, + relationshipUpdate: KtxLocalScanEnrichmentResult['relationshipUpdate'] | undefined, ): LiveDatabaseManifestJoinData[] { const accepted = acceptedRelationshipJoins(relationshipUpdate); const manual = accepted.filter((relationship) => relationship.source === 'manual'); @@ -159,7 +159,7 @@ function relationshipJoins( return [...manual, ...formalJoins(snapshot), ...generated]; } -function validColumns(snapshot: KloSchemaSnapshot): Map> { +function validColumns(snapshot: KtxSchemaSnapshot): Map> { return new Map(snapshot.tables.map((table) => [table.name, new Set(table.columns.map((column) => column.name))])); } @@ -190,9 +190,9 @@ function joinReferencesExistingColumns( } async function loadExistingManifestState( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, - snapshot: KloSchemaSnapshot, + snapshot: KtxSchemaSnapshot, ): Promise { const descriptions = new Map(); const preservedJoins = new Map(); @@ -245,7 +245,7 @@ async function loadExistingManifestState( } async function writeJsonArtifact( - project: KloLocalProject, + project: KtxLocalProject, path: string, value: unknown, commitMessage: string, @@ -340,7 +340,7 @@ export async function writeLocalScanEnrichmentArtifacts( } enrichmentArtifacts.push(relationshipsArtifact, relationshipProfileArtifact, relationshipDiagnosticsArtifact); const hasResolvedRelationships = input.enrichment.resolvedRelationships !== null; - const relationshipArtifacts = buildKloRelationshipArtifacts({ + const relationshipArtifacts = buildKtxRelationshipArtifacts({ connectionId: input.connectionId, resolvedRelationships: hasResolvedRelationships ? (input.enrichment.resolvedRelationships ?? []) : undefined, compositeRelationships: input.enrichment.compositeRelationships ?? undefined, @@ -353,12 +353,12 @@ export async function writeLocalScanEnrichmentArtifacts( }); const relationshipProfile = input.enrichment.relationshipProfile ?? - emptyKloRelationshipProfileArtifact({ + emptyKtxRelationshipProfileArtifact({ connectionId: input.connectionId, driver: input.driver, reason: 'relationship_profiling_not_run', }); - const relationshipDiagnostics = buildKloRelationshipDiagnostics({ + const relationshipDiagnostics = buildKtxRelationshipDiagnostics({ connectionId: input.connectionId, artifacts: relationshipArtifacts, profile: relationshipProfile, diff --git a/packages/context/src/scan/local-enrichment.test.ts b/packages/context/src/scan/local-enrichment.test.ts index 8ac0b59c..c25dae61 100644 --- a/packages/context/src/scan/local-enrichment.test.ts +++ b/packages/context/src/scan/local-enrichment.test.ts @@ -1,28 +1,28 @@ import Database from 'better-sqlite3'; import { describe, expect, it, vi } from 'vitest'; -import { buildDefaultKloProjectConfig } from '../project/config.js'; +import { buildDefaultKtxProjectConfig } from '../project/config.js'; import type { - KloScanEnrichmentCompletedStage, - KloScanEnrichmentFailedStage, - KloScanEnrichmentStageLookup, - KloScanEnrichmentStateStore, + KtxScanEnrichmentCompletedStage, + KtxScanEnrichmentFailedStage, + KtxScanEnrichmentStageLookup, + KtxScanEnrichmentStateStore, } from './enrichment-state.js'; import { createDeterministicLocalScanEnrichmentProviders, runLocalScanEnrichment, - snapshotToKloEnrichedSchema, + snapshotToKtxEnrichedSchema, } from './local-enrichment.js'; import { createLocalScanEnrichmentProvidersFromConfig } from './local-scan.js'; import { - createKloConnectorCapabilities, - type KloQueryResult, - type KloReadOnlyQueryInput, - type KloScanConnector, - type KloScanContext, - type KloSchemaSnapshot, + createKtxConnectorCapabilities, + type KtxQueryResult, + type KtxReadOnlyQueryInput, + type KtxScanConnector, + type KtxScanContext, + type KtxSchemaSnapshot, } from './types.js'; -const snapshot: KloSchemaSnapshot = { +const snapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-04-29T12:00:00.000Z', @@ -81,11 +81,11 @@ const snapshot: KloSchemaSnapshot = { ], }; -function connector(): KloScanConnector { +function connector(): KtxScanConnector { return { id: 'test:warehouse', driver: 'postgres', - capabilities: createKloConnectorCapabilities({ + capabilities: createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, readOnlySql: true, @@ -108,7 +108,7 @@ function connector(): KloScanConnector { class InMemorySqliteExecutor { readonly db = new Database(':memory:'); - executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); return Promise.resolve({ @@ -124,7 +124,7 @@ class InMemorySqliteExecutor { } } -function noDeclaredRelationshipSnapshot(): KloSchemaSnapshot { +function noDeclaredRelationshipSnapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'sqlite', @@ -185,16 +185,16 @@ function noDeclaredRelationshipSnapshot(): KloSchemaSnapshot { }; } -function memoryEnrichmentStateStore(): KloScanEnrichmentStateStore { - const records = new Map(); - const key = (input: Pick) => `${input.runId}:${input.stage}`; +function memoryEnrichmentStateStore(): KtxScanEnrichmentStateStore { + const records = new Map(); + const key = (input: Pick) => `${input.runId}:${input.stage}`; return { - async findCompletedStage(input: KloScanEnrichmentStageLookup) { + async findCompletedStage(input: KtxScanEnrichmentStageLookup) { const record = records.get(key(input)); if (!record || record.status !== 'completed' || record.inputHash !== input.inputHash) { return null; } - return record as KloScanEnrichmentCompletedStage; + return record as KtxScanEnrichmentCompletedStage; }, async saveCompletedStage(input) { records.set(key(input), { @@ -218,7 +218,7 @@ function memoryEnrichmentStateStore(): KloScanEnrichmentStateStore { describe('local scan enrichment', () => { it('maps a scan snapshot into relationship detector schema', () => { - const schema = snapshotToKloEnrichedSchema(snapshot); + const schema = snapshotToKtxEnrichedSchema(snapshot); expect(schema.connectionId).toBe('warehouse'); expect(schema.tables).toHaveLength(2); @@ -262,7 +262,7 @@ describe('local scan enrichment', () => { ), }; - const schema = snapshotToKloEnrichedSchema(snapshotWithForeignKey); + const schema = snapshotToKtxEnrichedSchema(snapshotWithForeignKey); expect(schema.relationships).toEqual([ { @@ -306,7 +306,7 @@ describe('local scan enrichment', () => { expect(result.summary.statisticalValidation).toBe('skipped'); expect(result.warnings).toContainEqual({ code: 'relationship_validation_failed', - message: 'KLO scan connector advertises readOnlySql but does not expose executeReadOnly', + message: 'KTX scan connector advertises readOnlySql but does not expose executeReadOnly', recoverable: true, metadata: { capability: 'readOnlySql' }, }); @@ -324,7 +324,7 @@ describe('local scan enrichment', () => { const scanConnector = { ...connector(), driver: 'sqlite' as const, - capabilities: createKloConnectorCapabilities({ readOnlySql: true, columnStats: true }), + capabilities: createKtxConnectorCapabilities({ readOnlySql: true, columnStats: true }), introspect: vi.fn(async () => noDeclaredRelationshipSnapshot()), executeReadOnly: executor.executeReadOnly.bind(executor), }; @@ -371,7 +371,7 @@ describe('local scan enrichment', () => { }, }, relationshipSettings: { - ...buildDefaultKloProjectConfig('warehouse').scan.relationships, + ...buildDefaultKtxProjectConfig('warehouse').scan.relationships, llmProposals: false, maxLlmTablesPerBatch: 40, }, @@ -383,7 +383,7 @@ describe('local scan enrichment', () => { it('skips relationship detection when scan relationships are disabled', async () => { const settings = { - ...buildDefaultKloProjectConfig('warehouse').scan.relationships, + ...buildDefaultKtxProjectConfig('warehouse').scan.relationships, enabled: false, }; const result = await runLocalScanEnrichment({ @@ -488,7 +488,7 @@ describe('local scan enrichment', () => { }); it('splits enrichment embedding requests by provider batch size', async () => { - const manyColumnSnapshot: KloSchemaSnapshot = { + const manyColumnSnapshot: KtxSchemaSnapshot = { ...snapshot, tables: [ { @@ -644,7 +644,7 @@ describe('local scan enrichment', () => { const scanConnector = { ...connector(), driver: 'sqlite' as const, - capabilities: createKloConnectorCapabilities({ readOnlySql: true, columnStats: true }), + capabilities: createKtxConnectorCapabilities({ readOnlySql: true, columnStats: true }), introspect: vi.fn(async () => noDeclaredRelationshipSnapshot()), executeReadOnly: executor.executeReadOnly.bind(executor), }; @@ -695,10 +695,10 @@ describe('local scan enrichment', () => { }); it('resolves gateway LLM providers and OpenAI embeddings from local scan config', () => { - const createKloLlmProvider = vi.fn(() => ({ + const createKtxLlmProvider = vi.fn(() => ({ getModel: vi.fn().mockReturnValue({ modelId: 'provider/language-model', provider: 'gateway' }), })); - const createKloEmbeddingProvider = vi.fn(() => ({ + const createKtxEmbeddingProvider = vi.fn(() => ({ dimensions: 1536, maxBatchSize: 8, embed: vi.fn(), @@ -724,18 +724,18 @@ describe('local scan enrichment', () => { models: { default: 'provider/language-model' }, }, { - createKloLlmProvider: createKloLlmProvider as any, - createKloEmbeddingProvider: createKloEmbeddingProvider as any, + createKtxLlmProvider: createKtxLlmProvider as any, + createKtxEmbeddingProvider: createKtxEmbeddingProvider as any, env: { OPENAI_API_KEY: 'openai-key' }, }, ); expect(providers?.embedding.dimensions).toBe(1536); expect(providers?.embedding.maxBatchSize).toBe(8); - expect(createKloLlmProvider).toHaveBeenCalledWith( + expect(createKtxLlmProvider).toHaveBeenCalledWith( expect.objectContaining({ backend: 'gateway', modelSlots: { default: 'provider/language-model' } }), ); - expect(createKloEmbeddingProvider).toHaveBeenCalledWith( + expect(createKtxEmbeddingProvider).toHaveBeenCalledWith( expect.objectContaining({ backend: 'openai', model: 'provider/embedding-model' }), ); }); diff --git a/packages/context/src/scan/local-enrichment.ts b/packages/context/src/scan/local-enrichment.ts index 51b425c8..cefecadb 100644 --- a/packages/context/src/scan/local-enrichment.ts +++ b/packages/context/src/scan/local-enrichment.ts @@ -1,43 +1,43 @@ -import type { KloLlmProvider } from '@klo/llm'; -import { buildDefaultKloProjectConfig, type KloScanRelationshipConfig } from '../project/config.js'; -import { type KloDescriptionColumnTable, KloDescriptionGenerator } from './description-generation.js'; -import { buildKloColumnEmbeddingText } from './embedding-text.js'; +import type { KtxLlmProvider } from '@ktx/llm'; +import { buildDefaultKtxProjectConfig, type KtxScanRelationshipConfig } from '../project/config.js'; +import { type KtxDescriptionColumnTable, KtxDescriptionGenerator } from './description-generation.js'; +import { buildKtxColumnEmbeddingText } from './embedding-text.js'; import { - completedKloScanEnrichmentStateSummary, - computeKloScanEnrichmentInputHash, - type KloScanEnrichmentStateStore, - summarizeKloScanEnrichmentState, + completedKtxScanEnrichmentStateSummary, + computeKtxScanEnrichmentInputHash, + type KtxScanEnrichmentStateStore, + summarizeKtxScanEnrichmentState, } from './enrichment-state.js'; -import { skippedKloScanEnrichmentSummary } from './enrichment-summary.js'; +import { skippedKtxScanEnrichmentSummary } from './enrichment-summary.js'; import type { - KloEmbeddingUpdate, - KloEnrichedColumn, - KloEnrichedRelationship, - KloEnrichedSchema, - KloEnrichedTable, - KloRelationshipEndpoint, - KloRelationshipUpdate, + KtxEmbeddingUpdate, + KtxEnrichedColumn, + KtxEnrichedRelationship, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxRelationshipEndpoint, + KtxRelationshipUpdate, } from './enrichment-types.js'; -import type { KloCompositeRelationshipCandidate } from './relationship-composite-candidates.js'; -import type { KloResolvedRelationshipDiscoveryCandidate } from './relationship-graph-resolver.js'; -import { discoverKloRelationships } from './relationship-discovery.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxCompositeRelationshipCandidate } from './relationship-composite-candidates.js'; +import type { KtxResolvedRelationshipDiscoveryCandidate } from './relationship-graph-resolver.js'; +import { discoverKtxRelationships } from './relationship-discovery.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; import type { - KloEmbeddingPort, - KloProgressPort, - KloScanConnector, - KloScanContext, - KloScanEnrichmentStage, - KloScanEnrichmentStateSummary, - KloScanEnrichmentSummary, - KloScanMode, - KloScanRelationshipSummary, - KloScanWarning, - KloSchemaColumn, - KloSchemaForeignKey, - KloSchemaSnapshot, - KloSchemaTable, - KloTableRef, + KtxEmbeddingPort, + KtxProgressPort, + KtxScanConnector, + KtxScanContext, + KtxScanEnrichmentStage, + KtxScanEnrichmentStateSummary, + KtxScanEnrichmentSummary, + KtxScanMode, + KtxScanRelationshipSummary, + KtxScanWarning, + KtxSchemaColumn, + KtxSchemaForeignKey, + KtxSchemaSnapshot, + KtxSchemaTable, + KtxTableRef, } from './types.js'; export interface DeterministicLocalScanEnrichmentProviderOptions { @@ -45,52 +45,52 @@ export interface DeterministicLocalScanEnrichmentProviderOptions { maxBatchSize?: number; } -export interface KloLocalScanEnrichmentProviders { - llm: KloLlmProvider; - embedding: KloEmbeddingPort; +export interface KtxLocalScanEnrichmentProviders { + llm: KtxLlmProvider; + embedding: KtxEmbeddingPort; } -export interface KloLocalScanEnrichmentInput { +export interface KtxLocalScanEnrichmentInput { connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; detectRelationships?: boolean; - connector: KloScanConnector; - context: KloScanContext; - providers: KloLocalScanEnrichmentProviders | null; - stateStore?: KloScanEnrichmentStateStore | null; + connector: KtxScanConnector; + context: KtxScanContext; + providers: KtxLocalScanEnrichmentProviders | null; + stateStore?: KtxScanEnrichmentStateStore | null; syncId?: string; providerIdentity?: Record; - relationshipSettings?: KloScanRelationshipConfig; + relationshipSettings?: KtxScanRelationshipConfig; now?: () => Date; } -export interface KloLocalScanEnrichmentResult { - snapshot: KloSchemaSnapshot; - summary: KloScanEnrichmentSummary; - relationships: KloScanRelationshipSummary; - state: KloScanEnrichmentStateSummary; - warnings: KloScanWarning[]; +export interface KtxLocalScanEnrichmentResult { + snapshot: KtxSchemaSnapshot; + summary: KtxScanEnrichmentSummary; + relationships: KtxScanRelationshipSummary; + state: KtxScanEnrichmentStateSummary; + warnings: KtxScanWarning[]; descriptionUpdates: Array<{ - table: KloTableRef; + table: KtxTableRef; tableDescription: string | null; columnDescriptions: Record; }>; - embeddingUpdates: KloEmbeddingUpdate[]; - relationshipUpdate: KloRelationshipUpdate | null; - relationshipProfile: KloRelationshipProfileArtifact | null; - resolvedRelationships: KloResolvedRelationshipDiscoveryCandidate[] | null; - compositeRelationships: KloCompositeRelationshipCandidate[] | null; + embeddingUpdates: KtxEmbeddingUpdate[]; + relationshipUpdate: KtxRelationshipUpdate | null; + relationshipProfile: KtxRelationshipProfileArtifact | null; + resolvedRelationships: KtxResolvedRelationshipDiscoveryCandidate[] | null; + compositeRelationships: KtxCompositeRelationshipCandidate[] | null; } -function tableId(table: KloSchemaTable): string { +function tableId(table: KtxSchemaTable): string { return [table.catalog, table.db, table.name].filter((value): value is string => Boolean(value)).join('.'); } -function columnId(table: KloSchemaTable, column: KloSchemaColumn): string { +function columnId(table: KtxSchemaTable, column: KtxSchemaColumn): string { return `${tableId(table)}.${column.name}`; } -function tableRef(table: KloSchemaTable): KloTableRef { +function tableRef(table: KtxSchemaTable): KtxTableRef { return { catalog: table.catalog, db: table.db, @@ -98,7 +98,7 @@ function tableRef(table: KloSchemaTable): KloTableRef { }; } -function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn): KloRelationshipEndpoint { +function endpoint(table: KtxEnrichedTable, column: KtxEnrichedColumn): KtxRelationshipEndpoint { return { tableId: table.id, columnIds: [column.id], @@ -107,11 +107,11 @@ function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn): KloRelati }; } -function relationshipId(from: KloRelationshipEndpoint, to: KloRelationshipEndpoint): string { +function relationshipId(from: KtxRelationshipEndpoint, to: KtxRelationshipEndpoint): string { return `${from.tableId}:(${from.columnIds.join(',')})->${to.tableId}:(${to.columnIds.join(',')})`; } -function targetMatchesForeignKey(table: KloEnrichedTable, foreignKey: KloSchemaForeignKey): boolean { +function targetMatchesForeignKey(table: KtxEnrichedTable, foreignKey: KtxSchemaForeignKey): boolean { return ( table.ref.name === foreignKey.toTable && (foreignKey.toCatalog === null || table.ref.catalog === foreignKey.toCatalog) && @@ -120,11 +120,11 @@ function targetMatchesForeignKey(table: KloEnrichedTable, foreignKey: KloSchemaF } function formalRelationshipsFromSnapshot( - snapshot: KloSchemaSnapshot, - tables: readonly KloEnrichedTable[], -): KloEnrichedRelationship[] { + snapshot: KtxSchemaSnapshot, + tables: readonly KtxEnrichedTable[], +): KtxEnrichedRelationship[] { const tableById = new Map(tables.map((table) => [table.id, table])); - const relationships: KloEnrichedRelationship[] = []; + const relationships: KtxEnrichedRelationship[] = []; for (const sourceTableSnapshot of snapshot.tables) { const sourceTable = tableById.get(tableId(sourceTableSnapshot)); @@ -157,7 +157,7 @@ function formalRelationshipsFromSnapshot( return relationships.sort((left, right) => left.id.localeCompare(right.id)); } -function providerlessEnrichedWarning(relationshipDetection: boolean): KloScanWarning { +function providerlessEnrichedWarning(relationshipDetection: boolean): KtxScanWarning { return { code: 'scan_enrichment_backend_not_configured', message: @@ -183,7 +183,7 @@ function hashEmbedding(text: string, dimensions: number): number[] { export function createDeterministicLocalScanEnrichmentProviders( options: DeterministicLocalScanEnrichmentProviderOptions = {}, -): KloLocalScanEnrichmentProviders { +): KtxLocalScanEnrichmentProviders { const dimensions = options.embeddingDimensions ?? 8; const maxBatchSize = options.maxBatchSize ?? 64; return { @@ -198,14 +198,14 @@ export function createDeterministicLocalScanEnrichmentProviders( }; } -function deterministicLlmProvider(): KloLlmProvider { +function deterministicLlmProvider(): KtxLlmProvider { const model = { modelId: 'deterministic-scan', provider: 'deterministic' }; return { getModel() { - return model as ReturnType; + return model as ReturnType; }, getModelByName() { - return model as ReturnType; + return model as ReturnType; }, cacheMarker() { return undefined; @@ -237,14 +237,14 @@ function deterministicLlmProvider(): KloLlmProvider { }; } -export function snapshotToKloEnrichedSchema( - snapshot: KloSchemaSnapshot, +export function snapshotToKtxEnrichedSchema( + snapshot: KtxSchemaSnapshot, embeddingsByColumnId: ReadonlyMap = new Map(), -): KloEnrichedSchema { - const tables: KloEnrichedTable[] = snapshot.tables.map((table) => { +): KtxEnrichedSchema { + const tables: KtxEnrichedTable[] = snapshot.tables.map((table) => { const id = tableId(table); const ref = tableRef(table); - const columns: KloEnrichedColumn[] = table.columns.map((column) => { + const columns: KtxEnrichedColumn[] = table.columns.map((column) => { const idForColumn = columnId(table, column); return { id: idForColumn, @@ -283,7 +283,7 @@ export function snapshotToKloEnrichedSchema( }; } -function descriptionTable(table: KloSchemaTable): KloDescriptionColumnTable { +function descriptionTable(table: KtxSchemaTable): KtxDescriptionColumnTable { return { catalog: table.catalog, db: table.db, @@ -300,13 +300,13 @@ function embeddingBatchSize(maxBatchSize: number): number { } async function generateDescriptions(input: { - snapshot: KloSchemaSnapshot; - connector: KloScanConnector; - context: KloScanContext; - providers: KloLocalScanEnrichmentProviders; - progress?: KloProgressPort; -}): Promise { - const generator = new KloDescriptionGenerator({ + snapshot: KtxSchemaSnapshot; + connector: KtxScanConnector; + context: KtxScanContext; + providers: KtxLocalScanEnrichmentProviders; + progress?: KtxProgressPort; +}): Promise { + const generator = new KtxDescriptionGenerator({ llmProvider: input.providers.llm, settings: { columnMaxWords: 16, @@ -316,7 +316,7 @@ async function generateDescriptions(input: { }, }); - const updates: KloLocalScanEnrichmentResult['descriptionUpdates'] = []; + const updates: KtxLocalScanEnrichmentResult['descriptionUpdates'] = []; const totalTables = input.snapshot.tables.length; if (totalTables === 0) { await input.progress?.update(1, 'No tables to describe'); @@ -362,11 +362,11 @@ async function generateDescriptions(input: { } async function buildEmbeddings(input: { - snapshot: KloSchemaSnapshot; - providers: KloLocalScanEnrichmentProviders; - descriptions: KloLocalScanEnrichmentResult['descriptionUpdates']; - progress?: KloProgressPort; -}): Promise<{ updates: KloEmbeddingUpdate[]; byColumnId: Map }> { + snapshot: KtxSchemaSnapshot; + providers: KtxLocalScanEnrichmentProviders; + descriptions: KtxLocalScanEnrichmentResult['descriptionUpdates']; + progress?: KtxProgressPort; +}): Promise<{ updates: KtxEmbeddingUpdate[]; byColumnId: Map }> { const descriptionByTable = new Map(input.descriptions.map((item) => [item.table.name, item])); const texts: Array<{ columnId: string; text: string }> = []; @@ -374,7 +374,7 @@ async function buildEmbeddings(input: { const tableDescriptions = descriptionByTable.get(table.name); for (const column of table.columns) { const id = columnId(table, column); - const text = buildKloColumnEmbeddingText({ + const text = buildKtxColumnEmbeddingText({ tableName: table.name, columnName: column.name, columnType: column.nativeType, @@ -429,17 +429,17 @@ async function buildEmbeddings(input: { } async function runEnrichmentStage(input: { - stateStore: KloScanEnrichmentStateStore | null | undefined; + stateStore: KtxScanEnrichmentStateStore | null | undefined; runId: string; connectionId: string; syncId: string; - mode: KloScanMode; - stage: KloScanEnrichmentStage; + mode: KtxScanMode; + stage: KtxScanEnrichmentStage; inputHash: string; now: () => Date; - resumedStages: KloScanEnrichmentStage[]; - completedStages: KloScanEnrichmentStage[]; - failedStages: KloScanEnrichmentStage[]; + resumedStages: KtxScanEnrichmentStage[]; + completedStages: KtxScanEnrichmentStage[]; + failedStages: KtxScanEnrichmentStage[]; compute: () => Promise; }): Promise { const existing = await input.stateStore?.findCompletedStage({ @@ -483,13 +483,13 @@ async function runEnrichmentStage(input: { } } -function embeddingsByColumnId(updates: KloEmbeddingUpdate[]): Map { +function embeddingsByColumnId(updates: KtxEmbeddingUpdate[]): Map { return new Map(updates.map((update) => [update.columnId, update.embedding])); } export async function runLocalScanEnrichment( - input: KloLocalScanEnrichmentInput, -): Promise { + input: KtxLocalScanEnrichmentInput, +): Promise { const progress = input.context.progress; await progress?.update(0, 'Loading enrichment schema snapshot'); const snapshot = await input.connector.introspect( @@ -504,22 +504,22 @@ export async function runLocalScanEnrichment( await progress?.update(0.05, `Loaded schema snapshot with ${snapshot.tables.length} tables`); const now = input.now ?? (() => new Date()); - const state = completedKloScanEnrichmentStateSummary(); + const state = completedKtxScanEnrichmentStateSummary(); const syncId = input.syncId ?? input.context.runId; const relationshipSettings = - input.relationshipSettings ?? buildDefaultKloProjectConfig(input.connectionId).scan.relationships; - const inputHash = computeKloScanEnrichmentInputHash({ + input.relationshipSettings ?? buildDefaultKtxProjectConfig(input.connectionId).scan.relationships; + const inputHash = computeKtxScanEnrichmentInputHash({ snapshot, mode: input.mode, detectRelationships: input.detectRelationships ?? false, providerIdentity: input.providerIdentity ?? {}, relationshipSettings, }); - const warnings: KloScanWarning[] = []; - let descriptions: KloLocalScanEnrichmentResult['descriptionUpdates'] = []; - let embeddingUpdates: KloEmbeddingUpdate[] = []; - let schema = snapshotToKloEnrichedSchema(snapshot); - const summary: KloScanEnrichmentSummary = { ...skippedKloScanEnrichmentSummary }; + const warnings: KtxScanWarning[] = []; + let descriptions: KtxLocalScanEnrichmentResult['descriptionUpdates'] = []; + let embeddingUpdates: KtxEmbeddingUpdate[] = []; + let schema = snapshotToKtxEnrichedSchema(snapshot); + const summary: KtxScanEnrichmentSummary = { ...skippedKtxScanEnrichmentSummary }; const relationshipDetectionEnabled = relationshipSettings.enabled; const shouldDetectRelationships = relationshipDetectionEnabled && @@ -576,18 +576,18 @@ export async function runLocalScanEnrichment( return embeddings.updates; }, }); - schema = snapshotToKloEnrichedSchema(snapshot, embeddingsByColumnId(embeddingUpdates)); + schema = snapshotToKtxEnrichedSchema(snapshot, embeddingsByColumnId(embeddingUpdates)); summary.dataDictionary = input.connector.sampleColumn ? 'completed' : 'skipped'; summary.tableDescriptions = 'completed'; summary.columnDescriptions = 'completed'; summary.embeddings = 'completed'; } - let relationshipUpdate: KloRelationshipUpdate | null = null; - let relationshipProfile: KloRelationshipProfileArtifact | null = null; - let resolvedRelationships: KloResolvedRelationshipDiscoveryCandidate[] | null = null; - let compositeRelationships: KloCompositeRelationshipCandidate[] | null = null; - let relationships: KloScanRelationshipSummary = { accepted: 0, review: 0, rejected: 0, skipped: 0 }; + let relationshipUpdate: KtxRelationshipUpdate | null = null; + let relationshipProfile: KtxRelationshipProfileArtifact | null = null; + let resolvedRelationships: KtxResolvedRelationshipDiscoveryCandidate[] | null = null; + let compositeRelationships: KtxCompositeRelationshipCandidate[] | null = null; + let relationships: KtxScanRelationshipSummary = { accepted: 0, review: 0, rejected: 0, skipped: 0 }; if (shouldDetectRelationships) { const relationshipProgress = progress?.startPhase(0.25); const relationshipStage = await runEnrichmentStage({ @@ -604,7 +604,7 @@ export async function runLocalScanEnrichment( failedStages: state.failedStages, compute: async () => { await relationshipProgress?.update(0, 'Detecting relationships'); - const detection = await discoverKloRelationships({ + const detection = await discoverKtxRelationships({ connectionId: input.connectionId, driver: snapshot.driver, connector: input.connector, @@ -647,7 +647,7 @@ export async function runLocalScanEnrichment( snapshot, summary, relationships, - state: summarizeKloScanEnrichmentState(state), + state: summarizeKtxScanEnrichmentState(state), warnings, descriptionUpdates: descriptions, embeddingUpdates, diff --git a/packages/context/src/scan/local-scan.test.ts b/packages/context/src/scan/local-scan.test.ts index 54688794..c74aad37 100644 --- a/packages/context/src/scan/local-scan.test.ts +++ b/packages/context/src/scan/local-scan.test.ts @@ -1,18 +1,18 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import YAML from 'yaml'; import type { SourceAdapter } from '../ingest/index.js'; -import { initKloProject, type KloLocalProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import { getLocalScanReport, getLocalScanStatus, runLocalScan } from './local-scan.js'; -import type { KloQueryResult, KloReadOnlyQueryInput } from './types.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput } from './types.js'; function relationshipSqlResult( - input: KloReadOnlyQueryInput, + input: KtxReadOnlyQueryInput, options: { throwOnCoverage?: boolean } = {}, -): KloQueryResult { +): KtxQueryResult { if (input.sql.includes('child_values')) { if (options.throwOnCoverage) { throw new Error('validation failed for postgres://reader:secret@example.test/db'); // pragma: allowlist secret @@ -79,7 +79,7 @@ function relationshipSqlResult( throw new Error(`Unexpected relationship SQL: ${input.sql}`); } -function deterministicLlmProvider(): KloLlmProvider { +function deterministicLlmProvider(): KtxLlmProvider { return { getModel: () => ({ provider: 'deterministic', modelId: 'deterministic' }) as never, getModelByName: () => ({ provider: 'deterministic', modelId: 'deterministic' }) as never, @@ -103,7 +103,7 @@ function deterministicLlmProvider(): KloLlmProvider { async function writeLiveDatabaseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -164,14 +164,14 @@ function fetchOnlyAdapter(options: { extractedAt?: () => string } = {}): SourceA describe('local scan', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-scan-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-scan-')); const projectDir = join(tempDir, 'project'); - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeLiveDatabaseConfig(projectDir); - project = await loadKloProject({ projectDir }); + project = await loadKtxProject({ projectDir }); }); afterEach(async () => { @@ -509,7 +509,7 @@ describe('local scan', () => { ], }; }, - async executeReadOnly(input: KloReadOnlyQueryInput) { + async executeReadOnly(input: KtxReadOnlyQueryInput) { return relationshipSqlResult(input); }, }; @@ -603,7 +603,7 @@ describe('local scan', () => { ], }; }, - async executeReadOnly(input: KloReadOnlyQueryInput) { + async executeReadOnly(input: KtxReadOnlyQueryInput) { return relationshipSqlResult(input); }, }; @@ -712,7 +712,7 @@ describe('local scan', () => { ], }; }, - async executeReadOnly(input: KloReadOnlyQueryInput) { + async executeReadOnly(input: KtxReadOnlyQueryInput) { return relationshipSqlResult(input); }, }; @@ -838,7 +838,7 @@ describe('local scan', () => { ], }; }, - async executeReadOnly(input: KloReadOnlyQueryInput) { + async executeReadOnly(input: KtxReadOnlyQueryInput) { return relationshipSqlResult(input); }, }; @@ -968,7 +968,7 @@ describe('local scan', () => { ], }; }, - async executeReadOnly(input: KloReadOnlyQueryInput) { + async executeReadOnly(input: KtxReadOnlyQueryInput) { return relationshipSqlResult(input, { throwOnCoverage: true }); }, }; @@ -999,7 +999,7 @@ describe('local scan', () => { it('runs enriched scans when deterministic standalone enrichment is configured', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1017,7 +1017,7 @@ describe('local scan', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const connector = { id: 'test:warehouse', @@ -1200,7 +1200,7 @@ describe('local scan', () => { expect(result.report.warnings).toEqual([ { code: 'enrichment_failed', - message: 'KLO scan enrichment failed after structural scan completed: embedding service timed out', + message: 'KTX scan enrichment failed after structural scan completed: embedding service timed out', recoverable: true, metadata: { mode: 'enriched', @@ -1356,7 +1356,7 @@ describe('local scan', () => { it('accepts sqlite as a native standalone scan driver when the host supplies a live-database adapter', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1371,7 +1371,7 @@ describe('local scan', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalScan({ project, @@ -1389,7 +1389,7 @@ describe('local scan', () => { it('accepts mysql as a native standalone scan driver when the host supplies a live-database adapter', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1404,7 +1404,7 @@ describe('local scan', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalScan({ project, @@ -1422,7 +1422,7 @@ describe('local scan', () => { it('accepts clickhouse as a native standalone scan driver when the host supplies a live-database adapter', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1440,7 +1440,7 @@ describe('local scan', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalScan({ project, @@ -1458,7 +1458,7 @@ describe('local scan', () => { it('accepts sqlserver as a native standalone scan driver when the host supplies a live-database adapter', async () => { await writeFile( - join(project.projectDir, 'klo.yaml'), + join(project.projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1476,7 +1476,7 @@ describe('local scan', () => { ].join('\n'), 'utf-8', ); - project = await loadKloProject({ projectDir: project.projectDir }); + project = await loadKtxProject({ projectDir: project.projectDir }); const result = await runLocalScan({ project, diff --git a/packages/context/src/scan/local-scan.ts b/packages/context/src/scan/local-scan.ts index e42a0a9c..0919843f 100644 --- a/packages/context/src/scan/local-scan.ts +++ b/packages/context/src/scan/local-scan.ts @@ -1,4 +1,4 @@ -import type { createKloEmbeddingProvider, createKloLlmProvider } from '@klo/llm'; +import type { createKtxEmbeddingProvider, createKtxLlmProvider } from '@ktx/llm'; import { createDefaultLocalIngestAdapters, getLocalStageOnlyIngestStatus, @@ -7,50 +7,50 @@ import { type SourceAdapter, } from '../ingest/index.js'; import { - createLocalKloEmbeddingProviderFromConfig, - createLocalKloLlmProviderFromConfig, - KloScanEmbeddingPortAdapter, + createLocalKtxEmbeddingProviderFromConfig, + createLocalKtxLlmProviderFromConfig, + KtxScanEmbeddingPortAdapter, } from '../llm/index.js'; -import type { KloProjectLlmConfig, KloScanEnrichmentConfig, KloScanRelationshipConfig } from '../project/config.js'; -import type { KloLocalProject } from '../project/index.js'; -import { kloLocalStateDbPath } from '../project/local-state-db.js'; -import { redactKloScanReport } from './credentials.js'; -import { completedKloScanEnrichmentStateSummary } from './enrichment-state.js'; -import { failedKloScanEnrichmentSummary, kloScanErrorMessage } from './enrichment-summary.js'; +import type { KtxProjectLlmConfig, KtxScanEnrichmentConfig, KtxScanRelationshipConfig } from '../project/config.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { ktxLocalStateDbPath } from '../project/local-state-db.js'; +import { redactKtxScanReport } from './credentials.js'; +import { completedKtxScanEnrichmentStateSummary } from './enrichment-state.js'; +import { failedKtxScanEnrichmentSummary, ktxScanErrorMessage } from './enrichment-summary.js'; import { createDeterministicLocalScanEnrichmentProviders, - type KloLocalScanEnrichmentProviders, + type KtxLocalScanEnrichmentProviders, runLocalScanEnrichment, } from './local-enrichment.js'; import { writeLocalScanEnrichmentArtifacts, writeLocalScanManifestShards } from './local-enrichment-artifacts.js'; import { readLocalScanStructuralSnapshot } from './local-structural-artifacts.js'; import { SqliteLocalScanEnrichmentStateStore } from './sqlite-local-enrichment-state-store.js'; import type { - KloConnectionDriver, - KloProgressPort, - KloScanConnector, - KloScanEnrichmentStateSummary, - KloScanMode, - KloScanReport, - KloScanTrigger, + KtxConnectionDriver, + KtxProgressPort, + KtxScanConnector, + KtxScanEnrichmentStateSummary, + KtxScanMode, + KtxScanReport, + KtxScanTrigger, } from './types.js'; export interface RunLocalScanOptions { - project: KloLocalProject; + project: KtxLocalProject; connectionId: string; - mode?: KloScanMode; + mode?: KtxScanMode; detectRelationships?: boolean; dryRun?: boolean; - trigger?: KloScanTrigger; + trigger?: KtxScanTrigger; databaseIntrospectionUrl?: string; adapters?: SourceAdapter[]; jobId?: string; now?: () => Date; - connector?: KloScanConnector; - createConnector?: (connectionId: string) => KloScanConnector | Promise; - enrichmentProviders?: KloLocalScanEnrichmentProviders | null; + connector?: KtxScanConnector; + createConnector?: (connectionId: string) => KtxScanConnector | Promise; + enrichmentProviders?: KtxLocalScanEnrichmentProviders | null; enrichmentStateStore?: SqliteLocalScanEnrichmentStateStore | null; - progress?: KloProgressPort; + progress?: KtxProgressPort; } export interface LocalScanRunResult { @@ -58,10 +58,10 @@ export interface LocalScanRunResult { status: 'done'; done: true; connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; dryRun: boolean; syncId: string; - report: KloScanReport; + report: KtxScanReport; } export interface LocalScanStatusResponse { @@ -69,14 +69,14 @@ export interface LocalScanStatusResponse { status: LocalIngestRunRecord['status']; done: boolean; connectionId: string; - mode: KloScanMode; + mode: KtxScanMode; dryRun: boolean; syncId: string; progress: number; startedAt: string; completedAt: string; reportPath: string | null; - warnings: KloScanReport['warnings']; + warnings: KtxScanReport['warnings']; } export interface LocalScanMcpOptions { @@ -84,15 +84,15 @@ export interface LocalScanMcpOptions { databaseIntrospectionUrl?: string; jobIdFactory?: () => string; now?: () => Date; - createConnector?: (connectionId: string) => KloScanConnector | Promise; + createConnector?: (connectionId: string) => KtxScanConnector | Promise; } const LIVE_DATABASE_ADAPTER = 'live-database'; const SCAN_REPORT_FILE = 'scan-report.json'; -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; -function normalizeDriver(driver: string | undefined): KloConnectionDriver { +function normalizeDriver(driver: string | undefined): KtxConnectionDriver { const normalized = (driver ?? '').toLowerCase(); if ( normalized === 'postgres' || @@ -109,7 +109,7 @@ function normalizeDriver(driver: string | undefined): KloConnectionDriver { return normalized === 'sqlite3' ? 'sqlite' : normalized; } throw new Error( - `Standalone klo scan supports postgres/postgresql/sqlite/mysql/clickhouse/sqlserver/bigquery/snowflake/posthog in this phase, received "${driver ?? 'unknown'}"`, + `Standalone ktx scan supports postgres/postgresql/sqlite/mysql/clickhouse/sqlserver/bigquery/snowflake/posthog in this phase, received "${driver ?? 'unknown'}"`, ); } @@ -125,13 +125,13 @@ function scanReportPath(connectionId: string, syncId: string): string { return `${rawSourcesDir(connectionId, syncId)}/${SCAN_REPORT_FILE}`; } -function assertSupportedMode(mode: KloScanMode): void { +function assertSupportedMode(mode: KtxScanMode): void { if (mode !== 'structural' && mode !== 'relationships' && mode !== 'enriched') { - throw new Error(`Unsupported KLO scan mode: ${mode}`); + throw new Error(`Unsupported KTX scan mode: ${mode}`); } } -async function resolveScanConnector(options: RunLocalScanOptions, mode: KloScanMode): Promise { +async function resolveScanConnector(options: RunLocalScanOptions, mode: KtxScanMode): Promise { if (mode === 'structural' && !options.detectRelationships) { return null; } @@ -141,20 +141,20 @@ async function resolveScanConnector(options: RunLocalScanOptions, mode: KloScanM if (options.createConnector) { return options.createConnector(options.connectionId); } - throw new Error('klo scan --enrich and --detect-relationships require a native standalone scan connector'); + throw new Error('ktx scan --enrich and --detect-relationships require a native standalone scan connector'); } interface LocalScanEnrichmentProviderDeps { - createKloLlmProvider?: typeof createKloLlmProvider; - createKloEmbeddingProvider?: typeof createKloEmbeddingProvider; + createKtxLlmProvider?: typeof createKtxLlmProvider; + createKtxEmbeddingProvider?: typeof createKtxEmbeddingProvider; env?: NodeJS.ProcessEnv; } export function createLocalScanEnrichmentProvidersFromConfig( - config: KloScanEnrichmentConfig, - llmConfig: KloProjectLlmConfig, + config: KtxScanEnrichmentConfig, + llmConfig: KtxProjectLlmConfig, deps: LocalScanEnrichmentProviderDeps = {}, -): KloLocalScanEnrichmentProviders | null { +): KtxLocalScanEnrichmentProviders | null { if (config.mode === 'deterministic') { return createDeterministicLocalScanEnrichmentProviders(); } @@ -163,15 +163,15 @@ export function createLocalScanEnrichmentProvidersFromConfig( return null; } - const llm = createLocalKloLlmProviderFromConfig(llmConfig, deps); - const embeddingProvider = createLocalKloEmbeddingProviderFromConfig(config.embeddings, deps); + const llm = createLocalKtxLlmProviderFromConfig(llmConfig, deps); + const embeddingProvider = createLocalKtxEmbeddingProviderFromConfig(config.embeddings, deps); if (!llm || !embeddingProvider) { return null; } return { llm, - embedding: new KloScanEmbeddingPortAdapter(embeddingProvider), + embedding: new KtxScanEmbeddingPortAdapter(embeddingProvider), }; } @@ -182,13 +182,13 @@ function createLocalScanEnrichmentStateStore(options: RunLocalScanOptions): Sqli if (options.enrichmentStateStore !== undefined) { return options.enrichmentStateStore; } - return new SqliteLocalScanEnrichmentStateStore({ dbPath: kloLocalStateDbPath(options.project) }); + return new SqliteLocalScanEnrichmentStateStore({ dbPath: ktxLocalStateDbPath(options.project) }); } function localScanProviderIdentity( - config: KloScanEnrichmentConfig, - llmConfig: KloProjectLlmConfig, - relationships: KloScanRelationshipConfig, + config: KtxScanEnrichmentConfig, + llmConfig: KtxProjectLlmConfig, + relationships: KtxScanRelationshipConfig, ): Record { return { mode: config.mode, @@ -203,12 +203,12 @@ function localScanProviderIdentity( function reportFromIngest(input: { record: LocalIngestRunRecord; - driver: KloConnectionDriver; - mode: KloScanMode; + driver: KtxConnectionDriver; + mode: KtxScanMode; dryRun: boolean; - trigger: KloScanTrigger; + trigger: KtxScanTrigger; createdAt: string; -}): KloScanReport { +}): KtxScanReport { const reportPath = input.dryRun ? null : scanReportPath(input.record.connectionId, input.record.syncId); return { connectionId: input.record.connectionId, @@ -254,12 +254,12 @@ function reportFromIngest(input: { capabilityGaps: [], warnings: [], relationships: { accepted: 0, review: 0, rejected: 0, skipped: 0 }, - enrichmentState: completedKloScanEnrichmentStateSummary(), + enrichmentState: completedKtxScanEnrichmentStateSummary(), createdAt: input.createdAt, }; } -async function writeScanReport(project: KloLocalProject, report: KloScanReport): Promise { +async function writeScanReport(project: KtxLocalProject, report: KtxScanReport): Promise { if (!report.artifactPaths.reportPath) { return; } @@ -272,7 +272,7 @@ async function writeScanReport(project: KloLocalProject, report: KloScanReport): ); } -function scanDiffSummaryFromRecord(record: LocalIngestRunRecord): KloScanReport['diffSummary'] { +function scanDiffSummaryFromRecord(record: LocalIngestRunRecord): KtxScanReport['diffSummary'] { return { tablesAdded: tablePathCount(record.diffPaths.added), tablesModified: tablePathCount(record.diffPaths.modified), @@ -293,7 +293,7 @@ function hasNoContentChanges(record: LocalIngestRunRecord): boolean { ); } -function scanChangeSummary(diffSummary: KloScanReport['diffSummary']): string { +function scanChangeSummary(diffSummary: KtxScanReport['diffSummary']): string { const changedTables = diffSummary.tablesAdded + diffSummary.tablesModified + diffSummary.tablesDeleted; const totalTables = changedTables + diffSummary.tablesUnchanged; const changeNoun = changedTables === 1 ? 'change' : 'changes'; @@ -302,13 +302,13 @@ function scanChangeSummary(diffSummary: KloScanReport['diffSummary']): string { } async function readScanReport( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, syncId: string, -): Promise { +): Promise { try { const raw = await project.fileStore.readFile(scanReportPath(connectionId, syncId)); - return JSON.parse(raw.content) as KloScanReport; + return JSON.parse(raw.content) as KtxScanReport; } catch { return null; } @@ -322,7 +322,7 @@ export async function runLocalScan(options: RunLocalScanOptions): Promise { +export async function getLocalScanReport(project: KtxLocalProject, runId: string): Promise { const status = await getLocalStageOnlyIngestStatus(project, runId); if (!status || status.adapter !== LIVE_DATABASE_ADAPTER) { return null; @@ -491,7 +491,7 @@ export async function getLocalScanReport(project: KloLocalProject, runId: string } export async function getLocalScanStatus( - project: KloLocalProject, + project: KtxLocalProject, runId: string, ): Promise { const status = await getLocalStageOnlyIngestStatus(project, runId); diff --git a/packages/context/src/scan/local-structural-artifacts.test.ts b/packages/context/src/scan/local-structural-artifacts.test.ts index ce628478..653c3b53 100644 --- a/packages/context/src/scan/local-structural-artifacts.test.ts +++ b/packages/context/src/scan/local-structural-artifacts.test.ts @@ -2,16 +2,16 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { readLocalScanStructuralSnapshot } from './local-structural-artifacts.js'; describe('readLocalScanStructuralSnapshot', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-structural-artifacts-')); - project = await initKloProject({ + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-structural-artifacts-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse', }); @@ -35,8 +35,8 @@ describe('readLocalScanStructuralSnapshot', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed connection artifact', ); await project.fileStore.writeFile( @@ -65,8 +65,8 @@ describe('readLocalScanStructuralSnapshot', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed customers artifact', ); await project.fileStore.writeFile( @@ -113,8 +113,8 @@ describe('readLocalScanStructuralSnapshot', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed orders artifact', ); @@ -171,15 +171,15 @@ describe('readLocalScanStructuralSnapshot', () => { await project.fileStore.writeFile( `${rawRoot}/connection.json`, '{"connectionId":"warehouse","metadata":{}}\n', - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed connection artifact without extractedAt', ); await project.fileStore.writeFile( `${rawRoot}/tables/orders.json`, '{"name":"orders","catalog":null,"db":null,"kind":"table","comment":null,"estimatedRows":null,"columns":[{"name":"id","nativeType":"integer","normalizedType":"integer","dimensionType":"number","nullable":false,"primaryKey":true,"comment":null}],"foreignKeys":[]}\n', - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed orders artifact', ); diff --git a/packages/context/src/scan/local-structural-artifacts.ts b/packages/context/src/scan/local-structural-artifacts.ts index afd31ee5..5eeeea6d 100644 --- a/packages/context/src/scan/local-structural-artifacts.ts +++ b/packages/context/src/scan/local-structural-artifacts.ts @@ -1,16 +1,16 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import type { - KloConnectionDriver, - KloSchemaColumn, - KloSchemaForeignKey, - KloSchemaSnapshot, - KloSchemaTable, + KtxConnectionDriver, + KtxSchemaColumn, + KtxSchemaForeignKey, + KtxSchemaSnapshot, + KtxSchemaTable, } from './types.js'; export interface ReadLocalScanStructuralSnapshotInput { - project: KloLocalProject; + project: KtxLocalProject; connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; rawSourcesDir: string; extractedAtFallback: string; } @@ -37,7 +37,7 @@ function optionalStringOrNull(value: unknown): string | null | undefined { return typeof value === 'string' ? value : null; } -function parseColumn(rawColumn: unknown, path: string): KloSchemaColumn { +function parseColumn(rawColumn: unknown, path: string): KtxSchemaColumn { if ( !isRecord(rawColumn) || typeof rawColumn.name !== 'string' || @@ -48,7 +48,7 @@ function parseColumn(rawColumn: unknown, path: string): KloSchemaColumn { rawColumn.dimensionType !== 'number' && rawColumn.dimensionType !== 'boolean') ) { - throw new Error(`Invalid KLO schema column artifact: ${path}`); + throw new Error(`Invalid KTX schema column artifact: ${path}`); } return { name: rawColumn.name, @@ -61,14 +61,14 @@ function parseColumn(rawColumn: unknown, path: string): KloSchemaColumn { }; } -function parseForeignKey(rawForeignKey: unknown, path: string): KloSchemaForeignKey { +function parseForeignKey(rawForeignKey: unknown, path: string): KtxSchemaForeignKey { if ( !isRecord(rawForeignKey) || typeof rawForeignKey.fromColumn !== 'string' || typeof rawForeignKey.toTable !== 'string' || typeof rawForeignKey.toColumn !== 'string' ) { - throw new Error(`Invalid KLO schema foreign key artifact: ${path}`); + throw new Error(`Invalid KTX schema foreign key artifact: ${path}`); } return { fromColumn: rawForeignKey.fromColumn, @@ -80,10 +80,10 @@ function parseForeignKey(rawForeignKey: unknown, path: string): KloSchemaForeign }; } -function parseTable(raw: string, path: string): KloSchemaTable { +function parseTable(raw: string, path: string): KtxSchemaTable { const parsed = JSON.parse(raw) as unknown; if (!isRecord(parsed) || typeof parsed.name !== 'string' || !Array.isArray(parsed.columns)) { - throw new Error(`Invalid KLO schema table artifact: ${path}`); + throw new Error(`Invalid KTX schema table artifact: ${path}`); } return { catalog: optionalStringOrNull(parsed.catalog) ?? null, @@ -102,13 +102,13 @@ function parseTable(raw: string, path: string): KloSchemaTable { export async function readLocalScanStructuralSnapshot( input: ReadLocalScanStructuralSnapshotInput, -): Promise { +): Promise { const connectionRaw = await input.project.fileStore.readFile(`${input.rawSourcesDir}/connection.json`); const connection = JSON.parse(connectionRaw.content) as LiveDatabaseConnectionArtifact; const listedTables = await input.project.fileStore.listFiles(`${input.rawSourcesDir}/tables`); const tablePaths = listedTables.files.filter((path) => path.endsWith('.json')).sort(); - const tables: KloSchemaTable[] = []; + const tables: KtxSchemaTable[] = []; for (const path of tablePaths) { const tableRaw = await input.project.fileStore.readFile(path); tables.push(parseTable(tableRaw.content, path)); diff --git a/packages/context/src/scan/orchestrator.test.ts b/packages/context/src/scan/orchestrator.test.ts index 401370e4..e44cbb5d 100644 --- a/packages/context/src/scan/orchestrator.test.ts +++ b/packages/context/src/scan/orchestrator.test.ts @@ -1,15 +1,15 @@ import { describe, expect, it, vi } from 'vitest'; import { - createKloConnectorCapabilities, - type KloScanConnector, - type KloScanContext, - type KloScanEnrichmentStateSummary, - type KloScanInput, - KloScanOrchestrator, - type KloSchemaSnapshot, + createKtxConnectorCapabilities, + type KtxScanConnector, + type KtxScanContext, + type KtxScanEnrichmentStateSummary, + type KtxScanInput, + KtxScanOrchestrator, + type KtxSchemaSnapshot, } from './index.js'; -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'postgres', @@ -42,8 +42,8 @@ function snapshot(): KloSchemaSnapshot { } function connector( - capabilities = createKloConnectorCapabilities({ tableSampling: true, columnSampling: true }), -): KloScanConnector { + capabilities = createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true }), +): KtxScanConnector { return { id: 'connector-1', driver: 'postgres', @@ -52,7 +52,7 @@ function connector( }; } -function context(): KloScanContext { +function context(): KtxScanContext { return { runId: 'scan-run-1', logger: { @@ -64,24 +64,24 @@ function context(): KloScanContext { }; } -const input: KloScanInput = { +const input: KtxScanInput = { connectionId: 'warehouse', driver: 'postgres', mode: 'structural', }; -describe('KloScanOrchestrator', () => { +describe('KtxScanOrchestrator', () => { it('runs structural scans through connector introspection and structural host callback', async () => { const scanConnector = connector(); const scanContext = context(); - const runStructural = vi.fn(async (scanSnapshot: KloSchemaSnapshot) => ({ + const runStructural = vi.fn(async (scanSnapshot: KtxSchemaSnapshot) => ({ result: { synced: true }, diffSummary: { tablesAdded: scanSnapshot.tables.length, columnsAdded: 1 }, structuralSyncStats: { tablesCreated: 1, columnsCreated: 1 }, artifactPaths: { manifestShards: ['semantic-layer/warehouse/_schema/public.yaml'] }, })); - const result = await new KloScanOrchestrator({ + const result = await new KtxScanOrchestrator({ now: () => new Date('2026-04-29T00:10:00.000Z'), syncIdFactory: () => 'sync-1', }).run({ @@ -137,7 +137,7 @@ describe('KloScanOrchestrator', () => { it('runs enriched scans through structural and enrichment host callbacks', async () => { const scanConnector = connector( - createKloConnectorCapabilities({ + createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: true, @@ -146,7 +146,7 @@ describe('KloScanOrchestrator', () => { ); const scanContext = context(); - const result = await new KloScanOrchestrator({ syncIdFactory: () => 'sync-2' }).run({ + const result = await new KtxScanOrchestrator({ syncIdFactory: () => 'sync-2' }).run({ connector: scanConnector, input: { ...input, mode: 'enriched', detectRelationships: true }, trigger: 'schema_scan', @@ -178,20 +178,20 @@ describe('KloScanOrchestrator', () => { it('reports host enrichment state summaries from enriched scan phases', async () => { const scanConnector = connector( - createKloConnectorCapabilities({ + createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: true, readOnlySql: true, }), ); - const enrichmentState: Partial = { + const enrichmentState: Partial = { resumedStages: ['relationships', 'descriptions', 'descriptions'], completedStages: ['embeddings', 'descriptions', 'relationships'], failedStages: [], }; - const result = await new KloScanOrchestrator({ syncIdFactory: () => 'sync-state' }).run({ + const result = await new KtxScanOrchestrator({ syncIdFactory: () => 'sync-state' }).run({ connector: scanConnector, input: { ...input, mode: 'enriched', detectRelationships: true }, trigger: 'schema_scan', @@ -211,8 +211,8 @@ describe('KloScanOrchestrator', () => { }); it('records recoverable warnings for missing optional capabilities during enriched scans', async () => { - const result = await new KloScanOrchestrator({ syncIdFactory: () => 'sync-3' }).run({ - connector: connector(createKloConnectorCapabilities()), + const result = await new KtxScanOrchestrator({ syncIdFactory: () => 'sync-3' }).run({ + connector: connector(createKtxConnectorCapabilities()), input: { ...input, mode: 'enriched', detectRelationships: true }, trigger: 'schema_scan', context: context(), @@ -231,9 +231,9 @@ describe('KloScanOrchestrator', () => { }); it('redacts structural and enrichment warning metadata before returning reports', async () => { - const result = await new KloScanOrchestrator({ syncIdFactory: () => 'sync-redacted' }).run({ + const result = await new KtxScanOrchestrator({ syncIdFactory: () => 'sync-redacted' }).run({ connector: connector( - createKloConnectorCapabilities({ + createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: true, @@ -301,7 +301,7 @@ describe('KloScanOrchestrator', () => { it('keeps structural results when the enrichment phase fails after structural sync', async () => { const scanConnector = connector( - createKloConnectorCapabilities({ + createKtxConnectorCapabilities({ tableSampling: true, columnSampling: true, columnStats: true, @@ -320,7 +320,7 @@ describe('KloScanOrchestrator', () => { throw new Error('AI Gateway timed out'); }); - const result = await new KloScanOrchestrator({ + const result = await new KtxScanOrchestrator({ now: () => new Date('2026-04-29T18:00:00.000Z'), syncIdFactory: () => 'sync-failed-enrichment', }).run({ @@ -348,7 +348,7 @@ describe('KloScanOrchestrator', () => { expect(result.report.warnings).toEqual([ { code: 'enrichment_failed', - message: 'KLO scan enrichment failed after structural scan completed: AI Gateway timed out', + message: 'KTX scan enrichment failed after structural scan completed: AI Gateway timed out', recoverable: true, metadata: { mode: 'enriched', @@ -361,7 +361,7 @@ describe('KloScanOrchestrator', () => { it('marks dry-run reports without changing host callback behavior', async () => { const runStructural = vi.fn(async () => ({ result: { planned: true }, manifestShardsWritten: 0 })); - const result = await new KloScanOrchestrator({ syncIdFactory: () => 'sync-4' }).run({ + const result = await new KtxScanOrchestrator({ syncIdFactory: () => 'sync-4' }).run({ connector: connector(), input: { ...input, dryRun: true }, trigger: 'cli', diff --git a/packages/context/src/scan/orchestrator.ts b/packages/context/src/scan/orchestrator.ts index 9fa4590f..d966bc04 100644 --- a/packages/context/src/scan/orchestrator.ts +++ b/packages/context/src/scan/orchestrator.ts @@ -1,79 +1,79 @@ -import { redactKloScanReport } from './credentials.js'; -import { completedKloScanEnrichmentStateSummary, summarizeKloScanEnrichmentState } from './enrichment-state.js'; +import { redactKtxScanReport } from './credentials.js'; +import { completedKtxScanEnrichmentStateSummary, summarizeKtxScanEnrichmentState } from './enrichment-state.js'; import { - failedKloScanEnrichmentSummary, - kloScanErrorMessage, - skippedKloScanEnrichmentSummary, + failedKtxScanEnrichmentSummary, + ktxScanErrorMessage, + skippedKtxScanEnrichmentSummary, } from './enrichment-summary.js'; import type { - KloConnectorCapabilities, - KloScanArtifactPaths, - KloScanConnector, - KloScanContext, - KloScanDiffSummary, - KloScanEnrichmentSummary, - KloScanEnrichmentStateSummary, - KloScanInput, - KloScanRelationshipSummary, - KloScanReport, - KloScanTrigger, - KloScanWarning, - KloSchemaSnapshot, - KloStructuralSyncStats, + KtxConnectorCapabilities, + KtxScanArtifactPaths, + KtxScanConnector, + KtxScanContext, + KtxScanDiffSummary, + KtxScanEnrichmentSummary, + KtxScanEnrichmentStateSummary, + KtxScanInput, + KtxScanRelationshipSummary, + KtxScanReport, + KtxScanTrigger, + KtxScanWarning, + KtxSchemaSnapshot, + KtxStructuralSyncStats, } from './types.js'; -type CapabilityGap = keyof Omit; +type CapabilityGap = keyof Omit; -export interface KloStructuralScanPhaseResult { +export interface KtxStructuralScanPhaseResult { result: TResult; - diffSummary?: Partial; - structuralSyncStats?: Partial; + diffSummary?: Partial; + structuralSyncStats?: Partial; manifestShardsWritten?: number; - artifactPaths?: Partial; - relationships?: Partial; - warnings?: KloScanWarning[]; + artifactPaths?: Partial; + relationships?: Partial; + warnings?: KtxScanWarning[]; } -export interface KloEnrichmentScanPhaseResult { +export interface KtxEnrichmentScanPhaseResult { result: TResult; - enrichment?: Partial; - enrichmentState?: Partial; + enrichment?: Partial; + enrichmentState?: Partial; manifestShardsWritten?: number; - artifactPaths?: Partial; - relationships?: Partial; - warnings?: KloScanWarning[]; + artifactPaths?: Partial; + relationships?: Partial; + warnings?: KtxScanWarning[]; } -export interface KloScanOrchestratorRunInput { - connector: KloScanConnector; - input: KloScanInput; - trigger: KloScanTrigger; - context: KloScanContext; +export interface KtxScanOrchestratorRunInput { + connector: KtxScanConnector; + input: KtxScanInput; + trigger: KtxScanTrigger; + context: KtxScanContext; syncId?: string; runStructural: ( - snapshot: KloSchemaSnapshot, - context: KloScanContext, - ) => Promise>; + snapshot: KtxSchemaSnapshot, + context: KtxScanContext, + ) => Promise>; runEnrichment?: ( - snapshot: KloSchemaSnapshot, - structural: KloStructuralScanPhaseResult, - context: KloScanContext, - ) => Promise>; + snapshot: KtxSchemaSnapshot, + structural: KtxStructuralScanPhaseResult, + context: KtxScanContext, + ) => Promise>; } -export interface KloScanOrchestratorRunResult { - snapshot: KloSchemaSnapshot; - structural: KloStructuralScanPhaseResult; - enrichment: KloEnrichmentScanPhaseResult | null; - report: KloScanReport; +export interface KtxScanOrchestratorRunResult { + snapshot: KtxSchemaSnapshot; + structural: KtxStructuralScanPhaseResult; + enrichment: KtxEnrichmentScanPhaseResult | null; + report: KtxScanReport; } -export interface KloScanOrchestratorOptions { +export interface KtxScanOrchestratorOptions { now?: () => Date; - syncIdFactory?: (input: KloScanInput, context: KloScanContext) => string; + syncIdFactory?: (input: KtxScanInput, context: KtxScanContext) => string; } -const emptyDiffSummary: KloScanDiffSummary = { +const emptyDiffSummary: KtxScanDiffSummary = { tablesAdded: 0, tablesModified: 0, tablesDeleted: 0, @@ -83,7 +83,7 @@ const emptyDiffSummary: KloScanDiffSummary = { columnsDeleted: 0, }; -const emptyStructuralSyncStats: KloStructuralSyncStats = { +const emptyStructuralSyncStats: KtxStructuralSyncStats = { tablesCreated: 0, tablesUpdated: 0, tablesDeleted: 0, @@ -92,31 +92,31 @@ const emptyStructuralSyncStats: KloStructuralSyncStats = { columnsDeleted: 0, }; -const emptyArtifactPaths: KloScanArtifactPaths = { +const emptyArtifactPaths: KtxScanArtifactPaths = { rawSourcesDir: null, reportPath: null, manifestShards: [], enrichmentArtifacts: [], }; -function mergeDiffSummary(input?: Partial): KloScanDiffSummary { +function mergeDiffSummary(input?: Partial): KtxScanDiffSummary { return { ...emptyDiffSummary, ...input }; } -function mergeStructuralSyncStats(input?: Partial): KloStructuralSyncStats { +function mergeStructuralSyncStats(input?: Partial): KtxStructuralSyncStats { return { ...emptyStructuralSyncStats, ...input }; } -function mergeEnrichmentSummary(input?: Partial): KloScanEnrichmentSummary { - return { ...skippedKloScanEnrichmentSummary, ...input }; +function mergeEnrichmentSummary(input?: Partial): KtxScanEnrichmentSummary { + return { ...skippedKtxScanEnrichmentSummary, ...input }; } -function mergeEnrichmentState(input?: Partial): KloScanEnrichmentStateSummary { +function mergeEnrichmentState(input?: Partial): KtxScanEnrichmentStateSummary { if (!input) { - return completedKloScanEnrichmentStateSummary(); + return completedKtxScanEnrichmentStateSummary(); } - return summarizeKloScanEnrichmentState({ + return summarizeKtxScanEnrichmentState({ resumedStages: input.resumedStages ?? [], completedStages: input.completedStages ?? [], failedStages: input.failedStages ?? [], @@ -124,9 +124,9 @@ function mergeEnrichmentState(input?: Partial): K } function mergeArtifactPaths( - structural?: Partial, - enrichment?: Partial, -): KloScanArtifactPaths { + structural?: Partial, + enrichment?: Partial, +): KtxScanArtifactPaths { return { ...emptyArtifactPaths, ...structural, @@ -137,9 +137,9 @@ function mergeArtifactPaths( } function mergeRelationshipSummary( - structural?: Partial, - enrichment?: Partial, -): KloScanRelationshipSummary { + structural?: Partial, + enrichment?: Partial, +): KtxScanRelationshipSummary { return { accepted: (structural?.accepted ?? 0) + (enrichment?.accepted ?? 0), review: (structural?.review ?? 0) + (enrichment?.review ?? 0), @@ -150,12 +150,12 @@ function mergeRelationshipSummary( function manifestShardsWritten(phase: { manifestShardsWritten?: number; - artifactPaths?: Partial; + artifactPaths?: Partial; }): number { return phase.manifestShardsWritten ?? phase.artifactPaths?.manifestShards?.length ?? 0; } -function requiredCapabilities(mode: KloScanInput['mode'], detectRelationships: boolean | undefined): CapabilityGap[] { +function requiredCapabilities(mode: KtxScanInput['mode'], detectRelationships: boolean | undefined): CapabilityGap[] { const required = new Set(); if (mode === 'enriched') { @@ -173,45 +173,45 @@ function requiredCapabilities(mode: KloScanInput['mode'], detectRelationships: b return [...required]; } -function capabilityGaps(capabilities: KloConnectorCapabilities, input: KloScanInput): CapabilityGap[] { +function capabilityGaps(capabilities: KtxConnectorCapabilities, input: KtxScanInput): CapabilityGap[] { return requiredCapabilities(input.mode ?? 'structural', input.detectRelationships).filter( (capability) => !capabilities[capability], ); } -function warningsForCapabilityGaps(gaps: CapabilityGap[]): KloScanWarning[] { +function warningsForCapabilityGaps(gaps: CapabilityGap[]): KtxScanWarning[] { return gaps.map((gap) => ({ code: 'connector_capability_missing', - message: `KLO scan connector is missing optional capability: ${gap}`, + message: `KTX scan connector is missing optional capability: ${gap}`, recoverable: true, metadata: { capability: gap }, })); } -function assertNotAborted(context: KloScanContext): void { +function assertNotAborted(context: KtxScanContext): void { if (context.signal?.aborted) { - throw new Error('KLO scan aborted'); + throw new Error('KTX scan aborted'); } } -export class KloScanOrchestrator { +export class KtxScanOrchestrator { private readonly now: () => Date; - private readonly syncIdFactory: (input: KloScanInput, context: KloScanContext) => string; + private readonly syncIdFactory: (input: KtxScanInput, context: KtxScanContext) => string; - constructor(options: KloScanOrchestratorOptions = {}) { + constructor(options: KtxScanOrchestratorOptions = {}) { this.now = options.now ?? (() => new Date()); this.syncIdFactory = options.syncIdFactory ?? ((_, context) => context.runId); } async run( - input: KloScanOrchestratorRunInput, - ): Promise> { + input: KtxScanOrchestratorRunInput, + ): Promise> { const mode = input.input.mode ?? 'structural'; const syncId = input.syncId ?? this.syncIdFactory(input.input, input.context); const gaps = capabilityGaps(input.connector.capabilities, input.input); const warnings = warningsForCapabilityGaps(gaps); - input.context.logger?.info('Starting KLO scan', { + input.context.logger?.info('Starting KTX scan', { connectionId: input.input.connectionId, connectorId: input.connector.id, mode, @@ -224,23 +224,23 @@ export class KloScanOrchestrator { assertNotAborted(input.context); const structural = await input.runStructural(snapshot, input.context); - let enrichment: KloEnrichmentScanPhaseResult | null = null; - let failedEnrichment: KloScanEnrichmentSummary | null = null; + let enrichment: KtxEnrichmentScanPhaseResult | null = null; + let failedEnrichment: KtxScanEnrichmentSummary | null = null; if (mode !== 'structural' || input.input.detectRelationships) { if (input.runEnrichment) { assertNotAborted(input.context); try { enrichment = await input.runEnrichment(snapshot, structural, input.context); } catch (error) { - const message = kloScanErrorMessage(error); - failedEnrichment = failedKloScanEnrichmentSummary(mode, input.input.detectRelationships ?? false); + const message = ktxScanErrorMessage(error); + failedEnrichment = failedKtxScanEnrichmentSummary(mode, input.input.detectRelationships ?? false); warnings.push({ code: 'enrichment_failed', - message: `KLO scan enrichment failed after structural scan completed: ${message}`, + message: `KTX scan enrichment failed after structural scan completed: ${message}`, recoverable: true, metadata: { mode, detectRelationships: input.input.detectRelationships ?? false }, }); - input.context.logger?.warn('KLO scan enrichment failed after structural scan completed', { + input.context.logger?.warn('KTX scan enrichment failed after structural scan completed', { connectionId: input.input.connectionId, runId: input.context.runId, mode, @@ -248,10 +248,10 @@ export class KloScanOrchestrator { }); } } else { - failedEnrichment = failedKloScanEnrichmentSummary(mode, input.input.detectRelationships ?? false); + failedEnrichment = failedKtxScanEnrichmentSummary(mode, input.input.detectRelationships ?? false); warnings.push({ code: 'connector_capability_missing', - message: 'KLO scan requested enrichment or relationship detection, but no enrichment phase was provided', + message: 'KTX scan requested enrichment or relationship detection, but no enrichment phase was provided', recoverable: true, metadata: { mode, detectRelationships: input.input.detectRelationships ?? false }, }); @@ -260,7 +260,7 @@ export class KloScanOrchestrator { const manifestShardCount = manifestShardsWritten(structural) + (enrichment ? manifestShardsWritten(enrichment) : 0); - const report: KloScanReport = redactKloScanReport({ + const report: KtxScanReport = redactKtxScanReport({ connectionId: input.input.connectionId, driver: input.input.driver, syncId, @@ -280,7 +280,7 @@ export class KloScanOrchestrator { createdAt: this.now().toISOString(), }); - input.context.logger?.info('Completed KLO scan', { + input.context.logger?.info('Completed KTX scan', { connectionId: report.connectionId, runId: report.runId, syncId: report.syncId, diff --git a/packages/context/src/scan/relationship-artifacts.test.ts b/packages/context/src/scan/relationship-artifacts.test.ts index 2c62880b..e5fd0190 100644 --- a/packages/context/src/scan/relationship-artifacts.test.ts +++ b/packages/context/src/scan/relationship-artifacts.test.ts @@ -2,12 +2,12 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { runLocalStageOnlyIngest, type SourceAdapter } from '../ingest/index.js'; -import { initKloProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, loadKtxProject } from '../project/index.js'; import { describe, expect, it } from 'vitest'; import { readLocalScanRelationshipArtifacts } from './relationship-artifacts.js'; -import type { KloRelationshipArtifact, KloRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloScanReport } from './types.js'; +import type { KtxRelationshipArtifact, KtxRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxScanReport } from './types.js'; async function writeProjectFile(projectDir: string, relativePath: string, content: string): Promise { const absolutePath = join(projectDir, relativePath); @@ -17,7 +17,7 @@ async function writeProjectFile(projectDir: string, relativePath: string, conten async function writeWarehouseConfig(projectDir: string): Promise { await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -68,9 +68,9 @@ function liveDatabaseAdapter(): SourceAdapter { } async function createLiveDatabaseRun(projectDir: string, runId: string) { - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeWarehouseConfig(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await runLocalStageOnlyIngest({ project, adapters: [liveDatabaseAdapter()], @@ -82,7 +82,7 @@ async function createLiveDatabaseRun(projectDir: string, runId: string) { return project; } -function scanReport(enrichmentArtifacts: string[], syncId = '2026-05-07-100000-scan-run-review'): KloScanReport { +function scanReport(enrichmentArtifacts: string[], syncId = '2026-05-07-100000-scan-run-review'): KtxScanReport { return { connectionId: 'warehouse', driver: 'sqlite', @@ -136,7 +136,7 @@ function scanReport(enrichmentArtifacts: string[], syncId = '2026-05-07-100000-s }; } -const relationshipArtifact: KloRelationshipArtifact = { +const relationshipArtifact: KtxRelationshipArtifact = { connectionId: 'warehouse', accepted: [], review: [ @@ -198,7 +198,7 @@ const relationshipArtifact: KloRelationshipArtifact = { skipped: [], }; -const diagnosticsArtifact: KloRelationshipDiagnosticsArtifact = { +const diagnosticsArtifact: KtxRelationshipDiagnosticsArtifact = { connectionId: 'warehouse', generatedAt: '2026-05-07T10:00:00.000Z', summary: { accepted: 0, review: 1, rejected: 1, skipped: 0 }, @@ -213,22 +213,22 @@ const diagnosticsArtifact: KloRelationshipDiagnosticsArtifact = { validationConcurrency: 4, }, warnings: [], - profileWarnings: ['KLO scan connector cannot run read-only SQL relationship validation'], + profileWarnings: ['KTX scan connector cannot run read-only SQL relationship validation'], }; -const profileArtifact: KloRelationshipProfileArtifact = { +const profileArtifact: KtxRelationshipProfileArtifact = { connectionId: 'warehouse', driver: 'sqlite', sqlAvailable: false, tables: [], columns: {}, queryCount: 0, - warnings: ['KLO scan connector cannot run read-only SQL relationship validation'], + warnings: ['KTX scan connector cannot run read-only SQL relationship validation'], }; describe('local scan relationship artifact reader', () => { it('loads relationship, diagnostics, and profile artifacts for a scan run', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-artifacts-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-artifacts-')); try { const project = await createLiveDatabaseRun(projectDir, 'scan-run-review'); const syncId = '2026-05-07-100000-scan-run-review'; @@ -282,10 +282,10 @@ describe('local scan relationship artifact reader', () => { }); it('returns null when the scan run has no report', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-artifacts-missing-run-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-artifacts-missing-run-')); try { - await initKloProject({ projectDir, projectName: 'warehouse' }); - const project = await loadKloProject({ projectDir }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); + const project = await loadKtxProject({ projectDir }); await expect(readLocalScanRelationshipArtifacts(project, 'missing-run')).resolves.toBeNull(); } finally { @@ -294,7 +294,7 @@ describe('local scan relationship artifact reader', () => { }); it('throws a focused error when a scan report does not reference relationships.json', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-artifacts-missing-artifact-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-artifacts-missing-artifact-')); try { const project = await createLiveDatabaseRun(projectDir, 'scan-run-review'); const report = scanReport([]); diff --git a/packages/context/src/scan/relationship-artifacts.ts b/packages/context/src/scan/relationship-artifacts.ts index e630e2a8..99751b71 100644 --- a/packages/context/src/scan/relationship-artifacts.ts +++ b/packages/context/src/scan/relationship-artifacts.ts @@ -1,19 +1,19 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { getLocalScanReport } from './local-scan.js'; -import type { KloRelationshipArtifact, KloRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloScanReport } from './types.js'; +import type { KtxRelationshipArtifact, KtxRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxScanReport } from './types.js'; -export type KloRelationshipArtifactStatus = 'accepted' | 'review' | 'rejected' | 'skipped' | 'all'; +export type KtxRelationshipArtifactStatus = 'accepted' | 'review' | 'rejected' | 'skipped' | 'all'; export interface ReadLocalScanRelationshipArtifactsResult { runId: string; connectionId: string; syncId: string; - report: KloScanReport; - relationships: KloRelationshipArtifact; - diagnostics: KloRelationshipDiagnosticsArtifact | null; - profile: KloRelationshipProfileArtifact | null; + report: KtxScanReport; + relationships: KtxRelationshipArtifact; + diagnostics: KtxRelationshipDiagnosticsArtifact | null; + profile: KtxRelationshipProfileArtifact | null; paths: { relationships: string; diagnostics: string | null; @@ -21,16 +21,16 @@ export interface ReadLocalScanRelationshipArtifactsResult { }; } -function findArtifactPath(report: KloScanReport, fileName: string): string | null { +function findArtifactPath(report: KtxScanReport, fileName: string): string | null { return report.artifactPaths.enrichmentArtifacts.find((path) => path.endsWith(`/enrichment/${fileName}`)) ?? null; } -async function readJsonArtifact(project: KloLocalProject, path: string): Promise { +async function readJsonArtifact(project: KtxLocalProject, path: string): Promise { const raw = await project.fileStore.readFile(path); return JSON.parse(raw.content) as T; } -async function readOptionalJsonArtifact(project: KloLocalProject, path: string | null): Promise { +async function readOptionalJsonArtifact(project: KtxLocalProject, path: string | null): Promise { if (!path) { return null; } @@ -42,7 +42,7 @@ async function readOptionalJsonArtifact(project: KloLocalProject, path: strin } export async function readLocalScanRelationshipArtifacts( - project: KloLocalProject, + project: KtxLocalProject, runId: string, ): Promise { const report = await getLocalScanReport(project, runId); @@ -63,9 +63,9 @@ export async function readLocalScanRelationshipArtifacts( connectionId: report.connectionId, syncId: report.syncId, report, - relationships: await readJsonArtifact(project, relationshipsPath), - diagnostics: await readOptionalJsonArtifact(project, diagnosticsPath), - profile: await readOptionalJsonArtifact(project, profilePath), + relationships: await readJsonArtifact(project, relationshipsPath), + diagnostics: await readOptionalJsonArtifact(project, diagnosticsPath), + profile: await readOptionalJsonArtifact(project, profilePath), paths: { relationships: relationshipsPath, diagnostics: diagnosticsPath, diff --git a/packages/context/src/scan/relationship-benchmark-report.test.ts b/packages/context/src/scan/relationship-benchmark-report.test.ts index 9b1effa0..8941eec1 100644 --- a/packages/context/src/scan/relationship-benchmark-report.test.ts +++ b/packages/context/src/scan/relationship-benchmark-report.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest'; import { - buildKloRelationshipBenchmarkReport, - formatKloRelationshipBenchmarkReportMarkdown, + buildKtxRelationshipBenchmarkReport, + formatKtxRelationshipBenchmarkReportMarkdown, } from './relationship-benchmark-report.js'; import type { - KloRelationshipBenchmarkCaseResult, - KloRelationshipBenchmarkFixture, - KloRelationshipBenchmarkSuiteResult, + KtxRelationshipBenchmarkCaseResult, + KtxRelationshipBenchmarkFixture, + KtxRelationshipBenchmarkSuiteResult, } from './relationship-benchmarks.js'; -type CaseResultOverrides = Omit, 'metrics'> & { - metrics?: Partial; +type CaseResultOverrides = Omit, 'metrics'> & { + metrics?: Partial; }; -function caseResult(overrides: CaseResultOverrides = {}): KloRelationshipBenchmarkCaseResult { +function caseResult(overrides: CaseResultOverrides = {}): KtxRelationshipBenchmarkCaseResult { return { fixtureId: overrides.fixtureId ?? 'demo_b2b_no_declared_constraints', mode: overrides.mode ?? 'declared_pks_and_declared_fks_removed', @@ -49,7 +49,7 @@ function caseResult(overrides: CaseResultOverrides = {}): KloRelationshipBenchma }; } -function fixture(overrides: Partial = {}): KloRelationshipBenchmarkFixture { +function fixture(overrides: Partial = {}): KtxRelationshipBenchmarkFixture { return { id: overrides.id ?? 'demo_b2b_no_declared_constraints', name: overrides.name ?? 'Packaged B2B demo with declared PK and FK metadata masked', @@ -74,7 +74,7 @@ function fixture(overrides: Partial = {}): KloR describe('relationship benchmark report', () => { it('classifies run, validation-blocked, and not-run benchmark cases', () => { - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult(), caseResult({ @@ -102,7 +102,7 @@ describe('relationship benchmark report', () => { }, }; - const report = buildKloRelationshipBenchmarkReport({ + const report = buildKtxRelationshipBenchmarkReport({ fixtures: [fixture()], suite, modes: ['declared_pks_and_declared_fks_removed', 'validation_disabled', 'profiling_disabled'], @@ -126,7 +126,7 @@ describe('relationship benchmark report', () => { }); it('surfaces validation budget review candidates in the report reason', () => { - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult({ fixtureId: 'scale_stress_no_declared_constraints', @@ -155,7 +155,7 @@ describe('relationship benchmark report', () => { }, }; - const report = buildKloRelationshipBenchmarkReport({ + const report = buildKtxRelationshipBenchmarkReport({ fixtures: [ fixture({ id: 'scale_stress_no_declared_constraints', @@ -170,7 +170,7 @@ describe('relationship benchmark report', () => { }); expect(report.cases[0]?.reason).toBe('review candidate validation reasons: validation_unattempted (1)'); - expect(formatKloRelationshipBenchmarkReportMarkdown(report)).toContain('validation_unattempted'); + expect(formatKtxRelationshipBenchmarkReportMarkdown(report)).toContain('validation_unattempted'); }); it('uses benchmark suite eligibility for product and smoke report rows', () => { @@ -182,7 +182,7 @@ describe('relationship benchmark report', () => { metrics: { fkRecall: 0, acceptedOrReviewRecall: 1, sqlQueries: 0 }, }); const smokeCase = caseResult({ fixtureId: 'smoke_even_if_marked' }); - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [productCase, productBlocked, smokeCase], validationBlockedCases: ['product_curated:validation_disabled'], aggregate: { @@ -197,7 +197,7 @@ describe('relationship benchmark report', () => { }, }; - const report = buildKloRelationshipBenchmarkReport({ + const report = buildKtxRelationshipBenchmarkReport({ fixtures: [ fixture({ id: 'product_curated', @@ -224,13 +224,13 @@ describe('relationship benchmark report', () => { 'smoke_even_if_marked:declared_pks_and_declared_fks_removed:false', 'smoke_even_if_marked:validation_disabled:false', ]); - expect(formatKloRelationshipBenchmarkReportMarkdown(report)).toContain( + expect(formatKtxRelationshipBenchmarkReportMarkdown(report)).toContain( '| product_curated | product | declared_pks_and_declared_fks_removed | run | yes |', ); }); it('formats a compact Markdown report with false negatives and blocked modes', () => { - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult({ metrics: { fkRecall: 0, acceptedOrReviewRecall: 0 }, @@ -250,15 +250,15 @@ describe('relationship benchmark report', () => { }, }; - const markdown = formatKloRelationshipBenchmarkReportMarkdown( - buildKloRelationshipBenchmarkReport({ + const markdown = formatKtxRelationshipBenchmarkReportMarkdown( + buildKtxRelationshipBenchmarkReport({ fixtures: [fixture()], suite, modes: ['declared_pks_and_declared_fks_removed'], }), ); - expect(markdown).toContain('# KLO Relationship Discovery Benchmark Evidence'); + expect(markdown).toContain('# KTX Relationship Discovery Benchmark Evidence'); expect(markdown).toContain( '| demo_b2b_no_declared_constraints | smoke | declared_pks_and_declared_fks_removed | run | no | 0.500 | 0.000 | 0.000 | 0 |', ); @@ -271,7 +271,7 @@ describe('relationship benchmark report', () => { }); it('keeps headline failures separate from non-headline failure details', () => { - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult({ fixtureId: 'product_curated', @@ -301,8 +301,8 @@ describe('relationship benchmark report', () => { }, }; - const markdown = formatKloRelationshipBenchmarkReportMarkdown( - buildKloRelationshipBenchmarkReport({ + const markdown = formatKtxRelationshipBenchmarkReportMarkdown( + buildKtxRelationshipBenchmarkReport({ fixtures: [ fixture({ id: 'product_curated', @@ -326,7 +326,7 @@ describe('relationship benchmark report', () => { }); it('formats headline failure context from remaining headline false negatives', () => { - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult({ fixtureId: 'public_headline_fixture', @@ -350,8 +350,8 @@ describe('relationship benchmark report', () => { }, }; - const markdown = formatKloRelationshipBenchmarkReportMarkdown( - buildKloRelationshipBenchmarkReport({ + const markdown = formatKtxRelationshipBenchmarkReportMarkdown( + buildKtxRelationshipBenchmarkReport({ fixtures: [ fixture({ id: 'public_headline_fixture', @@ -380,7 +380,7 @@ describe('relationship benchmark report', () => { it('formats skipped composite ground truth separately from false-negative details', () => { const compositePk = 'order_lines.(order_id,line_number)'; const compositeFk = 'order_line_allocations.(order_id,line_number)->order_lines.(order_id,line_number)'; - const suite: KloRelationshipBenchmarkSuiteResult = { + const suite: KtxRelationshipBenchmarkSuiteResult = { cases: [ caseResult({ fixtureId: 'composite_keys_no_declared_constraints', @@ -418,7 +418,7 @@ describe('relationship benchmark report', () => { }, }; - const report = buildKloRelationshipBenchmarkReport({ + const report = buildKtxRelationshipBenchmarkReport({ fixtures: [ fixture({ id: 'composite_keys_no_declared_constraints', @@ -436,7 +436,7 @@ describe('relationship benchmark report', () => { fk: [compositeFk], }); - const markdown = formatKloRelationshipBenchmarkReportMarkdown(report); + const markdown = formatKtxRelationshipBenchmarkReportMarkdown(report); expect(markdown).toContain('## Composite Ground Truth Skips'); expect(markdown).toContain( '### Skipped Composite PKs\n\n- `composite_keys_no_declared_constraints` / `declared_pks_and_declared_fks_removed` / `run`: order_lines.(order_id,line_number)', diff --git a/packages/context/src/scan/relationship-benchmark-report.ts b/packages/context/src/scan/relationship-benchmark-report.ts index 2c2f7275..626d08f8 100644 --- a/packages/context/src/scan/relationship-benchmark-report.ts +++ b/packages/context/src/scan/relationship-benchmark-report.ts @@ -1,19 +1,19 @@ -import { isKloRelationshipBenchmarkTuningEligible } from './relationship-benchmarks.js'; +import { isKtxRelationshipBenchmarkTuningEligible } from './relationship-benchmarks.js'; import type { - KloRelationshipBenchmarkCaseResult, - KloRelationshipBenchmarkFixture, - KloRelationshipBenchmarkMode, - KloRelationshipBenchmarkSuiteResult, + KtxRelationshipBenchmarkCaseResult, + KtxRelationshipBenchmarkFixture, + KtxRelationshipBenchmarkMode, + KtxRelationshipBenchmarkSuiteResult, } from './relationship-benchmarks.js'; -export type KloRelationshipBenchmarkReportCaseStatus = 'run' | 'validation_blocked' | 'not_run'; +export type KtxRelationshipBenchmarkReportCaseStatus = 'run' | 'validation_blocked' | 'not_run'; -export interface KloRelationshipBenchmarkReportCase { +export interface KtxRelationshipBenchmarkReportCase { fixtureId: string; fixtureName: string; tier: string; - mode: KloRelationshipBenchmarkMode; - status: KloRelationshipBenchmarkReportCaseStatus; + mode: KtxRelationshipBenchmarkMode; + status: KtxRelationshipBenchmarkReportCaseStatus; reason: string | null; tuningEligible: boolean; metrics: { @@ -39,7 +39,7 @@ export interface KloRelationshipBenchmarkReportCase { }; } -export interface KloRelationshipBenchmarkReport { +export interface KtxRelationshipBenchmarkReport { generatedAt: string; headline: { caseCount: number; @@ -50,10 +50,10 @@ export interface KloRelationshipBenchmarkReport { acceptedFalsePositiveCount: number; validationBlockedCount: number; }; - cases: KloRelationshipBenchmarkReportCase[]; + cases: KtxRelationshipBenchmarkReportCase[]; } -function key(fixtureId: string, mode: KloRelationshipBenchmarkMode): string { +function key(fixtureId: string, mode: KtxRelationshipBenchmarkMode): string { return `${fixtureId}:${mode}`; } @@ -62,8 +62,8 @@ function fixed(value: number | null): string { } function reportCaseReason(input: { - fixture: KloRelationshipBenchmarkFixture; - result: KloRelationshipBenchmarkCaseResult; + fixture: KtxRelationshipBenchmarkFixture; + result: KtxRelationshipBenchmarkCaseResult; }): string | null { if (input.result.validationBlocked) { return 'validation unavailable for this benchmark mode'; @@ -77,10 +77,10 @@ function reportCaseReason(input: { } function reportCaseFromResult(input: { - fixture: KloRelationshipBenchmarkFixture; - mode: KloRelationshipBenchmarkMode; - result: KloRelationshipBenchmarkCaseResult; -}): KloRelationshipBenchmarkReportCase { + fixture: KtxRelationshipBenchmarkFixture; + mode: KtxRelationshipBenchmarkMode; + result: KtxRelationshipBenchmarkCaseResult; +}): KtxRelationshipBenchmarkReportCase { const status = input.result.validationBlocked ? 'validation_blocked' : 'run'; return { fixtureId: input.fixture.id, @@ -89,7 +89,7 @@ function reportCaseFromResult(input: { mode: input.mode, status, reason: reportCaseReason({ fixture: input.fixture, result: input.result }), - tuningEligible: isKloRelationshipBenchmarkTuningEligible({ + tuningEligible: isKtxRelationshipBenchmarkTuningEligible({ fixture: input.fixture, mode: input.mode, validationBlocked: input.result.validationBlocked, @@ -110,10 +110,10 @@ function reportCaseFromResult(input: { } function notRunCase(input: { - fixture: KloRelationshipBenchmarkFixture; - mode: KloRelationshipBenchmarkMode; + fixture: KtxRelationshipBenchmarkFixture; + mode: KtxRelationshipBenchmarkMode; reason: string; -}): KloRelationshipBenchmarkReportCase { +}): KtxRelationshipBenchmarkReportCase { return { fixtureId: input.fixture.id, fixtureName: input.fixture.name, @@ -137,14 +137,14 @@ function notRunCase(input: { }; } -export function buildKloRelationshipBenchmarkReport(input: { - fixtures: readonly KloRelationshipBenchmarkFixture[]; - suite: KloRelationshipBenchmarkSuiteResult; - modes: readonly KloRelationshipBenchmarkMode[]; +export function buildKtxRelationshipBenchmarkReport(input: { + fixtures: readonly KtxRelationshipBenchmarkFixture[]; + suite: KtxRelationshipBenchmarkSuiteResult; + modes: readonly KtxRelationshipBenchmarkMode[]; generatedAt?: string; -}): KloRelationshipBenchmarkReport { +}): KtxRelationshipBenchmarkReport { const resultsByKey = new Map(input.suite.cases.map((result) => [key(result.fixtureId, result.mode), result])); - const cases: KloRelationshipBenchmarkReportCase[] = []; + const cases: KtxRelationshipBenchmarkReportCase[] = []; for (const fixture of input.fixtures) { const selectedModes = new Set(fixture.defaultModes); @@ -182,13 +182,13 @@ export function buildKloRelationshipBenchmarkReport(input: { }; } -type KloRelationshipBenchmarkFailureSelector = ( - item: KloRelationshipBenchmarkReportCase, +type KtxRelationshipBenchmarkFailureSelector = ( + item: KtxRelationshipBenchmarkReportCase, ) => readonly string[]; function sortedFailureLines(input: { - cases: readonly KloRelationshipBenchmarkReportCase[]; - select: KloRelationshipBenchmarkFailureSelector; + cases: readonly KtxRelationshipBenchmarkReportCase[]; + select: KtxRelationshipBenchmarkFailureSelector; }): string[] { return input.cases .flatMap((item) => @@ -209,14 +209,14 @@ function sortedFailureLines(input: { function failureBlock(input: { title: string; - cases: readonly KloRelationshipBenchmarkReportCase[]; - select: KloRelationshipBenchmarkFailureSelector; + cases: readonly KtxRelationshipBenchmarkReportCase[]; + select: KtxRelationshipBenchmarkFailureSelector; }): string[] { const values = sortedFailureLines({ cases: input.cases, select: input.select }); return ['', `### ${input.title}`, '', ...(values.length > 0 ? values : ['- none'])]; } -function headlineFailureContextBlocks(report: KloRelationshipBenchmarkReport): string[] { +function headlineFailureContextBlocks(report: KtxRelationshipBenchmarkReport): string[] { const headlineCases = report.cases.filter((item) => item.tuningEligible); const remainingPkMisses = sortedFailureLines({ cases: headlineCases, @@ -246,7 +246,7 @@ function headlineFailureContextBlocks(report: KloRelationshipBenchmarkReport): s ]; } -function failureDetailBlocks(report: KloRelationshipBenchmarkReport): string[] { +function failureDetailBlocks(report: KtxRelationshipBenchmarkReport): string[] { const headlineCases = report.cases.filter((item) => item.tuningEligible); const otherCases = report.cases.filter((item) => !item.tuningEligible); @@ -296,7 +296,7 @@ function failureDetailBlocks(report: KloRelationshipBenchmarkReport): string[] { ]; } -function compositeSkipBlocks(report: KloRelationshipBenchmarkReport): string[] { +function compositeSkipBlocks(report: KtxRelationshipBenchmarkReport): string[] { const headlineCases = report.cases.filter((item) => item.tuningEligible); return [ @@ -315,9 +315,9 @@ function compositeSkipBlocks(report: KloRelationshipBenchmarkReport): string[] { ]; } -export function formatKloRelationshipBenchmarkReportMarkdown(report: KloRelationshipBenchmarkReport): string { +export function formatKtxRelationshipBenchmarkReportMarkdown(report: KtxRelationshipBenchmarkReport): string { const lines = [ - '# KLO Relationship Discovery Benchmark Evidence', + '# KTX Relationship Discovery Benchmark Evidence', '', `Generated: ${report.generatedAt}`, '', diff --git a/packages/context/src/scan/relationship-benchmarks.test.ts b/packages/context/src/scan/relationship-benchmarks.test.ts index e1f7c1fc..ecc9e4a9 100644 --- a/packages/context/src/scan/relationship-benchmarks.test.ts +++ b/packages/context/src/scan/relationship-benchmarks.test.ts @@ -3,20 +3,20 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import type { - KloRelationshipBenchmarkExpectedLinks, - KloRelationshipBenchmarkFixture, + KtxRelationshipBenchmarkExpectedLinks, + KtxRelationshipBenchmarkFixture, } from './relationship-benchmarks.js'; import { - currentKloRelationshipBenchmarkDetector, - loadKloRelationshipBenchmarkFixture, - loadKloRelationshipBenchmarkFixtures, - maskKloRelationshipBenchmarkSnapshot, - runKloRelationshipBenchmarkCase, - runKloRelationshipBenchmarkSuite, + currentKtxRelationshipBenchmarkDetector, + loadKtxRelationshipBenchmarkFixture, + loadKtxRelationshipBenchmarkFixtures, + maskKtxRelationshipBenchmarkSnapshot, + runKtxRelationshipBenchmarkCase, + runKtxRelationshipBenchmarkSuite, } from './relationship-benchmarks.js'; -import type { KloSchemaSnapshot } from './types.js'; +import type { KtxSchemaSnapshot } from './types.js'; -const EXPECTED_LINKS: KloRelationshipBenchmarkExpectedLinks = { +const EXPECTED_LINKS: KtxRelationshipBenchmarkExpectedLinks = { expectedPks: [ { table: 'accounts', columns: ['id'] }, { table: 'users', columns: ['id'] }, @@ -53,7 +53,7 @@ const CHECKED_IN_FIXTURE_ORIGINS = { semantic_embedding_aliases_no_declared_constraints: 'synthetic', } as const; -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'sqlite', @@ -136,16 +136,16 @@ describe('relationship benchmarks', () => { it('keeps the current benchmark detector on the relationship-discovery path only', async () => { const source = await readFile(new URL('relationship-benchmarks.ts', import.meta.url), 'utf-8'); - expect(source).not.toMatch(/KloRelationshipDetector/); + expect(source).not.toMatch(/KtxRelationshipDetector/); expect(source).not.toMatch(/relationship-detection\.js/); expect(source).not.toMatch(/\bacceptedLinks\b/); - expect(source).toMatch(/generateKloRelationshipDiscoveryCandidates/); - expect(source).toMatch(/validateKloRelationshipDiscoveryCandidates/); - expect(source).toMatch(/resolveKloRelationshipGraph/); + expect(source).toMatch(/generateKtxRelationshipDiscoveryCandidates/); + expect(source).toMatch(/validateKtxRelationshipDiscoveryCandidates/); + expect(source).toMatch(/resolveKtxRelationshipGraph/); }); it('scores the current detector with declared metadata present', async () => { - const result = await runKloRelationshipBenchmarkCase({ + const result = await runKtxRelationshipBenchmarkCase({ fixture: { id: 'mini_declared', name: 'Mini declared fixture', @@ -158,7 +158,7 @@ describe('relationship benchmarks', () => { columnEmbeddings: {}, }, mode: 'metadata_present', - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); expect(result.metrics.pkRecall).toBe(1); @@ -170,7 +170,7 @@ describe('relationship benchmarks', () => { }); it('keeps no-declared-constraint misses in benchmark metrics', async () => { - const result = await runKloRelationshipBenchmarkCase({ + const result = await runKtxRelationshipBenchmarkCase({ fixture: { id: 'mini_no_declared', name: 'Mini no declared fixture', @@ -183,7 +183,7 @@ describe('relationship benchmarks', () => { columnEmbeddings: {}, }, mode: 'declared_pks_and_declared_fks_removed', - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); expect(result.metrics.pkRecall).toBe(0.5); @@ -197,7 +197,7 @@ describe('relationship benchmarks', () => { }); it('keeps composite ground truth in recall denominators and skipped-composite buckets', async () => { - const compositeExpected: KloRelationshipBenchmarkExpectedLinks = { + const compositeExpected: KtxRelationshipBenchmarkExpectedLinks = { expectedPks: [{ table: 'order_lines', columns: ['order_id', 'line_number'] }], expectedLinks: [ { @@ -222,7 +222,7 @@ describe('relationship benchmarks', () => { }, }; - const result = await runKloRelationshipBenchmarkCase({ + const result = await runKtxRelationshipBenchmarkCase({ fixture: { id: 'composite_no_declared', name: 'Composite relationship fixture without declared constraints', @@ -256,7 +256,7 @@ describe('relationship benchmarks', () => { it('loads the composite-key fixture and accepts composite ground truth as headline evidence', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'composite_keys_no_declared_constraints'), ); @@ -270,9 +270,9 @@ describe('relationship benchmarks', () => { ]); expect(fixture.dataPath).toMatch(/composite_keys_no_declared_constraints\/data\.sqlite$/); - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [fixture], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); const headline = suite.cases.find( (item) => @@ -319,7 +319,7 @@ describe('relationship benchmarks', () => { it('counts formal metadata links in metadata-present mode without SQL validation', async () => { const source = snapshot(); - const fixture: KloRelationshipBenchmarkFixture = { + const fixture: KtxRelationshipBenchmarkFixture = { id: 'declared_without_sql', name: 'Declared relationships without SQL validation', tier: 'unit', @@ -357,7 +357,7 @@ describe('relationship benchmarks', () => { columnEmbeddings: {}, }; - const result = await runKloRelationshipBenchmarkCase({ + const result = await runKtxRelationshipBenchmarkCase({ fixture, mode: 'metadata_present', }); @@ -369,8 +369,8 @@ describe('relationship benchmarks', () => { }); it('masks primary keys and foreign keys independently', () => { - const pksRemoved = maskKloRelationshipBenchmarkSnapshot(snapshot(), 'declared_pks_removed'); - const fksRemoved = maskKloRelationshipBenchmarkSnapshot(snapshot(), 'declared_fks_removed'); + const pksRemoved = maskKtxRelationshipBenchmarkSnapshot(snapshot(), 'declared_pks_removed'); + const fksRemoved = maskKtxRelationshipBenchmarkSnapshot(snapshot(), 'declared_fks_removed'); expect(pksRemoved.tables.flatMap((table) => table.columns.filter((column) => column.primaryKey))).toEqual([]); expect(pksRemoved.tables.find((table) => table.name === 'users')?.foreignKeys).toHaveLength(1); @@ -379,7 +379,7 @@ describe('relationship benchmarks', () => { }); it('loads fixture.yaml, snapshot.json, and expected-links.yaml from a fixture directory', async () => { - const fixtureDir = await mkdtemp(join(tmpdir(), 'klo-relationship-fixture-')); + const fixtureDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-fixture-')); try { await writeFile( join(fixtureDir, 'fixture.yaml'), @@ -425,7 +425,7 @@ describe('relationship benchmarks', () => { ].join('\n'), ); - await expect(loadKloRelationshipBenchmarkFixture(fixtureDir)).resolves.toMatchObject({ + await expect(loadKtxRelationshipBenchmarkFixture(fixtureDir)).resolves.toMatchObject({ id: 'mini_loaded', origin: 'synthetic', validationBudget: 3, @@ -470,7 +470,7 @@ describe('relationship benchmarks', () => { }, }; - await runKloRelationshipBenchmarkSuite({ + await runKtxRelationshipBenchmarkSuite({ fixtures: [ { id: 'budgeted_fixture', @@ -503,7 +503,7 @@ describe('relationship benchmarks', () => { }); it('requires relationship benchmark fixture origin provenance', async () => { - const fixtureDir = await mkdtemp(join(tmpdir(), 'klo-relationship-missing-origin-')); + const fixtureDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-missing-origin-')); try { await writeFile( join(fixtureDir, 'fixture.yaml'), @@ -522,14 +522,14 @@ describe('relationship benchmarks', () => { ['expectedPks:', ' - table: accounts', ' columns: [id]', 'expectedLinks: []', ''].join('\n'), ); - await expect(loadKloRelationshipBenchmarkFixture(fixtureDir)).rejects.toThrow(/origin/); + await expect(loadKtxRelationshipBenchmarkFixture(fixtureDir)).rejects.toThrow(/origin/); } finally { await rm(fixtureDir, { recursive: true, force: true }); } }); it('loads all benchmark fixture directories in stable order', async () => { - const fixtureRoot = await mkdtemp(join(tmpdir(), 'klo-relationship-fixture-root-')); + const fixtureRoot = await mkdtemp(join(tmpdir(), 'ktx-relationship-fixture-root-')); async function writeFixtureDir(dirName: string, fixtureId: string): Promise { const fixtureDir = join(fixtureRoot, dirName); @@ -570,7 +570,7 @@ describe('relationship benchmarks', () => { await writeFixtureDir('z_fixture', 'z_fixture'); await writeFixtureDir('a_fixture', 'a_fixture'); - await expect(loadKloRelationshipBenchmarkFixtures(fixtureRoot)).resolves.toMatchObject([ + await expect(loadKtxRelationshipBenchmarkFixtures(fixtureRoot)).resolves.toMatchObject([ { id: 'a_fixture', origin: 'synthetic' }, { id: 'z_fixture', origin: 'synthetic' }, ]); @@ -588,7 +588,7 @@ describe('relationship benchmarks', () => { expect(fixtureDirs).toEqual(Object.keys(CHECKED_IN_FIXTURE_ORIGINS).sort()); - const fixtures = await loadKloRelationshipBenchmarkFixtures(fixtureRoot.pathname); + const fixtures = await loadKtxRelationshipBenchmarkFixtures(fixtureRoot.pathname); expect(Object.fromEntries(fixtures.map((fixture) => [fixture.id, fixture.origin]))).toEqual( CHECKED_IN_FIXTURE_ORIGINS, ); @@ -596,7 +596,7 @@ describe('relationship benchmarks', () => { it('loads May 8 evidence-fusion adversarial fixtures as reported synthetic evidence', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixtures = await loadKloRelationshipBenchmarkFixtures(fixtureRoot.pathname); + const fixtures = await loadKtxRelationshipBenchmarkFixtures(fixtureRoot.pathname); const byId = new Map(fixtures.map((fixture) => [fixture.id, fixture])); const adversarialIds = [ 'non_english_naming_no_declared_constraints', @@ -629,7 +629,7 @@ describe('relationship benchmarks', () => { it('loads the May 8 scale stress fixture with bounded benchmark validation', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'scale_stress_no_declared_constraints'), ); @@ -646,14 +646,14 @@ describe('relationship benchmarks', () => { it('runs the scale stress fixture inside the benchmark validation budget', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'scale_stress_no_declared_constraints'), ); - const result = await runKloRelationshipBenchmarkCase({ + const result = await runKtxRelationshipBenchmarkCase({ fixture, mode: 'declared_pks_and_declared_fks_removed', - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); expect(result.metrics.runtimeSeconds).toBeLessThan(60); @@ -662,7 +662,7 @@ describe('relationship benchmarks', () => { }, 60_000); it('aggregates suite metrics without hiding validation-blocked cases', async () => { - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [ { id: 'mini_declared', @@ -687,7 +687,7 @@ describe('relationship benchmarks', () => { columnEmbeddings: {}, }, ], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); expect(suite.cases.map((item) => `${item.fixtureId}:${item.mode}`)).toEqual([ @@ -730,7 +730,7 @@ describe('relationship benchmarks', () => { }, }; - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [ { id: 'smoke_no_declared', @@ -792,7 +792,7 @@ describe('relationship benchmarks', () => { }, }; - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [ { id: 'product_not_curated', @@ -841,10 +841,10 @@ describe('relationship benchmarks', () => { it('loads the packaged B2B demo fixtures and records the current relationship-discovery baseline', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const declared = await loadKloRelationshipBenchmarkFixture( + const declared = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'demo_b2b_declared_metadata'), ); - const noDeclared = await loadKloRelationshipBenchmarkFixture( + const noDeclared = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'demo_b2b_no_declared_constraints'), ); @@ -868,9 +868,9 @@ describe('relationship benchmarks', () => { 'embeddings_disabled', ]); - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [declared, noDeclared], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); const declaredCase = suite.cases.find( @@ -922,7 +922,7 @@ describe('relationship benchmarks', () => { it('loads the public Chinook benchmark fixture with declared metadata', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'chinook_with_declared_metadata'), ); expect(fixture.tier).toBe('row_bearing'); @@ -940,7 +940,7 @@ describe('relationship benchmarks', () => { it('loads the public Northwind benchmark fixture with declared metadata', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'northwind_with_declared_metadata'), ); expect(fixture.tier).toBe('row_bearing'); @@ -956,7 +956,7 @@ describe('relationship benchmarks', () => { it('loads the public Sakila benchmark fixture with declared metadata', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'sakila_with_declared_metadata'), ); expect(fixture.tier).toBe('row_bearing'); @@ -972,7 +972,7 @@ describe('relationship benchmarks', () => { it('loads the public AdventureWorksLT benchmark fixture with declared metadata', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'adventureworkslt_with_declared_metadata'), ); @@ -1032,7 +1032,7 @@ describe('relationship benchmarks', () => { it('loads the full AdventureWorks OLTP benchmark fixture with declared metadata', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'adventureworks_oltp_with_declared_metadata'), ); @@ -1092,7 +1092,7 @@ describe('relationship benchmarks', () => { it('loads the row-bearing natural-key fixture and counts it as headline evidence', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const naturalKeys = await loadKloRelationshipBenchmarkFixture( + const naturalKeys = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'natural_keys_no_declared_constraints'), ); @@ -1105,9 +1105,9 @@ describe('relationship benchmarks', () => { 'embeddings_disabled', ]); - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [naturalKeys], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); const headline = suite.cases.find( (item) => @@ -1126,7 +1126,7 @@ describe('relationship benchmarks', () => { it('accepts plan-code suffix relationships only when validation is available', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'plan_code_no_declared_constraints'), ); @@ -1139,9 +1139,9 @@ describe('relationship benchmarks', () => { 'embeddings_disabled', ]); - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [fixture], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); const expectedAccepted = [ 'mart_account_segments.(current_plan_code)->stg_plans.(plan_code)', @@ -1187,7 +1187,7 @@ describe('relationship benchmarks', () => { it('uses embedding fixtures for semantic alias relationship benchmark cases', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'semantic_embedding_aliases_no_declared_constraints'), ); @@ -1196,15 +1196,15 @@ describe('relationship benchmarks', () => { 'orders.buyer_ref': [0.995, 0.005, 0], }); - const withEmbeddings = await runKloRelationshipBenchmarkCase({ + const withEmbeddings = await runKtxRelationshipBenchmarkCase({ fixture, mode: 'declared_pks_and_declared_fks_removed', - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); - const withoutEmbeddings = await runKloRelationshipBenchmarkCase({ + const withoutEmbeddings = await runKtxRelationshipBenchmarkCase({ fixture, mode: 'embeddings_disabled', - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); expect(withEmbeddings.predicted.acceptedFk).toEqual(['orders.(buyer_ref)->customers.(id)']); @@ -1218,7 +1218,7 @@ describe('relationship benchmarks', () => { it('loads the Orbit-style product fixture as curated relationship-discovery benchmark evidence', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'orbit_style_product_no_declared_constraints'), ); @@ -1232,9 +1232,9 @@ describe('relationship benchmarks', () => { 'embeddings_disabled', ]); - const suite = await runKloRelationshipBenchmarkSuite({ + const suite = await runKtxRelationshipBenchmarkSuite({ fixtures: [fixture], - detector: currentKloRelationshipBenchmarkDetector(), + detector: currentKtxRelationshipBenchmarkDetector(), }); const headline = suite.cases.find( (item) => diff --git a/packages/context/src/scan/relationship-benchmarks.ts b/packages/context/src/scan/relationship-benchmarks.ts index fca3d15e..09723947 100644 --- a/packages/context/src/scan/relationship-benchmarks.ts +++ b/packages/context/src/scan/relationship-benchmarks.ts @@ -6,30 +6,30 @@ import { gunzipSync } from 'node:zlib'; import Database from 'better-sqlite3'; import YAML from 'yaml'; import { z } from 'zod'; -import type { KloEnrichedRelationship, KloEnrichedSchema, KloRelationshipType } from './enrichment-types.js'; -import { snapshotToKloEnrichedSchema } from './local-enrichment.js'; -import type { KloRelationshipDiscoveryCandidate } from './relationship-candidates.js'; +import type { KtxEnrichedRelationship, KtxEnrichedSchema, KtxRelationshipType } from './enrichment-types.js'; +import { snapshotToKtxEnrichedSchema } from './local-enrichment.js'; +import type { KtxRelationshipDiscoveryCandidate } from './relationship-candidates.js'; import { - generateKloRelationshipDiscoveryCandidates, - mergeKloRelationshipDiscoveryCandidates, + generateKtxRelationshipDiscoveryCandidates, + mergeKtxRelationshipDiscoveryCandidates, } from './relationship-candidates.js'; -import type { KloLlmProvider } from '@klo/llm'; -import { proposeKloRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; +import type { KtxLlmProvider } from '@ktx/llm'; +import { proposeKtxRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; import { - discoverKloCompositeRelationships, - type KloCompositePrimaryKeyCandidate, - type KloCompositeRelationshipCandidate, + discoverKtxCompositeRelationships, + type KtxCompositePrimaryKeyCandidate, + type KtxCompositeRelationshipCandidate, } from './relationship-composite-candidates.js'; -import { emptyKloRelationshipProfileArtifact } from './relationship-diagnostics.js'; -import { collectKloFormalMetadataRelationships } from './relationship-formal-metadata.js'; -import { resolveKloRelationshipGraph } from './relationship-graph-resolver.js'; -import { type KloRelationshipReadOnlyExecutor, profileKloRelationshipSchema } from './relationship-profiling.js'; -import type { KloRelationshipValidationBudget } from './relationship-budget.js'; -import type { KloRelationshipFixtureOrigin } from './relationship-scoring.js'; -import { validateKloRelationshipDiscoveryCandidates } from './relationship-validation.js'; -import type { KloQueryResult, KloReadOnlyQueryInput, KloScanContext, KloSchemaSnapshot } from './types.js'; +import { emptyKtxRelationshipProfileArtifact } from './relationship-diagnostics.js'; +import { collectKtxFormalMetadataRelationships } from './relationship-formal-metadata.js'; +import { resolveKtxRelationshipGraph } from './relationship-graph-resolver.js'; +import { type KtxRelationshipReadOnlyExecutor, profileKtxRelationshipSchema } from './relationship-profiling.js'; +import type { KtxRelationshipValidationBudget } from './relationship-budget.js'; +import type { KtxRelationshipFixtureOrigin } from './relationship-scoring.js'; +import { validateKtxRelationshipDiscoveryCandidates } from './relationship-validation.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput, KtxScanContext, KtxSchemaSnapshot } from './types.js'; -export const KLO_RELATIONSHIP_BENCHMARK_MODES = [ +export const KTX_RELATIONSHIP_BENCHMARK_MODES = [ 'metadata_present', 'declared_fks_removed', 'declared_pks_removed', @@ -40,87 +40,87 @@ export const KLO_RELATIONSHIP_BENCHMARK_MODES = [ 'embeddings_disabled', ] as const; -export type KloRelationshipBenchmarkMode = (typeof KLO_RELATIONSHIP_BENCHMARK_MODES)[number]; +export type KtxRelationshipBenchmarkMode = (typeof KTX_RELATIONSHIP_BENCHMARK_MODES)[number]; -export const KLO_RELATIONSHIP_BENCHMARK_TIERS = ['unit', 'row_bearing', 'schema_only', 'smoke', 'product'] as const; +export const KTX_RELATIONSHIP_BENCHMARK_TIERS = ['unit', 'row_bearing', 'schema_only', 'smoke', 'product'] as const; -export type KloRelationshipBenchmarkTier = (typeof KLO_RELATIONSHIP_BENCHMARK_TIERS)[number]; +export type KtxRelationshipBenchmarkTier = (typeof KTX_RELATIONSHIP_BENCHMARK_TIERS)[number]; -export type KloRelationshipBenchmarkStatus = 'accepted' | 'review' | 'rejected'; +export type KtxRelationshipBenchmarkStatus = 'accepted' | 'review' | 'rejected'; -export interface KloRelationshipBenchmarkExpectedPk { +export interface KtxRelationshipBenchmarkExpectedPk { table: string; columns: string[]; } -export interface KloRelationshipBenchmarkExpectedLink { +export interface KtxRelationshipBenchmarkExpectedLink { fromTable: string; fromColumns: string[]; toTable: string; toColumns: string[]; - relationship: KloRelationshipType; + relationship: KtxRelationshipType; } -export interface KloRelationshipBenchmarkExpectedLinks { - expectedPks: KloRelationshipBenchmarkExpectedPk[]; - expectedLinks: KloRelationshipBenchmarkExpectedLink[]; +export interface KtxRelationshipBenchmarkExpectedLinks { + expectedPks: KtxRelationshipBenchmarkExpectedPk[]; + expectedLinks: KtxRelationshipBenchmarkExpectedLink[]; } -export interface KloRelationshipBenchmarkFixture { +export interface KtxRelationshipBenchmarkFixture { id: string; name: string; - tier: KloRelationshipBenchmarkTier; - origin: KloRelationshipFixtureOrigin; + tier: KtxRelationshipBenchmarkTier; + origin: KtxRelationshipFixtureOrigin; thresholdEligible?: boolean; - validationBudget?: KloRelationshipValidationBudget; - snapshot: KloSchemaSnapshot; - expected: KloRelationshipBenchmarkExpectedLinks; - defaultModes: KloRelationshipBenchmarkMode[]; + validationBudget?: KtxRelationshipValidationBudget; + snapshot: KtxSchemaSnapshot; + expected: KtxRelationshipBenchmarkExpectedLinks; + defaultModes: KtxRelationshipBenchmarkMode[]; dataPath: string | null; columnEmbeddings: Record; } -export interface KloRelationshipBenchmarkDetectedPk { +export interface KtxRelationshipBenchmarkDetectedPk { table: string; columns: string[]; score: number; - status: KloRelationshipBenchmarkStatus; + status: KtxRelationshipBenchmarkStatus; } -export interface KloRelationshipBenchmarkDetectedLink { +export interface KtxRelationshipBenchmarkDetectedLink { fromTable: string; fromColumns: string[]; toTable: string; toColumns: string[]; - relationship: KloRelationshipType; + relationship: KtxRelationshipType; score: number; - status: KloRelationshipBenchmarkStatus; + status: KtxRelationshipBenchmarkStatus; source: string; } -export interface KloRelationshipBenchmarkDetectorResult { - pks: KloRelationshipBenchmarkDetectedPk[]; - links: KloRelationshipBenchmarkDetectedLink[]; +export interface KtxRelationshipBenchmarkDetectorResult { + pks: KtxRelationshipBenchmarkDetectedPk[]; + links: KtxRelationshipBenchmarkDetectedLink[]; validationBlocked: boolean; sqlQueries: number; llmCalls: number; runtimeSeconds: number; } -export interface KloRelationshipBenchmarkDetectorInput { +export interface KtxRelationshipBenchmarkDetectorInput { fixtureId: string; - mode: KloRelationshipBenchmarkMode; - snapshot: KloSchemaSnapshot; - schema: KloEnrichedSchema; + mode: KtxRelationshipBenchmarkMode; + snapshot: KtxSchemaSnapshot; + schema: KtxEnrichedSchema; dataPath: string | null; - validationBudget?: KloRelationshipValidationBudget; + validationBudget?: KtxRelationshipValidationBudget; } -export interface KloRelationshipBenchmarkDetector { - detect(input: KloRelationshipBenchmarkDetectorInput): Promise; +export interface KtxRelationshipBenchmarkDetector { + detect(input: KtxRelationshipBenchmarkDetectorInput): Promise; } -export interface KloRelationshipBenchmarkMetrics { +export interface KtxRelationshipBenchmarkMetrics { pkPrecision: number; pkRecall: number; pkF1: number; @@ -135,10 +135,10 @@ export interface KloRelationshipBenchmarkMetrics { llmCalls: number; } -export interface KloRelationshipBenchmarkCaseResult { +export interface KtxRelationshipBenchmarkCaseResult { fixtureId: string; - mode: KloRelationshipBenchmarkMode; - metrics: KloRelationshipBenchmarkMetrics; + mode: KtxRelationshipBenchmarkMode; + metrics: KtxRelationshipBenchmarkMetrics; expected: { pk: string[]; fk: string[]; @@ -164,8 +164,8 @@ export interface KloRelationshipBenchmarkCaseResult { validationBlocked: boolean; } -export interface KloRelationshipBenchmarkSuiteResult { - cases: KloRelationshipBenchmarkCaseResult[]; +export interface KtxRelationshipBenchmarkSuiteResult { + cases: KtxRelationshipBenchmarkCaseResult[]; validationBlockedCases: string[]; aggregate: { caseCount: number; @@ -179,7 +179,7 @@ export interface KloRelationshipBenchmarkSuiteResult { }; } -class KloRelationshipBenchmarkSqliteExecutor implements KloRelationshipReadOnlyExecutor { +class KtxRelationshipBenchmarkSqliteExecutor implements KtxRelationshipReadOnlyExecutor { private readonly db: Database.Database; queryCount = 0; @@ -187,7 +187,7 @@ class KloRelationshipBenchmarkSqliteExecutor implements KloRelationshipReadOnlyE this.db = new Database(dataPath, { readonly: true, fileMustExist: true }); } - async executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.queryCount += 1; const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); @@ -236,7 +236,7 @@ async function fixtureDataPath(fixtureDir: string): Promise { return null; } const digest = createHash('sha256').update(fixtureDir).digest('hex').slice(0, 16); - const tempRoot = await mkdtemp(join(tmpdir(), `klo-relationship-benchmark-${digest}-`)); + const tempRoot = await mkdtemp(join(tmpdir(), `ktx-relationship-benchmark-${digest}-`)); const extractedPath = join(tempRoot, 'data.sqlite'); await writeFile(extractedPath, gunzipSync(await readFile(compressedPath))); return extractedPath; @@ -266,8 +266,8 @@ async function fixtureColumnEmbeddings(fixtureDir: string): Promise): string { +function pkKey(pk: Pick): string { return `${pk.table}.${tupleKey(pk.columns)}`; } function fkKey( - link: Pick, + link: Pick, ): string { return `${link.fromTable}.${tupleKey(link.fromColumns)}->${link.toTable}.${tupleKey(link.toColumns)}`; } -function relationshipKey(link: KloRelationshipBenchmarkDetectedLink): string { +function relationshipKey(link: KtxRelationshipBenchmarkDetectedLink): string { return fkKey(link); } -function relationshipToBenchmarkLink(candidate: KloEnrichedRelationship): KloRelationshipBenchmarkDetectedLink { +function relationshipToBenchmarkLink(candidate: KtxEnrichedRelationship): KtxRelationshipBenchmarkDetectedLink { return { fromTable: candidate.from.table.name, fromColumns: candidate.from.columns, @@ -335,8 +335,8 @@ function relationshipToBenchmarkLink(candidate: KloEnrichedRelationship): KloRel } function broadCandidateToBenchmarkLink( - candidate: Pick, -): KloRelationshipBenchmarkDetectedLink { + candidate: Pick, +): KtxRelationshipBenchmarkDetectedLink { return { fromTable: candidate.from.table.name, fromColumns: candidate.from.columns, @@ -349,7 +349,7 @@ function broadCandidateToBenchmarkLink( }; } -function compositePkToBenchmarkPk(candidate: KloCompositePrimaryKeyCandidate): KloRelationshipBenchmarkDetectedPk { +function compositePkToBenchmarkPk(candidate: KtxCompositePrimaryKeyCandidate): KtxRelationshipBenchmarkDetectedPk { return { table: candidate.table.name, columns: candidate.columns, @@ -359,8 +359,8 @@ function compositePkToBenchmarkPk(candidate: KloCompositePrimaryKeyCandidate): K } function compositeRelationshipToBenchmarkLink( - candidate: KloCompositeRelationshipCandidate, -): KloRelationshipBenchmarkDetectedLink { + candidate: KtxCompositeRelationshipCandidate, +): KtxRelationshipBenchmarkDetectedLink { return { fromTable: candidate.from.table.name, fromColumns: candidate.from.columns, @@ -391,30 +391,30 @@ function intersectionSize(left: readonly string[], right: readonly string[]): nu return left.filter((item) => rightSet.has(item)).length; } -function compositePkKeys(expected: KloRelationshipBenchmarkExpectedLinks): string[] { +function compositePkKeys(expected: KtxRelationshipBenchmarkExpectedLinks): string[] { return sortedUnique(expected.expectedPks.filter((pk) => pk.columns.length > 1).map(pkKey)); } -function compositeFkKeys(expected: KloRelationshipBenchmarkExpectedLinks): string[] { +function compositeFkKeys(expected: KtxRelationshipBenchmarkExpectedLinks): string[] { return sortedUnique( expected.expectedLinks.filter((link) => link.fromColumns.length > 1 || link.toColumns.length > 1).map(fkKey), ); } -function scalarExpectedPkKeys(expected: KloRelationshipBenchmarkExpectedLinks): string[] { +function scalarExpectedPkKeys(expected: KtxRelationshipBenchmarkExpectedLinks): string[] { return sortedUnique(expected.expectedPks.map(pkKey)); } -function scalarExpectedFkKeys(expected: KloRelationshipBenchmarkExpectedLinks): string[] { +function scalarExpectedFkKeys(expected: KtxRelationshipBenchmarkExpectedLinks): string[] { return sortedUnique(expected.expectedLinks.map(fkKey)); } function scoreBenchmarkCase(input: { fixtureId: string; - mode: KloRelationshipBenchmarkMode; - expected: KloRelationshipBenchmarkExpectedLinks; - detected: KloRelationshipBenchmarkDetectorResult; -}): KloRelationshipBenchmarkCaseResult { + mode: KtxRelationshipBenchmarkMode; + expected: KtxRelationshipBenchmarkExpectedLinks; + detected: KtxRelationshipBenchmarkDetectorResult; +}): KtxRelationshipBenchmarkCaseResult { const expectedPk = scalarExpectedPkKeys(input.expected); const expectedFk = scalarExpectedFkKeys(input.expected); const predictedPk = sortedUnique(input.detected.pks.map(pkKey)); @@ -478,10 +478,10 @@ function scoreBenchmarkCase(input: { }; } -export function maskKloRelationshipBenchmarkSnapshot( - snapshot: KloSchemaSnapshot, - mode: KloRelationshipBenchmarkMode, -): KloSchemaSnapshot { +export function maskKtxRelationshipBenchmarkSnapshot( + snapshot: KtxSchemaSnapshot, + mode: KtxRelationshipBenchmarkMode, +): KtxSchemaSnapshot { const relationshipDiscoveryMode = mode === 'declared_pks_and_declared_fks_removed' || mode === 'llm_disabled' || @@ -506,9 +506,9 @@ export function maskKloRelationshipBenchmarkSnapshot( }; } -export function isKloRelationshipBenchmarkTuningEligible(input: { - fixture: Pick; - mode: KloRelationshipBenchmarkMode; +export function isKtxRelationshipBenchmarkTuningEligible(input: { + fixture: Pick; + mode: KtxRelationshipBenchmarkMode; validationBlocked: boolean; }): boolean { if (input.validationBlocked || input.mode !== 'declared_pks_and_declared_fks_removed') { @@ -526,49 +526,49 @@ export function isKloRelationshipBenchmarkTuningEligible(input: { return input.fixture.tier === 'unit' || input.fixture.tier === 'row_bearing'; } -export function kloRelationshipBenchmarkDetectorWithLlm( - llmProvider: KloLlmProvider, -): KloRelationshipBenchmarkDetector { +export function ktxRelationshipBenchmarkDetectorWithLlm( + llmProvider: KtxLlmProvider, +): KtxRelationshipBenchmarkDetector { return { async detect(input) { const startedAt = performance.now(); - const formalMetadata = collectKloFormalMetadataRelationships(input.schema); + const formalMetadata = collectKtxFormalMetadataRelationships(input.schema); const formalLinks = formalMetadata.accepted.map((relationship) => relationshipToBenchmarkLink(relationship)); const acceptedKeys = new Set(formalLinks.map(fkKey)); const sqliteDataAvailable = Boolean(input.dataPath && input.snapshot.driver === 'sqlite'); const profilingExecutor = sqliteDataAvailable && input.mode !== 'profiling_disabled' - ? new KloRelationshipBenchmarkSqliteExecutor(input.dataPath as string) + ? new KtxRelationshipBenchmarkSqliteExecutor(input.dataPath as string) : null; const validationExecutor = profilingExecutor && input.mode !== 'validation_disabled' ? profilingExecutor : null; const profiles = input.mode === 'profiling_disabled' - ? emptyKloRelationshipProfileArtifact({ + ? emptyKtxRelationshipProfileArtifact({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, reason: 'relationship_benchmark_profiling_disabled', }) - : await profileKloRelationshipSchema({ + : await profileKtxRelationshipSchema({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, schema: input.schema, executor: profilingExecutor, ctx: { runId: `relationship-benchmark:${input.fixtureId}:${input.mode}:profile` }, }); - const broadRelationshipCandidates = generateKloRelationshipDiscoveryCandidates(input.schema, { + const broadRelationshipCandidates = generateKtxRelationshipDiscoveryCandidates(input.schema, { profiles, useEmbeddings: input.mode !== 'embeddings_disabled', }); const llmProposalResult = input.mode === 'llm_disabled' ? { candidates: [], warnings: [], llmCalls: 0, summary: 'skipped' as const } - : await proposeKloRelationshipCandidatesWithLlm({ + : await proposeKtxRelationshipCandidatesWithLlm({ connectionId: input.snapshot.connectionId, schema: input.schema, profile: profiles, llmProvider, }); - const candidates = mergeKloRelationshipDiscoveryCandidates([ + const candidates = mergeKtxRelationshipDiscoveryCandidates([ ...broadRelationshipCandidates, ...llmProposalResult.candidates, ]); @@ -578,7 +578,7 @@ export function kloRelationshipBenchmarkDetectorWithLlm( : input.validationBudget === undefined ? 'all' : Math.max(0, input.validationBudget - profiles.queryCount); - const validatedBroadCandidates = await validateKloRelationshipDiscoveryCandidates({ + const validatedBroadCandidates = await validateKtxRelationshipDiscoveryCandidates({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, candidates, @@ -595,7 +595,7 @@ export function kloRelationshipBenchmarkDetectorWithLlm( validationExecutor && input.mode !== 'profiling_disabled' && input.mode !== 'validation_disabled' - ? await discoverKloCompositeRelationships({ + ? await discoverKtxCompositeRelationships({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, schema: input.schema, @@ -605,7 +605,7 @@ export function kloRelationshipBenchmarkDetectorWithLlm( }) : { primaryKeys: [], relationships: [], queryCount: 0, warnings: [] }; profilingExecutor?.close(); - const graph = resolveKloRelationshipGraph({ + const graph = resolveKtxRelationshipGraph({ schema: input.schema, profiles, candidates: validatedBroadCandidates, @@ -663,34 +663,34 @@ export function kloRelationshipBenchmarkDetectorWithLlm( }; } -export function currentKloRelationshipBenchmarkDetector(): KloRelationshipBenchmarkDetector { +export function currentKtxRelationshipBenchmarkDetector(): KtxRelationshipBenchmarkDetector { return { async detect(input) { const startedAt = performance.now(); - const formalMetadata = collectKloFormalMetadataRelationships(input.schema); + const formalMetadata = collectKtxFormalMetadataRelationships(input.schema); const formalLinks = formalMetadata.accepted.map((relationship) => relationshipToBenchmarkLink(relationship)); const acceptedKeys = new Set(formalLinks.map(fkKey)); const sqliteDataAvailable = Boolean(input.dataPath && input.snapshot.driver === 'sqlite'); const profilingExecutor = sqliteDataAvailable && input.mode !== 'profiling_disabled' - ? new KloRelationshipBenchmarkSqliteExecutor(input.dataPath as string) + ? new KtxRelationshipBenchmarkSqliteExecutor(input.dataPath as string) : null; const validationExecutor = profilingExecutor && input.mode !== 'validation_disabled' ? profilingExecutor : null; const profiles = input.mode === 'profiling_disabled' - ? emptyKloRelationshipProfileArtifact({ + ? emptyKtxRelationshipProfileArtifact({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, reason: 'relationship_benchmark_profiling_disabled', }) - : await profileKloRelationshipSchema({ + : await profileKtxRelationshipSchema({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, schema: input.schema, executor: profilingExecutor, ctx: { runId: `relationship-benchmark:${input.fixtureId}:${input.mode}:profile` }, }); - const broadRelationshipCandidates = generateKloRelationshipDiscoveryCandidates(input.schema, { + const broadRelationshipCandidates = generateKtxRelationshipDiscoveryCandidates(input.schema, { profiles, useEmbeddings: input.mode !== 'embeddings_disabled', }); @@ -700,7 +700,7 @@ export function currentKloRelationshipBenchmarkDetector(): KloRelationshipBenchm : input.validationBudget === undefined ? 'all' : Math.max(0, input.validationBudget - profiles.queryCount); - const validatedBroadCandidates = await validateKloRelationshipDiscoveryCandidates({ + const validatedBroadCandidates = await validateKtxRelationshipDiscoveryCandidates({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, candidates: broadRelationshipCandidates, @@ -717,7 +717,7 @@ export function currentKloRelationshipBenchmarkDetector(): KloRelationshipBenchm validationExecutor && input.mode !== 'profiling_disabled' && input.mode !== 'validation_disabled' - ? await discoverKloCompositeRelationships({ + ? await discoverKtxCompositeRelationships({ connectionId: input.snapshot.connectionId, driver: input.snapshot.driver, schema: input.schema, @@ -727,7 +727,7 @@ export function currentKloRelationshipBenchmarkDetector(): KloRelationshipBenchm }) : { primaryKeys: [], relationships: [], queryCount: 0, warnings: [] }; profilingExecutor?.close(); - const graph = resolveKloRelationshipGraph({ + const graph = resolveKtxRelationshipGraph({ schema: input.schema, profiles, candidates: validatedBroadCandidates, @@ -785,9 +785,9 @@ export function currentKloRelationshipBenchmarkDetector(): KloRelationshipBenchm }; } -export async function loadKloRelationshipBenchmarkFixture( +export async function loadKtxRelationshipBenchmarkFixture( fixtureDir: string, -): Promise { +): Promise { const [fixtureRaw, snapshotRaw, expectedRaw] = await Promise.all([ fixtureText(fixtureDir, 'fixture.yaml'), fixtureText(fixtureDir, 'snapshot.json'), @@ -795,7 +795,7 @@ export async function loadKloRelationshipBenchmarkFixture( ]); const fixture = fixtureConfigSchema.parse(YAML.parse(fixtureRaw)); const expected = expectedLinksSchema.parse(YAML.parse(expectedRaw)); - const snapshot = JSON.parse(snapshotRaw) as KloSchemaSnapshot; + const snapshot = JSON.parse(snapshotRaw) as KtxSchemaSnapshot; return { ...fixture, @@ -806,30 +806,30 @@ export async function loadKloRelationshipBenchmarkFixture( }; } -export async function loadKloRelationshipBenchmarkFixtures( +export async function loadKtxRelationshipBenchmarkFixtures( fixtureRoot: string, -): Promise { +): Promise { const entries = await readdir(fixtureRoot, { withFileTypes: true }); const fixtureDirs = entries .filter((entry) => entry.isDirectory()) .map((entry) => join(fixtureRoot, entry.name)) .sort((left, right) => left.localeCompare(right)); - return Promise.all(fixtureDirs.map((fixtureDir) => loadKloRelationshipBenchmarkFixture(fixtureDir))); + return Promise.all(fixtureDirs.map((fixtureDir) => loadKtxRelationshipBenchmarkFixture(fixtureDir))); } -export async function runKloRelationshipBenchmarkCase(input: { - fixture: KloRelationshipBenchmarkFixture; - mode: KloRelationshipBenchmarkMode; - detector?: KloRelationshipBenchmarkDetector; -}): Promise { - const snapshot = maskKloRelationshipBenchmarkSnapshot(input.fixture.snapshot, input.mode); +export async function runKtxRelationshipBenchmarkCase(input: { + fixture: KtxRelationshipBenchmarkFixture; + mode: KtxRelationshipBenchmarkMode; + detector?: KtxRelationshipBenchmarkDetector; +}): Promise { + const snapshot = maskKtxRelationshipBenchmarkSnapshot(input.fixture.snapshot, input.mode); const embeddings = input.mode === 'embeddings_disabled' ? new Map() : new Map(Object.entries(input.fixture.columnEmbeddings)); - const schema = snapshotToKloEnrichedSchema(snapshot, embeddings); - const detected = await (input.detector ?? currentKloRelationshipBenchmarkDetector()).detect({ + const schema = snapshotToKtxEnrichedSchema(snapshot, embeddings); + const detected = await (input.detector ?? currentKtxRelationshipBenchmarkDetector()).detect({ fixtureId: input.fixture.id, mode: input.mode, snapshot, @@ -846,15 +846,15 @@ export async function runKloRelationshipBenchmarkCase(input: { }); } -export async function runKloRelationshipBenchmarkSuite(input: { - fixtures: KloRelationshipBenchmarkFixture[]; - detector?: KloRelationshipBenchmarkDetector; -}): Promise { - const cases: KloRelationshipBenchmarkCaseResult[] = []; +export async function runKtxRelationshipBenchmarkSuite(input: { + fixtures: KtxRelationshipBenchmarkFixture[]; + detector?: KtxRelationshipBenchmarkDetector; +}): Promise { + const cases: KtxRelationshipBenchmarkCaseResult[] = []; for (const fixture of input.fixtures) { for (const mode of fixture.defaultModes) { cases.push( - await runKloRelationshipBenchmarkCase({ + await runKtxRelationshipBenchmarkCase({ fixture, mode, detector: input.detector, @@ -867,7 +867,7 @@ export async function runKloRelationshipBenchmarkSuite(input: { const headlineCases = cases.filter((item) => { const fixture = fixtureById.get(item.fixtureId); return fixture - ? isKloRelationshipBenchmarkTuningEligible({ + ? isKtxRelationshipBenchmarkTuningEligible({ fixture, mode: item.mode, validationBlocked: item.validationBlocked, diff --git a/packages/context/src/scan/relationship-budget.test.ts b/packages/context/src/scan/relationship-budget.test.ts index d2b1f3f6..479e5b23 100644 --- a/packages/context/src/scan/relationship-budget.test.ts +++ b/packages/context/src/scan/relationship-budget.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { applyKloRelationshipValidationBudget, defaultKloRelationshipValidationBudget } from './relationship-budget.js'; +import { applyKtxRelationshipValidationBudget, defaultKtxRelationshipValidationBudget } from './relationship-budget.js'; interface Candidate { id: string; @@ -8,16 +8,16 @@ interface Candidate { describe('relationship validation budget', () => { it('computes the default validation budget from table count', () => { - expect(defaultKloRelationshipValidationBudget(0)).toBe(0); - expect(defaultKloRelationshipValidationBudget(3)).toBe(6); - expect(defaultKloRelationshipValidationBudget(400)).toBe(800); - expect(defaultKloRelationshipValidationBudget(900)).toBe(1000); - expect(defaultKloRelationshipValidationBudget(-4)).toBe(0); - expect(defaultKloRelationshipValidationBudget(3.8)).toBe(6); + expect(defaultKtxRelationshipValidationBudget(0)).toBe(0); + expect(defaultKtxRelationshipValidationBudget(3)).toBe(6); + expect(defaultKtxRelationshipValidationBudget(400)).toBe(800); + expect(defaultKtxRelationshipValidationBudget(900)).toBe(1000); + expect(defaultKtxRelationshipValidationBudget(-4)).toBe(0); + expect(defaultKtxRelationshipValidationBudget(3.8)).toBe(6); }); it('splits candidates by descending score with stable tie ordering', () => { - const result = applyKloRelationshipValidationBudget({ + const result = applyKtxRelationshipValidationBudget({ candidates: [ { id: 'first', confidence: 0.8 }, { id: 'second', confidence: 0.9 }, @@ -41,7 +41,7 @@ describe('relationship validation budget', () => { confidence: 1 - index / 10, })); - const result = applyKloRelationshipValidationBudget({ + const result = applyKtxRelationshipValidationBudget({ candidates, tableCount: 2, score: (candidate) => candidate.confidence, @@ -53,7 +53,7 @@ describe('relationship validation budget', () => { }); it('treats budget zero as disabling SQL validation', () => { - const result = applyKloRelationshipValidationBudget({ + const result = applyKtxRelationshipValidationBudget({ candidates: [ { id: 'first', confidence: 1 }, { id: 'second', confidence: 0.5 }, @@ -69,7 +69,7 @@ describe('relationship validation budget', () => { }); it('treats budget all as validating every candidate', () => { - const result = applyKloRelationshipValidationBudget({ + const result = applyKtxRelationshipValidationBudget({ candidates: [ { id: 'first', confidence: 0.1 }, { id: 'second', confidence: 0.9 }, diff --git a/packages/context/src/scan/relationship-budget.ts b/packages/context/src/scan/relationship-budget.ts index b6ddcf0d..13209301 100644 --- a/packages/context/src/scan/relationship-budget.ts +++ b/packages/context/src/scan/relationship-budget.ts @@ -1,32 +1,32 @@ -export type KloRelationshipValidationBudget = number | 'all' | undefined; +export type KtxRelationshipValidationBudget = number | 'all' | undefined; -export interface KloRelationshipBudgetedCandidate { +export interface KtxRelationshipBudgetedCandidate { candidate: TCandidate; originalIndex: number; score: number; } -export interface KloRelationshipValidationBudgetResult { +export interface KtxRelationshipValidationBudgetResult { effectiveBudget: number | 'all'; - toValidate: KloRelationshipBudgetedCandidate[]; - deferred: KloRelationshipBudgetedCandidate[]; + toValidate: KtxRelationshipBudgetedCandidate[]; + deferred: KtxRelationshipBudgetedCandidate[]; } -export interface ApplyKloRelationshipValidationBudgetInput { +export interface ApplyKtxRelationshipValidationBudgetInput { candidates: readonly TCandidate[]; tableCount: number; - budget?: KloRelationshipValidationBudget; + budget?: KtxRelationshipValidationBudget; score: (candidate: TCandidate) => number; } -export function defaultKloRelationshipValidationBudget(tableCount: number): number { +export function defaultKtxRelationshipValidationBudget(tableCount: number): number { const safeTableCount = Number.isFinite(tableCount) ? Math.max(0, Math.floor(tableCount)) : 0; return Math.min(2 * safeTableCount, 1000); } -export function applyKloRelationshipValidationBudget( - input: ApplyKloRelationshipValidationBudgetInput, -): KloRelationshipValidationBudgetResult { +export function applyKtxRelationshipValidationBudget( + input: ApplyKtxRelationshipValidationBudgetInput, +): KtxRelationshipValidationBudgetResult { const ranked = input.candidates .map((candidate, originalIndex) => ({ candidate, @@ -50,7 +50,7 @@ export function applyKloRelationshipValidationBudget( }; } - const effectiveBudget = input.budget ?? defaultKloRelationshipValidationBudget(input.tableCount); + const effectiveBudget = input.budget ?? defaultKtxRelationshipValidationBudget(input.tableCount); const safeBudget = Math.max(0, Math.floor(effectiveBudget)); return { effectiveBudget: safeBudget, diff --git a/packages/context/src/scan/relationship-candidates.test.ts b/packages/context/src/scan/relationship-candidates.test.ts index c89f029f..795d7791 100644 --- a/packages/context/src/scan/relationship-candidates.test.ts +++ b/packages/context/src/scan/relationship-candidates.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest'; -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; -import { normalizeKloRelationshipName } from './relationship-name-similarity.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; +import { normalizeKtxRelationshipName } from './relationship-name-similarity.js'; import { - generateKloRelationshipDiscoveryCandidates, - inferKloRelationshipTargetPks, - mergeKloRelationshipDiscoveryCandidates, + generateKtxRelationshipDiscoveryCandidates, + inferKtxRelationshipTargetPks, + mergeKtxRelationshipDiscoveryCandidates, } from './relationship-candidates.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; function column( tableId: string, id: string, name: string, - options: Partial = {}, -): KloEnrichedColumn { + options: Partial = {}, +): KtxEnrichedColumn { const tableRef = options.tableRef ?? { catalog: null, db: 'public', name: tableId }; return { id, @@ -33,7 +33,7 @@ function column( }; } -function table(id: string, name: string, columns: KloEnrichedColumn[]): KloEnrichedTable { +function table(id: string, name: string, columns: KtxEnrichedColumn[]): KtxEnrichedTable { const ref = { catalog: null, db: 'public', name }; return { id, @@ -44,7 +44,7 @@ function table(id: string, name: string, columns: KloEnrichedColumn[]): KloEnric }; } -function schema(tables: KloEnrichedTable[]): KloEnrichedSchema { +function schema(tables: KtxEnrichedTable[]): KtxEnrichedSchema { return { connectionId: 'warehouse', tables, @@ -52,7 +52,7 @@ function schema(tables: KloEnrichedTable[]): KloEnrichedSchema { }; } -function planCodeProfiles(): KloRelationshipProfileArtifact { +function planCodeProfiles(): KtxRelationshipProfileArtifact { return { connectionId: 'warehouse', driver: 'sqlite', @@ -192,7 +192,7 @@ describe('relationship discovery candidates', () => { column('invoices-id', 'account-id-col', 'account_id', { primaryKey: false }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([accounts, invoices])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([accounts, invoices])); expect(candidates).toHaveLength(1); expect(candidates[0]).toMatchObject({ @@ -232,7 +232,7 @@ describe('relationship discovery candidates', () => { column('album-id', 'artist-id-fk-col', 'ArtistId', { primaryKey: false }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([artists, albums])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([artists, albums])); expect( candidates.map( @@ -260,7 +260,7 @@ describe('relationship discovery candidates', () => { column('invoices-id', 'account-id-col', 'account_id'), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([accounts, invoices]), { + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([accounts, invoices]), { maxCandidateParentTables: 0, }); @@ -282,7 +282,7 @@ describe('relationship discovery candidates', () => { ]), ); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([albums, ...fillerTables, artists]), { + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([albums, ...fillerTables, artists]), { maxCandidateParentTables: 1, }); @@ -304,7 +304,7 @@ describe('relationship discovery candidates', () => { column('order-id', 'customer-id-fk-col', 'CustomerID', { primaryKey: false }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([customers, orders])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([customers, orders])); expect( candidates.map( @@ -337,7 +337,7 @@ describe('relationship discovery candidates', () => { column('subscriptions-id', 'customer-account-id-col', 'CustomerAccountID', { primaryKey: false }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([customerAccounts, subscriptions])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([customerAccounts, subscriptions])); expect( candidates.map( @@ -383,7 +383,7 @@ describe('relationship discovery candidates', () => { }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([customerAccounts, subscriptions])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([customerAccounts, subscriptions])); expect( candidates.map( @@ -403,7 +403,7 @@ describe('relationship discovery candidates', () => { }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([customerAccounts])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([customerAccounts])); expect( candidates.map( @@ -471,9 +471,9 @@ describe('relationship discovery candidates', () => { maxTextLength: 2, }, }, - } satisfies KloRelationshipProfileArtifact; + } satisfies KtxRelationshipProfileArtifact; - const candidates = generateKloRelationshipDiscoveryCandidates(schema([countries, accounts]), { profiles }); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([countries, accounts]), { profiles }); expect(candidates).toHaveLength(1); expect(candidates[0]).toMatchObject({ @@ -508,7 +508,7 @@ describe('relationship discovery candidates', () => { }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([accounts])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([accounts])); expect( candidates.map( @@ -524,7 +524,7 @@ describe('relationship discovery candidates', () => { column('employees-id', 'employees-parent-id-col', 'parent_id', { primaryKey: false }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([employees])); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([employees])); expect( candidates.map( @@ -603,7 +603,7 @@ describe('relationship discovery candidates', () => { }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([plans, accountSegments, mapping]), { + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([plans, accountSegments, mapping]), { profiles: planCodeProfiles(), }); const candidateKeys = candidates.map( @@ -698,9 +698,9 @@ describe('relationship discovery candidates', () => { maxTextLength: 1, }, }, - } satisfies KloRelationshipProfileArtifact; + } satisfies KtxRelationshipProfileArtifact; - const candidates = generateKloRelationshipDiscoveryCandidates(schema([users, plans, accounts]), { profiles }); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([users, plans, accounts]), { profiles }); const candidateKeys = candidates.map( (candidate) => `${candidate.from.table.name}.${candidate.from.columns[0]}->${candidate.to.table.name}.${candidate.to.columns[0]}`, @@ -734,7 +734,7 @@ describe('relationship discovery candidates', () => { }), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([customers, orders]), { + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([customers, orders]), { embeddingSimilarityThreshold: 0.95, }); @@ -769,7 +769,7 @@ describe('relationship discovery candidates', () => { column('events-id', 'account-id-col', 'account_id'), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([events, archivedAccounts, accounts]), { + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([events, archivedAccounts, accounts]), { maxCandidatesPerColumn: 1, }); @@ -790,8 +790,8 @@ describe('relationship discovery candidates', () => { column('events-id', 'user-id-col', 'user_id'), ]); - const candidates = generateKloRelationshipDiscoveryCandidates(schema([accounts, users, events])); - const inferredPks = inferKloRelationshipTargetPks(candidates); + const candidates = generateKtxRelationshipDiscoveryCandidates(schema([accounts, users, events])); + const inferredPks = inferKtxRelationshipTargetPks(candidates); expect(inferredPks).toEqual([ { @@ -821,21 +821,21 @@ describe('relationship discovery candidates', () => { column('invoices-id', 'account-id-col', 'account_id', { nativeType: 'INTEGER', normalizedType: 'integer' }), ]); - expect(generateKloRelationshipDiscoveryCandidates(schema([accounts, invoices]))).toEqual([]); + expect(generateKtxRelationshipDiscoveryCandidates(schema([accounts, invoices]))).toEqual([]); }); it('normalizes layer prefixes, punctuation, plural forms, and non-plural trailing s words', () => { - expect(normalizeKloRelationshipName('mart__Sales_Accounts')).toMatchObject({ + expect(normalizeKtxRelationshipName('mart__Sales_Accounts')).toMatchObject({ normalized: 'sales_accounts', singular: 'sales_account', tokens: ['sales', 'accounts'], }); - expect(normalizeKloRelationshipName('dim_users')).toMatchObject({ + expect(normalizeKtxRelationshipName('dim_users')).toMatchObject({ normalized: 'users', singular: 'user', tokens: ['users'], }); - expect(normalizeKloRelationshipName('Address')).toMatchObject({ + expect(normalizeKtxRelationshipName('Address')).toMatchObject({ normalized: 'address', singular: 'address', plural: 'addresses', @@ -846,7 +846,7 @@ describe('relationship discovery candidates', () => { it('merges duplicate deterministic and LLM proposal candidates without losing LLM rationale', () => { const accounts = table('accounts-id', 'accounts', [column('accounts-id', 'accounts-id-col', 'id')]); const invoices = table('invoices-id', 'invoices', [column('invoices-id', 'account-id-col', 'account_id')]); - const [deterministic] = generateKloRelationshipDiscoveryCandidates(schema([accounts, invoices])); + const [deterministic] = generateKtxRelationshipDiscoveryCandidates(schema([accounts, invoices])); if (!deterministic) { throw new Error('Expected deterministic relationship candidate'); } @@ -862,7 +862,7 @@ describe('relationship discovery candidates', () => { }, }; - const merged = mergeKloRelationshipDiscoveryCandidates([deterministic, llmCandidate]); + const merged = mergeKtxRelationshipDiscoveryCandidates([deterministic, llmCandidate]); expect(merged).toHaveLength(1); expect(merged[0]).toMatchObject({ diff --git a/packages/context/src/scan/relationship-candidates.ts b/packages/context/src/scan/relationship-candidates.ts index 884b8580..b10aa069 100644 --- a/packages/context/src/scan/relationship-candidates.ts +++ b/packages/context/src/scan/relationship-candidates.ts @@ -1,26 +1,26 @@ import type { - KloEnrichedColumn, - KloEnrichedSchema, - KloEnrichedTable, - KloRelationshipEndpoint, - KloRelationshipType, + KtxEnrichedColumn, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxRelationshipEndpoint, + KtxRelationshipType, } from './enrichment-types.js'; import { localCandidateTables } from './relationship-locality.js'; import { - normalizeKloRelationshipName, - pluralizeKloRelationshipToken, - singularizeKloRelationshipToken, + normalizeKtxRelationshipName, + pluralizeKtxRelationshipToken, + singularizeKtxRelationshipToken, } from './relationship-name-similarity.js'; -export type { KloRelationshipNormalizedName } from './relationship-name-similarity.js'; -export { normalizeKloRelationshipName } from './relationship-name-similarity.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; +export type { KtxRelationshipNormalizedName } from './relationship-name-similarity.js'; +export { normalizeKtxRelationshipName } from './relationship-name-similarity.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; import { - scoreKloRelationshipCandidate, - type KloRelationshipScoreBreakdown, - type KloRelationshipSignalVector, + scoreKtxRelationshipCandidate, + type KtxRelationshipScoreBreakdown, + type KtxRelationshipSignalVector, } from './relationship-scoring.js'; -export type KloRelationshipDiscoveryCandidateSource = +export type KtxRelationshipDiscoveryCandidateSource = | 'exact_column_match' | 'normalized_table_match' | 'parent_table_name_match' @@ -31,44 +31,44 @@ export type KloRelationshipDiscoveryCandidateSource = | 'embedding_similarity' | 'llm_proposal'; -export type KloRelationshipDiscoveryCandidateStatus = 'review'; +export type KtxRelationshipDiscoveryCandidateStatus = 'review'; -export interface KloRelationshipDiscoveryCandidateEvidence { +export interface KtxRelationshipDiscoveryCandidateEvidence { sourceColumnBase: string; targetTableBase: string; targetColumnBase: string; targetKeyScore: number; nameScore: number; reasons: string[]; - signalVector?: KloRelationshipSignalVector; - scoreBreakdown?: KloRelationshipScoreBreakdown; + signalVector?: KtxRelationshipSignalVector; + scoreBreakdown?: KtxRelationshipScoreBreakdown; embeddingSimilarity?: number; llmConfidence?: number; llmRationale?: string; } -export interface KloRelationshipDiscoveryCandidate { +export interface KtxRelationshipDiscoveryCandidate { id: string; - from: KloRelationshipEndpoint; - to: KloRelationshipEndpoint; - relationshipType: KloRelationshipType; + from: KtxRelationshipEndpoint; + to: KtxRelationshipEndpoint; + relationshipType: KtxRelationshipType; confidence: number; - source: KloRelationshipDiscoveryCandidateSource; - status: KloRelationshipDiscoveryCandidateStatus; - evidence: KloRelationshipDiscoveryCandidateEvidence; + source: KtxRelationshipDiscoveryCandidateSource; + status: KtxRelationshipDiscoveryCandidateStatus; + evidence: KtxRelationshipDiscoveryCandidateEvidence; } -export interface KloRelationshipDiscoveryCandidateOptions { +export interface KtxRelationshipDiscoveryCandidateOptions { maxCandidatesPerColumn?: number; maxCandidateParentTables?: number; maxEmbeddingCandidatesPerColumn?: number; minConfidence?: number; embeddingSimilarityThreshold?: number; useEmbeddings?: boolean; - profiles?: KloRelationshipProfileArtifact; + profiles?: KtxRelationshipProfileArtifact; } -export interface KloRelationshipInferredTargetPk { +export interface KtxRelationshipInferredTargetPk { table: string; columns: string[]; score: number; @@ -76,12 +76,12 @@ export interface KloRelationshipInferredTargetPk { incomingCandidateCount: number; } -interface KloRelationshipSourceColumnReference { +interface KtxRelationshipSourceColumnReference { base: string; reason: string; } -interface KloRelationshipTargetKeyEvidence { +interface KtxRelationshipTargetKeyEvidence { score: number; reasons: string[]; } @@ -98,26 +98,26 @@ const REFERENCE_SUFFIXES: Array<{ suffix: string; reason: string }> = [ ]; const RELATIONSHIP_KEY_TARGET_SUFFIXES = ['_id', '_key', '_code', '_uuid'] as const; -function isRelationshipKeyShapedTarget(column: KloEnrichedColumn): boolean { - const normalized = normalizeKloRelationshipName(column.name); +function isRelationshipKeyShapedTarget(column: KtxEnrichedColumn): boolean { + const normalized = normalizeKtxRelationshipName(column.name); return ( normalized.tokens.length >= 2 && RELATIONSHIP_KEY_TARGET_SUFFIXES.some((suffix) => normalized.normalized.endsWith(suffix)) ); } -function columnSuffixMatchesTarget(input: { fromColumn: KloEnrichedColumn; toColumn: KloEnrichedColumn }): boolean { - const source = normalizeKloRelationshipName(input.fromColumn.name).normalized; - const target = normalizeKloRelationshipName(input.toColumn.name).normalized; +function columnSuffixMatchesTarget(input: { fromColumn: KtxEnrichedColumn; toColumn: KtxEnrichedColumn }): boolean { + const source = normalizeKtxRelationshipName(input.fromColumn.name).normalized; + const target = normalizeKtxRelationshipName(input.toColumn.name).normalized; return source !== target && target.length > 0 && source.endsWith(`_${target}`); } -function normalizeType(column: KloEnrichedColumn): string { +function normalizeType(column: KtxEnrichedColumn): string { const rawType = (column.normalizedType || column.nativeType || '').toLowerCase().trim(); return rawType.includes('(') ? (rawType.split('(')[0] ?? '') : rawType; } -function typesCompatible(left: KloEnrichedColumn, right: KloEnrichedColumn): boolean { +function typesCompatible(left: KtxEnrichedColumn, right: KtxEnrichedColumn): boolean { const leftType = normalizeType(left); const rightType = normalizeType(right); if (leftType === rightType) { @@ -155,12 +155,12 @@ function cosineSimilarity(left: readonly number[] | null, right: readonly number return dot / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude)); } -function hasUsableEmbedding(column: KloEnrichedColumn): boolean { +function hasUsableEmbedding(column: KtxEnrichedColumn): boolean { return Array.isArray(column.embedding) && column.embedding.length > 0; } -function sourceColumnReference(column: KloEnrichedColumn): KloRelationshipSourceColumnReference | null { - const normalized = normalizeKloRelationshipName(column.name); +function sourceColumnReference(column: KtxEnrichedColumn): KtxRelationshipSourceColumnReference | null { + const normalized = normalizeKtxRelationshipName(column.name); if (SELF_REFERENCE_NAMES.has(normalized.normalized)) { return { base: normalized.normalized.replace(/_id$/u, ''), reason: 'foreign_key_suffix' }; } @@ -171,7 +171,7 @@ function sourceColumnReference(column: KloEnrichedColumn): KloRelationshipSource } const base = normalized.normalized.slice(0, -item.suffix.length); if (base.length > 1) { - return { base: singularizeKloRelationshipToken(base), reason: item.reason }; + return { base: singularizeKtxRelationshipToken(base), reason: item.reason }; } } @@ -179,7 +179,7 @@ function sourceColumnReference(column: KloEnrichedColumn): KloRelationshipSource } function addNormalizedTableAlias(aliases: Set, name: string): void { - const normalized = normalizeKloRelationshipName(name); + const normalized = normalizeKtxRelationshipName(name); if (normalized.normalized.length > 0) { aliases.add(normalized.normalized); } @@ -191,34 +191,34 @@ function addNormalizedTableAlias(aliases: Set, name: string): void { } } -function tableAliases(table: KloEnrichedTable): Set { - const normalized = normalizeKloRelationshipName(table.ref.name); +function tableAliases(table: KtxEnrichedTable): Set { + const normalized = normalizeKtxRelationshipName(table.ref.name); const aliases = new Set([normalized.normalized, normalized.singular, normalized.plural]); if (normalized.tokens.length > 1) { const lastToken = normalized.tokens[normalized.tokens.length - 1]; if (lastToken) { aliases.add(lastToken); - const singularLastToken = singularizeKloRelationshipToken(lastToken); + const singularLastToken = singularizeKtxRelationshipToken(lastToken); aliases.add(singularLastToken); - aliases.add(pluralizeKloRelationshipToken(singularLastToken)); + aliases.add(pluralizeKtxRelationshipToken(singularLastToken)); } } return aliases; } -function finalTableNamePart(table: KloEnrichedTable): string { +function finalTableNamePart(table: KtxEnrichedTable): string { const parts = table.ref.name.split(/[^\p{L}\p{N}]+/u).filter(Boolean); return parts[parts.length - 1] ?? table.ref.name; } -function parentTableNameAliases(table: KloEnrichedTable): Set { +function parentTableNameAliases(table: KtxEnrichedTable): Set { const aliases = tableAliases(table); addNormalizedTableAlias(aliases, finalTableNamePart(table)); return aliases; } -function targetKeyScore(table: KloEnrichedTable, column: KloEnrichedColumn): number { - const columnName = normalizeKloRelationshipName(column.name).normalized; +function targetKeyScore(table: KtxEnrichedTable, column: KtxEnrichedColumn): number { + const columnName = normalizeKtxRelationshipName(column.name).normalized; const tableKeyBases = parentTableNameAliases(table); if (column.primaryKey) { return 1; @@ -239,7 +239,7 @@ function targetKeyScore(table: KloEnrichedTable, column: KloEnrichedColumn): num } function profileColumn( - profiles: KloRelationshipProfileArtifact | undefined, + profiles: KtxRelationshipProfileArtifact | undefined, tableName: string, columnName: string, ) { @@ -247,11 +247,11 @@ function profileColumn( } function profileSampleOverlap(input: { - profiles: KloRelationshipProfileArtifact | undefined; - fromTable: KloEnrichedTable; - fromColumn: KloEnrichedColumn; - toTable: KloEnrichedTable; - toColumn: KloEnrichedColumn; + profiles: KtxRelationshipProfileArtifact | undefined; + fromTable: KtxEnrichedTable; + fromColumn: KtxEnrichedColumn; + toTable: KtxEnrichedTable; + toColumn: KtxEnrichedColumn; }): number { const source = profileColumn(input.profiles, input.fromTable.ref.name, input.fromColumn.name); const target = profileColumn(input.profiles, input.toTable.ref.name, input.toColumn.name); @@ -263,14 +263,14 @@ function profileSampleOverlap(input: { return overlap / source.sampleValues.length; } -function tableProfileRowCount(profiles: KloRelationshipProfileArtifact | undefined, tableName: string): number | null { +function tableProfileRowCount(profiles: KtxRelationshipProfileArtifact | undefined, tableName: string): number | null { return profiles?.tables.find((table) => table.table.name === tableName)?.rowCount ?? null; } function structuralPriorScore(input: { - profiles: KloRelationshipProfileArtifact | undefined; - fromTable: KloEnrichedTable; - toTable: KloEnrichedTable; + profiles: KtxRelationshipProfileArtifact | undefined; + fromTable: KtxEnrichedTable; + toTable: KtxEnrichedTable; }): number { if (input.fromTable.id === input.toTable.id) { return 0.72; @@ -290,16 +290,16 @@ function structuralPriorScore(input: { } function candidateSignalVector(input: { - profiles: KloRelationshipProfileArtifact | undefined; - fromTable: KloEnrichedTable; - fromColumn: KloEnrichedColumn; - toTable: KloEnrichedTable; - toColumn: KloEnrichedColumn; + profiles: KtxRelationshipProfileArtifact | undefined; + fromTable: KtxEnrichedTable; + fromColumn: KtxEnrichedColumn; + toTable: KtxEnrichedTable; + toColumn: KtxEnrichedColumn; targetKeyScore: number; nameScore: number; valueOverlap: number; embeddingSimilarity?: number; -}): KloRelationshipSignalVector { +}): KtxRelationshipSignalVector { const sourceProfile = profileColumn(input.profiles, input.fromTable.ref.name, input.fromColumn.name); const targetProfile = profileColumn(input.profiles, input.toTable.ref.name, input.toColumn.name); const targetUniqueness = targetProfile?.uniquenessRatio ?? input.targetKeyScore; @@ -321,11 +321,11 @@ function candidateSignalVector(input: { } function candidateParentTables(input: { - tables: readonly KloEnrichedTable[]; - fromTable: KloEnrichedTable; - fromColumn: KloEnrichedColumn; - options: KloRelationshipDiscoveryCandidateOptions; -}): KloEnrichedTable[] { + tables: readonly KtxEnrichedTable[]; + fromTable: KtxEnrichedTable; + fromColumn: KtxEnrichedColumn; + options: KtxRelationshipDiscoveryCandidateOptions; +}): KtxEnrichedTable[] { const maxParentTables = input.options.maxCandidateParentTables ?? 20; if (maxParentTables <= 0) { return []; @@ -338,7 +338,7 @@ function candidateParentTables(input: { maxParentTables, }).map((item) => item.table); - const normalizedColumn = normalizeKloRelationshipName(input.fromColumn.name).normalized; + const normalizedColumn = normalizeKtxRelationshipName(input.fromColumn.name).normalized; if (!SELF_REFERENCE_NAMES.has(normalizedColumn) || ranked.some((table) => table.id === input.fromTable.id)) { return ranked; } @@ -350,10 +350,10 @@ function candidateParentTables(input: { } function targetKeyEvidence( - table: KloEnrichedTable, - column: KloEnrichedColumn, - profiles: KloRelationshipProfileArtifact | undefined, -): KloRelationshipTargetKeyEvidence { + table: KtxEnrichedTable, + column: KtxEnrichedColumn, + profiles: KtxRelationshipProfileArtifact | undefined, +): KtxRelationshipTargetKeyEvidence { const deterministicScore = targetKeyScore(table, column); if (deterministicScore > 0) { return { score: deterministicScore, reasons: ['target_key_like'] }; @@ -364,7 +364,7 @@ function targetKeyEvidence( return { score: 0, reasons: [] }; } - const columnName = normalizeKloRelationshipName(column.name).normalized; + const columnName = normalizeKtxRelationshipName(column.name).normalized; if (columnName === 'code' || columnName.endsWith('_code') || columnName === 'key' || columnName.endsWith('_key')) { return { score: 0.86, reasons: ['profile_unique_target'] }; } @@ -372,7 +372,7 @@ function targetKeyEvidence( return { score: 0.78, reasons: ['profile_unique_target'] }; } -function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn): KloRelationshipEndpoint { +function endpoint(table: KtxEnrichedTable, column: KtxEnrichedColumn): KtxRelationshipEndpoint { return { tableId: table.id, columnIds: [column.id], @@ -381,11 +381,11 @@ function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn): KloRelati }; } -function relationshipId(from: KloRelationshipEndpoint, to: KloRelationshipEndpoint): string { +function relationshipId(from: KtxRelationshipEndpoint, to: KtxRelationshipEndpoint): string { return `${from.tableId}:(${from.columnIds.join(',')})->${to.tableId}:(${to.columnIds.join(',')})`; } -function endpointsHaveSameOrderedColumns(left: KloRelationshipEndpoint, right: KloRelationshipEndpoint): boolean { +function endpointsHaveSameOrderedColumns(left: KtxRelationshipEndpoint, right: KtxRelationshipEndpoint): boolean { if (left.columnIds.length !== right.columnIds.length || left.columns.length !== right.columns.length) { return false; } @@ -394,11 +394,11 @@ function endpointsHaveSameOrderedColumns(left: KloRelationshipEndpoint, right: K ); } -function isDegenerateSameColumnSelfLink(candidate: Pick): boolean { +function isDegenerateSameColumnSelfLink(candidate: Pick): boolean { return candidate.from.tableId === candidate.to.tableId && endpointsHaveSameOrderedColumns(candidate.from, candidate.to); } -function singleRelationshipColumn(endpointValue: KloRelationshipEndpoint): string { +function singleRelationshipColumn(endpointValue: KtxRelationshipEndpoint): string { const column = endpointValue.columns[0]; if (!column) { throw new Error(`Expected relationship endpoint ${endpointValue.table.name} to contain one column`); @@ -406,7 +406,7 @@ function singleRelationshipColumn(endpointValue: KloRelationshipEndpoint): strin return column; } -function candidateSortKey(candidate: KloRelationshipDiscoveryCandidate): string { +function candidateSortKey(candidate: KtxRelationshipDiscoveryCandidate): string { return `${candidate.from.table.name}.${singleRelationshipColumn(candidate.from)}->${candidate.to.table.name}.${singleRelationshipColumn(candidate.to)}`; } @@ -415,9 +415,9 @@ function uniqueReasons(values: readonly string[]): string[] { } function mergeCandidateEvidence( - left: KloRelationshipDiscoveryCandidate, - right: KloRelationshipDiscoveryCandidate, -): KloRelationshipDiscoveryCandidate { + left: KtxRelationshipDiscoveryCandidate, + right: KtxRelationshipDiscoveryCandidate, +): KtxRelationshipDiscoveryCandidate { const preferred = right.confidence > left.confidence && left.source === 'llm_proposal' ? right : left; const supplement = preferred === left ? right : left; return { @@ -432,7 +432,7 @@ function mergeCandidateEvidence( }; } -function sourceForEvidence(reasons: string[]): KloRelationshipDiscoveryCandidateSource { +function sourceForEvidence(reasons: string[]): KtxRelationshipDiscoveryCandidateSource { if (reasons.includes('self_reference')) { return 'self_reference'; } @@ -461,19 +461,19 @@ function sourceForEvidence(reasons: string[]): KloRelationshipDiscoveryCandidate } function createCandidate(input: { - fromTable: KloEnrichedTable; - fromColumn: KloEnrichedColumn; - toTable: KloEnrichedTable; - toColumn: KloEnrichedColumn; + fromTable: KtxEnrichedTable; + fromColumn: KtxEnrichedColumn; + toTable: KtxEnrichedTable; + toColumn: KtxEnrichedColumn; sourceBase: string; targetBase: string; targetKeyScore: number; nameScore: number; reasons: string[]; - profiles: KloRelationshipProfileArtifact | undefined; + profiles: KtxRelationshipProfileArtifact | undefined; valueOverlap: number; embeddingSimilarity?: number; -}): KloRelationshipDiscoveryCandidate { +}): KtxRelationshipDiscoveryCandidate { const from = endpoint(input.fromTable, input.fromColumn); const to = endpoint(input.toTable, input.toColumn); const signalVector = candidateSignalVector({ @@ -487,7 +487,7 @@ function createCandidate(input: { valueOverlap: input.valueOverlap, embeddingSimilarity: input.embeddingSimilarity, }); - const scoreBreakdown = scoreKloRelationshipCandidate(signalVector); + const scoreBreakdown = scoreKtxRelationshipCandidate(signalVector); return { id: relationshipId(from, to), @@ -500,7 +500,7 @@ function createCandidate(input: { evidence: { sourceColumnBase: input.sourceBase, targetTableBase: input.targetBase, - targetColumnBase: normalizeKloRelationshipName(input.toColumn.name).normalized, + targetColumnBase: normalizeKtxRelationshipName(input.toColumn.name).normalized, targetKeyScore: input.targetKeyScore, nameScore: input.nameScore, reasons: input.reasons, @@ -513,10 +513,10 @@ function createCandidate(input: { }; } -function generateKloEmbeddingRelationshipCandidates( - schema: KloEnrichedSchema, - options: KloRelationshipDiscoveryCandidateOptions, -): KloRelationshipDiscoveryCandidate[] { +function generateKtxEmbeddingRelationshipCandidates( + schema: KtxEnrichedSchema, + options: KtxRelationshipDiscoveryCandidateOptions, +): KtxRelationshipDiscoveryCandidate[] { if (options.useEmbeddings === false) { return []; } @@ -524,7 +524,7 @@ function generateKloEmbeddingRelationshipCandidates( const threshold = options.embeddingSimilarityThreshold ?? 0.92; const maxCandidatesPerColumn = options.maxEmbeddingCandidatesPerColumn ?? options.maxCandidatesPerColumn ?? 25; const tables = schema.tables.filter((table) => table.enabled); - const candidates: KloRelationshipDiscoveryCandidate[] = []; + const candidates: KtxRelationshipDiscoveryCandidate[] = []; for (const fromTable of tables) { for (const fromColumn of fromTable.columns) { @@ -532,7 +532,7 @@ function generateKloEmbeddingRelationshipCandidates( continue; } - const columnCandidates: KloRelationshipDiscoveryCandidate[] = []; + const columnCandidates: KtxRelationshipDiscoveryCandidate[] = []; for (const toTable of candidateParentTables({ tables, fromTable, fromColumn, options })) { if (fromTable.id === toTable.id) { continue; @@ -553,8 +553,8 @@ function generateKloEmbeddingRelationshipCandidates( continue; } - const sourceBase = normalizeKloRelationshipName(fromColumn.name).normalized; - const targetBase = normalizeKloRelationshipName(toTable.ref.name).singular; + const sourceBase = normalizeKtxRelationshipName(fromColumn.name).normalized; + const targetBase = normalizeKtxRelationshipName(toTable.ref.name).singular; const reasons = ['embedding_similarity', ...keyEvidence.reasons]; const candidate = createCandidate({ fromTable, @@ -592,14 +592,14 @@ function generateKloEmbeddingRelationshipCandidates( return candidates; } -export function generateKloRelationshipDiscoveryCandidates( - schema: KloEnrichedSchema, - options: KloRelationshipDiscoveryCandidateOptions = {}, -): KloRelationshipDiscoveryCandidate[] { +export function generateKtxRelationshipDiscoveryCandidates( + schema: KtxEnrichedSchema, + options: KtxRelationshipDiscoveryCandidateOptions = {}, +): KtxRelationshipDiscoveryCandidate[] { const maxCandidatesPerColumn = options.maxCandidatesPerColumn ?? 25; const minConfidence = options.minConfidence ?? 0.72; const tables = schema.tables.filter((table) => table.enabled); - const candidates: KloRelationshipDiscoveryCandidate[] = []; + const candidates: KtxRelationshipDiscoveryCandidate[] = []; for (const fromTable of tables) { for (const fromColumn of fromTable.columns) { @@ -612,15 +612,15 @@ export function generateKloRelationshipDiscoveryCandidates( } const sourceBase = sourceReference.base; - const columnCandidates: KloRelationshipDiscoveryCandidate[] = []; + const columnCandidates: KtxRelationshipDiscoveryCandidate[] = []; for (const toTable of candidateParentTables({ tables, fromTable, fromColumn, options })) { const strictAliases = tableAliases(toTable); const parentAliases = parentTableNameAliases(toTable); - const targetBase = normalizeKloRelationshipName(toTable.ref.name).singular; + const targetBase = normalizeKtxRelationshipName(toTable.ref.name).singular; const sameTable = fromTable.id === toTable.id; const nameMatchesTarget = strictAliases.has(sourceBase); const parentTableNameMatcher = !sameTable && !nameMatchesTarget && parentAliases.has(sourceBase); - const selfReference = sameTable && SELF_REFERENCE_NAMES.has(normalizeKloRelationshipName(fromColumn.name).normalized); + const selfReference = sameTable && SELF_REFERENCE_NAMES.has(normalizeKtxRelationshipName(fromColumn.name).normalized); const strictTableMatcher = (!sameTable && nameMatchesTarget) || selfReference; for (const toColumn of toTable.columns) { @@ -665,7 +665,7 @@ export function generateKloRelationshipDiscoveryCandidates( } else if (selfReference) { reasons.push('self_reference'); nameScore = 0.82; - } else if (!suffixMatcher && normalizeKloRelationshipName(toTable.ref.name).singular === sourceBase) { + } else if (!suffixMatcher && normalizeKtxRelationshipName(toTable.ref.name).singular === sourceBase) { reasons.push('normalized_table_name'); nameScore = 0.92; } else if (!suffixMatcher && strictAliases.has(sourceBase)) { @@ -675,7 +675,7 @@ export function generateKloRelationshipDiscoveryCandidates( if ( !suffixMatcher && !parentTableNameMatcher && - normalizeKloRelationshipName(fromColumn.name).normalized === normalizeKloRelationshipName(toColumn.name).normalized + normalizeKtxRelationshipName(fromColumn.name).normalized === normalizeKtxRelationshipName(toColumn.name).normalized ) { reasons.push('exact_column_name'); nameScore = Math.max(nameScore, 0.9); @@ -707,9 +707,9 @@ export function generateKloRelationshipDiscoveryCandidates( } } - candidates.push(...generateKloEmbeddingRelationshipCandidates(schema, options)); + candidates.push(...generateKtxEmbeddingRelationshipCandidates(schema, options)); - const byId = new Map(); + const byId = new Map(); for (const candidate of candidates) { const existing = byId.get(candidate.id); if (!existing || candidate.confidence > existing.confidence) { @@ -721,10 +721,10 @@ export function generateKloRelationshipDiscoveryCandidates( ); } -export function mergeKloRelationshipDiscoveryCandidates( - candidates: readonly KloRelationshipDiscoveryCandidate[], -): KloRelationshipDiscoveryCandidate[] { - const byId = new Map(); +export function mergeKtxRelationshipDiscoveryCandidates( + candidates: readonly KtxRelationshipDiscoveryCandidate[], +): KtxRelationshipDiscoveryCandidate[] { + const byId = new Map(); for (const candidate of candidates) { const existing = byId.get(candidate.id); byId.set(candidate.id, existing ? mergeCandidateEvidence(existing, candidate) : candidate); @@ -732,9 +732,9 @@ export function mergeKloRelationshipDiscoveryCandidates( return Array.from(byId.values()).sort((left, right) => candidateSortKey(left).localeCompare(candidateSortKey(right))); } -export function inferKloRelationshipTargetPks( - candidates: readonly KloRelationshipDiscoveryCandidate[], -): KloRelationshipInferredTargetPk[] { +export function inferKtxRelationshipTargetPks( + candidates: readonly KtxRelationshipDiscoveryCandidate[], +): KtxRelationshipInferredTargetPk[] { const incoming = new Map(); for (const candidate of candidates) { const toColumn = singleRelationshipColumn(candidate.to); diff --git a/packages/context/src/scan/relationship-composite-candidates.test.ts b/packages/context/src/scan/relationship-composite-candidates.test.ts index dbd5037f..abf495e1 100644 --- a/packages/context/src/scan/relationship-composite-candidates.test.ts +++ b/packages/context/src/scan/relationship-composite-candidates.test.ts @@ -1,20 +1,20 @@ import Database from 'better-sqlite3'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { snapshotToKloEnrichedSchema } from './local-enrichment.js'; -import { loadKloRelationshipBenchmarkFixture, maskKloRelationshipBenchmarkSnapshot } from './relationship-benchmarks.js'; -import { discoverKloCompositeRelationships } from './relationship-composite-candidates.js'; -import { profileKloRelationshipSchema, type KloRelationshipReadOnlyExecutor } from './relationship-profiling.js'; -import type { KloQueryResult, KloReadOnlyQueryInput, KloScanContext } from './types.js'; +import { snapshotToKtxEnrichedSchema } from './local-enrichment.js'; +import { loadKtxRelationshipBenchmarkFixture, maskKtxRelationshipBenchmarkSnapshot } from './relationship-benchmarks.js'; +import { discoverKtxCompositeRelationships } from './relationship-composite-candidates.js'; +import { profileKtxRelationshipSchema, type KtxRelationshipReadOnlyExecutor } from './relationship-profiling.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput, KtxScanContext } from './types.js'; -class TestSqliteExecutor implements KloRelationshipReadOnlyExecutor { +class TestSqliteExecutor implements KtxRelationshipReadOnlyExecutor { private readonly db: Database.Database; constructor(dataPath: string) { this.db = new Database(dataPath, { readonly: true, fileMustExist: true }); } - async executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + async executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); return { @@ -33,13 +33,13 @@ class TestSqliteExecutor implements KloRelationshipReadOnlyExecutor { describe('composite relationship discovery detector', () => { it('infers composite primary keys and validates composite foreign keys from row evidence', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture( + const fixture = await loadKtxRelationshipBenchmarkFixture( join(fixtureRoot.pathname, 'composite_keys_no_declared_constraints'), ); - const snapshot = maskKloRelationshipBenchmarkSnapshot(fixture.snapshot, 'declared_pks_and_declared_fks_removed'); - const schema = snapshotToKloEnrichedSchema(snapshot, new Map()); + const snapshot = maskKtxRelationshipBenchmarkSnapshot(fixture.snapshot, 'declared_pks_and_declared_fks_removed'); + const schema = snapshotToKtxEnrichedSchema(snapshot, new Map()); const executor = new TestSqliteExecutor(fixture.dataPath ?? ''); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: snapshot.connectionId, driver: snapshot.driver, schema, @@ -47,7 +47,7 @@ describe('composite relationship discovery detector', () => { ctx: { runId: 'test:composite-profile' }, }); - const result = await discoverKloCompositeRelationships({ + const result = await discoverKtxCompositeRelationships({ connectionId: snapshot.connectionId, driver: snapshot.driver, schema, diff --git a/packages/context/src/scan/relationship-composite-candidates.ts b/packages/context/src/scan/relationship-composite-candidates.ts index fa1d9bae..f93d5153 100644 --- a/packages/context/src/scan/relationship-composite-candidates.ts +++ b/packages/context/src/scan/relationship-composite-candidates.ts @@ -1,29 +1,29 @@ -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable, KloRelationshipType } from './enrichment-types.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable, KtxRelationshipType } from './enrichment-types.js'; import { - formatKloRelationshipTableRef, - quoteKloRelationshipIdentifier, - type KloRelationshipProfileArtifact, - type KloRelationshipReadOnlyExecutor, + formatKtxRelationshipTableRef, + quoteKtxRelationshipIdentifier, + type KtxRelationshipProfileArtifact, + type KtxRelationshipReadOnlyExecutor, } from './relationship-profiling.js'; -import type { KloConnectionDriver, KloQueryResult, KloScanContext, KloTableRef } from './types.js'; +import type { KtxConnectionDriver, KtxQueryResult, KtxScanContext, KtxTableRef } from './types.js'; -export type KloCompositeRelationshipStatus = 'accepted' | 'review' | 'rejected'; +export type KtxCompositeRelationshipStatus = 'accepted' | 'review' | 'rejected'; -export interface KloCompositeRelationshipTupleEndpoint { +export interface KtxCompositeRelationshipTupleEndpoint { tableId: string; columnIds: string[]; - table: KloTableRef; + table: KtxTableRef; columns: string[]; } -export interface KloCompositePrimaryKeyCandidate { +export interface KtxCompositePrimaryKeyCandidate { id: string; tableId: string; - table: KloTableRef; + table: KtxTableRef; columns: string[]; columnIds: string[]; score: number; - status: KloCompositeRelationshipStatus; + status: KtxCompositeRelationshipStatus; evidence: { rowCount: number; distinctCount: number; @@ -33,7 +33,7 @@ export interface KloCompositePrimaryKeyCandidate { }; } -export interface KloCompositeRelationshipValidationEvidence { +export interface KtxCompositeRelationshipValidationEvidence { targetUniqueness: number; sourceCoverage: number; violationCount: number; @@ -44,24 +44,24 @@ export interface KloCompositeRelationshipValidationEvidence { reasons: string[]; } -export interface KloCompositeRelationshipCandidate { +export interface KtxCompositeRelationshipCandidate { id: string; - from: KloCompositeRelationshipTupleEndpoint; - to: KloCompositeRelationshipTupleEndpoint; - relationshipType: KloRelationshipType; + from: KtxCompositeRelationshipTupleEndpoint; + to: KtxCompositeRelationshipTupleEndpoint; + relationshipType: KtxRelationshipType; confidence: number; - status: KloCompositeRelationshipStatus; + status: KtxCompositeRelationshipStatus; source: 'composite_profile_match'; - validation: KloCompositeRelationshipValidationEvidence; + validation: KtxCompositeRelationshipValidationEvidence; } -export interface DiscoverKloCompositeRelationshipsInput { +export interface DiscoverKtxCompositeRelationshipsInput { connectionId: string; - driver: KloConnectionDriver; - schema: KloEnrichedSchema; - profiles: KloRelationshipProfileArtifact; - executor: KloRelationshipReadOnlyExecutor | null; - ctx: KloScanContext; + driver: KtxConnectionDriver; + schema: KtxEnrichedSchema; + profiles: KtxRelationshipProfileArtifact; + executor: KtxRelationshipReadOnlyExecutor | null; + ctx: KtxScanContext; maxCompositeWidth?: number; maxColumnsPerTable?: number; minPrimaryKeyUniqueness?: number; @@ -69,9 +69,9 @@ export interface DiscoverKloCompositeRelationshipsInput { maxViolationRatio?: number; } -export interface DiscoverKloCompositeRelationshipsResult { - primaryKeys: KloCompositePrimaryKeyCandidate[]; - relationships: KloCompositeRelationshipCandidate[]; +export interface DiscoverKtxCompositeRelationshipsResult { + primaryKeys: KtxCompositePrimaryKeyCandidate[]; + relationships: KtxCompositeRelationshipCandidate[]; queryCount: number; warnings: string[]; } @@ -83,11 +83,11 @@ const DEFAULT_MIN_PRIMARY_KEY_UNIQUENESS = 0.98; const DEFAULT_MIN_SOURCE_COVERAGE = 0.9; const DEFAULT_MAX_VIOLATION_RATIO = 0.01; -function enabledTables(schema: KloEnrichedSchema): KloEnrichedTable[] { +function enabledTables(schema: KtxEnrichedSchema): KtxEnrichedTable[] { return schema.tables.filter((table) => table.enabled); } -function tableRowCount(profiles: KloRelationshipProfileArtifact, tableName: string): number { +function tableRowCount(profiles: KtxRelationshipProfileArtifact, tableName: string): number { return profiles.tables.find((item) => item.table.name === tableName)?.rowCount ?? 0; } @@ -95,7 +95,7 @@ function profileKey(tableName: string, columnName: string): string { return `${tableName}.${columnName}`; } -function profileNullRate(profiles: KloRelationshipProfileArtifact, tableName: string, columnName: string): number { +function profileNullRate(profiles: KtxRelationshipProfileArtifact, tableName: string, columnName: string): number { return profiles.columns[profileKey(tableName, columnName)]?.nullRate ?? 1; } @@ -106,7 +106,7 @@ function normalizedColumnName(name: string): string { .replace(/^_+|_+$/gu, ''); } -function columnNameScore(column: KloEnrichedColumn): number { +function columnNameScore(column: KtxEnrichedColumn): number { const parts = normalizedColumnName(column.name).split('_').filter(Boolean); if (parts.some((part) => KEY_NAME_PARTS.has(part))) { return 1; @@ -122,7 +122,7 @@ function keyLikeTableNameParts(tableName: string): Set { return new Set(nameParts(tableName).filter((part) => KEY_NAME_PARTS.has(part))); } -function tupleCoversTableNameKeyParts(tableName: string, columns: readonly KloEnrichedColumn[]): boolean { +function tupleCoversTableNameKeyParts(tableName: string, columns: readonly KtxEnrichedColumn[]): boolean { const required = keyLikeTableNameParts(tableName); if (required.size === 0) { return true; @@ -132,10 +132,10 @@ function tupleCoversTableNameKeyParts(tableName: string, columns: readonly KloEn } function candidateKeyColumns(input: { - table: KloEnrichedTable; - profiles: KloRelationshipProfileArtifact; + table: KtxEnrichedTable; + profiles: KtxRelationshipProfileArtifact; maxColumnsPerTable: number; -}): KloEnrichedColumn[] { +}): KtxEnrichedColumn[] { return input.table.columns .map((column, index) => ({ column, index })) .filter(({ column }) => { @@ -154,8 +154,8 @@ function candidateKeyColumns(input: { } function hasStrongSingleColumnKey(input: { - table: KloEnrichedTable; - profiles: KloRelationshipProfileArtifact; + table: KtxEnrichedTable; + profiles: KtxRelationshipProfileArtifact; minPrimaryKeyUniqueness: number; }): boolean { return input.table.columns.some((column) => { @@ -196,7 +196,7 @@ function relationshipKey(input: { return `${tupleKey(input.fromTable, input.fromColumns)}->${tupleKey(input.toTable, input.toColumns)}`; } -function tupleEndpoint(table: KloEnrichedTable, columns: readonly KloEnrichedColumn[]): KloCompositeRelationshipTupleEndpoint { +function tupleEndpoint(table: KtxEnrichedTable, columns: readonly KtxEnrichedColumn[]): KtxCompositeRelationshipTupleEndpoint { return { tableId: table.id, columnIds: columns.map((column) => column.id), @@ -205,11 +205,11 @@ function tupleEndpoint(table: KloEnrichedTable, columns: readonly KloEnrichedCol }; } -function row(result: KloQueryResult): unknown[] { +function row(result: KtxQueryResult): unknown[] { return result.rows[0] ?? []; } -function numberAt(result: KloQueryResult, header: string): number { +function numberAt(result: KtxQueryResult, header: string): number { const index = result.headers.findIndex((candidate) => candidate.toLowerCase() === header.toLowerCase()); const value = row(result)[index]; if (typeof value === 'number') { @@ -224,28 +224,28 @@ function numberAt(result: KloQueryResult, header: string): number { return 0; } -function topSql(driver: KloConnectionDriver, limit: number): string { +function topSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ` TOP (${Math.max(1, Math.floor(limit))})`; } return ''; } -function limitSql(driver: KloConnectionDriver, limit: number): string { +function limitSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ''; } return ` LIMIT ${Math.max(1, Math.floor(limit))}`; } -function aliasedTupleSelect(driver: KloConnectionDriver, columns: readonly string[]): string { +function aliasedTupleSelect(driver: KtxConnectionDriver, columns: readonly string[]): string { return columns - .map((column, index) => `${quoteKloRelationshipIdentifier(driver, column)} AS c${index}`) + .map((column, index) => `${quoteKtxRelationshipIdentifier(driver, column)} AS c${index}`) .join(', '); } -function nonNullPredicate(driver: KloConnectionDriver, columns: readonly string[]): string { - return columns.map((column) => `${quoteKloRelationshipIdentifier(driver, column)} IS NOT NULL`).join(' AND '); +function nonNullPredicate(driver: KtxConnectionDriver, columns: readonly string[]): string { + return columns.map((column) => `${quoteKtxRelationshipIdentifier(driver, column)} IS NOT NULL`).join(' AND '); } function tupleEquality(columns: number): string { @@ -255,11 +255,11 @@ function tupleEquality(columns: number): string { } function buildTupleDistinctSql(input: { - driver: KloConnectionDriver; - table: KloTableRef; + driver: KtxConnectionDriver; + table: KtxTableRef; columns: readonly string[]; }): string { - const tableSql = formatKloRelationshipTableRef(input.driver, input.table); + const tableSql = formatKtxRelationshipTableRef(input.driver, input.table); return [ 'WITH tuple_values AS (', `SELECT DISTINCT ${aliasedTupleSelect(input.driver, input.columns)} FROM ${tableSql}`, @@ -270,15 +270,15 @@ function buildTupleDistinctSql(input: { } function buildCompositeCoverageSql(input: { - driver: KloConnectionDriver; - childTable: KloTableRef; + driver: KtxConnectionDriver; + childTable: KtxTableRef; childColumns: readonly string[]; - parentTable: KloTableRef; + parentTable: KtxTableRef; parentColumns: readonly string[]; maxDistinctSourceValues: number; }): string { - const childTableSql = formatKloRelationshipTableRef(input.driver, input.childTable); - const parentTableSql = formatKloRelationshipTableRef(input.driver, input.parentTable); + const childTableSql = formatKtxRelationshipTableRef(input.driver, input.childTable); + const parentTableSql = formatKtxRelationshipTableRef(input.driver, input.parentTable); const top = topSql(input.driver, input.maxDistinctSourceValues); const limit = limitSql(input.driver, input.maxDistinctSourceValues); return [ @@ -305,7 +305,7 @@ function relationshipStatus(input: { violationRatio: number; minSourceCoverage: number; maxViolationRatio: number; -}): KloCompositeRelationshipStatus { +}): KtxCompositeRelationshipStatus { if ( input.targetUniqueness >= DEFAULT_MIN_PRIMARY_KEY_UNIQUENESS && input.sourceCoverage >= input.minSourceCoverage && @@ -320,7 +320,7 @@ function relationshipStatus(input: { } function hasAcceptedSubset( - accepted: readonly KloCompositePrimaryKeyCandidate[], + accepted: readonly KtxCompositePrimaryKeyCandidate[], tableName: string, columns: readonly string[], ): boolean { @@ -335,15 +335,15 @@ function hasAcceptedSubset( async function detectCompositePrimaryKeys(input: { connectionId: string; - driver: KloConnectionDriver; - table: KloEnrichedTable; - profiles: KloRelationshipProfileArtifact; - executor: KloRelationshipReadOnlyExecutor; - ctx: KloScanContext; + driver: KtxConnectionDriver; + table: KtxEnrichedTable; + profiles: KtxRelationshipProfileArtifact; + executor: KtxRelationshipReadOnlyExecutor; + ctx: KtxScanContext; maxCompositeWidth: number; maxColumnsPerTable: number; minPrimaryKeyUniqueness: number; -}): Promise<{ primaryKeys: KloCompositePrimaryKeyCandidate[]; queryCount: number }> { +}): Promise<{ primaryKeys: KtxCompositePrimaryKeyCandidate[]; queryCount: number }> { const rowCount = tableRowCount(input.profiles, input.table.ref.name); if (rowCount === 0) { return { primaryKeys: [], queryCount: 0 }; @@ -363,7 +363,7 @@ async function detectCompositePrimaryKeys(input: { profiles: input.profiles, maxColumnsPerTable: input.maxColumnsPerTable, }); - const primaryKeys: KloCompositePrimaryKeyCandidate[] = []; + const primaryKeys: KtxCompositePrimaryKeyCandidate[] = []; let queryCount = 0; for (let width = 2; width <= input.maxCompositeWidth; width += 1) { @@ -423,11 +423,11 @@ async function detectCompositePrimaryKeys(input: { }; } -function columnsByName(table: KloEnrichedTable): Map { +function columnsByName(table: KtxEnrichedTable): Map { return new Map(table.columns.map((column) => [column.name, column])); } -function compatibleTuple(sourceColumns: readonly KloEnrichedColumn[], targetColumns: readonly KloEnrichedColumn[]): boolean { +function compatibleTuple(sourceColumns: readonly KtxEnrichedColumn[], targetColumns: readonly KtxEnrichedColumn[]): boolean { if (sourceColumns.length !== targetColumns.length) { return false; } @@ -439,17 +439,17 @@ function compatibleTuple(sourceColumns: readonly KloEnrichedColumn[], targetColu async function validateCompositeRelationship(input: { connectionId: string; - driver: KloConnectionDriver; - sourceTable: KloEnrichedTable; - sourceColumns: readonly KloEnrichedColumn[]; - targetKey: KloCompositePrimaryKeyCandidate; - targetTable: KloEnrichedTable; - targetColumns: readonly KloEnrichedColumn[]; - executor: KloRelationshipReadOnlyExecutor; - ctx: KloScanContext; + driver: KtxConnectionDriver; + sourceTable: KtxEnrichedTable; + sourceColumns: readonly KtxEnrichedColumn[]; + targetKey: KtxCompositePrimaryKeyCandidate; + targetTable: KtxEnrichedTable; + targetColumns: readonly KtxEnrichedColumn[]; + executor: KtxRelationshipReadOnlyExecutor; + ctx: KtxScanContext; minSourceCoverage: number; maxViolationRatio: number; -}): Promise<{ relationship: KloCompositeRelationshipCandidate; queryCount: number }> { +}): Promise<{ relationship: KtxCompositeRelationshipCandidate; queryCount: number }> { const result = await input.executor.executeReadOnly( { connectionId: input.connectionId, @@ -525,9 +525,9 @@ async function validateCompositeRelationship(input: { }; } -export async function discoverKloCompositeRelationships( - input: DiscoverKloCompositeRelationshipsInput, -): Promise { +export async function discoverKtxCompositeRelationships( + input: DiscoverKtxCompositeRelationshipsInput, +): Promise { if (!input.executor || !input.profiles.sqlAvailable) { return { primaryKeys: [], @@ -546,7 +546,7 @@ export async function discoverKloCompositeRelationships( }; const tables = enabledTables(input.schema); const tableByName = new Map(tables.map((table) => [table.ref.name, table])); - const primaryKeys: KloCompositePrimaryKeyCandidate[] = []; + const primaryKeys: KtxCompositePrimaryKeyCandidate[] = []; let queryCount = 0; for (const table of tables) { @@ -565,7 +565,7 @@ export async function discoverKloCompositeRelationships( queryCount += result.queryCount; } - const relationships: KloCompositeRelationshipCandidate[] = []; + const relationships: KtxCompositeRelationshipCandidate[] = []; for (const targetKey of primaryKeys) { const targetTable = tableByName.get(targetKey.table.name); if (!targetTable) { diff --git a/packages/context/src/scan/relationship-diagnostics.test.ts b/packages/context/src/scan/relationship-diagnostics.test.ts index 825b2eb4..3f3bad1b 100644 --- a/packages/context/src/scan/relationship-diagnostics.test.ts +++ b/packages/context/src/scan/relationship-diagnostics.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; -import type { KloEnrichedRelationship, KloRelationshipEndpoint } from './enrichment-types.js'; -import type { KloResolvedRelationshipDiscoveryCandidate } from './relationship-graph-resolver.js'; +import type { KtxEnrichedRelationship, KtxRelationshipEndpoint } from './enrichment-types.js'; +import type { KtxResolvedRelationshipDiscoveryCandidate } from './relationship-graph-resolver.js'; import { - buildKloRelationshipArtifacts, - buildKloRelationshipDiagnostics, - emptyKloRelationshipProfileArtifact, + buildKtxRelationshipArtifacts, + buildKtxRelationshipDiagnostics, + emptyKtxRelationshipProfileArtifact, } from './relationship-diagnostics.js'; -function endpoint(table: string, column: string): KloRelationshipEndpoint { +function endpoint(table: string, column: string): KtxRelationshipEndpoint { return { tableId: table, columnIds: [`${table}.${column}`], @@ -23,7 +23,7 @@ function enrichedRelationship(input: { toTable: string; toColumn: string; confidence?: number; -}): KloEnrichedRelationship { +}): KtxEnrichedRelationship { return { id: input.id, source: 'inferred', @@ -43,7 +43,7 @@ function resolvedRelationship(input: { pkScore?: number; validationReasons?: string[]; graphReasons?: string[]; -}): KloResolvedRelationshipDiscoveryCandidate { +}): KtxResolvedRelationshipDiscoveryCandidate { return { id: input.id, from: endpoint('orders', 'customer_id'), @@ -99,7 +99,7 @@ function resolvedRelationship(input: { describe('relationship diagnostics artifacts', () => { it('groups graph-resolved relationships and preserves evidence reasons', () => { - const artifacts = buildKloRelationshipArtifacts({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse', resolvedRelationships: [ resolvedRelationship({ id: 'accepted-edge', status: 'accepted', source: 'llm_proposal' }), @@ -142,7 +142,7 @@ describe('relationship diagnostics artifacts', () => { }); it('adapts legacy relationship updates into the richer artifact shape', () => { - const artifacts = buildKloRelationshipArtifacts({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse', relationshipUpdate: { connectionId: 'warehouse', @@ -184,7 +184,7 @@ describe('relationship diagnostics artifacts', () => { }); it('deduplicates resolved and formal relationship update artifacts by edge id', () => { - const artifacts = buildKloRelationshipArtifacts({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse', resolvedRelationships: [ { @@ -254,7 +254,7 @@ describe('relationship diagnostics artifacts', () => { }); it('explains validation-unavailable review candidates', () => { - const artifacts = buildKloRelationshipArtifacts({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse', resolvedRelationships: [ resolvedRelationship({ @@ -265,13 +265,13 @@ describe('relationship diagnostics artifacts', () => { }), ], }); - const profile = emptyKloRelationshipProfileArtifact({ + const profile = emptyKtxRelationshipProfileArtifact({ connectionId: 'warehouse', driver: 'sqlite', reason: 'read_only_sql_unavailable', }); - const diagnostics = buildKloRelationshipDiagnostics({ + const diagnostics = buildKtxRelationshipDiagnostics({ connectionId: 'warehouse', generatedAt: '2026-05-07T12:00:00.000Z', artifacts, @@ -279,7 +279,7 @@ describe('relationship diagnostics artifacts', () => { warnings: [ { code: 'connector_capability_missing', - message: 'KLO scan connector cannot run standalone statistical relationship validation', + message: 'KTX scan connector cannot run standalone statistical relationship validation', recoverable: true, metadata: { capability: 'readOnlySql' }, }, @@ -300,12 +300,12 @@ describe('relationship diagnostics artifacts', () => { }); it('explains empty relationship output as a no-candidate outcome', () => { - const artifacts = buildKloRelationshipArtifacts({ connectionId: 'warehouse' }); - const diagnostics = buildKloRelationshipDiagnostics({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse' }); + const diagnostics = buildKtxRelationshipDiagnostics({ connectionId: 'warehouse', generatedAt: '2026-05-07T12:00:00.000Z', artifacts, - profile: emptyKloRelationshipProfileArtifact({ + profile: emptyKtxRelationshipProfileArtifact({ connectionId: 'warehouse', driver: 'sqlite', reason: 'relationship_profiling_not_run', @@ -318,7 +318,7 @@ describe('relationship diagnostics artifacts', () => { }); it('records composite relationship endpoints in relationship artifacts', () => { - const artifacts = buildKloRelationshipArtifacts({ + const artifacts = buildKtxRelationshipArtifacts({ connectionId: 'warehouse', compositeRelationships: [ { diff --git a/packages/context/src/scan/relationship-diagnostics.ts b/packages/context/src/scan/relationship-diagnostics.ts index b0d8fde3..aa9564ae 100644 --- a/packages/context/src/scan/relationship-diagnostics.ts +++ b/packages/context/src/scan/relationship-diagnostics.ts @@ -1,18 +1,18 @@ import type { - KloEnrichedRelationship, - KloRelationshipEndpoint, - KloRelationshipType, - KloRelationshipUpdate, + KtxEnrichedRelationship, + KtxRelationshipEndpoint, + KtxRelationshipType, + KtxRelationshipUpdate, } from './enrichment-types.js'; import type { - KloResolvedRelationshipDiscoveryCandidate, - KloResolvedRelationshipStatus, + KtxResolvedRelationshipDiscoveryCandidate, + KtxResolvedRelationshipStatus, } from './relationship-graph-resolver.js'; -import type { KloCompositeRelationshipCandidate } from './relationship-composite-candidates.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloConnectionDriver, KloScanWarning } from './types.js'; +import type { KtxCompositeRelationshipCandidate } from './relationship-composite-candidates.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxConnectionDriver, KtxScanWarning } from './types.js'; -export interface KloRelationshipArtifactEndpoint { +export interface KtxRelationshipArtifactEndpoint { tableId: string; columnIds: string[]; table: { @@ -23,13 +23,13 @@ export interface KloRelationshipArtifactEndpoint { columns: string[]; } -export interface KloRelationshipArtifactEdge { +export interface KtxRelationshipArtifactEdge { id: string; - status: KloResolvedRelationshipStatus; + status: KtxResolvedRelationshipStatus; source: string; - from: KloRelationshipArtifactEndpoint; - to: KloRelationshipArtifactEndpoint; - relationshipType: KloRelationshipType; + from: KtxRelationshipArtifactEndpoint; + to: KtxRelationshipArtifactEndpoint; + relationshipType: KtxRelationshipType; confidence: number; pkScore: number | null; fkScore: number | null; @@ -40,88 +40,88 @@ export interface KloRelationshipArtifactEdge { reasons: string[]; } -export interface KloRelationshipArtifact { +export interface KtxRelationshipArtifact { connectionId: string; - accepted: KloRelationshipArtifactEdge[]; - review: KloRelationshipArtifactEdge[]; - rejected: KloRelationshipArtifactEdge[]; - skipped: KloRelationshipUpdate['skipped']; + accepted: KtxRelationshipArtifactEdge[]; + review: KtxRelationshipArtifactEdge[]; + rejected: KtxRelationshipArtifactEdge[]; + skipped: KtxRelationshipUpdate['skipped']; } -export interface KloRelationshipDiagnosticsSummary { +export interface KtxRelationshipDiagnosticsSummary { accepted: number; review: number; rejected: number; skipped: number; } -export interface KloRelationshipDiagnosticsValidation { +export interface KtxRelationshipDiagnosticsValidation { available: boolean; sqlAvailable: boolean; queryCount: number; } -export interface KloRelationshipDiagnosticsThresholds { +export interface KtxRelationshipDiagnosticsThresholds { acceptThreshold: number; reviewThreshold: number; } -export interface KloRelationshipDiagnosticsPolicy { +export interface KtxRelationshipDiagnosticsPolicy { validationRequiredForManifest: boolean; maxCandidatesPerColumn: number; profileSampleRows: number; validationConcurrency: number; } -export interface KloRelationshipDiagnosticsArtifact { +export interface KtxRelationshipDiagnosticsArtifact { connectionId: string; generatedAt: string; - summary: KloRelationshipDiagnosticsSummary; + summary: KtxRelationshipDiagnosticsSummary; noAcceptedReason: string | null; candidateCountsBySource: Record; - validation: KloRelationshipDiagnosticsValidation; - thresholds: KloRelationshipDiagnosticsThresholds; - policy: KloRelationshipDiagnosticsPolicy; - warnings: KloScanWarning[]; + validation: KtxRelationshipDiagnosticsValidation; + thresholds: KtxRelationshipDiagnosticsThresholds; + policy: KtxRelationshipDiagnosticsPolicy; + warnings: KtxScanWarning[]; profileWarnings: string[]; } -export interface BuildKloRelationshipArtifactsInput { +export interface BuildKtxRelationshipArtifactsInput { connectionId: string; - relationshipUpdate?: KloRelationshipUpdate | null; - resolvedRelationships?: readonly KloResolvedRelationshipDiscoveryCandidate[]; - compositeRelationships?: readonly KloCompositeRelationshipCandidate[]; + relationshipUpdate?: KtxRelationshipUpdate | null; + resolvedRelationships?: readonly KtxResolvedRelationshipDiscoveryCandidate[]; + compositeRelationships?: readonly KtxCompositeRelationshipCandidate[]; } -export interface BuildKloRelationshipDiagnosticsInput { +export interface BuildKtxRelationshipDiagnosticsInput { connectionId: string; - artifacts: KloRelationshipArtifact; - profile: KloRelationshipProfileArtifact; - warnings?: readonly KloScanWarning[]; - thresholds?: Partial; - policy?: Partial; + artifacts: KtxRelationshipArtifact; + profile: KtxRelationshipProfileArtifact; + warnings?: readonly KtxScanWarning[]; + thresholds?: Partial; + policy?: Partial; generatedAt?: string; } -export interface EmptyKloRelationshipProfileArtifactInput { +export interface EmptyKtxRelationshipProfileArtifactInput { connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; reason: string; } -const DEFAULT_THRESHOLDS: KloRelationshipDiagnosticsThresholds = { +const DEFAULT_THRESHOLDS: KtxRelationshipDiagnosticsThresholds = { acceptThreshold: 0.85, reviewThreshold: 0.55, }; -const DEFAULT_POLICY: KloRelationshipDiagnosticsPolicy = { +const DEFAULT_POLICY: KtxRelationshipDiagnosticsPolicy = { validationRequiredForManifest: true, maxCandidatesPerColumn: 25, profileSampleRows: 10000, validationConcurrency: 4, }; -function endpointArtifact(endpoint: KloRelationshipEndpoint): KloRelationshipArtifactEndpoint { +function endpointArtifact(endpoint: KtxRelationshipEndpoint): KtxRelationshipArtifactEndpoint { return { tableId: endpoint.tableId, columnIds: endpoint.columnIds, @@ -139,9 +139,9 @@ function uniqueReasons(values: readonly string[]): string[] { } function relationshipUpdateEdge( - relationship: KloEnrichedRelationship, + relationship: KtxEnrichedRelationship, status: 'accepted' | 'rejected', -): KloRelationshipArtifactEdge { +): KtxRelationshipArtifactEdge { const acceptedReason = relationship.source === 'formal' ? 'formal_metadata_accepted' : 'accepted_relationship_update'; return { id: relationship.id, @@ -161,7 +161,7 @@ function relationshipUpdateEdge( }; } -function resolvedEdge(candidate: KloResolvedRelationshipDiscoveryCandidate): KloRelationshipArtifactEdge { +function resolvedEdge(candidate: KtxResolvedRelationshipDiscoveryCandidate): KtxRelationshipArtifactEdge { return { id: candidate.id, status: candidate.status, @@ -184,7 +184,7 @@ function resolvedEdge(candidate: KloResolvedRelationshipDiscoveryCandidate): Klo }; } -function compositeEndpointArtifact(endpoint: KloCompositeRelationshipCandidate['from']): KloRelationshipArtifactEndpoint { +function compositeEndpointArtifact(endpoint: KtxCompositeRelationshipCandidate['from']): KtxRelationshipArtifactEndpoint { return { tableId: endpoint.tableId, columnIds: endpoint.columnIds, @@ -197,7 +197,7 @@ function compositeEndpointArtifact(endpoint: KloCompositeRelationshipCandidate[' }; } -function compositeEdge(candidate: KloCompositeRelationshipCandidate): KloRelationshipArtifactEdge { +function compositeEdge(candidate: KtxCompositeRelationshipCandidate): KtxRelationshipArtifactEdge { return { id: candidate.id, status: candidate.status, @@ -216,7 +216,7 @@ function compositeEdge(candidate: KloCompositeRelationshipCandidate): KloRelatio }; } -function emptyArtifacts(connectionId: string): KloRelationshipArtifact { +function emptyArtifacts(connectionId: string): KtxRelationshipArtifact { return { connectionId, accepted: [], @@ -226,13 +226,13 @@ function emptyArtifacts(connectionId: string): KloRelationshipArtifact { }; } -function pushUniqueEdge(edges: KloRelationshipArtifactEdge[], edge: KloRelationshipArtifactEdge): void { +function pushUniqueEdge(edges: KtxRelationshipArtifactEdge[], edge: KtxRelationshipArtifactEdge): void { if (!edges.some((item) => item.id === edge.id)) { edges.push(edge); } } -export function buildKloRelationshipArtifacts(input: BuildKloRelationshipArtifactsInput): KloRelationshipArtifact { +export function buildKtxRelationshipArtifacts(input: BuildKtxRelationshipArtifactsInput): KtxRelationshipArtifact { const artifacts = emptyArtifacts(input.connectionId); if (input.resolvedRelationships) { @@ -279,11 +279,11 @@ export function buildKloRelationshipArtifacts(input: BuildKloRelationshipArtifac }; } -function allEdges(artifacts: KloRelationshipArtifact): KloRelationshipArtifactEdge[] { +function allEdges(artifacts: KtxRelationshipArtifact): KtxRelationshipArtifactEdge[] { return [...artifacts.accepted, ...artifacts.review, ...artifacts.rejected]; } -function candidateCountsBySource(artifacts: KloRelationshipArtifact): Record { +function candidateCountsBySource(artifacts: KtxRelationshipArtifact): Record { const counts: Record = {}; for (const edge of allEdges(artifacts)) { counts[edge.source] = (counts[edge.source] ?? 0) + 1; @@ -291,13 +291,13 @@ function candidateCountsBySource(artifacts: KloRelationshipArtifact): Record left.localeCompare(right))); } -function hasReason(artifacts: KloRelationshipArtifact, reason: string): boolean { +function hasReason(artifacts: KtxRelationshipArtifact, reason: string): boolean { return allEdges(artifacts).some((edge) => edge.reasons.includes(reason)); } function noAcceptedReason(input: { - artifacts: KloRelationshipArtifact; - profile: KloRelationshipProfileArtifact; + artifacts: KtxRelationshipArtifact; + profile: KtxRelationshipProfileArtifact; }): string | null { if (input.artifacts.accepted.length > 0) { return null; @@ -319,9 +319,9 @@ function noAcceptedReason(input: { return 'no candidate pairs passed type compatibility'; } -export function emptyKloRelationshipProfileArtifact( - input: EmptyKloRelationshipProfileArtifactInput, -): KloRelationshipProfileArtifact { +export function emptyKtxRelationshipProfileArtifact( + input: EmptyKtxRelationshipProfileArtifactInput, +): KtxRelationshipProfileArtifact { return { connectionId: input.connectionId, driver: input.driver, @@ -333,12 +333,12 @@ export function emptyKloRelationshipProfileArtifact( }; } -export function buildKloRelationshipDiagnostics( - input: BuildKloRelationshipDiagnosticsInput, -): KloRelationshipDiagnosticsArtifact { +export function buildKtxRelationshipDiagnostics( + input: BuildKtxRelationshipDiagnosticsInput, +): KtxRelationshipDiagnosticsArtifact { const thresholds = { ...DEFAULT_THRESHOLDS, ...input.thresholds }; const policy = { ...DEFAULT_POLICY, ...input.policy }; - const summary: KloRelationshipDiagnosticsSummary = { + const summary: KtxRelationshipDiagnosticsSummary = { accepted: input.artifacts.accepted.length, review: input.artifacts.review.length, rejected: input.artifacts.rejected.length, diff --git a/packages/context/src/scan/relationship-discovery.test.ts b/packages/context/src/scan/relationship-discovery.test.ts index 79cc6722..5d958b2d 100644 --- a/packages/context/src/scan/relationship-discovery.test.ts +++ b/packages/context/src/scan/relationship-discovery.test.ts @@ -1,21 +1,21 @@ -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import Database from 'better-sqlite3'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { buildDefaultKloProjectConfig } from '../project/config.js'; -import { snapshotToKloEnrichedSchema } from './local-enrichment.js'; +import { buildDefaultKtxProjectConfig } from '../project/config.js'; +import { snapshotToKtxEnrichedSchema } from './local-enrichment.js'; import { - loadKloRelationshipBenchmarkFixture, - maskKloRelationshipBenchmarkSnapshot, + loadKtxRelationshipBenchmarkFixture, + maskKtxRelationshipBenchmarkSnapshot, } from './relationship-benchmarks.js'; -import { discoverKloRelationships } from './relationship-discovery.js'; -import { createKloConnectorCapabilities } from './types.js'; -import type { KloQueryResult, KloReadOnlyQueryInput, KloScanConnector, KloScanContext, KloSchemaSnapshot } from './types.js'; +import { discoverKtxRelationships } from './relationship-discovery.js'; +import { createKtxConnectorCapabilities } from './types.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput, KtxScanConnector, KtxScanContext, KtxSchemaSnapshot } from './types.js'; class InMemorySqliteExecutor { readonly db = new Database(':memory:'); queryCount = 0; - executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.queryCount += 1; const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); @@ -32,7 +32,7 @@ class InMemorySqliteExecutor { } } -function snapshot(): KloSchemaSnapshot { +function snapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'sqlite', @@ -102,7 +102,7 @@ function snapshot(): KloSchemaSnapshot { }; } -function declaredForeignKeySnapshot(): KloSchemaSnapshot { +function declaredForeignKeySnapshot(): KtxSchemaSnapshot { const source = snapshot(); return { ...source, @@ -131,7 +131,7 @@ function declaredForeignKeySnapshot(): KloSchemaSnapshot { }; } -function naturalKeySnapshot(): KloSchemaSnapshot { +function naturalKeySnapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'sqlite', @@ -201,11 +201,11 @@ function naturalKeySnapshot(): KloSchemaSnapshot { }; } -function connector(executor: InMemorySqliteExecutor | null): KloScanConnector { +function connector(executor: InMemorySqliteExecutor | null): KtxScanConnector { return { id: 'sqlite:test', driver: 'sqlite', - capabilities: createKloConnectorCapabilities({ + capabilities: createKtxConnectorCapabilities({ readOnlySql: executor !== null, columnStats: executor !== null, tableSampling: false, @@ -216,11 +216,11 @@ function connector(executor: InMemorySqliteExecutor | null): KloScanConnector { }; } -function llmProvider(): KloLlmProvider { +function llmProvider(): KtxLlmProvider { const model = { modelId: 'claude-sonnet-4-6', provider: 'anthropic' }; return { - getModel: vi.fn(() => model as ReturnType), - getModelByName: vi.fn(() => model as ReturnType), + getModel: vi.fn(() => model as ReturnType), + getModelByName: vi.fn(() => model as ReturnType), cacheMarker: vi.fn(), repairToolCallHandler: vi.fn(), thinkingProviderOptions: vi.fn(() => ({})), @@ -236,17 +236,17 @@ function llmProvider(): KloLlmProvider { cacheTools: true, cacheHistory: true, vertexFallbackTo5m: false, - }) as ReturnType, + }) as ReturnType, ), - activeBackend: vi.fn(() => 'anthropic' as ReturnType), + activeBackend: vi.fn(() => 'anthropic' as ReturnType), }; } function relationshipSettings() { - return buildDefaultKloProjectConfig('warehouse').scan.relationships; + return buildDefaultKtxProjectConfig('warehouse').scan.relationships; } -function llmOnlyRelationshipSnapshot(): KloSchemaSnapshot { +function llmOnlyRelationshipSnapshot(): KtxSchemaSnapshot { return { connectionId: 'warehouse', driver: 'sqlite', @@ -324,11 +324,11 @@ describe('production relationship discovery', () => { INSERT INTO orders (id, account_id) VALUES (10, 1), (11, 1), (12, 2); `); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: connector(executor), - schema: snapshotToKloEnrichedSchema(snapshot()), + schema: snapshotToKtxEnrichedSchema(snapshot()), context: { runId: 'relationship-run-1' }, settings: relationshipSettings(), }); @@ -363,14 +363,14 @@ describe('production relationship discovery', () => { `); const schema = naturalKeySnapshot(); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: { ...connector(executor), introspect: async () => schema, }, - schema: snapshotToKloEnrichedSchema(schema), + schema: snapshotToKtxEnrichedSchema(schema), context: { runId: 'natural-key-relationship-run' }, settings: relationshipSettings(), }); @@ -403,7 +403,7 @@ describe('production relationship discovery', () => { `); const sourceSnapshot = llmOnlyRelationshipSnapshot(); - const schema = snapshotToKloEnrichedSchema( + const schema = snapshotToKtxEnrichedSchema( sourceSnapshot, new Map([ ['customers.id', [1, 0, 0]], @@ -413,7 +413,7 @@ describe('production relationship discovery', () => { ]), ); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: { @@ -446,11 +446,11 @@ describe('production relationship discovery', () => { }); it('keeps candidates review-only when read-only SQL is unavailable', async () => { - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: connector(null), - schema: snapshotToKloEnrichedSchema(snapshot()), + schema: snapshotToKtxEnrichedSchema(snapshot()), context: { runId: 'relationship-run-no-sql' }, settings: relationshipSettings(), }); @@ -464,7 +464,7 @@ describe('production relationship discovery', () => { }); expect(result.warnings).toContainEqual({ code: 'connector_capability_missing', - message: 'KLO scan connector cannot run read-only SQL relationship validation', + message: 'KTX scan connector cannot run read-only SQL relationship validation', recoverable: true, metadata: { capability: 'readOnlySql' }, }); @@ -472,11 +472,11 @@ describe('production relationship discovery', () => { it('accepts formal metadata relationships when read-only SQL is unavailable', async () => { const sourceSnapshot = declaredForeignKeySnapshot(); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: connector(null), - schema: snapshotToKloEnrichedSchema(sourceSnapshot), + schema: snapshotToKtxEnrichedSchema(sourceSnapshot), context: { runId: 'formal-metadata-no-sql' }, settings: relationshipSettings(), }); @@ -521,11 +521,11 @@ describe('production relationship discovery', () => { }, })); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: connector(executor), - schema: snapshotToKloEnrichedSchema(llmOnlyRelationshipSnapshot()), + schema: snapshotToKtxEnrichedSchema(llmOnlyRelationshipSnapshot()), context: { runId: 'llm-relationship-orchestrator' }, settings: relationshipSettings(), llmProvider: llmProvider(), @@ -557,16 +557,16 @@ describe('production relationship discovery', () => { `); const settings = { - ...buildDefaultKloProjectConfig('warehouse').scan.relationships, + ...buildDefaultKtxProjectConfig('warehouse').scan.relationships, acceptThreshold: 0.99, reviewThreshold: 0.55, }; - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: connector(executor), - schema: snapshotToKloEnrichedSchema(snapshot()), + schema: snapshotToKtxEnrichedSchema(snapshot()), context: { runId: 'configured-thresholds' }, settings, }); @@ -623,17 +623,17 @@ describe('production relationship discovery', () => { ], }); - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: 'warehouse', driver: 'sqlite', connector: { ...connector(executor), introspect: async () => richSnapshot, }, - schema: snapshotToKloEnrichedSchema(richSnapshot), + schema: snapshotToKtxEnrichedSchema(richSnapshot), context: { runId: 'candidate-cap' }, settings: { - ...buildDefaultKloProjectConfig('warehouse').scan.relationships, + ...buildDefaultKtxProjectConfig('warehouse').scan.relationships, maxCandidatesPerColumn: 1, }, }); @@ -652,13 +652,13 @@ describe('production relationship discovery', () => { '../../test/fixtures/relationship-benchmarks/composite_keys_no_declared_constraints', import.meta.url, ); - const fixture = await loadKloRelationshipBenchmarkFixture(fixtureRoot.pathname); - const maskedSnapshot = maskKloRelationshipBenchmarkSnapshot(fixture.snapshot, 'declared_pks_and_declared_fks_removed'); + const fixture = await loadKtxRelationshipBenchmarkFixture(fixtureRoot.pathname); + const maskedSnapshot = maskKtxRelationshipBenchmarkSnapshot(fixture.snapshot, 'declared_pks_and_declared_fks_removed'); const database = new Database(fixture.dataPath ?? '', { readonly: true, fileMustExist: true }); - const testConnector: KloScanConnector = { + const testConnector: KtxScanConnector = { id: 'sqlite:composite', driver: 'sqlite', - capabilities: createKloConnectorCapabilities({ + capabilities: createKtxConnectorCapabilities({ readOnlySql: true, columnStats: true, tableSampling: false, @@ -677,11 +677,11 @@ describe('production relationship discovery', () => { }, }; - const result = await discoverKloRelationships({ + const result = await discoverKtxRelationships({ connectionId: maskedSnapshot.connectionId, driver: maskedSnapshot.driver, connector: testConnector, - schema: snapshotToKloEnrichedSchema(maskedSnapshot, new Map()), + schema: snapshotToKtxEnrichedSchema(maskedSnapshot, new Map()), context: { runId: 'test:production-composite' }, settings: relationshipSettings(), }); diff --git a/packages/context/src/scan/relationship-discovery.ts b/packages/context/src/scan/relationship-discovery.ts index 8a03ec6c..b1af1492 100644 --- a/packages/context/src/scan/relationship-discovery.ts +++ b/packages/context/src/scan/relationship-discovery.ts @@ -1,63 +1,63 @@ -import type { KloLlmProvider } from '@klo/llm'; -import type { KloScanRelationshipConfig } from '../project/config.js'; -import type { KloEnrichedRelationship, KloEnrichedSchema, KloRelationshipUpdate } from './enrichment-types.js'; +import type { KtxLlmProvider } from '@ktx/llm'; +import type { KtxScanRelationshipConfig } from '../project/config.js'; +import type { KtxEnrichedRelationship, KtxEnrichedSchema, KtxRelationshipUpdate } from './enrichment-types.js'; import { - generateKloRelationshipDiscoveryCandidates, - type KloRelationshipDiscoveryCandidate, - mergeKloRelationshipDiscoveryCandidates, + generateKtxRelationshipDiscoveryCandidates, + type KtxRelationshipDiscoveryCandidate, + mergeKtxRelationshipDiscoveryCandidates, } from './relationship-candidates.js'; import { - discoverKloCompositeRelationships, - type KloCompositeRelationshipCandidate, + discoverKtxCompositeRelationships, + type KtxCompositeRelationshipCandidate, } from './relationship-composite-candidates.js'; -import { collectKloFormalMetadataRelationships } from './relationship-formal-metadata.js'; +import { collectKtxFormalMetadataRelationships } from './relationship-formal-metadata.js'; import { - type KloResolvedRelationshipDiscoveryCandidate, - resolveKloRelationshipGraph, + type KtxResolvedRelationshipDiscoveryCandidate, + resolveKtxRelationshipGraph, } from './relationship-graph-resolver.js'; import { - type KloRelationshipLlmProposalGenerateText, - proposeKloRelationshipCandidatesWithLlm, + type KtxRelationshipLlmProposalGenerateText, + proposeKtxRelationshipCandidatesWithLlm, } from './relationship-llm-proposal.js'; import { - createKloRelationshipProfileCache, - type KloRelationshipProfileArtifact, - type KloRelationshipReadOnlyExecutor, - profileKloRelationshipSchema, + createKtxRelationshipProfileCache, + type KtxRelationshipProfileArtifact, + type KtxRelationshipReadOnlyExecutor, + profileKtxRelationshipSchema, } from './relationship-profiling.js'; -import { validateKloRelationshipDiscoveryCandidates } from './relationship-validation.js'; +import { validateKtxRelationshipDiscoveryCandidates } from './relationship-validation.js'; import type { - KloConnectionDriver, - KloScanConnector, - KloScanContext, - KloScanEnrichmentSummary, - KloScanRelationshipSummary, - KloScanWarning, + KtxConnectionDriver, + KtxScanConnector, + KtxScanContext, + KtxScanEnrichmentSummary, + KtxScanRelationshipSummary, + KtxScanWarning, } from './types.js'; -export interface DiscoverKloRelationshipsInput { +export interface DiscoverKtxRelationshipsInput { connectionId: string; - driver: KloConnectionDriver; - connector: KloScanConnector; - schema: KloEnrichedSchema; - context: KloScanContext; - settings: KloScanRelationshipConfig; - llmProvider?: KloLlmProvider | null; - generateText?: KloRelationshipLlmProposalGenerateText; + driver: KtxConnectionDriver; + connector: KtxScanConnector; + schema: KtxEnrichedSchema; + context: KtxScanContext; + settings: KtxScanRelationshipConfig; + llmProvider?: KtxLlmProvider | null; + generateText?: KtxRelationshipLlmProposalGenerateText; } -export interface DiscoverKloRelationshipsResult { - relationshipUpdate: KloRelationshipUpdate; - relationships: KloScanRelationshipSummary; - profile: KloRelationshipProfileArtifact; - resolvedRelationships: KloResolvedRelationshipDiscoveryCandidate[]; - compositeRelationships: KloCompositeRelationshipCandidate[]; - statisticalValidation: KloScanEnrichmentSummary['statisticalValidation']; - llmRelationshipValidation: KloScanEnrichmentSummary['llmRelationshipValidation']; - warnings: KloScanWarning[]; +export interface DiscoverKtxRelationshipsResult { + relationshipUpdate: KtxRelationshipUpdate; + relationships: KtxScanRelationshipSummary; + profile: KtxRelationshipProfileArtifact; + resolvedRelationships: KtxResolvedRelationshipDiscoveryCandidate[]; + compositeRelationships: KtxCompositeRelationshipCandidate[]; + statisticalValidation: KtxScanEnrichmentSummary['statisticalValidation']; + llmRelationshipValidation: KtxScanEnrichmentSummary['llmRelationshipValidation']; + warnings: KtxScanWarning[]; } -function relationshipFromResolved(candidate: KloResolvedRelationshipDiscoveryCandidate): KloEnrichedRelationship { +function relationshipFromResolved(candidate: KtxResolvedRelationshipDiscoveryCandidate): KtxEnrichedRelationship { return { id: candidate.id, source: 'inferred', @@ -69,7 +69,7 @@ function relationshipFromResolved(candidate: KloResolvedRelationshipDiscoveryCan }; } -function relationshipFromComposite(candidate: KloCompositeRelationshipCandidate): KloEnrichedRelationship { +function relationshipFromComposite(candidate: KtxCompositeRelationshipCandidate): KtxEnrichedRelationship { return { id: candidate.id, source: 'inferred', @@ -91,22 +91,22 @@ function relationshipFromComposite(candidate: KloCompositeRelationshipCandidate) }; } -function relationshipId(input: Pick): string { +function relationshipId(input: Pick): string { return `${input.from.tableId}:(${input.from.columnIds.join(',')})->${input.to.tableId}:(${input.to.columnIds.join(',')})`; } function nonFormalAcceptedRelationships(input: { formalIds: ReadonlySet; - resolvedRelationships: readonly KloResolvedRelationshipDiscoveryCandidate[]; -}): KloEnrichedRelationship[] { + resolvedRelationships: readonly KtxResolvedRelationshipDiscoveryCandidate[]; +}): KtxEnrichedRelationship[] { return input.resolvedRelationships .filter((candidate) => candidate.status === 'accepted' && !input.formalIds.has(candidate.id)) .map(relationshipFromResolved); } function relationshipSummary( - resolvedRelationships: readonly KloResolvedRelationshipDiscoveryCandidate[], -): KloScanRelationshipSummary { + resolvedRelationships: readonly KtxResolvedRelationshipDiscoveryCandidate[], +): KtxScanRelationshipSummary { return { accepted: resolvedRelationships.filter((candidate) => candidate.status === 'accepted').length, review: resolvedRelationships.filter((candidate) => candidate.status === 'review').length, @@ -115,7 +115,7 @@ function relationshipSummary( }; } -function compositeSummary(relationships: readonly KloCompositeRelationshipCandidate[]): KloScanRelationshipSummary { +function compositeSummary(relationships: readonly KtxCompositeRelationshipCandidate[]): KtxScanRelationshipSummary { return { accepted: relationships.filter((candidate) => candidate.status === 'accepted').length, review: relationships.filter((candidate) => candidate.status === 'review').length, @@ -126,18 +126,18 @@ function compositeSummary(relationships: readonly KloCompositeRelationshipCandid async function detectCompositeRelationships(input: { connectionId: string; - driver: DiscoverKloRelationshipsInput['driver']; - schema: KloEnrichedSchema; - profile: KloRelationshipProfileArtifact; - executor: KloRelationshipReadOnlyExecutor | null; - context: DiscoverKloRelationshipsInput['context']; - warnings: KloScanWarning[]; -}): Promise { + driver: DiscoverKtxRelationshipsInput['driver']; + schema: KtxEnrichedSchema; + profile: KtxRelationshipProfileArtifact; + executor: KtxRelationshipReadOnlyExecutor | null; + context: DiscoverKtxRelationshipsInput['context']; + warnings: KtxScanWarning[]; +}): Promise { if (!input.executor || !input.profile.sqlAvailable) { return []; } try { - const compositeDetection = await discoverKloCompositeRelationships({ + const compositeDetection = await discoverKtxCompositeRelationships({ connectionId: input.connectionId, driver: input.driver, schema: input.schema, @@ -157,7 +157,7 @@ async function detectCompositeRelationships(input: { } catch (error) { input.warnings.push({ code: 'relationship_validation_failed', - message: `KLO composite relationship detection failed: ${error instanceof Error ? error.message : String(error)}`, + message: `KTX composite relationship detection failed: ${error instanceof Error ? error.message : String(error)}`, recoverable: true, metadata: { source: 'composite_relationship_detection' }, }); @@ -168,8 +168,8 @@ async function detectCompositeRelationships(input: { function combinedRelationshipSummary(input: { formalAccepted: number; formalSkipped: number; - resolvedRelationships: readonly KloResolvedRelationshipDiscoveryCandidate[]; -}): KloScanRelationshipSummary { + resolvedRelationships: readonly KtxResolvedRelationshipDiscoveryCandidate[]; +}): KtxScanRelationshipSummary { const graph = relationshipSummary(input.resolvedRelationships); return { accepted: input.formalAccepted + graph.accepted, @@ -179,9 +179,9 @@ function combinedRelationshipSummary(input: { }; } -function sqlExecutor(input: DiscoverKloRelationshipsInput): { - executor: KloRelationshipReadOnlyExecutor | null; - warnings: KloScanWarning[]; +function sqlExecutor(input: DiscoverKtxRelationshipsInput): { + executor: KtxRelationshipReadOnlyExecutor | null; + warnings: KtxScanWarning[]; } { if (!input.connector.capabilities.readOnlySql) { return { @@ -189,7 +189,7 @@ function sqlExecutor(input: DiscoverKloRelationshipsInput): { warnings: [ { code: 'connector_capability_missing', - message: 'KLO scan connector cannot run read-only SQL relationship validation', + message: 'KTX scan connector cannot run read-only SQL relationship validation', recoverable: true, metadata: { capability: 'readOnlySql' }, }, @@ -203,7 +203,7 @@ function sqlExecutor(input: DiscoverKloRelationshipsInput): { warnings: [ { code: 'relationship_validation_failed', - message: 'KLO scan connector advertises readOnlySql but does not expose executeReadOnly', + message: 'KTX scan connector advertises readOnlySql but does not expose executeReadOnly', recoverable: true, metadata: { capability: 'readOnlySql' }, }, @@ -219,13 +219,13 @@ function sqlExecutor(input: DiscoverKloRelationshipsInput): { }; } -export async function discoverKloRelationships( - input: DiscoverKloRelationshipsInput, -): Promise { +export async function discoverKtxRelationships( + input: DiscoverKtxRelationshipsInput, +): Promise { const { executor, warnings } = sqlExecutor(input); - const formalMetadata = collectKloFormalMetadataRelationships(input.schema); - const profileCache = createKloRelationshipProfileCache(); - const profile = await profileKloRelationshipSchema({ + const formalMetadata = collectKtxFormalMetadataRelationships(input.schema); + const profileCache = createKtxRelationshipProfileCache(); + const profile = await profileKtxRelationshipSchema({ connectionId: input.connectionId, driver: input.driver, schema: input.schema, @@ -234,7 +234,7 @@ export async function discoverKloRelationships( profileSampleRows: input.settings.profileSampleRows, cache: profileCache, }); - const deterministicCandidates: KloRelationshipDiscoveryCandidate[] = generateKloRelationshipDiscoveryCandidates( + const deterministicCandidates: KtxRelationshipDiscoveryCandidate[] = generateKtxRelationshipDiscoveryCandidates( input.schema, { maxCandidatesPerColumn: input.settings.maxCandidatesPerColumn, @@ -242,7 +242,7 @@ export async function discoverKloRelationships( }, ); const llmProposalResult = input.settings.llmProposals - ? await proposeKloRelationshipCandidatesWithLlm({ + ? await proposeKtxRelationshipCandidatesWithLlm({ connectionId: input.connectionId, schema: input.schema, profile, @@ -253,12 +253,12 @@ export async function discoverKloRelationships( generateText: input.generateText, }) : { candidates: [], warnings: [], llmCalls: 0, summary: 'skipped' as const }; - const candidates = mergeKloRelationshipDiscoveryCandidates([ + const candidates = mergeKtxRelationshipDiscoveryCandidates([ ...deterministicCandidates, ...llmProposalResult.candidates, ]).filter((candidate) => !formalMetadata.acceptedIds.has(candidate.id)); warnings.push(...llmProposalResult.warnings); - const validated = await validateKloRelationshipDiscoveryCandidates({ + const validated = await validateKtxRelationshipDiscoveryCandidates({ connectionId: input.connectionId, driver: input.driver, candidates, @@ -274,7 +274,7 @@ export async function discoverKloRelationships( validationBudget: input.settings.validationBudget, }, }); - const graph = resolveKloRelationshipGraph({ + const graph = resolveKtxRelationshipGraph({ schema: input.schema, profiles: profile, candidates: validated, diff --git a/packages/context/src/scan/relationship-feedback-calibration.test.ts b/packages/context/src/scan/relationship-feedback-calibration.test.ts index 54de3cbe..43090ad4 100644 --- a/packages/context/src/scan/relationship-feedback-calibration.test.ts +++ b/packages/context/src/scan/relationship-feedback-calibration.test.ts @@ -1,19 +1,19 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { describe, expect, it, vi } from 'vitest'; import { - buildKloRelationshipFeedbackCalibrationReport, + buildKtxRelationshipFeedbackCalibrationReport, calibrateLocalRelationshipFeedbackLabels, - formatKloRelationshipFeedbackCalibrationMarkdown, + formatKtxRelationshipFeedbackCalibrationMarkdown, } from './relationship-feedback-calibration.js'; import type { ExportLocalRelationshipFeedbackLabelsResult, - KloRelationshipFeedbackLabel, + KtxRelationshipFeedbackLabel, } from './relationship-feedback-export.js'; function label( - input: Partial & - Pick, -): KloRelationshipFeedbackLabel { + input: Partial & + Pick, +): KtxRelationshipFeedbackLabel { return { schemaVersion: 1, previousStatus: 'review', @@ -38,7 +38,7 @@ function label( }; } -function feedback(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult { +function feedback(labels: KtxRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult { return { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: null, decision: 'all' }, @@ -56,7 +56,7 @@ function feedback(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelationsh describe('relationship feedback calibration', () => { it('builds score buckets and threshold-band summary from feedback labels', () => { - const report = buildKloRelationshipFeedbackCalibrationReport( + const report = buildKtxRelationshipFeedbackCalibrationReport( feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', @@ -124,7 +124,7 @@ describe('relationship feedback calibration', () => { }); it('keeps unscored labels visible without treating them as threshold predictions', () => { - const report = buildKloRelationshipFeedbackCalibrationReport( + const report = buildKtxRelationshipFeedbackCalibrationReport( feedback([ label({ candidateId: 'orders:orders.note_id->notes:notes.id', @@ -161,7 +161,7 @@ describe('relationship feedback calibration', () => { }); it('formats a stable markdown summary for human CLI output', () => { - const report = buildKloRelationshipFeedbackCalibrationReport( + const report = buildKtxRelationshipFeedbackCalibrationReport( feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'accepted', score: 0.91 }), label({ candidateId: 'orders:orders.note_id->notes:notes.id', decision: 'rejected', score: 0.21 }), @@ -172,18 +172,18 @@ describe('relationship feedback calibration', () => { }, ); - expect(formatKloRelationshipFeedbackCalibrationMarkdown(report)).toContain( - 'KLO relationship feedback calibration', + expect(formatKtxRelationshipFeedbackCalibrationMarkdown(report)).toContain( + 'KTX relationship feedback calibration', ); - expect(formatKloRelationshipFeedbackCalibrationMarkdown(report)).toContain('Total labels: 2'); - expect(formatKloRelationshipFeedbackCalibrationMarkdown(report)).toContain('Accepted-band precision: 1.000'); - expect(formatKloRelationshipFeedbackCalibrationMarkdown(report)).toContain( + expect(formatKtxRelationshipFeedbackCalibrationMarkdown(report)).toContain('Total labels: 2'); + expect(formatKtxRelationshipFeedbackCalibrationMarkdown(report)).toContain('Accepted-band precision: 1.000'); + expect(formatKtxRelationshipFeedbackCalibrationMarkdown(report)).toContain( '0.75-1.00: total=1 accepted=1 rejected=0 acceptanceRate=1.000', ); }); it('wraps the feedback exporter and preserves exporter warnings', async () => { - const project = { projectDir: '/tmp/klo-project' } as KloLocalProject; + const project = { projectDir: '/tmp/ktx-project' } as KtxLocalProject; const exportLocalRelationshipFeedbackLabels = vi.fn(async () => ({ ...feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'accepted', score: 0.91 }), diff --git a/packages/context/src/scan/relationship-feedback-calibration.ts b/packages/context/src/scan/relationship-feedback-calibration.ts index 025d8b57..5d471907 100644 --- a/packages/context/src/scan/relationship-feedback-calibration.ts +++ b/packages/context/src/scan/relationship-feedback-calibration.ts @@ -1,36 +1,36 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { exportLocalRelationshipFeedbackLabels, type ExportLocalRelationshipFeedbackLabelsInput, type ExportLocalRelationshipFeedbackLabelsResult, - type KloRelationshipFeedbackExportWarning, - type KloRelationshipFeedbackLabel, + type KtxRelationshipFeedbackExportWarning, + type KtxRelationshipFeedbackLabel, } from './relationship-feedback-export.js'; -import type { KloResolvedRelationshipStatus } from './relationship-graph-resolver.js'; -import type { KloRelationshipReviewDecisionValue } from './relationship-review-decisions.js'; +import type { KtxResolvedRelationshipStatus } from './relationship-graph-resolver.js'; +import type { KtxRelationshipReviewDecisionValue } from './relationship-review-decisions.js'; const DEFAULT_ACCEPT_THRESHOLD = 0.85; const DEFAULT_REVIEW_THRESHOLD = 0.55; -type CalibrationPredictedStatus = KloResolvedRelationshipStatus | 'unscored'; +type CalibrationPredictedStatus = KtxResolvedRelationshipStatus | 'unscored'; interface Thresholds { accept: number; review: number; } -export interface BuildKloRelationshipFeedbackCalibrationReportInput { +export interface BuildKtxRelationshipFeedbackCalibrationReportInput { acceptThreshold?: number; reviewThreshold?: number; } export interface CalibrateLocalRelationshipFeedbackLabelsInput extends ExportLocalRelationshipFeedbackLabelsInput, - BuildKloRelationshipFeedbackCalibrationReportInput { + BuildKtxRelationshipFeedbackCalibrationReportInput { exportLocalRelationshipFeedbackLabels?: typeof exportLocalRelationshipFeedbackLabels; } -export interface KloRelationshipFeedbackCalibrationBucket { +export interface KtxRelationshipFeedbackCalibrationBucket { label: string; minInclusive: number; maxInclusive: number; @@ -40,10 +40,10 @@ export interface KloRelationshipFeedbackCalibrationBucket { acceptanceRate: number | null; } -export interface KloRelationshipFeedbackCalibrationLabel { +export interface KtxRelationshipFeedbackCalibrationLabel { candidateId: string; - decision: KloRelationshipReviewDecisionValue; - previousStatus: KloRelationshipFeedbackLabel['previousStatus']; + decision: KtxRelationshipReviewDecisionValue; + previousStatus: KtxRelationshipFeedbackLabel['previousStatus']; predictedStatus: CalibrationPredictedStatus; bucket: string; score: number | null; @@ -59,7 +59,7 @@ export interface KloRelationshipFeedbackCalibrationLabel { reasons: string[]; } -export interface KloRelationshipFeedbackCalibrationReport { +export interface KtxRelationshipFeedbackCalibrationReport { generatedAt: string; filters: ExportLocalRelationshipFeedbackLabelsResult['filters']; thresholds: Thresholds; @@ -78,9 +78,9 @@ export interface KloRelationshipFeedbackCalibrationReport { meanAcceptedScore: number | null; meanRejectedScore: number | null; }; - buckets: KloRelationshipFeedbackCalibrationBucket[]; - labels: KloRelationshipFeedbackCalibrationLabel[]; - warnings: KloRelationshipFeedbackExportWarning[]; + buckets: KtxRelationshipFeedbackCalibrationBucket[]; + labels: KtxRelationshipFeedbackCalibrationLabel[]; + warnings: KtxRelationshipFeedbackExportWarning[]; } const BUCKETS = [ @@ -90,7 +90,7 @@ const BUCKETS = [ { label: '0.75-1.00', minInclusive: 0.75, maxInclusive: 1 }, ] as const; -function thresholds(input: BuildKloRelationshipFeedbackCalibrationReportInput): Thresholds { +function thresholds(input: BuildKtxRelationshipFeedbackCalibrationReportInput): Thresholds { return { accept: input.acceptThreshold ?? DEFAULT_ACCEPT_THRESHOLD, review: input.reviewThreshold ?? DEFAULT_REVIEW_THRESHOLD, @@ -133,9 +133,9 @@ function predictedStatus(score: number | null, currentThresholds: Thresholds): C } function calibrationLabel( - label: KloRelationshipFeedbackLabel, + label: KtxRelationshipFeedbackLabel, currentThresholds: Thresholds, -): KloRelationshipFeedbackCalibrationLabel { +): KtxRelationshipFeedbackCalibrationLabel { return { candidateId: label.candidateId, decision: label.decision, @@ -157,8 +157,8 @@ function calibrationLabel( } function summarize( - labels: readonly KloRelationshipFeedbackCalibrationLabel[], -): KloRelationshipFeedbackCalibrationReport['summary'] { + labels: readonly KtxRelationshipFeedbackCalibrationLabel[], +): KtxRelationshipFeedbackCalibrationReport['summary'] { const scored = labels.filter((label) => label.score !== null); const predictedAccepted = scored.filter((label) => label.predictedStatus === 'accepted'); const predictedReview = scored.filter((label) => label.predictedStatus === 'review'); @@ -193,8 +193,8 @@ function summarize( } function buildBuckets( - labels: readonly KloRelationshipFeedbackCalibrationLabel[], -): KloRelationshipFeedbackCalibrationBucket[] { + labels: readonly KtxRelationshipFeedbackCalibrationLabel[], +): KtxRelationshipFeedbackCalibrationBucket[] { return BUCKETS.map((bucket) => { const bucketLabels = labels.filter((label) => label.bucket === bucket.label); const accepted = bucketLabels.filter((label) => label.decision === 'accepted').length; @@ -218,10 +218,10 @@ function buildBuckets( }); } -export function buildKloRelationshipFeedbackCalibrationReport( +export function buildKtxRelationshipFeedbackCalibrationReport( feedback: ExportLocalRelationshipFeedbackLabelsResult, - input: BuildKloRelationshipFeedbackCalibrationReportInput = {}, -): KloRelationshipFeedbackCalibrationReport { + input: BuildKtxRelationshipFeedbackCalibrationReportInput = {}, +): KtxRelationshipFeedbackCalibrationReport { const currentThresholds = thresholds(input); const labels = feedback.labels .map((label) => calibrationLabel(label, currentThresholds)) @@ -244,26 +244,26 @@ export function buildKloRelationshipFeedbackCalibrationReport( } export async function calibrateLocalRelationshipFeedbackLabels( - project: KloLocalProject, + project: KtxLocalProject, input: CalibrateLocalRelationshipFeedbackLabelsInput = {}, -): Promise { +): Promise { const exporter = input.exportLocalRelationshipFeedbackLabels ?? exportLocalRelationshipFeedbackLabels; const feedback = await exporter(project, { connectionId: input.connectionId, decision: input.decision, }); - return buildKloRelationshipFeedbackCalibrationReport(feedback, input); + return buildKtxRelationshipFeedbackCalibrationReport(feedback, input); } function formatMetric(value: number | null): string { return value === null ? 'n/a' : value.toFixed(3); } -export function formatKloRelationshipFeedbackCalibrationMarkdown( - report: KloRelationshipFeedbackCalibrationReport, +export function formatKtxRelationshipFeedbackCalibrationMarkdown( + report: KtxRelationshipFeedbackCalibrationReport, ): string { const lines = [ - 'KLO relationship feedback calibration', + 'KTX relationship feedback calibration', `Generated: ${report.generatedAt}`, `Filter connection: ${report.filters.connectionId ?? 'all'}`, `Filter decision: ${report.filters.decision}`, diff --git a/packages/context/src/scan/relationship-feedback-export.test.ts b/packages/context/src/scan/relationship-feedback-export.test.ts index 93a130c8..512c29ad 100644 --- a/packages/context/src/scan/relationship-feedback-export.test.ts +++ b/packages/context/src/scan/relationship-feedback-export.test.ts @@ -1,12 +1,12 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { describe, expect, it, vi } from 'vitest'; import { exportLocalRelationshipFeedbackLabels, - formatKloRelationshipFeedbackLabelsJsonl, + formatKtxRelationshipFeedbackLabelsJsonl, } from './relationship-feedback-export.js'; -import type { KloRelationshipReviewDecisionArtifact } from './relationship-review-decisions.js'; +import type { KtxRelationshipReviewDecisionArtifact } from './relationship-review-decisions.js'; -function projectWithFiles(files: Record): KloLocalProject { +function projectWithFiles(files: Record): KtxLocalProject { const contentByPath = new Map( Object.entries(files).map(([path, value]) => [ path, @@ -14,7 +14,7 @@ function projectWithFiles(files: Record): KloLocalProject { ]), ); return { - projectDir: '/tmp/klo-project', + projectDir: '/tmp/ktx-project', fileStore: { async listFiles(path: string) { return { @@ -33,15 +33,15 @@ function projectWithFiles(files: Record): KloLocalProject { getFileHistory: vi.fn(), forWorktree: vi.fn(), }, - } as unknown as KloLocalProject; + } as unknown as KtxLocalProject; } function decisionsArtifact(input: { connectionId: string; runId: string; syncId: string; - decisions: KloRelationshipReviewDecisionArtifact['decisions']; -}): KloRelationshipReviewDecisionArtifact { + decisions: KtxRelationshipReviewDecisionArtifact['decisions']; +}): KtxRelationshipReviewDecisionArtifact { return { connectionId: input.connectionId, runId: input.runId, @@ -121,7 +121,7 @@ const acceptedInvoiceAccount = { runId: 'scan-run-b', syncId: 'sync-b', decidedAt: '2026-05-07T12:10:00.000Z', - reviewer: 'klo', + reviewer: 'ktx', note: null, from: { tableId: 'invoices', @@ -232,7 +232,7 @@ describe('relationship feedback export', () => { now: () => new Date('2026-05-07T13:00:00.000Z'), }); - const lines = formatKloRelationshipFeedbackLabelsJsonl(result).trim().split('\n').map((line) => JSON.parse(line)); + const lines = formatKtxRelationshipFeedbackLabelsJsonl(result).trim().split('\n').map((line) => JSON.parse(line)); expect(lines).toHaveLength(1); expect(lines[0]).toMatchObject({ diff --git a/packages/context/src/scan/relationship-feedback-export.ts b/packages/context/src/scan/relationship-feedback-export.ts index f2b191a5..d4690cef 100644 --- a/packages/context/src/scan/relationship-feedback-export.ts +++ b/packages/context/src/scan/relationship-feedback-export.ts @@ -1,33 +1,33 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import type { - KloRelationshipReviewDecisionArtifact, - KloRelationshipReviewDecisionEntry, - KloRelationshipReviewDecisionValue, + KtxRelationshipReviewDecisionArtifact, + KtxRelationshipReviewDecisionEntry, + KtxRelationshipReviewDecisionValue, } from './relationship-review-decisions.js'; const DECISION_ARTIFACT_SUFFIX = '/enrichment/relationship-review-decisions.json'; const FEEDBACK_SCHEMA_VERSION = 1; -export type KloRelationshipFeedbackDecisionFilter = KloRelationshipReviewDecisionValue | 'all'; +export type KtxRelationshipFeedbackDecisionFilter = KtxRelationshipReviewDecisionValue | 'all'; export interface ExportLocalRelationshipFeedbackLabelsInput { connectionId?: string | null; - decision?: KloRelationshipFeedbackDecisionFilter; + decision?: KtxRelationshipFeedbackDecisionFilter; now?: () => Date; } -export interface KloRelationshipFeedbackLabel { +export interface KtxRelationshipFeedbackLabel { schemaVersion: 1; candidateId: string; - decision: KloRelationshipReviewDecisionValue; - previousStatus: KloRelationshipReviewDecisionEntry['previousStatus']; + decision: KtxRelationshipReviewDecisionValue; + previousStatus: KtxRelationshipReviewDecisionEntry['previousStatus']; connectionId: string; runId: string; syncId: string; decidedAt: string; reviewer: string; note: string | null; - relationshipType: KloRelationshipReviewDecisionEntry['relationshipType']; + relationshipType: KtxRelationshipReviewDecisionEntry['relationshipType']; source: string; score: number | null; confidence: number; @@ -41,7 +41,7 @@ export interface KloRelationshipFeedbackLabel { artifactPath: string; } -export interface KloRelationshipFeedbackExportWarning { +export interface KtxRelationshipFeedbackExportWarning { path: string; message: string; } @@ -50,7 +50,7 @@ export interface ExportLocalRelationshipFeedbackLabelsResult { generatedAt: string; filters: { connectionId: string | null; - decision: KloRelationshipFeedbackDecisionFilter; + decision: KtxRelationshipFeedbackDecisionFilter; }; summary: { total: number; @@ -59,16 +59,16 @@ export interface ExportLocalRelationshipFeedbackLabelsResult { connections: number; runs: number; }; - labels: KloRelationshipFeedbackLabel[]; - warnings: KloRelationshipFeedbackExportWarning[]; + labels: KtxRelationshipFeedbackLabel[]; + warnings: KtxRelationshipFeedbackExportWarning[]; } -function qualifiedTableName(entry: KloRelationshipReviewDecisionEntry, side: 'from' | 'to'): string { +function qualifiedTableName(entry: KtxRelationshipReviewDecisionEntry, side: 'from' | 'to'): string { const table = entry[side].table; return [table.catalog, table.db, table.name].filter((part): part is string => Boolean(part)).join('.'); } -function labelFromDecision(entry: KloRelationshipReviewDecisionEntry, artifactPath: string): KloRelationshipFeedbackLabel { +function labelFromDecision(entry: KtxRelationshipReviewDecisionEntry, artifactPath: string): KtxRelationshipFeedbackLabel { return { schemaVersion: FEEDBACK_SCHEMA_VERSION, candidateId: entry.candidateId, @@ -95,7 +95,7 @@ function labelFromDecision(entry: KloRelationshipReviewDecisionEntry, artifactPa }; } -function sortLabels(labels: KloRelationshipFeedbackLabel[]): KloRelationshipFeedbackLabel[] { +function sortLabels(labels: KtxRelationshipFeedbackLabel[]): KtxRelationshipFeedbackLabel[] { return [...labels].sort((left, right) => { return ( left.connectionId.localeCompare(right.connectionId) || @@ -107,8 +107,8 @@ function sortLabels(labels: KloRelationshipFeedbackLabel[]): KloRelationshipFeed } function passesFilters( - label: KloRelationshipFeedbackLabel, - filters: { connectionId: string | null; decision: KloRelationshipFeedbackDecisionFilter }, + label: KtxRelationshipFeedbackLabel, + filters: { connectionId: string | null; decision: KtxRelationshipFeedbackDecisionFilter }, ): boolean { if (filters.connectionId && label.connectionId !== filters.connectionId) { return false; @@ -121,16 +121,16 @@ function messageFromUnknownError(error: unknown): string { } async function readDecisionLabels( - project: KloLocalProject, + project: KtxLocalProject, artifactPath: string, -): Promise { +): Promise { const raw = await project.fileStore.readFile(artifactPath); - const parsed = JSON.parse(raw.content) as KloRelationshipReviewDecisionArtifact; + const parsed = JSON.parse(raw.content) as KtxRelationshipReviewDecisionArtifact; const decisions = Array.isArray(parsed.decisions) ? parsed.decisions : []; return decisions.map((entry) => labelFromDecision(entry, artifactPath)); } -function summarize(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult['summary'] { +function summarize(labels: KtxRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult['summary'] { return { total: labels.length, accepted: labels.filter((label) => label.decision === 'accepted').length, @@ -141,7 +141,7 @@ function summarize(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelations } export async function exportLocalRelationshipFeedbackLabels( - project: KloLocalProject, + project: KtxLocalProject, input: ExportLocalRelationshipFeedbackLabelsInput = {}, ): Promise { const filters = { @@ -150,8 +150,8 @@ export async function exportLocalRelationshipFeedbackLabels( }; const listed = await project.fileStore.listFiles('raw-sources'); const artifactPaths = listed.files.filter((path) => path.endsWith(DECISION_ARTIFACT_SUFFIX)).sort(); - const labels: KloRelationshipFeedbackLabel[] = []; - const warnings: KloRelationshipFeedbackExportWarning[] = []; + const labels: KtxRelationshipFeedbackLabel[] = []; + const warnings: KtxRelationshipFeedbackExportWarning[] = []; for (const artifactPath of artifactPaths) { try { @@ -171,7 +171,7 @@ export async function exportLocalRelationshipFeedbackLabels( }; } -export function formatKloRelationshipFeedbackLabelsJsonl(result: ExportLocalRelationshipFeedbackLabelsResult): string { +export function formatKtxRelationshipFeedbackLabelsJsonl(result: ExportLocalRelationshipFeedbackLabelsResult): string { if (result.labels.length === 0) { return ''; } diff --git a/packages/context/src/scan/relationship-formal-metadata.test.ts b/packages/context/src/scan/relationship-formal-metadata.test.ts index 231ec122..8e4a57ff 100644 --- a/packages/context/src/scan/relationship-formal-metadata.test.ts +++ b/packages/context/src/scan/relationship-formal-metadata.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import type { KloEnrichedRelationship, KloEnrichedSchema } from './enrichment-types.js'; -import { collectKloFormalMetadataRelationships } from './relationship-formal-metadata.js'; +import type { KtxEnrichedRelationship, KtxEnrichedSchema } from './enrichment-types.js'; +import { collectKtxFormalMetadataRelationships } from './relationship-formal-metadata.js'; -function schema(relationships: KloEnrichedRelationship[]): KloEnrichedSchema { +function schema(relationships: KtxEnrichedRelationship[]): KtxEnrichedSchema { return { connectionId: 'warehouse', tables: [ @@ -59,7 +59,7 @@ function schema(relationships: KloEnrichedRelationship[]): KloEnrichedSchema { }; } -function formalRelationship(overrides: Partial = {}): KloEnrichedRelationship { +function formalRelationship(overrides: Partial = {}): KtxEnrichedRelationship { return { id: 'orders:orders.account_id->accounts:accounts.id', source: 'formal', @@ -84,7 +84,7 @@ function formalRelationship(overrides: Partial = {}): K describe('formal metadata relationship collection', () => { it('accepts valid formal relationships with ground-truth confidence', () => { - const result = collectKloFormalMetadataRelationships(schema([formalRelationship()])); + const result = collectKtxFormalMetadataRelationships(schema([formalRelationship()])); expect(result.accepted).toEqual([ expect.objectContaining({ @@ -99,7 +99,7 @@ describe('formal metadata relationship collection', () => { }); it('skips duplicate and invalid formal relationships with reasons', () => { - const result = collectKloFormalMetadataRelationships( + const result = collectKtxFormalMetadataRelationships( schema([ formalRelationship(), formalRelationship(), diff --git a/packages/context/src/scan/relationship-formal-metadata.ts b/packages/context/src/scan/relationship-formal-metadata.ts index 33866fa1..277ab667 100644 --- a/packages/context/src/scan/relationship-formal-metadata.ts +++ b/packages/context/src/scan/relationship-formal-metadata.ts @@ -1,12 +1,12 @@ -import type { KloEnrichedRelationship, KloEnrichedSchema, KloSkippedRelationship } from './enrichment-types.js'; +import type { KtxEnrichedRelationship, KtxEnrichedSchema, KtxSkippedRelationship } from './enrichment-types.js'; -export interface KloFormalMetadataRelationshipCollection { - accepted: KloEnrichedRelationship[]; - skipped: KloSkippedRelationship[]; +export interface KtxFormalMetadataRelationshipCollection { + accepted: KtxEnrichedRelationship[]; + skipped: KtxSkippedRelationship[]; acceptedIds: Set; } -function relationshipEndpointExists(schema: KloEnrichedSchema, relationship: KloEnrichedRelationship): boolean { +function relationshipEndpointExists(schema: KtxEnrichedSchema, relationship: KtxEnrichedRelationship): boolean { const fromTable = schema.tables.find((table) => table.id === relationship.from.tableId && table.enabled); const toTable = schema.tables.find((table) => table.id === relationship.to.tableId && table.enabled); const fromColumn = fromTable?.columns.some( @@ -18,11 +18,11 @@ function relationshipEndpointExists(schema: KloEnrichedSchema, relationship: Klo return Boolean(fromTable && toTable && fromColumn && toColumn); } -export function collectKloFormalMetadataRelationships( - schema: KloEnrichedSchema, -): KloFormalMetadataRelationshipCollection { - const accepted: KloEnrichedRelationship[] = []; - const skipped: KloSkippedRelationship[] = []; +export function collectKtxFormalMetadataRelationships( + schema: KtxEnrichedSchema, +): KtxFormalMetadataRelationshipCollection { + const accepted: KtxEnrichedRelationship[] = []; + const skipped: KtxSkippedRelationship[] = []; const acceptedIds = new Set(); for (const relationship of schema.relationships) { diff --git a/packages/context/src/scan/relationship-graph-resolver.test.ts b/packages/context/src/scan/relationship-graph-resolver.test.ts index f0759deb..945e8257 100644 --- a/packages/context/src/scan/relationship-graph-resolver.test.ts +++ b/packages/context/src/scan/relationship-graph-resolver.test.ts @@ -1,15 +1,15 @@ import { describe, expect, it } from 'vitest'; import type { - KloEnrichedColumn, - KloEnrichedSchema, - KloEnrichedTable, - KloRelationshipEndpoint, + KtxEnrichedColumn, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxRelationshipEndpoint, } from './enrichment-types.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloValidatedRelationshipDiscoveryCandidate } from './relationship-validation.js'; -import { resolveKloRelationshipGraph } from './relationship-graph-resolver.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxValidatedRelationshipDiscoveryCandidate } from './relationship-validation.js'; +import { resolveKtxRelationshipGraph } from './relationship-graph-resolver.js'; -function column(tableId: string, name: string, overrides: Partial = {}): KloEnrichedColumn { +function column(tableId: string, name: string, overrides: Partial = {}): KtxEnrichedColumn { const tableRef = overrides.tableRef ?? { catalog: null, db: null, name: tableId }; return { id: `${tableId}.${name}`, @@ -30,7 +30,7 @@ function column(tableId: string, name: string, overrides: Partial = {}, -): KloValidatedRelationshipDiscoveryCandidate { + overrides: Partial = {}, +): KtxValidatedRelationshipDiscoveryCandidate { const from = overrides.from ?? endpoint('users', 'account_id'); const to = overrides.to ?? endpoint('accounts', 'id'); return { @@ -170,7 +170,7 @@ function validatedCandidate( describe('relationship graph resolver', () => { it('promotes validated relationship discovery references to accepted relationships and inferred PKs', () => { - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: schema(), profiles: profiles(), candidates: [validatedCandidate()], @@ -206,7 +206,7 @@ describe('relationship graph resolver', () => { }); it('keeps validation-unavailable candidates in review even when name evidence is strong', () => { - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: schema(), profiles: { ...profiles(), sqlAvailable: false, columns: {}, warnings: ['read_only_sql_unavailable'] }, candidates: [ @@ -257,7 +257,7 @@ describe('relationship graph resolver', () => { }, }); - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: schema(), profiles: profiles(), candidates: [loser, winner], @@ -275,7 +275,7 @@ describe('relationship graph resolver', () => { }); it('preserves declared primary keys as accepted even without incoming candidates', () => { - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: schema({ accountsPrimaryKey: true }), profiles: profiles(), candidates: [], @@ -316,7 +316,7 @@ describe('relationship graph resolver', () => { }), ]); const baseProfiles = profiles(); - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: { ...baseSchema, tables: [...baseSchema.tables, invoices] }, profiles: { ...baseProfiles, @@ -458,7 +458,7 @@ describe('relationship graph resolver', () => { ], }, ], - } satisfies KloEnrichedSchema; + } satisfies KtxEnrichedSchema; const profiles = { connectionId: 'warehouse', driver: 'sqlite' as const, @@ -483,7 +483,7 @@ describe('relationship graph resolver', () => { }, }, }; - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema, profiles, candidates: [ @@ -579,7 +579,7 @@ describe('relationship graph resolver', () => { maxTextLength: 3, }; - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: baseSchema, profiles: baseProfiles, candidates: [], @@ -629,7 +629,7 @@ describe('relationship graph resolver', () => { maxTextLength: 3, }; - const result = resolveKloRelationshipGraph({ + const result = resolveKtxRelationshipGraph({ schema: baseSchema, profiles: baseProfiles, candidates: [], diff --git a/packages/context/src/scan/relationship-graph-resolver.ts b/packages/context/src/scan/relationship-graph-resolver.ts index fc9f361c..36ab44b2 100644 --- a/packages/context/src/scan/relationship-graph-resolver.ts +++ b/packages/context/src/scan/relationship-graph-resolver.ts @@ -1,24 +1,24 @@ import type { - KloEnrichedColumn, - KloEnrichedSchema, - KloEnrichedTable, - KloRelationshipEndpoint, + KtxEnrichedColumn, + KtxEnrichedSchema, + KtxEnrichedTable, + KtxRelationshipEndpoint, } from './enrichment-types.js'; -import { normalizeKloRelationshipName } from './relationship-candidates.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import { scoreKloRelationshipCandidate } from './relationship-scoring.js'; -import type { KloValidatedRelationshipDiscoveryCandidate } from './relationship-validation.js'; +import { normalizeKtxRelationshipName } from './relationship-candidates.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import { scoreKtxRelationshipCandidate } from './relationship-scoring.js'; +import type { KtxValidatedRelationshipDiscoveryCandidate } from './relationship-validation.js'; -export type KloResolvedRelationshipStatus = 'accepted' | 'review' | 'rejected'; +export type KtxResolvedRelationshipStatus = 'accepted' | 'review' | 'rejected'; -export interface KloRelationshipGraphResolverSettings { +export interface KtxRelationshipGraphResolverSettings { acceptThreshold: number; reviewThreshold: number; minTargetPkScoreForAcceptance: number; validationRequiredForManifest: boolean; } -export interface KloResolvedRelationshipPkEvidence { +export interface KtxResolvedRelationshipPkEvidence { declaredPrimaryKey: boolean; targetUniqueness: number; incomingAcceptedCount: number; @@ -26,43 +26,43 @@ export interface KloResolvedRelationshipPkEvidence { reasons: string[]; } -export interface KloResolvedRelationshipPk { +export interface KtxResolvedRelationshipPk { table: string; columns: string[]; pkScore: number; - status: KloResolvedRelationshipStatus; + status: KtxResolvedRelationshipStatus; incomingCandidateCount: number; - evidence: KloResolvedRelationshipPkEvidence; + evidence: KtxResolvedRelationshipPkEvidence; } -export interface KloResolvedRelationshipGraphEvidence { +export interface KtxResolvedRelationshipGraphEvidence { targetPkScore: number; incomingCandidateCount: number; conflictRank: number; reasons: string[]; } -export interface KloResolvedRelationshipDiscoveryCandidate - extends Omit { - status: KloResolvedRelationshipStatus; +export interface KtxResolvedRelationshipDiscoveryCandidate + extends Omit { + status: KtxResolvedRelationshipStatus; pkScore: number; fkScore: number; - graph: KloResolvedRelationshipGraphEvidence; + graph: KtxResolvedRelationshipGraphEvidence; } -export interface KloRelationshipGraphResolutionResult { - pks: KloResolvedRelationshipPk[]; - relationships: KloResolvedRelationshipDiscoveryCandidate[]; +export interface KtxRelationshipGraphResolutionResult { + pks: KtxResolvedRelationshipPk[]; + relationships: KtxResolvedRelationshipDiscoveryCandidate[]; } -export interface ResolveKloRelationshipGraphInput { - schema: KloEnrichedSchema; - profiles: KloRelationshipProfileArtifact; - candidates: readonly KloValidatedRelationshipDiscoveryCandidate[]; - settings?: Partial; +export interface ResolveKtxRelationshipGraphInput { + schema: KtxEnrichedSchema; + profiles: KtxRelationshipProfileArtifact; + candidates: readonly KtxValidatedRelationshipDiscoveryCandidate[]; + settings?: Partial; } -const DEFAULT_SETTINGS: KloRelationshipGraphResolverSettings = { +const DEFAULT_SETTINGS: KtxRelationshipGraphResolverSettings = { acceptThreshold: 0.85, reviewThreshold: 0.55, minTargetPkScoreForAcceptance: 0.78, @@ -72,8 +72,8 @@ const DEFAULT_SETTINGS: KloRelationshipGraphResolverSettings = { const PROFILE_ONLY_PK_MEASURE_NAME_TOKENS = new Set(['amount', 'count', 'price', 'quantity', 'subtotal', 'total']); function mergeSettings( - settings: Partial | undefined, -): KloRelationshipGraphResolverSettings { + settings: Partial | undefined, +): KtxRelationshipGraphResolverSettings { return { ...DEFAULT_SETTINGS, ...settings }; } @@ -81,15 +81,15 @@ function roundScore(value: number): number { return Number(Math.max(0, Math.min(1, value)).toFixed(3)); } -function endpointKey(endpoint: KloRelationshipEndpoint): string { +function endpointKey(endpoint: KtxRelationshipEndpoint): string { return `${endpoint.table.name}.${singleRelationshipColumn(endpoint)}`; } -function sourceKey(endpoint: KloRelationshipEndpoint): string { +function sourceKey(endpoint: KtxRelationshipEndpoint): string { return `${endpoint.tableId}:${endpoint.columnIds.join(',')}`; } -function singleRelationshipColumn(endpoint: KloRelationshipEndpoint): string { +function singleRelationshipColumn(endpoint: KtxRelationshipEndpoint): string { const column = endpoint.columns[0]; if (!column) { throw new Error(`Expected relationship endpoint ${endpoint.table.name} to contain one column`); @@ -97,19 +97,19 @@ function singleRelationshipColumn(endpoint: KloRelationshipEndpoint): string { return column; } -function pkKey(pk: Pick): string { +function pkKey(pk: Pick): string { return `${pk.table}.(${pk.columns.join(',')})`; } -function candidateSortKey(candidate: Pick): string { +function candidateSortKey(candidate: Pick): string { return `${candidate.from.table.name}.${singleRelationshipColumn(candidate.from)}->${candidate.to.table.name}.${singleRelationshipColumn(candidate.to)}`; } function statusForScore( score: number, - settings: KloRelationshipGraphResolverSettings, + settings: KtxRelationshipGraphResolverSettings, acceptedAllowed: boolean, -): KloResolvedRelationshipStatus { +): KtxResolvedRelationshipStatus { if (acceptedAllowed && score >= settings.acceptThreshold) { return 'accepted'; } @@ -119,19 +119,19 @@ function statusForScore( return 'rejected'; } -function candidateHasValidationPassed(candidate: KloValidatedRelationshipDiscoveryCandidate): boolean { +function candidateHasValidationPassed(candidate: KtxValidatedRelationshipDiscoveryCandidate): boolean { return candidate.validation.reasons.includes('validation_passed'); } -function candidateIsValidationUnavailable(candidate: KloValidatedRelationshipDiscoveryCandidate): boolean { +function candidateIsValidationUnavailable(candidate: KtxValidatedRelationshipDiscoveryCandidate): boolean { return ( candidate.validation.reasons.includes('validation_unavailable') || candidate.validation.reasons.includes('profile_unavailable') ); } -function declaredPrimaryKeys(schema: KloEnrichedSchema): KloResolvedRelationshipPk[] { - const pks: KloResolvedRelationshipPk[] = []; +function declaredPrimaryKeys(schema: KtxEnrichedSchema): KtxResolvedRelationshipPk[] { + const pks: KtxResolvedRelationshipPk[] = []; for (const table of schema.tables.filter((candidate) => candidate.enabled)) { for (const column of table.columns.filter((candidate) => candidate.primaryKey)) { pks.push({ @@ -153,27 +153,27 @@ function declaredPrimaryKeys(schema: KloEnrichedSchema): KloResolvedRelationship return pks; } -function schemaTargetColumns(schema: KloEnrichedSchema): Array<{ table: KloEnrichedTable; column: KloEnrichedColumn }> { +function schemaTargetColumns(schema: KtxEnrichedSchema): Array<{ table: KtxEnrichedTable; column: KtxEnrichedColumn }> { return schema.tables .filter((table) => table.enabled) .flatMap((table) => table.columns.map((column) => ({ table, column }))); } -function profileUniqueness(profiles: KloRelationshipProfileArtifact, tableName: string, columnName: string): number { +function profileUniqueness(profiles: KtxRelationshipProfileArtifact, tableName: string, columnName: string): number { return profiles.columns[`${tableName}.${columnName}`]?.uniquenessRatio ?? 0; } -function profileNullRate(profiles: KloRelationshipProfileArtifact, tableName: string, columnName: string): number { +function profileNullRate(profiles: KtxRelationshipProfileArtifact, tableName: string, columnName: string): number { return profiles.columns[`${tableName}.${columnName}`]?.nullRate ?? 1; } -function profileColumnExists(profiles: KloRelationshipProfileArtifact, tableName: string, columnName: string): boolean { +function profileColumnExists(profiles: KtxRelationshipProfileArtifact, tableName: string, columnName: string): boolean { return Boolean(profiles.columns[`${tableName}.${columnName}`]); } function profileOnlyPkNameScore(tableName: string, columnName: string): number { - const table = normalizeKloRelationshipName(tableName).singular; - const column = normalizeKloRelationshipName(columnName).normalized; + const table = normalizeKtxRelationshipName(tableName).singular; + const column = normalizeKtxRelationshipName(columnName).normalized; if (column === 'id') { return 1; } @@ -190,12 +190,12 @@ function profileOnlyPkNameScore(tableName: string, columnName: string): number { } function profileOnlyPkTypeCompatibility(columnName: string): number { - const tokens = normalizeKloRelationshipName(columnName).normalized.split('_').filter(Boolean); + const tokens = normalizeKtxRelationshipName(columnName).normalized.split('_').filter(Boolean); return tokens.some((token) => PROFILE_ONLY_PK_MEASURE_NAME_TOKENS.has(token)) ? 0 : 1; } function profileOnlyPkEvidence(input: { - profiles: KloRelationshipProfileArtifact; + profiles: KtxRelationshipProfileArtifact; tableName: string; columnName: string; }): { nameScore: number; nullRate: number; uniqueness: number; pkScore: number; weakName: boolean } | null { @@ -209,7 +209,7 @@ function profileOnlyPkEvidence(input: { return null; } const typeCompatibility = profileOnlyPkTypeCompatibility(input.columnName); - const scoreBreakdown = scoreKloRelationshipCandidate( + const scoreBreakdown = scoreKtxRelationshipCandidate( { nameSimilarity: nameScore, typeCompatibility, @@ -240,12 +240,12 @@ function profileOnlyPkEvidence(input: { function resolveTargetPk(input: { table: string; column: string; - declared: KloResolvedRelationshipPk | undefined; - profiles: KloRelationshipProfileArtifact; - incoming: readonly KloValidatedRelationshipDiscoveryCandidate[]; - settings: KloRelationshipGraphResolverSettings; + declared: KtxResolvedRelationshipPk | undefined; + profiles: KtxRelationshipProfileArtifact; + incoming: readonly KtxValidatedRelationshipDiscoveryCandidate[]; + settings: KtxRelationshipGraphResolverSettings; profileOnly?: { nameScore: number; nullRate: number; uniqueness: number; pkScore: number; weakName: boolean } | null; -}): KloResolvedRelationshipPk { +}): KtxResolvedRelationshipPk { if (input.declared) { return input.declared; } @@ -322,10 +322,10 @@ function resolveTargetPk(input: { } function baseRelationshipResolution(input: { - candidate: KloValidatedRelationshipDiscoveryCandidate; - pk: KloResolvedRelationshipPk; - settings: KloRelationshipGraphResolverSettings; -}): KloResolvedRelationshipDiscoveryCandidate { + candidate: KtxValidatedRelationshipDiscoveryCandidate; + pk: KtxResolvedRelationshipPk; + settings: KtxRelationshipGraphResolverSettings; +}): KtxResolvedRelationshipDiscoveryCandidate { const reasons: string[] = []; if (input.candidate.status === 'rejected') { reasons.push('candidate_validation_rejected'); @@ -349,7 +349,7 @@ function baseRelationshipResolution(input: { 0.14 * input.candidate.confidence + 0.08 * validationPassBonus, ); - let status: KloResolvedRelationshipStatus; + let status: KtxResolvedRelationshipStatus; if (input.candidate.status === 'rejected') { status = 'rejected'; @@ -387,8 +387,8 @@ function baseRelationshipResolution(input: { } function relationshipRank( - left: KloResolvedRelationshipDiscoveryCandidate, - right: KloResolvedRelationshipDiscoveryCandidate, + left: KtxResolvedRelationshipDiscoveryCandidate, + right: KtxResolvedRelationshipDiscoveryCandidate, ): number { return ( right.fkScore - left.fkScore || @@ -399,15 +399,15 @@ function relationshipRank( } function applySourceConflicts( - relationships: readonly KloResolvedRelationshipDiscoveryCandidate[], -): KloResolvedRelationshipDiscoveryCandidate[] { - const bySource = new Map(); + relationships: readonly KtxResolvedRelationshipDiscoveryCandidate[], +): KtxResolvedRelationshipDiscoveryCandidate[] { + const bySource = new Map(); for (const relationship of relationships) { const key = sourceKey(relationship.from); bySource.set(key, [...(bySource.get(key) ?? []), relationship]); } - const resolved: KloResolvedRelationshipDiscoveryCandidate[] = []; + const resolved: KtxResolvedRelationshipDiscoveryCandidate[] = []; for (const group of bySource.values()) { const ranked = [...group].sort(relationshipRank); let acceptedSeen = false; @@ -441,20 +441,20 @@ function applySourceConflicts( return resolved.sort(relationshipRank); } -export function resolveKloRelationshipGraph( - input: ResolveKloRelationshipGraphInput, -): KloRelationshipGraphResolutionResult { +export function resolveKtxRelationshipGraph( + input: ResolveKtxRelationshipGraphInput, +): KtxRelationshipGraphResolutionResult { const settings = mergeSettings(input.settings); const declared = declaredPrimaryKeys(input.schema); const declaredByKey = new Map(declared.map((pk) => [pkKey(pk), pk])); - const incomingByTarget = new Map(); + const incomingByTarget = new Map(); for (const candidate of input.candidates) { const key = endpointKey(candidate.to); incomingByTarget.set(key, [...(incomingByTarget.get(key) ?? []), candidate]); } - const pkCandidates = new Map(); + const pkCandidates = new Map(); for (const item of schemaTargetColumns(input.schema)) { const key = `${item.table.ref.name}.(${item.column.name})`; const incoming = incomingByTarget.get(`${item.table.ref.name}.${item.column.name}`) ?? []; diff --git a/packages/context/src/scan/relationship-llm-proposal.test.ts b/packages/context/src/scan/relationship-llm-proposal.test.ts index 0a3dbb57..89097d3c 100644 --- a/packages/context/src/scan/relationship-llm-proposal.test.ts +++ b/packages/context/src/scan/relationship-llm-proposal.test.ts @@ -1,14 +1,14 @@ -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import { describe, expect, it, vi } from 'vitest'; -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import { proposeKloRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import { proposeKtxRelationshipCandidatesWithLlm } from './relationship-llm-proposal.js'; -function llmProvider(provider = 'anthropic'): KloLlmProvider { +function llmProvider(provider = 'anthropic'): KtxLlmProvider { const model = { modelId: 'claude-sonnet-4-6', provider }; return { - getModel: vi.fn(() => model as ReturnType), - getModelByName: vi.fn(() => model as ReturnType), + getModel: vi.fn(() => model as ReturnType), + getModelByName: vi.fn(() => model as ReturnType), cacheMarker: vi.fn(), repairToolCallHandler: vi.fn(), thinkingProviderOptions: vi.fn(() => ({})), @@ -24,13 +24,13 @@ function llmProvider(provider = 'anthropic'): KloLlmProvider { cacheTools: true, cacheHistory: true, vertexFallbackTo5m: false, - }) as ReturnType, + }) as ReturnType, ), - activeBackend: vi.fn(() => provider as ReturnType), + activeBackend: vi.fn(() => provider as ReturnType), }; } -function column(tableId: string, name: string, overrides: Partial = {}): KloEnrichedColumn { +function column(tableId: string, name: string, overrides: Partial = {}): KtxEnrichedColumn { const tableRef = overrides.tableRef ?? { catalog: null, db: null, name: tableId }; return { id: `${tableId}.${name}`, @@ -51,7 +51,7 @@ function column(tableId: string, name: string, overrides: Partial { }, })); - const result = await proposeKloRelationshipCandidatesWithLlm({ + const result = await proposeKtxRelationshipCandidatesWithLlm({ connectionId: 'warehouse', schema: schema(), profile: profile(), @@ -179,7 +179,7 @@ describe('relationship LLM proposals', () => { it('skips deterministic providers without calling generateText', async () => { const generateText = vi.fn(); - const result = await proposeKloRelationshipCandidatesWithLlm({ + const result = await proposeKtxRelationshipCandidatesWithLlm({ connectionId: 'warehouse', schema: schema(), profile: profile(), @@ -193,7 +193,7 @@ describe('relationship LLM proposals', () => { }); it('returns recoverable warnings for invalid references and generation failures', async () => { - const invalidReference = await proposeKloRelationshipCandidatesWithLlm({ + const invalidReference = await proposeKtxRelationshipCandidatesWithLlm({ connectionId: 'warehouse', schema: schema(), profile: profile(), @@ -221,7 +221,7 @@ describe('relationship LLM proposals', () => { recoverable: true, }); - const failed = await proposeKloRelationshipCandidatesWithLlm({ + const failed = await proposeKtxRelationshipCandidatesWithLlm({ connectionId: 'warehouse', schema: schema(), profile: profile(), @@ -233,7 +233,7 @@ describe('relationship LLM proposals', () => { expect(failed).toMatchObject({ candidates: [], llmCalls: 1, summary: 'failed' }); expect(failed.warnings[0]).toMatchObject({ code: 'relationship_llm_proposal_failed', - message: 'KLO relationship LLM proposal failed: model unavailable', + message: 'KTX relationship LLM proposal failed: model unavailable', recoverable: true, }); }); diff --git a/packages/context/src/scan/relationship-llm-proposal.ts b/packages/context/src/scan/relationship-llm-proposal.ts index 2f4b37c1..ca5d0acc 100644 --- a/packages/context/src/scan/relationship-llm-proposal.ts +++ b/packages/context/src/scan/relationship-llm-proposal.ts @@ -1,14 +1,14 @@ -import type { KloLlmProvider } from '@klo/llm'; +import type { KtxLlmProvider } from '@ktx/llm'; import type { generateText } from 'ai'; import { z } from 'zod'; -import { generateKloObject } from '../llm/index.js'; -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; +import { generateKtxObject } from '../llm/index.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; import { - normalizeKloRelationshipName, - type KloRelationshipDiscoveryCandidate, + normalizeKtxRelationshipName, + type KtxRelationshipDiscoveryCandidate, } from './relationship-candidates.js'; -import type { KloRelationshipColumnProfile, KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloScanEnrichmentSummary, KloScanWarning, KloTableRef } from './types.js'; +import type { KtxRelationshipColumnProfile, KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxScanEnrichmentSummary, KtxScanWarning, KtxTableRef } from './types.js'; const relationshipLlmProposalSchema = z.object({ pkCandidates: z.array( @@ -31,36 +31,36 @@ const relationshipLlmProposalSchema = z.object({ ), }); -type KloRelationshipLlmProposalOutput = z.infer; +type KtxRelationshipLlmProposalOutput = z.infer; type GenerateTextInput = Parameters[0]; -export type KloRelationshipLlmProposalGenerateText = ( +export type KtxRelationshipLlmProposalGenerateText = ( input: GenerateTextInput, ) => Promise<{ text?: string; output?: unknown }>; -export interface KloRelationshipLlmProposalSettings { +export interface KtxRelationshipLlmProposalSettings { maxTablesPerBatch: number; maxColumnsPerTable: number; maxSampleValuesPerColumn: number; minConfidence: number; } -export interface ProposeKloRelationshipCandidatesWithLlmInput { +export interface ProposeKtxRelationshipCandidatesWithLlmInput { connectionId: string; - schema: KloEnrichedSchema; - profile: KloRelationshipProfileArtifact; - llmProvider: KloLlmProvider | null; - settings?: Partial; - generateText?: KloRelationshipLlmProposalGenerateText; + schema: KtxEnrichedSchema; + profile: KtxRelationshipProfileArtifact; + llmProvider: KtxLlmProvider | null; + settings?: Partial; + generateText?: KtxRelationshipLlmProposalGenerateText; } -export interface KloRelationshipLlmProposalResult { - candidates: KloRelationshipDiscoveryCandidate[]; - warnings: KloScanWarning[]; +export interface KtxRelationshipLlmProposalResult { + candidates: KtxRelationshipDiscoveryCandidate[]; + warnings: KtxScanWarning[]; llmCalls: number; - summary: KloScanEnrichmentSummary['llmRelationshipValidation']; + summary: KtxScanEnrichmentSummary['llmRelationshipValidation']; } -const DEFAULT_SETTINGS: KloRelationshipLlmProposalSettings = { +const DEFAULT_SETTINGS: KtxRelationshipLlmProposalSettings = { maxTablesPerBatch: 40, maxColumnsPerTable: 80, maxSampleValuesPerColumn: 5, @@ -68,8 +68,8 @@ const DEFAULT_SETTINGS: KloRelationshipLlmProposalSettings = { }; function mergeSettings( - settings: Partial | undefined, -): KloRelationshipLlmProposalSettings { + settings: Partial | undefined, +): KtxRelationshipLlmProposalSettings { return { ...DEFAULT_SETTINGS, ...settings }; } @@ -77,41 +77,41 @@ function clampConfidence(value: number): number { return Number(Math.max(0, Math.min(1, value)).toFixed(3)); } -function modelIsDeterministic(llmProvider: KloLlmProvider): boolean { +function modelIsDeterministic(llmProvider: KtxLlmProvider): boolean { const model = llmProvider.getModel('candidateExtraction'); return (model as { provider?: string }).provider === 'deterministic'; } -function findTable(schema: KloEnrichedSchema, name: string): KloEnrichedTable | null { +function findTable(schema: KtxEnrichedSchema, name: string): KtxEnrichedTable | null { const normalized = name.toLowerCase(); return schema.tables.find((table) => table.ref.name.toLowerCase() === normalized) ?? null; } -function findColumn(table: KloEnrichedTable, name: string): KloEnrichedColumn | null { +function findColumn(table: KtxEnrichedTable, name: string): KtxEnrichedColumn | null { const normalized = name.toLowerCase(); return table.columns.find((column) => column.name.toLowerCase() === normalized) ?? null; } -function profileKey(table: KloTableRef, column: KloEnrichedColumn): string { +function profileKey(table: KtxTableRef, column: KtxEnrichedColumn): string { return `${table.name}.${column.name}`; } function profileForColumn( - profile: KloRelationshipProfileArtifact, - table: KloEnrichedTable, - column: KloEnrichedColumn, -): KloRelationshipColumnProfile | null { + profile: KtxRelationshipProfileArtifact, + table: KtxEnrichedTable, + column: KtxEnrichedColumn, +): KtxRelationshipColumnProfile | null { return profile.columns[profileKey(table.ref, column)] ?? null; } -function rowCountForTable(profile: KloRelationshipProfileArtifact, table: KloEnrichedTable): number | null { +function rowCountForTable(profile: KtxRelationshipProfileArtifact, table: KtxEnrichedTable): number | null { return profile.tables.find((item) => item.table.name.toLowerCase() === table.ref.name.toLowerCase())?.rowCount ?? null; } function buildEvidencePacket( - schema: KloEnrichedSchema, - profile: KloRelationshipProfileArtifact, - settings: KloRelationshipLlmProposalSettings, + schema: KtxEnrichedSchema, + profile: KtxRelationshipProfileArtifact, + settings: KtxRelationshipLlmProposalSettings, ): Record { return { connectionId: schema.connectionId, @@ -153,7 +153,7 @@ function pkProposalKey(table: string, column: string): string { return `${table.toLowerCase()}.${column.toLowerCase()}`; } -function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn) { +function endpoint(table: KtxEnrichedTable, column: KtxEnrichedColumn) { return { tableId: table.id, columnIds: [column.id], @@ -162,11 +162,11 @@ function endpoint(table: KloEnrichedTable, column: KloEnrichedColumn) { }; } -function relationshipId(fromTable: KloEnrichedTable, fromColumn: KloEnrichedColumn, toTable: KloEnrichedTable, toColumn: KloEnrichedColumn): string { +function relationshipId(fromTable: KtxEnrichedTable, fromColumn: KtxEnrichedColumn, toTable: KtxEnrichedTable, toColumn: KtxEnrichedColumn): string { return `${fromTable.id}:(${fromColumn.id})->${toTable.id}:(${toColumn.id})`; } -function invalidReferenceWarning(message: string, metadata: Record): KloScanWarning { +function invalidReferenceWarning(message: string, metadata: Record): KtxScanWarning { return { code: 'relationship_llm_invalid_reference', message, @@ -176,13 +176,13 @@ function invalidReferenceWarning(message: string, metadata: Record pkProposalKey(item.table, item.column))); - const candidates: KloRelationshipDiscoveryCandidate[] = []; + const candidates: KtxRelationshipDiscoveryCandidate[] = []; for (const item of output.fkCandidates) { if (item.confidence < settings.minConfidence) { @@ -194,7 +194,7 @@ function mapValidProposals( const toColumn = toTable ? findColumn(toTable, item.toColumn) : null; if (!fromTable || !toTable || !fromColumn || !toColumn) { warnings.push( - invalidReferenceWarning('KLO relationship LLM proposal referenced a table or column that is not in the schema.', { + invalidReferenceWarning('KTX relationship LLM proposal referenced a table or column that is not in the schema.', { proposal: item, }), ); @@ -211,9 +211,9 @@ function mapValidProposals( relationshipType: 'many_to_one', confidence: clampConfidence(item.confidence), evidence: { - sourceColumnBase: normalizeKloRelationshipName(fromColumn.name).singular, - targetTableBase: normalizeKloRelationshipName(toTable.ref.name).singular, - targetColumnBase: normalizeKloRelationshipName(toColumn.name).singular, + sourceColumnBase: normalizeKtxRelationshipName(fromColumn.name).singular, + targetTableBase: normalizeKtxRelationshipName(toTable.ref.name).singular, + targetColumnBase: normalizeKtxRelationshipName(toColumn.name).singular, targetKeyScore: pkProposalExists ? 0.88 : 0.68, nameScore: 0.45, reasons: pkProposalExists ? ['llm_proposal', 'llm_pk_proposal'] : ['llm_proposal'], @@ -226,18 +226,18 @@ function mapValidProposals( return { candidates, warnings }; } -function generationFailureWarning(error: unknown): KloScanWarning { +function generationFailureWarning(error: unknown): KtxScanWarning { const message = error instanceof Error ? error.message : String(error); return { code: 'relationship_llm_proposal_failed', - message: `KLO relationship LLM proposal failed: ${message}`, + message: `KTX relationship LLM proposal failed: ${message}`, recoverable: true, }; } -export async function proposeKloRelationshipCandidatesWithLlm( - input: ProposeKloRelationshipCandidatesWithLlmInput, -): Promise { +export async function proposeKtxRelationshipCandidatesWithLlm( + input: ProposeKtxRelationshipCandidatesWithLlmInput, +): Promise { if (!input.llmProvider || modelIsDeterministic(input.llmProvider)) { return { candidates: [], warnings: [], llmCalls: 0, summary: 'skipped' }; } @@ -245,15 +245,15 @@ export async function proposeKloRelationshipCandidatesWithLlm( const settings = mergeSettings(input.settings); const evidence = buildEvidencePacket(input.schema, input.profile, settings); const prompt = [ - 'You are helping KLO review possible SQL relationships before validation.', + 'You are helping KTX review possible SQL relationships before validation.', 'Use only the compact schema evidence. Propose likely primary keys and foreign keys for later SQL validation.', 'Return structured output only; never assume a join is accepted.', JSON.stringify(evidence), ].join('\n\n'); try { - const generated = await generateKloObject< - KloRelationshipLlmProposalOutput, + const generated = await generateKtxObject< + KtxRelationshipLlmProposalOutput, typeof relationshipLlmProposalSchema >({ llmProvider: input.llmProvider, diff --git a/packages/context/src/scan/relationship-locality.test.ts b/packages/context/src/scan/relationship-locality.test.ts index 2b925984..85dd4350 100644 --- a/packages/context/src/scan/relationship-locality.test.ts +++ b/packages/context/src/scan/relationship-locality.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; -import type { KloEnrichedColumn, KloEnrichedTable } from './enrichment-types.js'; +import type { KtxEnrichedColumn, KtxEnrichedTable } from './enrichment-types.js'; import { localCandidateTables } from './relationship-locality.js'; function column( tableId: string, id: string, name: string, - options: Partial = {}, -): KloEnrichedColumn { + options: Partial = {}, +): KtxEnrichedColumn { const tableRef = options.tableRef ?? { catalog: null, db: 'public', name: tableId }; return { id, @@ -27,7 +27,7 @@ function column( }; } -function table(id: string, name: string, columns: KloEnrichedColumn[]): KloEnrichedTable { +function table(id: string, name: string, columns: KtxEnrichedColumn[]): KtxEnrichedTable { const ref = { catalog: null, db: 'public', name }; return { id, diff --git a/packages/context/src/scan/relationship-locality.ts b/packages/context/src/scan/relationship-locality.ts index b0e851cc..5b180430 100644 --- a/packages/context/src/scan/relationship-locality.ts +++ b/packages/context/src/scan/relationship-locality.ts @@ -1,18 +1,18 @@ -import type { KloEnrichedColumn, KloEnrichedTable } from './enrichment-types.js'; -import { normalizeKloRelationshipName, tokenizeKloRelationshipName } from './relationship-name-similarity.js'; +import type { KtxEnrichedColumn, KtxEnrichedTable } from './enrichment-types.js'; +import { normalizeKtxRelationshipName, tokenizeKtxRelationshipName } from './relationship-name-similarity.js'; -export interface KloRelationshipLocalityCandidateTable { - table: KloEnrichedTable; +export interface KtxRelationshipLocalityCandidateTable { + table: KtxEnrichedTable; score: number; tokenScore: number; embeddingScore: number; reasons: string[]; } -export interface LocalKloRelationshipCandidateTablesInput { - childTable: KloEnrichedTable; - childColumn: KloEnrichedColumn; - parentTables: readonly KloEnrichedTable[]; +export interface LocalKtxRelationshipCandidateTablesInput { + childTable: KtxEnrichedTable; + childColumn: KtxEnrichedColumn; + parentTables: readonly KtxEnrichedTable[]; maxParentTables?: number; } @@ -24,17 +24,17 @@ function roundedScore(value: number): number { } function normalizedTokenVariants(name: string): string[] { - const normalized = normalizeKloRelationshipName(name); + const normalized = normalizeKtxRelationshipName(name); return Array.from( new Set([ ...normalized.tokens, - ...tokenizeKloRelationshipName(normalized.singular), - ...tokenizeKloRelationshipName(normalized.plural), + ...tokenizeKtxRelationshipName(normalized.singular), + ...tokenizeKtxRelationshipName(normalized.plural), ]), ).filter(Boolean); } -function childColumnLocalityTokens(column: KloEnrichedColumn): string[] { +function childColumnLocalityTokens(column: KtxEnrichedColumn): string[] { const tokens = normalizedTokenVariants(column.name); const withoutSuffix = tokens.filter((token) => !RELATIONSHIP_SUFFIX_TOKENS.has(token)); return withoutSuffix.length > 0 ? withoutSuffix : tokens; @@ -78,7 +78,7 @@ function cosineSimilarity(left: readonly number[] | null, right: readonly number return dot / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude)); } -function parentEmbeddingScore(childColumn: KloEnrichedColumn, parentTable: KloEnrichedTable): number { +function parentEmbeddingScore(childColumn: KtxEnrichedColumn, parentTable: KtxEnrichedTable): number { if (!Array.isArray(childColumn.embedding) || childColumn.embedding.length === 0) { return 0; } @@ -91,9 +91,9 @@ function parentEmbeddingScore(childColumn: KloEnrichedColumn, parentTable: KloEn } function tableTokenScore(input: { - childTable: KloEnrichedTable; - childColumn: KloEnrichedColumn; - parentTable: KloEnrichedTable; + childTable: KtxEnrichedTable; + childColumn: KtxEnrichedColumn; + parentTable: KtxEnrichedTable; }): number { const childTableTokens = normalizedTokenVariants(input.childTable.ref.name); const childColumnTokens = childColumnLocalityTokens(input.childColumn); @@ -107,10 +107,10 @@ function tableTokenScore(input: { } function localityScore(input: { - childTable: KloEnrichedTable; - childColumn: KloEnrichedColumn; - parentTable: KloEnrichedTable; -}): Omit { + childTable: KtxEnrichedTable; + childColumn: KtxEnrichedColumn; + parentTable: KtxEnrichedTable; +}): Omit { const tokenScore = roundedScore(tableTokenScore(input)); const embeddingScore = roundedScore(parentEmbeddingScore(input.childColumn, input.parentTable)); const score = @@ -136,8 +136,8 @@ function localityScore(input: { } export function localCandidateTables( - input: LocalKloRelationshipCandidateTablesInput, -): KloRelationshipLocalityCandidateTable[] { + input: LocalKtxRelationshipCandidateTablesInput, +): KtxRelationshipLocalityCandidateTable[] { const limit = input.maxParentTables ?? DEFAULT_MAX_PARENT_TABLES; if (!Number.isFinite(limit) || limit <= 0) { return []; diff --git a/packages/context/src/scan/relationship-name-similarity.test.ts b/packages/context/src/scan/relationship-name-similarity.test.ts index ef77cc1a..34730c81 100644 --- a/packages/context/src/scan/relationship-name-similarity.test.ts +++ b/packages/context/src/scan/relationship-name-similarity.test.ts @@ -1,73 +1,73 @@ import { describe, expect, it } from 'vitest'; import { - normalizeKloRelationshipName, - pluralizeKloRelationshipToken, - singularizeKloRelationshipToken, + normalizeKtxRelationshipName, + pluralizeKtxRelationshipToken, + singularizeKtxRelationshipToken, tokenSimilarity, - tokenizeKloRelationshipName, + tokenizeKtxRelationshipName, } from './relationship-name-similarity.js'; describe('relationship name similarity', () => { it('tokenizes common warehouse naming styles', () => { - expect(normalizeKloRelationshipName('AlbumId')).toMatchObject({ + expect(normalizeKtxRelationshipName('AlbumId')).toMatchObject({ normalized: 'album_id', singular: 'album_id', plural: 'album_ids', tokens: ['album', 'id'], }); - expect(normalizeKloRelationshipName('artistID')).toMatchObject({ + expect(normalizeKtxRelationshipName('artistID')).toMatchObject({ normalized: 'artist_id', tokens: ['artist', 'id'], }); - expect(normalizeKloRelationshipName('SalesLT.CustomerID')).toMatchObject({ + expect(normalizeKtxRelationshipName('SalesLT.CustomerID')).toMatchObject({ normalized: 'sales_lt_customer_id', singular: 'sales_lt_customer_id', tokens: ['sales', 'lt', 'customer', 'id'], }); - expect(normalizeKloRelationshipName('SCREAMING_CUSTOMER_UUID')).toMatchObject({ + expect(normalizeKtxRelationshipName('SCREAMING_CUSTOMER_UUID')).toMatchObject({ normalized: 'screaming_customer_uuid', tokens: ['screaming', 'customer', 'uuid'], }); - expect(normalizeKloRelationshipName('billing-account-key')).toMatchObject({ + expect(normalizeKtxRelationshipName('billing-account-key')).toMatchObject({ normalized: 'billing_account_key', tokens: ['billing', 'account', 'key'], }); }); it('removes only leading warehouse layer prefixes', () => { - expect(normalizeKloRelationshipName('mart__Sales_Accounts')).toMatchObject({ + expect(normalizeKtxRelationshipName('mart__Sales_Accounts')).toMatchObject({ normalized: 'sales_accounts', singular: 'sales_account', plural: 'sales_accounts', tokens: ['sales', 'accounts'], }); - expect(normalizeKloRelationshipName('dim_users')).toMatchObject({ + expect(normalizeKtxRelationshipName('dim_users')).toMatchObject({ normalized: 'users', singular: 'user', plural: 'users', tokens: ['users'], }); - expect(normalizeKloRelationshipName('customer_dim_id')).toMatchObject({ + expect(normalizeKtxRelationshipName('customer_dim_id')).toMatchObject({ normalized: 'customer_dim_id', tokens: ['customer', 'dim', 'id'], }); }); it('folds accents and preserves non-suffix trailing s words', () => { - expect(normalizeKloRelationshipName('KundénID')).toMatchObject({ + expect(normalizeKtxRelationshipName('KundénID')).toMatchObject({ normalized: 'kunden_id', tokens: ['kunden', 'id'], }); - expect(singularizeKloRelationshipToken('address')).toBe('address'); - expect(singularizeKloRelationshipToken('addresses')).toBe('address'); - expect(singularizeKloRelationshipToken('status')).toBe('status'); - expect(pluralizeKloRelationshipToken('address')).toBe('addresses'); - expect(pluralizeKloRelationshipToken('company')).toBe('companies'); + expect(singularizeKtxRelationshipToken('address')).toBe('address'); + expect(singularizeKtxRelationshipToken('addresses')).toBe('address'); + expect(singularizeKtxRelationshipToken('status')).toBe('status'); + expect(pluralizeKtxRelationshipToken('address')).toBe('addresses'); + expect(pluralizeKtxRelationshipToken('company')).toBe('companies'); }); it('returns deterministic tokens for direct tokenization calls', () => { - expect(tokenizeKloRelationshipName('HTTPResponseCode')).toEqual(['http', 'response', 'code']); - expect(tokenizeKloRelationshipName('customer2AddressID')).toEqual(['customer', '2', 'address', 'id']); + expect(tokenizeKtxRelationshipName('HTTPResponseCode')).toEqual(['http', 'response', 'code']); + expect(tokenizeKtxRelationshipName('customer2AddressID')).toEqual(['customer', '2', 'address', 'id']); }); it('scores token overlap and ordered suffix similarity', () => { diff --git a/packages/context/src/scan/relationship-name-similarity.ts b/packages/context/src/scan/relationship-name-similarity.ts index 19212557..14d53fe3 100644 --- a/packages/context/src/scan/relationship-name-similarity.ts +++ b/packages/context/src/scan/relationship-name-similarity.ts @@ -1,4 +1,4 @@ -export interface KloRelationshipNormalizedName { +export interface KtxRelationshipNormalizedName { raw: string; normalized: string; singular: string; @@ -6,7 +6,7 @@ export interface KloRelationshipNormalizedName { tokens: string[]; } -export type KloRelationshipTokenInput = string | readonly string[] | KloRelationshipNormalizedName; +export type KtxRelationshipTokenInput = string | readonly string[] | KtxRelationshipNormalizedName; const WAREHOUSE_LAYER_PREFIXES = new Set(['stg', 'stage', 'staging', 'dim', 'fct', 'fact', 'int', 'mart']); @@ -27,7 +27,7 @@ function foldAccents(value: string): string { .replace(/œ/giu, 'oe'); } -export function singularizeKloRelationshipToken(value: string): string { +export function singularizeKtxRelationshipToken(value: string): string { if (value.length <= 2) { return value; } @@ -46,7 +46,7 @@ export function singularizeKloRelationshipToken(value: string): string { return value; } -export function pluralizeKloRelationshipToken(value: string): string { +export function pluralizeKtxRelationshipToken(value: string): string { if (value.endsWith('y')) { return `${value.slice(0, -1)}ies`; } @@ -63,7 +63,7 @@ function singularizeTokens(tokens: readonly string[]): string[] { const result = [...tokens]; const last = result[result.length - 1]; if (last) { - result[result.length - 1] = singularizeKloRelationshipToken(last); + result[result.length - 1] = singularizeKtxRelationshipToken(last); } return result; } @@ -75,12 +75,12 @@ function pluralizeTokens(tokens: readonly string[]): string[] { const result = [...tokens]; const last = result[result.length - 1]; if (last) { - result[result.length - 1] = pluralizeKloRelationshipToken(last); + result[result.length - 1] = pluralizeKtxRelationshipToken(last); } return result; } -export function tokenizeKloRelationshipName(name: string): string[] { +export function tokenizeKtxRelationshipName(name: string): string[] { const boundarySeparated = splitCaseBoundaries(foldAccents(name.trim())); const tokens = boundarySeparated .toLowerCase() @@ -92,8 +92,8 @@ export function tokenizeKloRelationshipName(name: string): string[] { return tokens.filter((token, index) => index > 0 || !WAREHOUSE_LAYER_PREFIXES.has(token)); } -export function normalizeKloRelationshipName(name: string): KloRelationshipNormalizedName { - const tokens = tokenizeKloRelationshipName(name); +export function normalizeKtxRelationshipName(name: string): KtxRelationshipNormalizedName { + const tokens = tokenizeKtxRelationshipName(name); const singularTokens = singularizeTokens(tokens); const pluralTokens = pluralizeTokens(singularTokens); @@ -106,14 +106,14 @@ export function normalizeKloRelationshipName(name: string): KloRelationshipNorma }; } -function tokensFromInput(input: KloRelationshipTokenInput): string[] { +function tokensFromInput(input: KtxRelationshipTokenInput): string[] { if (typeof input === 'string') { - return tokenizeKloRelationshipName(input); + return tokenizeKtxRelationshipName(input); } if ('tokens' in input) { return input.tokens; } - return input.map((token) => normalizeKloRelationshipName(token).normalized).filter(Boolean); + return input.map((token) => normalizeKtxRelationshipName(token).normalized).filter(Boolean); } function longestCommonSuffixLength(left: readonly string[], right: readonly string[]): number { @@ -132,7 +132,7 @@ function roundedScore(value: number): number { return Number(Math.max(0, Math.min(1, value)).toFixed(3)); } -export function tokenSimilarity(leftInput: KloRelationshipTokenInput, rightInput: KloRelationshipTokenInput): number { +export function tokenSimilarity(leftInput: KtxRelationshipTokenInput, rightInput: KtxRelationshipTokenInput): number { const left = tokensFromInput(leftInput); const right = tokensFromInput(rightInput); if (left.length === 0 || right.length === 0) { diff --git a/packages/context/src/scan/relationship-profiling.test.ts b/packages/context/src/scan/relationship-profiling.test.ts index 01d3a07b..d034f1d9 100644 --- a/packages/context/src/scan/relationship-profiling.test.ts +++ b/packages/context/src/scan/relationship-profiling.test.ts @@ -2,22 +2,22 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import Database from 'better-sqlite3'; import { afterEach, describe, expect, it } from 'vitest'; -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; -import { snapshotToKloEnrichedSchema } from './local-enrichment.js'; -import { loadKloRelationshipBenchmarkFixture, maskKloRelationshipBenchmarkSnapshot } from './relationship-benchmarks.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; +import { snapshotToKtxEnrichedSchema } from './local-enrichment.js'; +import { loadKtxRelationshipBenchmarkFixture, maskKtxRelationshipBenchmarkSnapshot } from './relationship-benchmarks.js'; import { - createKloRelationshipProfileCache, - formatKloRelationshipTableRef, - profileKloRelationshipSchema, - quoteKloRelationshipIdentifier, + createKtxRelationshipProfileCache, + formatKtxRelationshipTableRef, + profileKtxRelationshipSchema, + quoteKtxRelationshipIdentifier, } from './relationship-profiling.js'; -import type { KloQueryResult, KloReadOnlyQueryInput, KloScanContext } from './types.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput, KtxScanContext } from './types.js'; class InMemorySqliteExecutor { readonly db = new Database(':memory:'); queryCount = 0; - executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.queryCount += 1; const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); @@ -42,7 +42,7 @@ class FileSqliteExecutor { this.db = new Database(dataPath, { readonly: true, fileMustExist: true }); } - executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.queryCount += 1; const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); @@ -59,7 +59,7 @@ class FileSqliteExecutor { } } -function column(tableId: string, name: string, overrides: Partial = {}): KloEnrichedColumn { +function column(tableId: string, name: string, overrides: Partial = {}): KtxEnrichedColumn { const tableRef = overrides.tableRef ?? { catalog: null, db: null, name: tableId }; return { id: `${tableId}.${name}`, @@ -80,7 +80,7 @@ function column(tableId: string, name: string, overrides: Partial { }); it('quotes identifiers and formats table refs for supported local SQL drivers', () => { - expect(quoteKloRelationshipIdentifier('sqlite', 'odd"name')).toBe('"odd""name"'); - expect(quoteKloRelationshipIdentifier('mysql', 'odd`name')).toBe('`odd``name`'); - expect(quoteKloRelationshipIdentifier('sqlserver', 'odd]name')).toBe('[odd]]name]'); - expect(formatKloRelationshipTableRef('sqlite', { catalog: null, db: null, name: 'accounts' })).toBe('"accounts"'); - expect(formatKloRelationshipTableRef('postgres', { catalog: null, db: 'analytics', name: 'accounts' })).toBe( + expect(quoteKtxRelationshipIdentifier('sqlite', 'odd"name')).toBe('"odd""name"'); + expect(quoteKtxRelationshipIdentifier('mysql', 'odd`name')).toBe('`odd``name`'); + expect(quoteKtxRelationshipIdentifier('sqlserver', 'odd]name')).toBe('[odd]]name]'); + expect(formatKtxRelationshipTableRef('sqlite', { catalog: null, db: null, name: 'accounts' })).toBe('"accounts"'); + expect(formatKtxRelationshipTableRef('postgres', { catalog: null, db: 'analytics', name: 'accounts' })).toBe( '"analytics"."accounts"', ); }); @@ -133,7 +133,7 @@ describe('relationship profiling', () => { (4, 'C-3', 2); `); - const result = await profileKloRelationshipSchema({ + const result = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: schema([ @@ -195,7 +195,7 @@ describe('relationship profiling', () => { (12, 2); `); - const result = await profileKloRelationshipSchema({ + const result = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: schema([ @@ -238,7 +238,7 @@ describe('relationship profiling', () => { INSERT INTO accounts VALUES (1, 'a1'), (2, 'a2'), (3, 'a3'), (4, 'a4'); `); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: schema([ @@ -287,9 +287,9 @@ describe('relationship profiling', () => { }), ]), ]); - const cache = createKloRelationshipProfileCache(); + const cache = createKtxRelationshipProfileCache(); - const first = await profileKloRelationshipSchema({ + const first = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: relationshipSchema, @@ -297,7 +297,7 @@ describe('relationship profiling', () => { ctx: { runId: 'profile-cache-run' }, cache, }); - const second = await profileKloRelationshipSchema({ + const second = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: relationshipSchema, @@ -305,13 +305,13 @@ describe('relationship profiling', () => { ctx: { runId: 'profile-cache-run' }, cache, }); - const third = await profileKloRelationshipSchema({ + const third = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: relationshipSchema, executor, ctx: { runId: 'profile-cache-fresh-run' }, - cache: createKloRelationshipProfileCache(), + cache: createKtxRelationshipProfileCache(), }); expect(first.queryCount).toBe(1); @@ -324,20 +324,20 @@ describe('relationship profiling', () => { it('profiles the checked-in scale stress fixture with one query per table', async () => { const fixtureRoot = new URL('../../test/fixtures/relationship-benchmarks/', import.meta.url); - const fixture = await loadKloRelationshipBenchmarkFixture(join(fixtureRoot.pathname, 'scale_stress_no_declared_constraints')); + const fixture = await loadKtxRelationshipBenchmarkFixture(join(fixtureRoot.pathname, 'scale_stress_no_declared_constraints')); if (!fixture.dataPath) { throw new Error('scale_stress_no_declared_constraints is missing data.sqlite'); } - const maskedSnapshot = maskKloRelationshipBenchmarkSnapshot( + const maskedSnapshot = maskKtxRelationshipBenchmarkSnapshot( fixture.snapshot, 'declared_pks_and_declared_fks_removed', ); const scaleExecutor = new FileSqliteExecutor(fixture.dataPath); try { - const result = await profileKloRelationshipSchema({ + const result = await profileKtxRelationshipSchema({ connectionId: fixture.snapshot.connectionId, driver: fixture.snapshot.driver, - schema: snapshotToKloEnrichedSchema(maskedSnapshot, new Map()), + schema: snapshotToKtxEnrichedSchema(maskedSnapshot, new Map()), executor: scaleExecutor, ctx: { runId: 'scale-stress-profile-query-count' }, profileSampleRows: 3, diff --git a/packages/context/src/scan/relationship-profiling.ts b/packages/context/src/scan/relationship-profiling.ts index 6c5a4b8a..1fbeccd4 100644 --- a/packages/context/src/scan/relationship-profiling.ts +++ b/packages/context/src/scan/relationship-profiling.ts @@ -1,18 +1,18 @@ -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; import type { - KloConnectionDriver, - KloQueryResult, - KloReadOnlyQueryInput, - KloScanContext, - KloTableRef, + KtxConnectionDriver, + KtxQueryResult, + KtxReadOnlyQueryInput, + KtxScanContext, + KtxTableRef, } from './types.js'; -export interface KloRelationshipReadOnlyExecutor { - executeReadOnly(input: KloReadOnlyQueryInput, ctx: KloScanContext): Promise; +export interface KtxRelationshipReadOnlyExecutor { + executeReadOnly(input: KtxReadOnlyQueryInput, ctx: KtxScanContext): Promise; } -export interface KloRelationshipColumnProfile { - table: KloTableRef; +export interface KtxRelationshipColumnProfile { + table: KtxTableRef; column: string; nativeType: string; normalizedType: string; @@ -26,43 +26,43 @@ export interface KloRelationshipColumnProfile { maxTextLength: number | null; } -export interface KloRelationshipTableProfile { - table: KloTableRef; +export interface KtxRelationshipTableProfile { + table: KtxTableRef; rowCount: number; } -export interface KloRelationshipProfileArtifact { +export interface KtxRelationshipProfileArtifact { connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; sqlAvailable: boolean; queryCount: number; - tables: KloRelationshipTableProfile[]; - columns: Record; + tables: KtxRelationshipTableProfile[]; + columns: Record; warnings: string[]; } -interface KloRelationshipCachedTableProfile { - table: KloRelationshipTableProfile; - columns: Record; +interface KtxRelationshipCachedTableProfile { + table: KtxRelationshipTableProfile; + columns: Record; warnings: string[]; } -export interface KloRelationshipProfileCache { - readonly tableProfiles: Map; +export interface KtxRelationshipProfileCache { + readonly tableProfiles: Map; } -export interface ProfileKloRelationshipSchemaInput { +export interface ProfileKtxRelationshipSchemaInput { connectionId: string; - driver: KloConnectionDriver; - schema: KloEnrichedSchema; - executor: KloRelationshipReadOnlyExecutor | null; - ctx: KloScanContext; + driver: KtxConnectionDriver; + schema: KtxEnrichedSchema; + executor: KtxRelationshipReadOnlyExecutor | null; + ctx: KtxScanContext; sampleValuesPerColumn?: number; profileSampleRows?: number; - cache?: KloRelationshipProfileCache; + cache?: KtxRelationshipProfileCache; } -export function createKloRelationshipProfileCache(): KloRelationshipProfileCache { +export function createKtxRelationshipProfileCache(): KtxRelationshipProfileCache { return { tableProfiles: new Map() }; } @@ -70,7 +70,7 @@ const SAMPLE_VALUE_DELIMITER = '\u001f'; type QuoteStyle = 'double' | 'backtick' | 'bracket'; -function quoteStyle(driver: KloConnectionDriver): QuoteStyle { +function quoteStyle(driver: KtxConnectionDriver): QuoteStyle { if (driver === 'mysql' || driver === 'clickhouse' || driver === 'posthog') { return 'backtick'; } @@ -80,7 +80,7 @@ function quoteStyle(driver: KloConnectionDriver): QuoteStyle { return 'double'; } -export function quoteKloRelationshipIdentifier(driver: KloConnectionDriver, identifier: string): string { +export function quoteKtxRelationshipIdentifier(driver: KtxConnectionDriver, identifier: string): string { switch (quoteStyle(driver)) { case 'backtick': return `\`${identifier.replace(/`/g, '``')}\``; @@ -91,15 +91,15 @@ export function quoteKloRelationshipIdentifier(driver: KloConnectionDriver, iden } } -export function formatKloRelationshipTableRef(driver: KloConnectionDriver, table: KloTableRef): string { +export function formatKtxRelationshipTableRef(driver: KtxConnectionDriver, table: KtxTableRef): string { const parts = driver === 'sqlite' || driver === 'posthog' ? [table.name] : [table.catalog, table.db, table.name].filter((value): value is string => Boolean(value)); - return parts.map((part) => quoteKloRelationshipIdentifier(driver, part)).join('.'); + return parts.map((part) => quoteKtxRelationshipIdentifier(driver, part)).join('.'); } -function textLengthExpression(driver: KloConnectionDriver, columnSql: string): string { +function textLengthExpression(driver: KtxConnectionDriver, columnSql: string): string { if (driver === 'mysql') { return `CHAR_LENGTH(CAST(${columnSql} AS CHAR))`; } @@ -115,21 +115,21 @@ function textLengthExpression(driver: KloConnectionDriver, columnSql: string): s return `LENGTH(CAST(${columnSql} AS TEXT))`; } -function limitSql(driver: KloConnectionDriver, limit: number): string { +function limitSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ''; } return ` LIMIT ${Math.max(1, Math.floor(limit))}`; } -function topSql(driver: KloConnectionDriver, limit: number): string { +function topSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ` TOP (${Math.max(1, Math.floor(limit))})`; } return ''; } -function sampledTableSql(driver: KloConnectionDriver, tableSql: string, limit: number): string { +function sampledTableSql(driver: KtxConnectionDriver, tableSql: string, limit: number): string { const safeLimit = Math.max(1, Math.floor(limit)); if (driver === 'sqlserver') { return `(SELECT TOP (${safeLimit}) * FROM ${tableSql}) AS relationship_profile_sample`; @@ -137,15 +137,15 @@ function sampledTableSql(driver: KloConnectionDriver, tableSql: string, limit: n return `(SELECT * FROM ${tableSql}${limitSql(driver, safeLimit)}) AS relationship_profile_sample`; } -function firstRow(result: KloQueryResult): unknown[] { +function firstRow(result: KtxQueryResult): unknown[] { return result.rows[0] ?? []; } -function headerIndex(result: KloQueryResult, header: string): number { +function headerIndex(result: KtxQueryResult, header: string): number { return result.headers.findIndex((candidate) => candidate.toLowerCase() === header.toLowerCase()); } -function valueAt(result: KloQueryResult, row: unknown[], header: string): unknown { +function valueAt(result: KtxQueryResult, row: unknown[], header: string): unknown { return row[headerIndex(result, header)]; } @@ -178,19 +178,19 @@ function nullableNumberFromValue(value: unknown): number | null { return null; } -function numberAt(result: KloQueryResult, header: string): number { +function numberAt(result: KtxQueryResult, header: string): number { return numberFromValue(valueAt(result, firstRow(result), header)); } -function columnKey(table: KloEnrichedTable, column: KloEnrichedColumn): string { +function columnKey(table: KtxEnrichedTable, column: KtxEnrichedColumn): string { return `${table.ref.name}.${column.name}`; } function tableProfileCacheKey(input: { connectionId: string; - driver: KloConnectionDriver; - ctx: KloScanContext; - table: KloTableRef; + driver: KtxConnectionDriver; + ctx: KtxScanContext; + table: KtxTableRef; sampleValuesPerColumn: number; profileSampleRows: number; }): string { @@ -210,7 +210,7 @@ function sqlStringLiteral(value: string): string { return `'${value.replace(/'/g, "''")}'`; } -function sampleAggregateSql(driver: KloConnectionDriver, innerSql: string): string { +function sampleAggregateSql(driver: KtxConnectionDriver, innerSql: string): string { if (driver === 'postgres') { return `(SELECT STRING_AGG(CAST(value AS TEXT), CHR(31)) FROM (${innerSql}) AS relationship_profile_values)`; } @@ -230,7 +230,7 @@ function sampleAggregateSql(driver: KloConnectionDriver, innerSql: string): stri } function sampleValuesSql(input: { - driver: KloConnectionDriver; + driver: KtxConnectionDriver; tableSql: string; columnSql: string; limit: number; @@ -246,13 +246,13 @@ function sampleValuesSql(input: { } function columnProfileSelectSql(input: { - connectionDriver: KloConnectionDriver; + connectionDriver: KtxConnectionDriver; tableSql: string; profileTableSql: string; - column: KloEnrichedColumn; + column: KtxEnrichedColumn; sampleValuesPerColumn: number; }): string { - const columnSql = quoteKloRelationshipIdentifier(input.connectionDriver, input.column.name); + const columnSql = quoteKtxRelationshipIdentifier(input.connectionDriver, input.column.name); const textLengthSql = textLengthExpression(input.connectionDriver, columnSql); const samplesSql = sampleAggregateSql( input.connectionDriver, @@ -290,12 +290,12 @@ function splitSampleValues(value: unknown): string[] { async function queryCount(input: { connectionId: string; - driver: KloConnectionDriver; - table: KloTableRef; - executor: KloRelationshipReadOnlyExecutor; - ctx: KloScanContext; + driver: KtxConnectionDriver; + table: KtxTableRef; + executor: KtxRelationshipReadOnlyExecutor; + ctx: KtxScanContext; }): Promise<{ rowCount: number; queryCount: number }> { - const tableSql = formatKloRelationshipTableRef(input.driver, input.table); + const tableSql = formatKtxRelationshipTableRef(input.driver, input.table); const result = await input.executor.executeReadOnly( { connectionId: input.connectionId, sql: `SELECT COUNT(*) AS row_count FROM ${tableSql}`, maxRows: 1 }, input.ctx, @@ -305,15 +305,15 @@ async function queryCount(input: { async function queryTableProfile(input: { connectionId: string; - driver: KloConnectionDriver; - table: KloEnrichedTable; - executor: KloRelationshipReadOnlyExecutor; - ctx: KloScanContext; + driver: KtxConnectionDriver; + table: KtxEnrichedTable; + executor: KtxRelationshipReadOnlyExecutor; + ctx: KtxScanContext; sampleValuesPerColumn: number; profileSampleRows: number; }): Promise<{ - table: KloRelationshipTableProfile; - columns: Record; + table: KtxRelationshipTableProfile; + columns: Record; queryCount: number; }> { if (input.table.columns.length === 0) { @@ -331,7 +331,7 @@ async function queryTableProfile(input: { }; } - const tableSql = formatKloRelationshipTableRef(input.driver, input.table.ref); + const tableSql = formatKtxRelationshipTableRef(input.driver, input.table.ref); const profileTableSql = sampledTableSql(input.driver, tableSql, input.profileSampleRows); const sql = input.table.columns .map((column) => @@ -349,7 +349,7 @@ async function queryTableProfile(input: { input.ctx, ); const columnsByName = new Map(input.table.columns.map((column) => [column.name, column])); - const profiles: Record = {}; + const profiles: Record = {}; let tableRowCount = 0; for (const row of result.rows) { @@ -385,9 +385,9 @@ async function queryTableProfile(input: { }; } -export async function profileKloRelationshipSchema( - input: ProfileKloRelationshipSchemaInput, -): Promise { +export async function profileKtxRelationshipSchema( + input: ProfileKtxRelationshipSchemaInput, +): Promise { if (!input.executor) { return { connectionId: input.connectionId, @@ -401,8 +401,8 @@ export async function profileKloRelationshipSchema( } let queryTotal = 0; - const tables: KloRelationshipTableProfile[] = []; - const columns: Record = {}; + const tables: KtxRelationshipTableProfile[] = []; + const columns: Record = {}; const warnings: string[] = []; for (const table of input.schema.tables.filter((candidate) => candidate.enabled)) { diff --git a/packages/context/src/scan/relationship-review-apply.test.ts b/packages/context/src/scan/relationship-review-apply.test.ts index 9b9cb497..7a8e597f 100644 --- a/packages/context/src/scan/relationship-review-apply.test.ts +++ b/packages/context/src/scan/relationship-review-apply.test.ts @@ -1,16 +1,16 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { KloLocalProject } from '../project/index.js'; -import { initKloProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { initKtxProject } from '../project/index.js'; import { describe, expect, it, vi } from 'vitest'; import { applyLocalScanRelationshipReviewDecisions } from './relationship-review-apply.js'; -import type { KloRelationshipReviewDecisionArtifact } from './relationship-review-decisions.js'; +import type { KtxRelationshipReviewDecisionArtifact } from './relationship-review-decisions.js'; import type { ReadLocalScanRelationshipArtifactsResult } from './relationship-artifacts.js'; import type { WriteLocalScanManifestShardsResult } from './local-enrichment-artifacts.js'; -import type { KloSchemaSnapshot } from './types.js'; +import type { KtxSchemaSnapshot } from './types.js'; -const acceptedDecisionArtifact: KloRelationshipReviewDecisionArtifact = { +const acceptedDecisionArtifact: KtxRelationshipReviewDecisionArtifact = { connectionId: 'warehouse', runId: 'scan-run-a', syncId: 'sync-a', @@ -146,7 +146,7 @@ const artifacts: ReadLocalScanRelationshipArtifactsResult = { }, }; -const snapshot: KloSchemaSnapshot = { +const snapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-05-07T12:00:00.000Z', @@ -198,17 +198,17 @@ const snapshot: KloSchemaSnapshot = { async function projectWithDecisions( decisions = acceptedDecisionArtifact, -): Promise<{ project: KloLocalProject; tempDir: string }> { - const tempDir = await mkdtemp(join(tmpdir(), 'klo-relationship-review-apply-')); - const project = await initKloProject({ +): Promise<{ project: KtxLocalProject; tempDir: string }> { + const tempDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-apply-')); + const project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse', }); await project.fileStore.writeFile( 'raw-sources/warehouse/live-database/sync-a/enrichment/relationship-review-decisions.json', `${JSON.stringify(decisions)}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed relationship review decisions', ); return { project, tempDir }; diff --git a/packages/context/src/scan/relationship-review-apply.ts b/packages/context/src/scan/relationship-review-apply.ts index 521ea2ed..3afa6a7e 100644 --- a/packages/context/src/scan/relationship-review-apply.ts +++ b/packages/context/src/scan/relationship-review-apply.ts @@ -1,4 +1,4 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { readLocalScanRelationshipArtifacts, type ReadLocalScanRelationshipArtifactsResult, @@ -12,10 +12,10 @@ import { type WriteLocalScanManifestShardsInput, type WriteLocalScanManifestShardsResult, } from './local-enrichment-artifacts.js'; -import type { KloEnrichedRelationship, KloRelationshipUpdate } from './enrichment-types.js'; +import type { KtxEnrichedRelationship, KtxRelationshipUpdate } from './enrichment-types.js'; import type { - KloRelationshipReviewDecisionArtifact, - KloRelationshipReviewDecisionEntry, + KtxRelationshipReviewDecisionArtifact, + KtxRelationshipReviewDecisionEntry, } from './relationship-review-decisions.js'; const DECISIONS_FILE = 'relationship-review-decisions.json'; @@ -39,7 +39,7 @@ export interface AppliedRelationshipReviewDecision { decidedAt: string; reviewer: string; note: string | null; - relationship: KloEnrichedRelationship; + relationship: KtxEnrichedRelationship; } export interface ApplyLocalScanRelationshipReviewDecisionsResult { @@ -50,7 +50,7 @@ export interface ApplyLocalScanRelationshipReviewDecisionsResult { decisionsPath: string; selectedDecisions: number; appliedRelationships: number; - relationships: KloEnrichedRelationship[]; + relationships: KtxEnrichedRelationship[]; manifestShards: string[]; manifestShardsWritten: number; } @@ -60,17 +60,17 @@ function decisionsPathFromRelationshipsPath(relationshipsPath: string): string { } async function readDecisionArtifact( - project: KloLocalProject, + project: KtxLocalProject, path: string, runId: string, -): Promise { +): Promise { let raw: { content: string }; try { raw = await project.fileStore.readFile(path); } catch { throw new Error(`Relationship review decisions were not found for scan run "${runId}"`); } - const parsed = JSON.parse(raw.content) as KloRelationshipReviewDecisionArtifact; + const parsed = JSON.parse(raw.content) as KtxRelationshipReviewDecisionArtifact; return { connectionId: parsed.connectionId, runId: parsed.runId, @@ -91,16 +91,16 @@ function assertSelection(input: ApplyLocalScanRelationshipReviewDecisionsInput): } function selectAcceptedDecisions( - artifact: KloRelationshipReviewDecisionArtifact, + artifact: KtxRelationshipReviewDecisionArtifact, input: ApplyLocalScanRelationshipReviewDecisionsInput, -): KloRelationshipReviewDecisionEntry[] { +): KtxRelationshipReviewDecisionEntry[] { assertSelection(input); if (input.applyAllAccepted === true) { return artifact.decisions.filter((decision) => decision.decision === 'accepted'); } const decisionsById = new Map(artifact.decisions.map((decision) => [decision.candidateId, decision])); - const selected: KloRelationshipReviewDecisionEntry[] = []; + const selected: KtxRelationshipReviewDecisionEntry[] = []; for (const candidateId of input.candidateIds ?? []) { const decision = decisionsById.get(candidateId); if (!decision) { @@ -114,16 +114,16 @@ function selectAcceptedDecisions( return selected; } -function tableId(table: KloRelationshipReviewDecisionEntry['from']['table']): string { +function tableId(table: KtxRelationshipReviewDecisionEntry['from']['table']): string { return [table.catalog, table.db, table.name].filter((part): part is string => Boolean(part)).join('.'); } -function columnIds(table: KloRelationshipReviewDecisionEntry['from']['table'], columns: readonly string[]): string[] { +function columnIds(table: KtxRelationshipReviewDecisionEntry['from']['table'], columns: readonly string[]): string[] { const prefix = tableId(table); return columns.map((column) => `${prefix}.${column}`); } -function relationshipFromDecision(decision: KloRelationshipReviewDecisionEntry): KloEnrichedRelationship { +function relationshipFromDecision(decision: KtxRelationshipReviewDecisionEntry): KtxEnrichedRelationship { return { id: decision.candidateId, source: 'manual', @@ -147,8 +147,8 @@ function relationshipFromDecision(decision: KloRelationshipReviewDecisionEntry): function relationshipUpdate( connectionId: string, - relationships: readonly KloEnrichedRelationship[], -): KloRelationshipUpdate { + relationships: readonly KtxEnrichedRelationship[], +): KtxRelationshipUpdate { return { connectionId, accepted: [...relationships], @@ -166,7 +166,7 @@ function assertApplyableArtifacts(artifacts: ReadLocalScanRelationshipArtifactsR } export async function applyLocalScanRelationshipReviewDecisions( - project: KloLocalProject, + project: KtxLocalProject, input: ApplyLocalScanRelationshipReviewDecisionsInput, ): Promise { const readArtifacts = input.readLocalScanRelationshipArtifacts ?? readLocalScanRelationshipArtifacts; diff --git a/packages/context/src/scan/relationship-review-decisions.test.ts b/packages/context/src/scan/relationship-review-decisions.test.ts index 238e0b04..3b92e345 100644 --- a/packages/context/src/scan/relationship-review-decisions.test.ts +++ b/packages/context/src/scan/relationship-review-decisions.test.ts @@ -2,12 +2,12 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { runLocalStageOnlyIngest, type SourceAdapter } from '../ingest/index.js'; -import { initKloProject, loadKloProject } from '../project/index.js'; +import { initKtxProject, loadKtxProject } from '../project/index.js'; import { describe, expect, it } from 'vitest'; import { writeLocalScanRelationshipReviewDecision } from './relationship-review-decisions.js'; -import type { KloRelationshipArtifact, KloRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import type { KloScanReport } from './types.js'; +import type { KtxRelationshipArtifact, KtxRelationshipDiagnosticsArtifact } from './relationship-diagnostics.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import type { KtxScanReport } from './types.js'; const RUN_ID = 'scan-run-review'; const SYNC_ID = '2026-05-07-100000-scan-run-review'; @@ -19,9 +19,9 @@ async function writeProjectFile(projectDir: string, relativePath: string, conten } async function createProject(projectDir: string): Promise { - await initKloProject({ projectDir, projectName: 'warehouse' }); + await initKtxProject({ projectDir, projectName: 'warehouse' }); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -73,7 +73,7 @@ function liveDatabaseAdapter(): SourceAdapter { async function createLiveDatabaseRun(projectDir: string): Promise { await createProject(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await runLocalStageOnlyIngest({ project, adapters: [liveDatabaseAdapter()], @@ -84,7 +84,7 @@ async function createLiveDatabaseRun(projectDir: string): Promise { }); } -function reviewRelationships(): KloRelationshipArtifact { +function reviewRelationships(): KtxRelationshipArtifact { return { connectionId: 'warehouse', accepted: [], @@ -121,7 +121,7 @@ function reviewRelationships(): KloRelationshipArtifact { }; } -function diagnostics(): KloRelationshipDiagnosticsArtifact { +function diagnostics(): KtxRelationshipDiagnosticsArtifact { return { connectionId: 'warehouse', generatedAt: '2026-05-07T10:00:00.000Z', @@ -141,7 +141,7 @@ function diagnostics(): KloRelationshipDiagnosticsArtifact { }; } -function profile(): KloRelationshipProfileArtifact { +function profile(): KtxRelationshipProfileArtifact { return { connectionId: 'warehouse', driver: 'sqlite', @@ -153,7 +153,7 @@ function profile(): KloRelationshipProfileArtifact { }; } -function report(): KloScanReport { +function report(): KtxScanReport { return { connectionId: 'warehouse', driver: 'sqlite', @@ -236,11 +236,11 @@ async function writeScanArtifacts(projectDir: string): Promise { describe('relationship review decisions', () => { it('writes an accepted decision beside the scan relationship artifacts', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-review-decisions-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-decisions-')); try { await createLiveDatabaseRun(projectDir); await writeScanArtifacts(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); const result = await writeLocalScanRelationshipReviewDecision(project, { runId: 'scan-run-review', @@ -280,11 +280,11 @@ describe('relationship review decisions', () => { }); it('replaces the existing decision for the same candidate id', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-review-replace-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-replace-')); try { await createLiveDatabaseRun(projectDir); await writeScanArtifacts(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await writeLocalScanRelationshipReviewDecision(project, { runId: 'scan-run-review', @@ -319,10 +319,10 @@ describe('relationship review decisions', () => { }); it('returns null when the scan run does not exist', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-review-missing-run-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-missing-run-')); try { await createProject(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await expect( writeLocalScanRelationshipReviewDecision(project, { @@ -340,11 +340,11 @@ describe('relationship review decisions', () => { }); it('rejects unknown candidate ids for an existing scan run', async () => { - const projectDir = await mkdtemp(join(tmpdir(), 'klo-relationship-review-missing-candidate-')); + const projectDir = await mkdtemp(join(tmpdir(), 'ktx-relationship-review-missing-candidate-')); try { await createLiveDatabaseRun(projectDir); await writeScanArtifacts(projectDir); - const project = await loadKloProject({ projectDir }); + const project = await loadKtxProject({ projectDir }); await expect( writeLocalScanRelationshipReviewDecision(project, { diff --git a/packages/context/src/scan/relationship-review-decisions.ts b/packages/context/src/scan/relationship-review-decisions.ts index 06d78571..bf459d8a 100644 --- a/packages/context/src/scan/relationship-review-decisions.ts +++ b/packages/context/src/scan/relationship-review-decisions.ts @@ -1,40 +1,40 @@ -import type { KloLocalProject } from '../project/index.js'; -import type { KloRelationshipType } from './enrichment-types.js'; +import type { KtxLocalProject } from '../project/index.js'; +import type { KtxRelationshipType } from './enrichment-types.js'; import { readLocalScanRelationshipArtifacts } from './relationship-artifacts.js'; import type { - KloRelationshipArtifactEdge, - KloRelationshipArtifactEndpoint, + KtxRelationshipArtifactEdge, + KtxRelationshipArtifactEndpoint, } from './relationship-diagnostics.js'; -import type { KloResolvedRelationshipStatus } from './relationship-graph-resolver.js'; +import type { KtxResolvedRelationshipStatus } from './relationship-graph-resolver.js'; -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; const DECISIONS_FILE = 'relationship-review-decisions.json'; -export type KloRelationshipReviewDecisionValue = 'accepted' | 'rejected'; +export type KtxRelationshipReviewDecisionValue = 'accepted' | 'rejected'; export interface WriteLocalScanRelationshipReviewDecisionInput { runId: string; candidateId: string; - decision: KloRelationshipReviewDecisionValue; + decision: KtxRelationshipReviewDecisionValue; reviewer: string; note: string | null; decidedAt?: string; } -export interface KloRelationshipReviewDecisionEntry { +export interface KtxRelationshipReviewDecisionEntry { candidateId: string; - decision: KloRelationshipReviewDecisionValue; - previousStatus: KloResolvedRelationshipStatus; + decision: KtxRelationshipReviewDecisionValue; + previousStatus: KtxResolvedRelationshipStatus; connectionId: string; runId: string; syncId: string; decidedAt: string; reviewer: string; note: string | null; - from: KloRelationshipArtifactEndpoint; - to: KloRelationshipArtifactEndpoint; - relationshipType: KloRelationshipType; + from: KtxRelationshipArtifactEndpoint; + to: KtxRelationshipArtifactEndpoint; + relationshipType: KtxRelationshipType; source: string; score: number | null; confidence: number; @@ -43,25 +43,25 @@ export interface KloRelationshipReviewDecisionEntry { reasons: string[]; } -export interface KloRelationshipReviewDecisionArtifact { +export interface KtxRelationshipReviewDecisionArtifact { connectionId: string; runId: string; syncId: string; generatedAt: string; - decisions: KloRelationshipReviewDecisionEntry[]; + decisions: KtxRelationshipReviewDecisionEntry[]; } export interface WriteLocalScanRelationshipReviewDecisionResult { path: string; - decision: KloRelationshipReviewDecisionEntry; - artifact: KloRelationshipReviewDecisionArtifact; + decision: KtxRelationshipReviewDecisionEntry; + artifact: KtxRelationshipReviewDecisionArtifact; } function reviewDecisionPath(relationshipsPath: string): string { return relationshipsPath.replace(/relationships\.json$/u, DECISIONS_FILE); } -function allCandidateEdges(result: Awaited>): KloRelationshipArtifactEdge[] { +function allCandidateEdges(result: Awaited>): KtxRelationshipArtifactEdge[] { if (!result) { return []; } @@ -69,13 +69,13 @@ function allCandidateEdges(result: Awaited, -): Promise { + fallback: Omit, +): Promise { try { const raw = await project.fileStore.readFile(path); - const parsed = JSON.parse(raw.content) as KloRelationshipReviewDecisionArtifact; + const parsed = JSON.parse(raw.content) as KtxRelationshipReviewDecisionArtifact; return { connectionId: parsed.connectionId, runId: parsed.runId, @@ -89,15 +89,15 @@ async function readExistingDecisions( } function decisionEntry(input: { - candidate: KloRelationshipArtifactEdge; + candidate: KtxRelationshipArtifactEdge; connectionId: string; runId: string; syncId: string; - decision: KloRelationshipReviewDecisionValue; + decision: KtxRelationshipReviewDecisionValue; reviewer: string; note: string | null; decidedAt: string; -}): KloRelationshipReviewDecisionEntry { +}): KtxRelationshipReviewDecisionEntry { return { candidateId: input.candidate.id, decision: input.decision, @@ -121,16 +121,16 @@ function decisionEntry(input: { } function upsertDecision( - existing: readonly KloRelationshipReviewDecisionEntry[], - next: KloRelationshipReviewDecisionEntry, -): KloRelationshipReviewDecisionEntry[] { + existing: readonly KtxRelationshipReviewDecisionEntry[], + next: KtxRelationshipReviewDecisionEntry, +): KtxRelationshipReviewDecisionEntry[] { return [...existing.filter((item) => item.candidateId !== next.candidateId), next].sort((left, right) => left.candidateId.localeCompare(right.candidateId), ); } export async function writeLocalScanRelationshipReviewDecision( - project: KloLocalProject, + project: KtxLocalProject, input: WriteLocalScanRelationshipReviewDecisionInput, ): Promise { const artifacts = await readLocalScanRelationshipArtifacts(project, input.runId); @@ -162,7 +162,7 @@ export async function writeLocalScanRelationshipReviewDecision( note: input.note, decidedAt, }); - const artifact: KloRelationshipReviewDecisionArtifact = { + const artifact: KtxRelationshipReviewDecisionArtifact = { connectionId: artifacts.connectionId, runId: artifacts.runId, syncId: artifacts.syncId, diff --git a/packages/context/src/scan/relationship-scoring.test.ts b/packages/context/src/scan/relationship-scoring.test.ts index 94a02de0..30127913 100644 --- a/packages/context/src/scan/relationship-scoring.test.ts +++ b/packages/context/src/scan/relationship-scoring.test.ts @@ -1,13 +1,13 @@ import { describe, expect, it } from 'vitest'; import { calibrateWeightsFromSyntheticFixtures, - defaultKloRelationshipScoreWeights, - normalizeKloRelationshipScoreWeights, - scoreKloRelationshipCandidate, - type KloRelationshipSignalVector, + defaultKtxRelationshipScoreWeights, + normalizeKtxRelationshipScoreWeights, + scoreKtxRelationshipCandidate, + type KtxRelationshipSignalVector, } from './relationship-scoring.js'; -function signals(overrides: Partial = {}): KloRelationshipSignalVector { +function signals(overrides: Partial = {}): KtxRelationshipSignalVector { return { nameSimilarity: 0.5, typeCompatibility: 1, @@ -22,7 +22,7 @@ function signals(overrides: Partial = {}): KloRelat describe('relationship scoring', () => { it('scores stronger evidence higher without hard-gating on names', () => { - const weakNameStrongProfile = scoreKloRelationshipCandidate( + const weakNameStrongProfile = scoreKtxRelationshipCandidate( signals({ nameSimilarity: 0.05, typeCompatibility: 1, @@ -32,7 +32,7 @@ describe('relationship scoring', () => { structuralPrior: 0.7, }), ); - const strongNameWeakProfile = scoreKloRelationshipCandidate( + const strongNameWeakProfile = scoreKtxRelationshipCandidate( signals({ nameSimilarity: 0.95, typeCompatibility: 1, @@ -49,7 +49,7 @@ describe('relationship scoring', () => { }); it('normalizes partial and invalid weights into a usable vector', () => { - const weights = normalizeKloRelationshipScoreWeights({ + const weights = normalizeKtxRelationshipScoreWeights({ nameSimilarity: 3, typeCompatibility: -1, valueOverlap: Number.POSITIVE_INFINITY, @@ -64,8 +64,8 @@ describe('relationship scoring', () => { }); it('returns deterministic defaults as a defensive copy', () => { - const first = defaultKloRelationshipScoreWeights(); - const second = defaultKloRelationshipScoreWeights(); + const first = defaultKtxRelationshipScoreWeights(); + const second = defaultKtxRelationshipScoreWeights(); expect(first).toEqual(second); expect(first).not.toBe(second); diff --git a/packages/context/src/scan/relationship-scoring.ts b/packages/context/src/scan/relationship-scoring.ts index a6b1565f..ce76f9f4 100644 --- a/packages/context/src/scan/relationship-scoring.ts +++ b/packages/context/src/scan/relationship-scoring.ts @@ -1,4 +1,4 @@ -export const KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS = [ +export const KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS = [ 'nameSimilarity', 'typeCompatibility', 'valueOverlap', @@ -8,11 +8,11 @@ export const KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS = [ 'structuralPrior', ] as const; -export type KloRelationshipScoreSignal = (typeof KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS)[number]; +export type KtxRelationshipScoreSignal = (typeof KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS)[number]; -export type KloRelationshipFixtureOrigin = 'synthetic' | 'public' | 'customer'; +export type KtxRelationshipFixtureOrigin = 'synthetic' | 'public' | 'customer'; -export interface KloRelationshipSignalVector { +export interface KtxRelationshipSignalVector { nameSimilarity: number; typeCompatibility: number; valueOverlap: number; @@ -22,23 +22,23 @@ export interface KloRelationshipSignalVector { structuralPrior: number; } -export type KloRelationshipScoreWeights = Record; +export type KtxRelationshipScoreWeights = Record; -export interface KloRelationshipScoreBreakdown { +export interface KtxRelationshipScoreBreakdown { score: number; - signals: KloRelationshipSignalVector; - weights: KloRelationshipScoreWeights; - contributions: KloRelationshipScoreWeights; + signals: KtxRelationshipSignalVector; + weights: KtxRelationshipScoreWeights; + contributions: KtxRelationshipScoreWeights; } -export interface KloRelationshipScoringCalibrationObservation { +export interface KtxRelationshipScoringCalibrationObservation { fixtureId: string; - origin: KloRelationshipFixtureOrigin; + origin: KtxRelationshipFixtureOrigin; expectedRelationship: boolean; - signals: KloRelationshipSignalVector; + signals: KtxRelationshipSignalVector; } -const DEFAULT_WEIGHTS: KloRelationshipScoreWeights = { +const DEFAULT_WEIGHTS: KtxRelationshipScoreWeights = { nameSimilarity: 0.24, typeCompatibility: 0.1, valueOverlap: 0.22, @@ -59,7 +59,7 @@ function roundScore(value: number): number { return Number(clampScore(value).toFixed(3)); } -function sanitizeSignalVector(signals: KloRelationshipSignalVector): KloRelationshipSignalVector { +function sanitizeSignalVector(signals: KtxRelationshipSignalVector): KtxRelationshipSignalVector { return { nameSimilarity: roundScore(signals.nameSimilarity), typeCompatibility: roundScore(signals.typeCompatibility), @@ -71,38 +71,38 @@ function sanitizeSignalVector(signals: KloRelationshipSignalVector): KloRelation }; } -export function defaultKloRelationshipScoreWeights(): KloRelationshipScoreWeights { +export function defaultKtxRelationshipScoreWeights(): KtxRelationshipScoreWeights { return { ...DEFAULT_WEIGHTS }; } -export function normalizeKloRelationshipScoreWeights( - weights: Partial = DEFAULT_WEIGHTS, -): KloRelationshipScoreWeights { - const rawEntries = KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => { +export function normalizeKtxRelationshipScoreWeights( + weights: Partial = DEFAULT_WEIGHTS, +): KtxRelationshipScoreWeights { + const rawEntries = KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => { const value = weights[key] ?? 0; return [key, Number.isFinite(value) ? Math.max(0, value) : 0] as const; }); const total = rawEntries.reduce((sum, [, value]) => sum + value, 0); if (total <= 0) { - return defaultKloRelationshipScoreWeights(); + return defaultKtxRelationshipScoreWeights(); } - return Object.fromEntries(rawEntries.map(([key, value]) => [key, value / total])) as KloRelationshipScoreWeights; + return Object.fromEntries(rawEntries.map(([key, value]) => [key, value / total])) as KtxRelationshipScoreWeights; } -export function scoreKloRelationshipCandidate( - signals: KloRelationshipSignalVector, - weights: Partial = DEFAULT_WEIGHTS, -): KloRelationshipScoreBreakdown { +export function scoreKtxRelationshipCandidate( + signals: KtxRelationshipSignalVector, + weights: Partial = DEFAULT_WEIGHTS, +): KtxRelationshipScoreBreakdown { const sanitizedSignals = sanitizeSignalVector(signals); - const normalizedWeights = normalizeKloRelationshipScoreWeights(weights); + const normalizedWeights = normalizeKtxRelationshipScoreWeights(weights); const contributions = Object.fromEntries( - KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => [ + KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => [ key, Number((sanitizedSignals[key] * normalizedWeights[key]).toFixed(6)), ]), - ) as KloRelationshipScoreWeights; - const rawWeightedScore = KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS.reduce((sum, key) => sum + contributions[key], 0); + ) as KtxRelationshipScoreWeights; + const rawWeightedScore = KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS.reduce((sum, key) => sum + contributions[key], 0); const scoredConfidence = sanitizedSignals.typeCompatibility <= 0 ? 0 : 0.56 + rawWeightedScore * 0.65; return { @@ -114,8 +114,8 @@ export function scoreKloRelationshipCandidate( } function averageSignal( - observations: readonly KloRelationshipScoringCalibrationObservation[], - key: KloRelationshipScoreSignal, + observations: readonly KtxRelationshipScoringCalibrationObservation[], + key: KtxRelationshipScoreSignal, ): number { if (observations.length === 0) { return 0; @@ -124,8 +124,8 @@ function averageSignal( } export function calibrateWeightsFromSyntheticFixtures( - observations: readonly KloRelationshipScoringCalibrationObservation[], -): KloRelationshipScoreWeights { + observations: readonly KtxRelationshipScoringCalibrationObservation[], +): KtxRelationshipScoreWeights { const nonSynthetic = observations.find((observation) => observation.origin !== 'synthetic'); if (nonSynthetic) { throw new Error( @@ -133,23 +133,23 @@ export function calibrateWeightsFromSyntheticFixtures( ); } if (observations.length === 0) { - return defaultKloRelationshipScoreWeights(); + return defaultKtxRelationshipScoreWeights(); } const positives = observations.filter((observation) => observation.expectedRelationship); const negatives = observations.filter((observation) => !observation.expectedRelationship); if (positives.length === 0 || negatives.length === 0) { - return defaultKloRelationshipScoreWeights(); + return defaultKtxRelationshipScoreWeights(); } const calibrated = Object.fromEntries( - KLO_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => { + KTX_RELATIONSHIP_SCORE_SIGNAL_KEYS.map((key) => { const positiveAverage = averageSignal(positives, key); const negativeAverage = averageSignal(negatives, key); const separation = Math.max(0, positiveAverage - negativeAverage); return [key, separation + DEFAULT_WEIGHTS[key] * 0.25]; }), - ) as KloRelationshipScoreWeights; + ) as KtxRelationshipScoreWeights; - return normalizeKloRelationshipScoreWeights(calibrated); + return normalizeKtxRelationshipScoreWeights(calibrated); } diff --git a/packages/context/src/scan/relationship-threshold-advice.test.ts b/packages/context/src/scan/relationship-threshold-advice.test.ts index 1943caaa..aceb19c2 100644 --- a/packages/context/src/scan/relationship-threshold-advice.test.ts +++ b/packages/context/src/scan/relationship-threshold-advice.test.ts @@ -1,18 +1,18 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { describe, expect, it, vi } from 'vitest'; import { adviseLocalRelationshipFeedbackThresholds, - buildKloRelationshipThresholdAdviceReport, - formatKloRelationshipThresholdAdviceMarkdown, + buildKtxRelationshipThresholdAdviceReport, + formatKtxRelationshipThresholdAdviceMarkdown, } from './relationship-threshold-advice.js'; import type { ExportLocalRelationshipFeedbackLabelsResult, - KloRelationshipFeedbackLabel, + KtxRelationshipFeedbackLabel, } from './relationship-feedback-export.js'; function label( - input: Partial & Pick, -): KloRelationshipFeedbackLabel { + input: Partial & Pick, +): KtxRelationshipFeedbackLabel { return { schemaVersion: 1, previousStatus: 'review', @@ -37,7 +37,7 @@ function label( }; } -function feedback(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult { +function feedback(labels: KtxRelationshipFeedbackLabel[]): ExportLocalRelationshipFeedbackLabelsResult { return { generatedAt: '2026-05-07T13:00:00.000Z', filters: { connectionId: null, decision: 'all' }, @@ -55,7 +55,7 @@ function feedback(labels: KloRelationshipFeedbackLabel[]): ExportLocalRelationsh describe('relationship threshold advice', () => { it('selects the highest-quality threshold candidate when enough labels exist', () => { - const report = buildKloRelationshipThresholdAdviceReport( + const report = buildKtxRelationshipThresholdAdviceReport( feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', @@ -125,7 +125,7 @@ describe('relationship threshold advice', () => { }); it('reports insufficient labels without hiding evaluated candidates', () => { - const report = buildKloRelationshipThresholdAdviceReport( + const report = buildKtxRelationshipThresholdAdviceReport( feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'accepted', score: 0.91 }), label({ candidateId: 'orders:orders.note_id->notes:notes.id', decision: 'rejected', score: 0.21 }), @@ -157,7 +157,7 @@ describe('relationship threshold advice', () => { }); it('reports no eligible thresholds when label counts pass but quality gates fail', () => { - const report = buildKloRelationshipThresholdAdviceReport( + const report = buildKtxRelationshipThresholdAdviceReport( feedback([ label({ candidateId: 'a', decision: 'accepted', score: 0.92 }), label({ candidateId: 'b', decision: 'accepted', score: 0.58 }), @@ -186,7 +186,7 @@ describe('relationship threshold advice', () => { }); it('wraps the feedback exporter and preserves warnings', async () => { - const project = { projectDir: '/tmp/klo-project' } as KloLocalProject; + const project = { projectDir: '/tmp/ktx-project' } as KtxLocalProject; const exportLocalRelationshipFeedbackLabels = vi.fn(async () => ({ ...feedback([]), warnings: [ @@ -216,7 +216,7 @@ describe('relationship threshold advice', () => { }); it('formats a stable human-readable report', () => { - const report = buildKloRelationshipThresholdAdviceReport( + const report = buildKtxRelationshipThresholdAdviceReport( feedback([ label({ candidateId: 'orders:orders.customer_id->customers:customers.id', decision: 'accepted', score: 0.91 }), label({ candidateId: 'orders:orders.account_id->accounts:accounts.id', decision: 'accepted', score: 0.61 }), @@ -233,9 +233,9 @@ describe('relationship threshold advice', () => { }, ); - expect(formatKloRelationshipThresholdAdviceMarkdown(report)).toContain('KLO relationship threshold advice'); - expect(formatKloRelationshipThresholdAdviceMarkdown(report)).toContain('Status: ready'); - expect(formatKloRelationshipThresholdAdviceMarkdown(report)).toContain('Recommended: accept=0.90 review=0.55'); - expect(formatKloRelationshipThresholdAdviceMarkdown(report)).toContain('acceptedPrecision=1.000'); + expect(formatKtxRelationshipThresholdAdviceMarkdown(report)).toContain('KTX relationship threshold advice'); + expect(formatKtxRelationshipThresholdAdviceMarkdown(report)).toContain('Status: ready'); + expect(formatKtxRelationshipThresholdAdviceMarkdown(report)).toContain('Recommended: accept=0.90 review=0.55'); + expect(formatKtxRelationshipThresholdAdviceMarkdown(report)).toContain('acceptedPrecision=1.000'); }); }); diff --git a/packages/context/src/scan/relationship-threshold-advice.ts b/packages/context/src/scan/relationship-threshold-advice.ts index f5b7141e..d94abc2e 100644 --- a/packages/context/src/scan/relationship-threshold-advice.ts +++ b/packages/context/src/scan/relationship-threshold-advice.ts @@ -1,20 +1,20 @@ -import type { KloLocalProject } from '../project/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { exportLocalRelationshipFeedbackLabels, type ExportLocalRelationshipFeedbackLabelsInput, type ExportLocalRelationshipFeedbackLabelsResult, - type KloRelationshipFeedbackExportWarning, - type KloRelationshipFeedbackLabel, + type KtxRelationshipFeedbackExportWarning, + type KtxRelationshipFeedbackLabel, } from './relationship-feedback-export.js'; -import type { KloResolvedRelationshipStatus } from './relationship-graph-resolver.js'; +import type { KtxResolvedRelationshipStatus } from './relationship-graph-resolver.js'; const DEFAULT_ACCEPT_THRESHOLDS = [0.95, 0.9, 0.85, 0.8, 0.75] as const; const DEFAULT_REVIEW_THRESHOLDS = [0.65, 0.6, 0.55, 0.5, 0.45] as const; -type AdvicePredictedStatus = KloResolvedRelationshipStatus; -export type KloRelationshipThresholdAdviceStatus = 'ready' | 'insufficient_labels' | 'no_eligible_thresholds'; +type AdvicePredictedStatus = KtxResolvedRelationshipStatus; +export type KtxRelationshipThresholdAdviceStatus = 'ready' | 'insufficient_labels' | 'no_eligible_thresholds'; -export interface BuildKloRelationshipThresholdAdviceReportInput { +export interface BuildKtxRelationshipThresholdAdviceReportInput { acceptThresholds?: readonly number[]; reviewThresholds?: readonly number[]; minTotalLabels?: number; @@ -27,11 +27,11 @@ export interface BuildKloRelationshipThresholdAdviceReportInput { export interface AdviseLocalRelationshipFeedbackThresholdsInput extends Omit, - BuildKloRelationshipThresholdAdviceReportInput { + BuildKtxRelationshipThresholdAdviceReportInput { exportLocalRelationshipFeedbackLabels?: typeof exportLocalRelationshipFeedbackLabels; } -export interface KloRelationshipThresholdAdviceCandidate { +export interface KtxRelationshipThresholdAdviceCandidate { acceptThreshold: number; reviewThreshold: number; eligible: boolean; @@ -47,10 +47,10 @@ export interface KloRelationshipThresholdAdviceCandidate { falseRejectedAcceptedLabels: number; } -export interface KloRelationshipThresholdAdviceReport { +export interface KtxRelationshipThresholdAdviceReport { generatedAt: string; filters: ExportLocalRelationshipFeedbackLabelsResult['filters']; - status: KloRelationshipThresholdAdviceStatus; + status: KtxRelationshipThresholdAdviceStatus; gates: { minTotalLabels: number; minAcceptedLabels: number; @@ -68,10 +68,10 @@ export interface KloRelationshipThresholdAdviceReport { evaluatedCandidates: number; eligibleCandidates: number; }; - recommended: KloRelationshipThresholdAdviceCandidate | null; - candidates: KloRelationshipThresholdAdviceCandidate[]; + recommended: KtxRelationshipThresholdAdviceCandidate | null; + candidates: KtxRelationshipThresholdAdviceCandidate[]; reasons: string[]; - warnings: KloRelationshipFeedbackExportWarning[]; + warnings: KtxRelationshipFeedbackExportWarning[]; } interface ResolvedAdviceInput { @@ -85,7 +85,7 @@ interface ResolvedAdviceInput { minRejectedBandPrecision: number; } -function resolveInput(input: BuildKloRelationshipThresholdAdviceReportInput): ResolvedAdviceInput { +function resolveInput(input: BuildKtxRelationshipThresholdAdviceReportInput): ResolvedAdviceInput { return { acceptThresholds: [...(input.acceptThresholds ?? DEFAULT_ACCEPT_THRESHOLDS)].sort((left, right) => right - left), reviewThresholds: [...(input.reviewThresholds ?? DEFAULT_REVIEW_THRESHOLDS)].sort((left, right) => right - left), @@ -121,12 +121,12 @@ function isMetricAtLeast(value: number | null, minimum: number): boolean { } function thresholdCandidate( - labels: readonly KloRelationshipFeedbackLabel[], + labels: readonly KtxRelationshipFeedbackLabel[], acceptThreshold: number, reviewThreshold: number, gates: ResolvedAdviceInput, -): KloRelationshipThresholdAdviceCandidate { - const scored = labels.filter((label): label is KloRelationshipFeedbackLabel & { score: number } => label.score !== null); +): KtxRelationshipThresholdAdviceCandidate { + const scored = labels.filter((label): label is KtxRelationshipFeedbackLabel & { score: number } => label.score !== null); const acceptedLabels = scored.filter((label) => label.decision === 'accepted'); const rejectedLabels = scored.filter((label) => label.decision === 'rejected'); const predictions = scored.map((label) => ({ @@ -182,8 +182,8 @@ function metricRank(value: number | null): number { } function sortCandidates( - candidates: readonly KloRelationshipThresholdAdviceCandidate[], -): KloRelationshipThresholdAdviceCandidate[] { + candidates: readonly KtxRelationshipThresholdAdviceCandidate[], +): KtxRelationshipThresholdAdviceCandidate[] { return [...candidates].sort( (left, right) => Number(right.eligible) - Number(left.eligible) || @@ -195,7 +195,7 @@ function sortCandidates( ); } -function labelGateReasons(labels: readonly KloRelationshipFeedbackLabel[], gates: ResolvedAdviceInput): string[] { +function labelGateReasons(labels: readonly KtxRelationshipFeedbackLabel[], gates: ResolvedAdviceInput): string[] { const scored = labels.filter((label) => label.score !== null); const accepted = scored.filter((label) => label.decision === 'accepted'); const rejected = scored.filter((label) => label.decision === 'rejected'); @@ -212,10 +212,10 @@ function labelGateReasons(labels: readonly KloRelationshipFeedbackLabel[], gates return reasons; } -export function buildKloRelationshipThresholdAdviceReport( +export function buildKtxRelationshipThresholdAdviceReport( feedback: ExportLocalRelationshipFeedbackLabelsResult, - input: BuildKloRelationshipThresholdAdviceReportInput = {}, -): KloRelationshipThresholdAdviceReport { + input: BuildKtxRelationshipThresholdAdviceReportInput = {}, +): KtxRelationshipThresholdAdviceReport { const gates = resolveInput(input); const scored = feedback.labels.filter((label) => label.score !== null); const acceptedLabels = scored.filter((label) => label.decision === 'accepted'); @@ -231,7 +231,7 @@ export function buildKloRelationshipThresholdAdviceReport( ); const labelReasons = labelGateReasons(feedback.labels, gates); const eligibleCandidates = candidates.filter((candidate) => candidate.eligible); - const status: KloRelationshipThresholdAdviceStatus = + const status: KtxRelationshipThresholdAdviceStatus = labelReasons.length > 0 ? 'insufficient_labels' : eligibleCandidates.length > 0 ? 'ready' : 'no_eligible_thresholds'; const reasons = status === 'insufficient_labels' @@ -269,22 +269,22 @@ export function buildKloRelationshipThresholdAdviceReport( } export async function adviseLocalRelationshipFeedbackThresholds( - project: KloLocalProject, + project: KtxLocalProject, input: AdviseLocalRelationshipFeedbackThresholdsInput = {}, -): Promise { +): Promise { const exporter = input.exportLocalRelationshipFeedbackLabels ?? exportLocalRelationshipFeedbackLabels; const feedback = await exporter(project, { connectionId: input.connectionId, decision: 'all', }); - return buildKloRelationshipThresholdAdviceReport(feedback, input); + return buildKtxRelationshipThresholdAdviceReport(feedback, input); } function formatMetric(value: number | null): string { return value === null ? 'n/a' : value.toFixed(3); } -function candidateLine(candidate: KloRelationshipThresholdAdviceCandidate): string { +function candidateLine(candidate: KtxRelationshipThresholdAdviceCandidate): string { return [ `accept=${candidate.acceptThreshold.toFixed(2)}`, `review=${candidate.reviewThreshold.toFixed(2)}`, @@ -299,9 +299,9 @@ function candidateLine(candidate: KloRelationshipThresholdAdviceCandidate): stri ].join(' '); } -export function formatKloRelationshipThresholdAdviceMarkdown(report: KloRelationshipThresholdAdviceReport): string { +export function formatKtxRelationshipThresholdAdviceMarkdown(report: KtxRelationshipThresholdAdviceReport): string { const lines = [ - 'KLO relationship threshold advice', + 'KTX relationship threshold advice', `Generated: ${report.generatedAt}`, `Filter connection: ${report.filters.connectionId ?? 'all'}`, `Status: ${report.status}`, diff --git a/packages/context/src/scan/relationship-validation.test.ts b/packages/context/src/scan/relationship-validation.test.ts index e328a90b..337848a6 100644 --- a/packages/context/src/scan/relationship-validation.test.ts +++ b/packages/context/src/scan/relationship-validation.test.ts @@ -1,17 +1,17 @@ import Database from 'better-sqlite3'; import { afterEach, describe, expect, it } from 'vitest'; -import type { KloEnrichedColumn, KloEnrichedSchema, KloEnrichedTable } from './enrichment-types.js'; -import { generateKloRelationshipDiscoveryCandidates } from './relationship-candidates.js'; -import type { KloRelationshipProfileArtifact } from './relationship-profiling.js'; -import { profileKloRelationshipSchema } from './relationship-profiling.js'; -import { validateKloRelationshipDiscoveryCandidates } from './relationship-validation.js'; -import type { KloQueryResult, KloReadOnlyQueryInput, KloScanContext } from './types.js'; +import type { KtxEnrichedColumn, KtxEnrichedSchema, KtxEnrichedTable } from './enrichment-types.js'; +import { generateKtxRelationshipDiscoveryCandidates } from './relationship-candidates.js'; +import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; +import { profileKtxRelationshipSchema } from './relationship-profiling.js'; +import { validateKtxRelationshipDiscoveryCandidates } from './relationship-validation.js'; +import type { KtxQueryResult, KtxReadOnlyQueryInput, KtxScanContext } from './types.js'; class InMemorySqliteExecutor { readonly db = new Database(':memory:'); queryCount = 0; - executeReadOnly(input: KloReadOnlyQueryInput, _ctx: KloScanContext): Promise { + executeReadOnly(input: KtxReadOnlyQueryInput, _ctx: KtxScanContext): Promise { this.queryCount += 1; const rows = this.db.prepare(input.sql).all() as Record[]; const headers = Object.keys(rows[0] ?? {}); @@ -28,7 +28,7 @@ class InMemorySqliteExecutor { } } -function column(tableId: string, name: string, overrides: Partial = {}): KloEnrichedColumn { +function column(tableId: string, name: string, overrides: Partial = {}): KtxEnrichedColumn { const tableRef = overrides.tableRef ?? { catalog: null, db: null, name: tableId }; return { id: `${tableId}.${name}`, @@ -49,7 +49,7 @@ function column(tableId: string, name: string, overrides: Partial { INSERT INTO invoices (id, account_id) VALUES (20, 1), (21, 2), (22, 999); `); const testSchema = schema(); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, executor, ctx: { runId: 'validate-test' }, }); - const candidates = generateKloRelationshipDiscoveryCandidates(testSchema).filter( + const candidates = generateKtxRelationshipDiscoveryCandidates(testSchema).filter( (candidate) => candidate.from.table.name === 'users', ); - const validated = await validateKloRelationshipDiscoveryCandidates({ + const validated = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates, @@ -145,18 +145,18 @@ describe('relationship validation', () => { INSERT INTO invoices (id, account_id) VALUES (20, 1), (21, 999), (22, 1000); `); const testSchema = schema(); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, executor, ctx: { runId: 'validate-test' }, }); - const candidates = generateKloRelationshipDiscoveryCandidates(testSchema).filter( + const candidates = generateKtxRelationshipDiscoveryCandidates(testSchema).filter( (candidate) => candidate.from.table.name === 'invoices', ); - const validated = await validateKloRelationshipDiscoveryCandidates({ + const validated = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates, @@ -194,7 +194,7 @@ describe('relationship validation', () => { INSERT INTO invoices (id, account_id) VALUES (20, 1), (21, 2), (22, 3); `); const testSchema = schema(); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, @@ -202,12 +202,12 @@ describe('relationship validation', () => { ctx: { runId: 'validate-budget-profile' }, }); executor.queryCount = 0; - const candidates = generateKloRelationshipDiscoveryCandidates(testSchema).map((candidate) => ({ + const candidates = generateKtxRelationshipDiscoveryCandidates(testSchema).map((candidate) => ({ ...candidate, confidence: candidate.from.table.name === 'users' ? 0.99 : 0.5, })); - const validated = await validateKloRelationshipDiscoveryCandidates({ + const validated = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates, @@ -249,7 +249,7 @@ describe('relationship validation', () => { ]), table('users', [column('users', 'id', { nullable: false }), column('users', 'account_id', { nullable: false })]), ]); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, @@ -257,9 +257,9 @@ describe('relationship validation', () => { ctx: { runId: 'validate-zero-budget-profile' }, }); executor.queryCount = 0; - const candidates = generateKloRelationshipDiscoveryCandidates(testSchema); + const candidates = generateKtxRelationshipDiscoveryCandidates(testSchema); - const validated = await validateKloRelationshipDiscoveryCandidates({ + const validated = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates, @@ -296,14 +296,14 @@ describe('relationship validation', () => { table('customers', [column('customers', 'id', { nullable: false })]), table('orders', [column('orders', 'buyer_ref')]), ]); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, executor, ctx: { runId: 'llm-rejected-validation' }, }); - const [candidate] = generateKloRelationshipDiscoveryCandidates( + const [candidate] = generateKtxRelationshipDiscoveryCandidates( schema([ table('customers', [column('customers', 'id', { nullable: false })]), table('orders', [column('orders', 'customer_id')]), @@ -325,7 +325,7 @@ describe('relationship validation', () => { }, }; - const [validated] = await validateKloRelationshipDiscoveryCandidates({ + const [validated] = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates: [llmCandidate], @@ -354,7 +354,7 @@ describe('relationship validation', () => { let active = 0; let maxActive = 0; const throttled = { - executeReadOnly: async (input: KloReadOnlyQueryInput, ctx: KloScanContext) => { + executeReadOnly: async (input: KtxReadOnlyQueryInput, ctx: KtxScanContext) => { active += 1; maxActive = Math.max(maxActive, active); await new Promise((resolve) => setTimeout(resolve, input.sql.includes('WITH child_values') ? 10 : 0)); @@ -369,16 +369,16 @@ describe('relationship validation', () => { table('orders', [column('orders', 'id', { nullable: false }), column('orders', 'account_id')]), table('invoices', [column('invoices', 'id', { nullable: false }), column('invoices', 'account_id')]), ]); - const profiles = await profileKloRelationshipSchema({ + const profiles = await profileKtxRelationshipSchema({ connectionId: 'warehouse', driver: 'sqlite', schema: testSchema, executor, ctx: { runId: 'validation-concurrency-profile' }, }); - const candidates = generateKloRelationshipDiscoveryCandidates(testSchema); + const candidates = generateKtxRelationshipDiscoveryCandidates(testSchema); - await validateKloRelationshipDiscoveryCandidates({ + await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates, @@ -457,7 +457,7 @@ describe('relationship validation', () => { maxTextLength: 10, }, }, - } satisfies KloRelationshipProfileArtifact; + } satisfies KtxRelationshipProfileArtifact; const executor = { async executeReadOnly() { return { @@ -469,7 +469,7 @@ describe('relationship validation', () => { }, }; - const [validated] = await validateKloRelationshipDiscoveryCandidates({ + const [validated] = await validateKtxRelationshipDiscoveryCandidates({ connectionId: 'warehouse', driver: 'sqlite', candidates: [candidate], diff --git a/packages/context/src/scan/relationship-validation.ts b/packages/context/src/scan/relationship-validation.ts index 209bdab0..d1bb91d8 100644 --- a/packages/context/src/scan/relationship-validation.ts +++ b/packages/context/src/scan/relationship-validation.ts @@ -1,17 +1,17 @@ -import type { KloRelationshipEndpoint } from './enrichment-types.js'; -import { applyKloRelationshipValidationBudget, type KloRelationshipValidationBudget } from './relationship-budget.js'; -import type { KloRelationshipDiscoveryCandidate } from './relationship-candidates.js'; +import type { KtxRelationshipEndpoint } from './enrichment-types.js'; +import { applyKtxRelationshipValidationBudget, type KtxRelationshipValidationBudget } from './relationship-budget.js'; +import type { KtxRelationshipDiscoveryCandidate } from './relationship-candidates.js'; import { - formatKloRelationshipTableRef, - type KloRelationshipProfileArtifact, - type KloRelationshipReadOnlyExecutor, - quoteKloRelationshipIdentifier, + formatKtxRelationshipTableRef, + type KtxRelationshipProfileArtifact, + type KtxRelationshipReadOnlyExecutor, + quoteKtxRelationshipIdentifier, } from './relationship-profiling.js'; -import type { KloConnectionDriver, KloQueryResult, KloScanContext } from './types.js'; +import type { KtxConnectionDriver, KtxQueryResult, KtxScanContext } from './types.js'; -export type KloValidatedRelationshipStatus = 'accepted' | 'review' | 'rejected'; +export type KtxValidatedRelationshipStatus = 'accepted' | 'review' | 'rejected'; -export interface KloRelationshipValidationSettings { +export interface KtxRelationshipValidationSettings { acceptThreshold: number; reviewThreshold: number; minTargetUniqueness: number; @@ -19,10 +19,10 @@ export interface KloRelationshipValidationSettings { maxViolationRatio: number; maxDistinctSourceValues: number; concurrency: number; - validationBudget?: KloRelationshipValidationBudget; + validationBudget?: KtxRelationshipValidationBudget; } -export interface KloRelationshipValidationEvidence { +export interface KtxRelationshipValidationEvidence { targetUniqueness: number; sourceCoverage: number; violationCount: number; @@ -36,25 +36,25 @@ export interface KloRelationshipValidationEvidence { reasons: string[]; } -export interface KloValidatedRelationshipDiscoveryCandidate - extends Omit { - status: KloValidatedRelationshipStatus; +export interface KtxValidatedRelationshipDiscoveryCandidate + extends Omit { + status: KtxValidatedRelationshipStatus; score: number; - validation: KloRelationshipValidationEvidence; + validation: KtxRelationshipValidationEvidence; } -export interface ValidateKloRelationshipDiscoveryCandidatesInput { +export interface ValidateKtxRelationshipDiscoveryCandidatesInput { connectionId: string; - driver: KloConnectionDriver; - candidates: readonly KloRelationshipDiscoveryCandidate[]; - profiles: KloRelationshipProfileArtifact; - executor: KloRelationshipReadOnlyExecutor | null; - ctx: KloScanContext; + driver: KtxConnectionDriver; + candidates: readonly KtxRelationshipDiscoveryCandidate[]; + profiles: KtxRelationshipProfileArtifact; + executor: KtxRelationshipReadOnlyExecutor | null; + ctx: KtxScanContext; tableCount?: number; - settings?: Partial; + settings?: Partial; } -const DEFAULT_SETTINGS: KloRelationshipValidationSettings = { +const DEFAULT_SETTINGS: KtxRelationshipValidationSettings = { acceptThreshold: 0.85, reviewThreshold: 0.55, minTargetUniqueness: 0.9, @@ -65,8 +65,8 @@ const DEFAULT_SETTINGS: KloRelationshipValidationSettings = { }; function mergeSettings( - settings: Partial | undefined, -): KloRelationshipValidationSettings { + settings: Partial | undefined, +): KtxRelationshipValidationSettings { return { ...DEFAULT_SETTINGS, ...settings }; } @@ -74,7 +74,7 @@ function profileKey(table: string, column: string): string { return `${table}.${column}`; } -function singleRelationshipColumn(endpointValue: KloRelationshipEndpoint): string { +function singleRelationshipColumn(endpointValue: KtxRelationshipEndpoint): string { const column = endpointValue.columns[0]; if (!column) { throw new Error(`Expected relationship endpoint ${endpointValue.table.name} to contain one column`); @@ -82,15 +82,15 @@ function singleRelationshipColumn(endpointValue: KloRelationshipEndpoint): strin return column; } -function headerIndex(result: KloQueryResult, header: string): number { +function headerIndex(result: KtxQueryResult, header: string): number { return result.headers.findIndex((candidate) => candidate.toLowerCase() === header.toLowerCase()); } -function firstRow(result: KloQueryResult): unknown[] { +function firstRow(result: KtxQueryResult): unknown[] { return result.rows[0] ?? []; } -function numberAt(result: KloQueryResult, header: string): number { +function numberAt(result: KtxQueryResult, header: string): number { const value = firstRow(result)[headerIndex(result, header)]; if (typeof value === 'number') { return value; @@ -104,14 +104,14 @@ function numberAt(result: KloQueryResult, header: string): number { return 0; } -function limitSql(driver: KloConnectionDriver, limit: number): string { +function limitSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ''; } return ` LIMIT ${Math.max(1, Math.floor(limit))}`; } -function topSql(driver: KloConnectionDriver, limit: number): string { +function topSql(driver: KtxConnectionDriver, limit: number): string { if (driver === 'sqlserver') { return ` TOP (${Math.max(1, Math.floor(limit))})`; } @@ -119,17 +119,17 @@ function topSql(driver: KloConnectionDriver, limit: number): string { } function buildCoverageSql(input: { - driver: KloConnectionDriver; + driver: KtxConnectionDriver; childTable: string; childColumn: string; parentTable: string; parentColumn: string; maxDistinctSourceValues: number; }): string { - const childTable = formatKloRelationshipTableRef(input.driver, { catalog: null, db: null, name: input.childTable }); - const parentTable = formatKloRelationshipTableRef(input.driver, { catalog: null, db: null, name: input.parentTable }); - const childColumn = quoteKloRelationshipIdentifier(input.driver, input.childColumn); - const parentColumn = quoteKloRelationshipIdentifier(input.driver, input.parentColumn); + const childTable = formatKtxRelationshipTableRef(input.driver, { catalog: null, db: null, name: input.childTable }); + const parentTable = formatKtxRelationshipTableRef(input.driver, { catalog: null, db: null, name: input.parentTable }); + const childColumn = quoteKtxRelationshipIdentifier(input.driver, input.childColumn); + const parentColumn = quoteKtxRelationshipIdentifier(input.driver, input.parentColumn); const limit = limitSql(input.driver, input.maxDistinctSourceValues); const top = topSql(input.driver, input.maxDistinctSourceValues); @@ -170,8 +170,8 @@ function score(input: { function statusFor(input: { score: number; reasons: readonly string[]; - settings: KloRelationshipValidationSettings; -}): KloValidatedRelationshipStatus { + settings: KtxRelationshipValidationSettings; +}): KtxValidatedRelationshipStatus { if ( input.reasons.includes('low_target_uniqueness') || input.reasons.includes('low_source_coverage') || @@ -215,10 +215,10 @@ async function mapWithConcurrency( } function reviewWithoutValidation( - candidate: KloRelationshipDiscoveryCandidate, - profiles: KloRelationshipProfileArtifact, + candidate: KtxRelationshipDiscoveryCandidate, + profiles: KtxRelationshipProfileArtifact, reason: 'validation_unavailable' | 'profile_unavailable' | 'validation_unattempted', -): KloValidatedRelationshipDiscoveryCandidate { +): KtxValidatedRelationshipDiscoveryCandidate { const sourceColumn = singleRelationshipColumn(candidate.from); const targetColumn = singleRelationshipColumn(candidate.to); const sourceProfile = profiles.columns[profileKey(candidate.from.table.name, sourceColumn)]; @@ -244,9 +244,9 @@ function reviewWithoutValidation( }; } -export async function validateKloRelationshipDiscoveryCandidates( - input: ValidateKloRelationshipDiscoveryCandidatesInput, -): Promise { +export async function validateKtxRelationshipDiscoveryCandidates( + input: ValidateKtxRelationshipDiscoveryCandidatesInput, +): Promise { const settings = mergeSettings(input.settings); if (!input.executor || !input.profiles.sqlAvailable) { return input.candidates.map((candidate) => @@ -257,8 +257,8 @@ export async function validateKloRelationshipDiscoveryCandidates( const executor = input.executor; async function validateCandidate( - candidate: KloRelationshipDiscoveryCandidate, - ): Promise { + candidate: KtxRelationshipDiscoveryCandidate, + ): Promise { const sourceColumn = singleRelationshipColumn(candidate.from); const targetColumn = singleRelationshipColumn(candidate.to); const sourceProfile = input.profiles.columns[profileKey(candidate.from.table.name, sourceColumn)]; @@ -334,7 +334,7 @@ export async function validateKloRelationshipDiscoveryCandidates( }; } - const budgeted = applyKloRelationshipValidationBudget({ + const budgeted = applyKtxRelationshipValidationBudget({ candidates: input.candidates, tableCount: input.tableCount ?? 0, budget: settings.validationBudget ?? (input.tableCount === undefined ? 'all' : undefined), @@ -345,7 +345,7 @@ export async function validateKloRelationshipDiscoveryCandidates( settings.concurrency, validateCandidate, ); - const byOriginalIndex = new Map(); + const byOriginalIndex = new Map(); for (let index = 0; index < budgeted.toValidate.length; index += 1) { const originalIndex = budgeted.toValidate[index]?.originalIndex; const candidate = validated[index]; diff --git a/packages/context/src/scan/sqlite-local-enrichment-state-store.ts b/packages/context/src/scan/sqlite-local-enrichment-state-store.ts index 886d97aa..e4570de4 100644 --- a/packages/context/src/scan/sqlite-local-enrichment-state-store.ts +++ b/packages/context/src/scan/sqlite-local-enrichment-state-store.ts @@ -2,13 +2,13 @@ import { mkdirSync } from 'node:fs'; import { dirname } from 'node:path'; import Database from 'better-sqlite3'; import type { - KloScanEnrichmentCompletedStage, - KloScanEnrichmentFailedStage, - KloScanEnrichmentStageLookup, - KloScanEnrichmentStageRecord, - KloScanEnrichmentStateStore, + KtxScanEnrichmentCompletedStage, + KtxScanEnrichmentFailedStage, + KtxScanEnrichmentStageLookup, + KtxScanEnrichmentStageRecord, + KtxScanEnrichmentStateStore, } from './enrichment-state.js'; -import type { KloScanEnrichmentStage, KloScanMode } from './types.js'; +import type { KtxScanEnrichmentStage, KtxScanMode } from './types.js'; export interface SqliteLocalScanEnrichmentStateStoreOptions { dbPath: string; @@ -18,8 +18,8 @@ interface StageRow { run_id: string; connection_id: string; sync_id: string; - mode: KloScanMode; - stage: KloScanEnrichmentStage; + mode: KtxScanMode; + stage: KtxScanEnrichmentStage; input_hash: string; status: 'completed' | 'failed'; output_json: string | null; @@ -27,7 +27,7 @@ interface StageRow { updated_at: string; } -function parseStageRow(row: StageRow): KloScanEnrichmentStageRecord { +function parseStageRow(row: StageRow): KtxScanEnrichmentStageRecord { if (row.status === 'completed') { return { runId: row.run_id, @@ -61,7 +61,7 @@ function isSafeRunId(runId: string): boolean { return /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(runId); } -export class SqliteLocalScanEnrichmentStateStore implements KloScanEnrichmentStateStore { +export class SqliteLocalScanEnrichmentStateStore implements KtxScanEnrichmentStateStore { private readonly db: Database.Database; constructor(options: SqliteLocalScanEnrichmentStateStoreOptions) { @@ -89,8 +89,8 @@ export class SqliteLocalScanEnrichmentStateStore implements KloScanEnrichmentSta } async findCompletedStage( - input: KloScanEnrichmentStageLookup, - ): Promise | null> { + input: KtxScanEnrichmentStageLookup, + ): Promise | null> { if (!isSafeRunId(input.runId)) { return null; } @@ -115,7 +115,7 @@ export class SqliteLocalScanEnrichmentStateStore implements KloScanEnrichmentSta } async saveCompletedStage( - input: Omit, 'status' | 'errorMessage'>, + input: Omit, 'status' | 'errorMessage'>, ): Promise { this.db .prepare( @@ -167,7 +167,7 @@ export class SqliteLocalScanEnrichmentStateStore implements KloScanEnrichmentSta }); } - async saveFailedStage(input: Omit): Promise { + async saveFailedStage(input: Omit): Promise { this.db .prepare( ` @@ -218,7 +218,7 @@ export class SqliteLocalScanEnrichmentStateStore implements KloScanEnrichmentSta }); } - async listRunStages(runId: string): Promise { + async listRunStages(runId: string): Promise { if (!isSafeRunId(runId)) { return []; } diff --git a/packages/context/src/scan/type-normalization.test.ts b/packages/context/src/scan/type-normalization.test.ts index bedb4416..5dc0adf2 100644 --- a/packages/context/src/scan/type-normalization.test.ts +++ b/packages/context/src/scan/type-normalization.test.ts @@ -1,22 +1,22 @@ import { describe, expect, it } from 'vitest'; -import { inferKloDimensionType, kloColumnTypeMappingFromNative, normalizeKloNativeType } from './type-normalization.js'; +import { inferKtxDimensionType, ktxColumnTypeMappingFromNative, normalizeKtxNativeType } from './type-normalization.js'; -describe('KLO scan type normalization', () => { +describe('KTX scan type normalization', () => { it('normalizes native database type strings', () => { - expect(normalizeKloNativeType(' NUMERIC(12, 2) ')).toBe('numeric'); - expect(normalizeKloNativeType('TIMESTAMP WITH TIME ZONE')).toBe('timestamp with time zone'); - expect(normalizeKloNativeType('')).toBe('unknown'); + expect(normalizeKtxNativeType(' NUMERIC(12, 2) ')).toBe('numeric'); + expect(normalizeKtxNativeType('TIMESTAMP WITH TIME ZONE')).toBe('timestamp with time zone'); + expect(normalizeKtxNativeType('')).toBe('unknown'); }); it('infers dimension types from native types', () => { - expect(inferKloDimensionType('BOOLEAN')).toBe('boolean'); - expect(inferKloDimensionType('timestamp with time zone')).toBe('time'); - expect(inferKloDimensionType('decimal(10,2)')).toBe('number'); - expect(inferKloDimensionType('varchar(255)')).toBe('string'); + expect(inferKtxDimensionType('BOOLEAN')).toBe('boolean'); + expect(inferKtxDimensionType('timestamp with time zone')).toBe('time'); + expect(inferKtxDimensionType('decimal(10,2)')).toBe('number'); + expect(inferKtxDimensionType('varchar(255)')).toBe('string'); }); it('builds a complete column type mapping', () => { - expect(kloColumnTypeMappingFromNative('BIGINT')).toEqual({ + expect(ktxColumnTypeMappingFromNative('BIGINT')).toEqual({ normalizedType: 'bigint', dimensionType: 'number', }); diff --git a/packages/context/src/scan/type-normalization.ts b/packages/context/src/scan/type-normalization.ts index cb075d44..5c696339 100644 --- a/packages/context/src/scan/type-normalization.ts +++ b/packages/context/src/scan/type-normalization.ts @@ -1,17 +1,17 @@ -import type { KloSchemaDimensionType } from './types.js'; +import type { KtxSchemaDimensionType } from './types.js'; -export interface KloColumnTypeMapping { +export interface KtxColumnTypeMapping { normalizedType: string; - dimensionType: KloSchemaDimensionType; + dimensionType: KtxSchemaDimensionType; } -export function normalizeKloNativeType(nativeType: string): string { +export function normalizeKtxNativeType(nativeType: string): string { const normalized = nativeType.toLowerCase().replace(/\([^)]*\)/g, '').replace(/\s+/g, ' ').trim(); return normalized.length > 0 ? normalized : 'unknown'; } -export function inferKloDimensionType(nativeType: string): KloSchemaDimensionType { - const normalized = normalizeKloNativeType(nativeType); +export function inferKtxDimensionType(nativeType: string): KtxSchemaDimensionType { + const normalized = normalizeKtxNativeType(nativeType); if (/\b(bool|boolean)\b/.test(normalized)) { return 'boolean'; } @@ -24,9 +24,9 @@ export function inferKloDimensionType(nativeType: string): KloSchemaDimensionTyp return 'string'; } -export function kloColumnTypeMappingFromNative(nativeType: string): KloColumnTypeMapping { +export function ktxColumnTypeMappingFromNative(nativeType: string): KtxColumnTypeMapping { return { - normalizedType: normalizeKloNativeType(nativeType), - dimensionType: inferKloDimensionType(nativeType), + normalizedType: normalizeKtxNativeType(nativeType), + dimensionType: inferKtxDimensionType(nativeType), }; } diff --git a/packages/context/src/scan/types.test.ts b/packages/context/src/scan/types.test.ts index 3918e5d0..3b68411a 100644 --- a/packages/context/src/scan/types.test.ts +++ b/packages/context/src/scan/types.test.ts @@ -1,25 +1,25 @@ import { describe, expect, it } from 'vitest'; import { - createKloConnectorCapabilities, - type KloEventPropertyDiscovery, - type KloEventPropertyDiscoveryInput, - type KloEventPropertyValuesInput, - type KloEventPropertyValuesResult, - type KloEventStreamDiscoveryPort, - type KloEventTypeDiscovery, - type KloEventTypeDiscoveryInput, - type KloNetworkEndpoint, - type KloNetworkTunnelPort, - type KloQueryResult, - type KloScanConnector, - type KloScanContext, - type KloScanInput, - type KloSchemaSnapshot, + createKtxConnectorCapabilities, + type KtxEventPropertyDiscovery, + type KtxEventPropertyDiscoveryInput, + type KtxEventPropertyValuesInput, + type KtxEventPropertyValuesResult, + type KtxEventStreamDiscoveryPort, + type KtxEventTypeDiscovery, + type KtxEventTypeDiscoveryInput, + type KtxNetworkEndpoint, + type KtxNetworkTunnelPort, + type KtxQueryResult, + type KtxScanConnector, + type KtxScanContext, + type KtxScanInput, + type KtxSchemaSnapshot, } from './types.js'; -describe('KLO scan contract types', () => { +describe('KTX scan contract types', () => { it('defaults to structural-only connector capabilities', () => { - expect(createKloConnectorCapabilities()).toEqual({ + expect(createKtxConnectorCapabilities()).toEqual({ structuralIntrospection: true, tableSampling: false, columnSampling: false, @@ -34,7 +34,7 @@ describe('KLO scan contract types', () => { it('keeps structural introspection mandatory when optional capabilities are enabled', () => { expect( - createKloConnectorCapabilities({ + createKtxConnectorCapabilities({ tableSampling: true, readOnlySql: true, eventStreamDiscovery: true, @@ -54,7 +54,7 @@ describe('KLO scan contract types', () => { }); it('describes the connector surface without requiring enrichment methods', async () => { - const snapshot: KloSchemaSnapshot = { + const snapshot: KtxSchemaSnapshot = { connectionId: 'warehouse', driver: 'postgres', extractedAt: '2026-04-29T00:00:00.000Z', @@ -84,11 +84,11 @@ describe('KLO scan contract types', () => { ], }; - const connector: KloScanConnector = { + const connector: KtxScanConnector = { id: 'test-postgres', driver: 'postgres', - capabilities: createKloConnectorCapabilities({ estimatedRowCounts: true }), - async introspect(input: KloScanInput, ctx: KloScanContext) { + capabilities: createKtxConnectorCapabilities({ estimatedRowCounts: true }), + async introspect(input: KtxScanInput, ctx: KtxScanContext) { expect(input.connectionId).toBe('warehouse'); expect(ctx.runId).toBe('scan-run-1'); return snapshot; @@ -109,11 +109,11 @@ describe('KLO scan contract types', () => { }); it('models optional event-stream discovery as a connector capability and port', async () => { - const eventTypes: KloEventTypeDiscovery[] = [{ value: '$pageview', count: 42 }]; - const propertyKeys: KloEventPropertyDiscovery[] = [{ key: '$browser', count: 31 }]; - const propertyValues: KloEventPropertyValuesResult = { values: ['Chrome', 'Safari'], cardinality: 2 }; - const discovery: KloEventStreamDiscoveryPort = { - async listEventTypes(input: KloEventTypeDiscoveryInput) { + const eventTypes: KtxEventTypeDiscovery[] = [{ value: '$pageview', count: 42 }]; + const propertyKeys: KtxEventPropertyDiscovery[] = [{ key: '$browser', count: 31 }]; + const propertyValues: KtxEventPropertyValuesResult = { values: ['Chrome', 'Safari'], cardinality: 2 }; + const discovery: KtxEventStreamDiscoveryPort = { + async listEventTypes(input: KtxEventTypeDiscoveryInput) { expect(input).toEqual({ connectionId: 'product', table: { catalog: '157881', db: null, name: 'events' }, @@ -124,7 +124,7 @@ describe('KLO scan contract types', () => { }); return eventTypes; }, - async listPropertyKeys(input: KloEventPropertyDiscoveryInput) { + async listPropertyKeys(input: KtxEventPropertyDiscoveryInput) { expect(input).toEqual({ connectionId: 'product', table: { catalog: '157881', db: null, name: 'events' }, @@ -135,7 +135,7 @@ describe('KLO scan contract types', () => { }); return propertyKeys; }, - async listPropertyValues(input: KloEventPropertyValuesInput) { + async listPropertyValues(input: KtxEventPropertyValuesInput) { expect(input).toEqual({ connectionId: 'product', table: { catalog: '157881', db: null, name: 'events' }, @@ -149,10 +149,10 @@ describe('KLO scan contract types', () => { }, }; - const connector: KloScanConnector = { + const connector: KtxScanConnector = { id: 'posthog:product', driver: 'posthog', - capabilities: createKloConnectorCapabilities({ eventStreamDiscovery: true }), + capabilities: createKtxConnectorCapabilities({ eventStreamDiscovery: true }), eventStreamDiscovery: discovery, async introspect() { return { @@ -209,7 +209,7 @@ describe('KLO scan contract types', () => { }); it('keeps read-only query results separate from schema snapshots', () => { - const result: KloQueryResult = { + const result: KtxQueryResult = { headers: ['id', 'amount'], headerTypes: ['integer', 'numeric'], rows: [[1, 10.5]], @@ -227,12 +227,12 @@ describe('KLO scan contract types', () => { }); it('models host-provided network tunnel endpoint resolution without app imports', async () => { - const endpoint: KloNetworkEndpoint = { + const endpoint: KtxNetworkEndpoint = { host: '127.0.0.1', port: 15432, close: async () => undefined, }; - const tunnelPort: KloNetworkTunnelPort<{ networkProxy?: { type: 'ssh_tunnel' } }> = { + const tunnelPort: KtxNetworkTunnelPort<{ networkProxy?: { type: 'ssh_tunnel' } }> = { async resolveEndpoint(input) { expect(input).toEqual({ connectionId: 'warehouse', diff --git a/packages/context/src/scan/types.ts b/packages/context/src/scan/types.ts index bf08a0ab..66f70ba2 100644 --- a/packages/context/src/scan/types.ts +++ b/packages/context/src/scan/types.ts @@ -1,4 +1,4 @@ -export type KloConnectionDriver = +export type KtxConnectionDriver = | 'sqlite' | 'postgres' | 'postgresql' @@ -9,11 +9,11 @@ export type KloConnectionDriver = | 'mysql' | 'clickhouse'; -export type KloScanMode = 'structural' | 'relationships' | 'enriched'; +export type KtxScanMode = 'structural' | 'relationships' | 'enriched'; -export type KloScanTrigger = 'cli' | 'mcp' | 'schema_scan' | 'scheduled' | 'manual'; +export type KtxScanTrigger = 'cli' | 'mcp' | 'schema_scan' | 'scheduled' | 'manual'; -export interface KloConnectorCapabilities { +export interface KtxConnectorCapabilities { structuralIntrospection: true; tableSampling: boolean; columnSampling: boolean; @@ -25,11 +25,11 @@ export interface KloConnectorCapabilities { estimatedRowCounts: boolean; } -export type KloOptionalConnectorCapabilities = Partial>; +export type KtxOptionalConnectorCapabilities = Partial>; -export function createKloConnectorCapabilities( - capabilities: KloOptionalConnectorCapabilities = {}, -): KloConnectorCapabilities { +export function createKtxConnectorCapabilities( + capabilities: KtxOptionalConnectorCapabilities = {}, +): KtxConnectorCapabilities { return { structuralIntrospection: true, tableSampling: capabilities.tableSampling ?? false, @@ -43,27 +43,27 @@ export function createKloConnectorCapabilities( }; } -export interface KloSchemaScope { +export interface KtxSchemaScope { catalogs?: string[]; schemas?: string[]; datasets?: string[]; } -export type KloSchemaTableKind = 'table' | 'view' | 'external' | 'event_stream'; +export type KtxSchemaTableKind = 'table' | 'view' | 'external' | 'event_stream'; -export type KloSchemaDimensionType = 'time' | 'string' | 'number' | 'boolean'; +export type KtxSchemaDimensionType = 'time' | 'string' | 'number' | 'boolean'; -export interface KloSchemaColumn { +export interface KtxSchemaColumn { name: string; nativeType: string; normalizedType: string; - dimensionType: KloSchemaDimensionType; + dimensionType: KtxSchemaDimensionType; nullable: boolean; primaryKey: boolean; comment: string | null; } -export interface KloSchemaForeignKey { +export interface KtxSchemaForeignKey { fromColumn: string; toCatalog: string | null; toDb: string | null; @@ -72,139 +72,139 @@ export interface KloSchemaForeignKey { constraintName: string | null; } -export interface KloSchemaTable { +export interface KtxSchemaTable { catalog: string | null; db: string | null; name: string; - kind: KloSchemaTableKind; + kind: KtxSchemaTableKind; comment: string | null; estimatedRows: number | null; - columns: KloSchemaColumn[]; - foreignKeys: KloSchemaForeignKey[]; + columns: KtxSchemaColumn[]; + foreignKeys: KtxSchemaForeignKey[]; } -export interface KloSchemaSnapshot { +export interface KtxSchemaSnapshot { connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; extractedAt: string; - scope: KloSchemaScope; - tables: KloSchemaTable[]; + scope: KtxSchemaScope; + tables: KtxSchemaTable[]; metadata: Record; } -export interface KloCredentialEnvReference { +export interface KtxCredentialEnvReference { kind: 'env'; name: string; } -export interface KloCredentialFileReference { +export interface KtxCredentialFileReference { kind: 'file'; path: string; } -export interface KloResolvedCredentialEnvelope { +export interface KtxResolvedCredentialEnvelope { kind: 'resolved'; source: 'standalone' | 'host'; values: Record; redacted?: boolean; } -export type KloCredentialEnvelope = - | KloCredentialEnvReference - | KloCredentialFileReference - | KloResolvedCredentialEnvelope; +export type KtxCredentialEnvelope = + | KtxCredentialEnvReference + | KtxCredentialFileReference + | KtxResolvedCredentialEnvelope; -export interface KloNetworkEndpoint { +export interface KtxNetworkEndpoint { host: string; port: number; close?: () => Promise; } -export interface KloNetworkTunnelRequest> { +export interface KtxNetworkTunnelRequest> { connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; host: string; port: number; connection: TConnection; } -export interface KloNetworkTunnelPort> { - resolveEndpoint(input: KloNetworkTunnelRequest): Promise; +export interface KtxNetworkTunnelPort> { + resolveEndpoint(input: KtxNetworkTunnelRequest): Promise; } -export interface KloScanInput { +export interface KtxScanInput { connectionId: string; - driver: KloConnectionDriver; - scope?: KloSchemaScope; - mode?: KloScanMode; + driver: KtxConnectionDriver; + scope?: KtxSchemaScope; + mode?: KtxScanMode; dryRun?: boolean; detectRelationships?: boolean; - credentials?: KloCredentialEnvelope; + credentials?: KtxCredentialEnvelope; metadata?: Record; } -export interface KloProgressUpdateOptions { +export interface KtxProgressUpdateOptions { transient?: boolean; } -export interface KloProgressPort { - update(progress: number, message?: string, options?: KloProgressUpdateOptions): Promise; - startPhase(weight: number): KloProgressPort; +export interface KtxProgressPort { + update(progress: number, message?: string, options?: KtxProgressUpdateOptions): Promise; + startPhase(weight: number): KtxProgressPort; } -export interface KloScanLoggerPort { +export interface KtxScanLoggerPort { debug(message: string, metadata?: Record): void; info(message: string, metadata?: Record): void; warn(message: string, metadata?: Record): void; error(message: string, metadata?: Record): void; } -export interface KloScanContext { +export interface KtxScanContext { runId: string; signal?: AbortSignal; - progress?: KloProgressPort; - logger?: KloScanLoggerPort; + progress?: KtxProgressPort; + logger?: KtxScanLoggerPort; } -export interface KloTableRef { +export interface KtxTableRef { catalog: string | null; db: string | null; name: string; } -export interface KloTableSampleInput { +export interface KtxTableSampleInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; columns?: string[]; limit: number; } -export interface KloTableSampleResult { +export interface KtxTableSampleResult { headers: string[]; rows: unknown[][]; totalRows: number; } -export interface KloColumnSampleInput { +export interface KtxColumnSampleInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; column: string; limit: number; } -export interface KloColumnSampleResult { +export interface KtxColumnSampleResult { values: unknown[]; nullCount: number | null; distinctCount: number | null; } -export interface KloColumnStatsInput { +export interface KtxColumnStatsInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; column: string; } -export interface KloColumnStatsResult { +export interface KtxColumnStatsResult { min: unknown; max: unknown; average: number | null; @@ -212,37 +212,37 @@ export interface KloColumnStatsResult { distinctCount: number | null; } -export interface KloEventTypeDiscoveryInput { +export interface KtxEventTypeDiscoveryInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; eventColumn: string; limit: number; minCount?: number; lookbackDays?: number; } -export interface KloEventTypeDiscovery { +export interface KtxEventTypeDiscovery { value: string; count: number; } -export interface KloEventPropertyDiscoveryInput { +export interface KtxEventPropertyDiscoveryInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; jsonColumn: string; sampleSize: number; limit: number; lookbackDays?: number; } -export interface KloEventPropertyDiscovery { +export interface KtxEventPropertyDiscovery { key: string; count: number; } -export interface KloEventPropertyValuesInput { +export interface KtxEventPropertyValuesInput { connectionId: string; - table: KloTableRef; + table: KtxTableRef; jsonColumn: string; propertyKey: string; limit: number; @@ -250,27 +250,27 @@ export interface KloEventPropertyValuesInput { lookbackDays?: number; } -export interface KloEventPropertyValuesResult { +export interface KtxEventPropertyValuesResult { values: string[]; cardinality: number; } -export interface KloEventStreamDiscoveryPort { - listEventTypes(input: KloEventTypeDiscoveryInput, ctx: KloScanContext): Promise; - listPropertyKeys(input: KloEventPropertyDiscoveryInput, ctx: KloScanContext): Promise; +export interface KtxEventStreamDiscoveryPort { + listEventTypes(input: KtxEventTypeDiscoveryInput, ctx: KtxScanContext): Promise; + listPropertyKeys(input: KtxEventPropertyDiscoveryInput, ctx: KtxScanContext): Promise; listPropertyValues( - input: KloEventPropertyValuesInput, - ctx: KloScanContext, - ): Promise; + input: KtxEventPropertyValuesInput, + ctx: KtxScanContext, + ): Promise; } -export interface KloReadOnlyQueryInput { +export interface KtxReadOnlyQueryInput { connectionId: string; sql: string; maxRows?: number; } -export interface KloQueryResult { +export interface KtxQueryResult { headers: string[]; headerTypes?: string[]; rows: unknown[][]; @@ -278,26 +278,26 @@ export interface KloQueryResult { rowCount: number | null; } -export interface KloScanConnector { +export interface KtxScanConnector { id: string; - driver: KloConnectionDriver; - capabilities: KloConnectorCapabilities; - eventStreamDiscovery?: KloEventStreamDiscoveryPort; - introspect(input: KloScanInput, ctx: KloScanContext): Promise; - sampleColumn?(input: KloColumnSampleInput, ctx: KloScanContext): Promise; - sampleTable?(input: KloTableSampleInput, ctx: KloScanContext): Promise; - columnStats?(input: KloColumnStatsInput, ctx: KloScanContext): Promise; - executeReadOnly?(input: KloReadOnlyQueryInput, ctx: KloScanContext): Promise; + driver: KtxConnectionDriver; + capabilities: KtxConnectorCapabilities; + eventStreamDiscovery?: KtxEventStreamDiscoveryPort; + introspect(input: KtxScanInput, ctx: KtxScanContext): Promise; + sampleColumn?(input: KtxColumnSampleInput, ctx: KtxScanContext): Promise; + sampleTable?(input: KtxTableSampleInput, ctx: KtxScanContext): Promise; + columnStats?(input: KtxColumnStatsInput, ctx: KtxScanContext): Promise; + executeReadOnly?(input: KtxReadOnlyQueryInput, ctx: KtxScanContext): Promise; cleanup?(): Promise; } -export interface KloEmbeddingPort { +export interface KtxEmbeddingPort { dimensions: number; maxBatchSize: number; embedBatch(texts: string[]): Promise; } -export interface KloStructuralSyncStats { +export interface KtxStructuralSyncStats { tablesCreated: number; tablesUpdated: number; tablesDeleted: number; @@ -306,7 +306,7 @@ export interface KloStructuralSyncStats { columnsDeleted: number; } -export interface KloScanDiffSummary { +export interface KtxScanDiffSummary { tablesAdded: number; tablesModified: number; tablesDeleted: number; @@ -316,14 +316,14 @@ export interface KloScanDiffSummary { columnsDeleted: number; } -export interface KloScanArtifactPaths { +export interface KtxScanArtifactPaths { rawSourcesDir: string | null; reportPath: string | null; manifestShards: string[]; enrichmentArtifacts: string[]; } -export type KloScanWarningCode = +export type KtxScanWarningCode = | 'connector_capability_missing' | 'sampling_failed' | 'statistics_failed' @@ -336,8 +336,8 @@ export type KloScanWarningCode = | 'credential_redacted' | 'enrichment_failed'; -export interface KloScanWarning { - code: KloScanWarningCode; +export interface KtxScanWarning { + code: KtxScanWarningCode; message: string; table?: string; column?: string; @@ -345,7 +345,7 @@ export interface KloScanWarning { metadata?: Record; } -export interface KloScanEnrichmentSummary { +export interface KtxScanEnrichmentSummary { dataDictionary: 'skipped' | 'completed' | 'failed'; tableDescriptions: 'skipped' | 'completed' | 'failed'; columnDescriptions: 'skipped' | 'completed' | 'failed'; @@ -355,37 +355,37 @@ export interface KloScanEnrichmentSummary { statisticalValidation: 'skipped' | 'completed' | 'failed'; } -export interface KloScanRelationshipSummary { +export interface KtxScanRelationshipSummary { accepted: number; review: number; rejected: number; skipped: number; } -export type KloScanEnrichmentStage = 'descriptions' | 'embeddings' | 'relationships'; +export type KtxScanEnrichmentStage = 'descriptions' | 'embeddings' | 'relationships'; -export interface KloScanEnrichmentStateSummary { - resumedStages: KloScanEnrichmentStage[]; - completedStages: KloScanEnrichmentStage[]; - failedStages: KloScanEnrichmentStage[]; +export interface KtxScanEnrichmentStateSummary { + resumedStages: KtxScanEnrichmentStage[]; + completedStages: KtxScanEnrichmentStage[]; + failedStages: KtxScanEnrichmentStage[]; } -export interface KloScanReport { +export interface KtxScanReport { connectionId: string; - driver: KloConnectionDriver; + driver: KtxConnectionDriver; syncId: string; runId: string; - trigger: KloScanTrigger; - mode: KloScanMode; + trigger: KtxScanTrigger; + mode: KtxScanMode; dryRun: boolean; - artifactPaths: KloScanArtifactPaths; - diffSummary: KloScanDiffSummary; + artifactPaths: KtxScanArtifactPaths; + diffSummary: KtxScanDiffSummary; manifestShardsWritten: number; - structuralSyncStats: KloStructuralSyncStats; - enrichment: KloScanEnrichmentSummary; - capabilityGaps: Array>; - warnings: KloScanWarning[]; - relationships: KloScanRelationshipSummary; - enrichmentState: KloScanEnrichmentStateSummary; + structuralSyncStats: KtxStructuralSyncStats; + enrichment: KtxScanEnrichmentSummary; + capabilityGaps: Array>; + warnings: KtxScanWarning[]; + relationships: KtxScanRelationshipSummary; + enrichmentState: KtxScanEnrichmentStateSummary; createdAt: string; } diff --git a/packages/context/src/search/backend-conformance.test.ts b/packages/context/src/search/backend-conformance.test.ts index 0a378307..e033082d 100644 --- a/packages/context/src/search/backend-conformance.test.ts +++ b/packages/context/src/search/backend-conformance.test.ts @@ -4,7 +4,7 @@ import { join } from 'node:path'; import { afterEach, beforeEach, describe, it } from 'vitest'; import { SqliteContextEvidenceStore } from '../ingest/context-evidence/index.js'; import type { JsonValue } from '../ingest/ports.js'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { type LocalSlSourceSearchResult, searchLocalSlSources, writeLocalSlSource } from '../sl/local-sl.js'; import type { ContextEvidenceSearchResult } from '../tools/context-evidence-tool-store.js'; import { @@ -97,7 +97,7 @@ function toContextConformanceResult(result: ContextEvidenceSearchResult): Search }; } -async function seedSemanticLayerProject(project: KloLocalProject): Promise { +async function seedSemanticLayerProject(project: KtxLocalProject): Promise { await writeLocalSlSource(project, { connectionId: 'warehouse', sourceName: 'orders', @@ -138,13 +138,13 @@ async function seedSemanticLayerProject(project: KloLocalProject): Promise null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed dictionary profile', ); } -async function seedWikiProject(project: KloLocalProject): Promise { +async function seedWikiProject(project: KtxLocalProject): Promise { await writeLocalKnowledgePage(project, { key: 'metrics/revenue', scope: 'GLOBAL', @@ -235,13 +235,13 @@ async function seedContextDocument( describe('SQLite hybrid search backend conformance', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-search-conformance-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-search-conformance-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); }); afterEach(async () => { diff --git a/packages/context/src/search/pglite-owner-process.test.ts b/packages/context/src/search/pglite-owner-process.test.ts index ff3defdb..0121b1e2 100644 --- a/packages/context/src/search/pglite-owner-process.test.ts +++ b/packages/context/src/search/pglite-owner-process.test.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { Client } from 'pg'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { assertSearchBackendCapabilities, assertSearchBackendConformanceCase } from './index.js'; -import { KloPGliteOwnerProcess, PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES } from './pglite-owner-process.js'; +import { KtxPGliteOwnerProcess, PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES } from './pglite-owner-process.js'; async function allocatePort(): Promise { const server = createServer(); @@ -26,7 +26,7 @@ async function allocatePort(): Promise { return address.port; } -async function createHybridSearchFixture(owner: KloPGliteOwnerProcess): Promise { +async function createHybridSearchFixture(owner: KtxPGliteOwnerProcess): Promise { await owner.query(` CREATE TABLE prototype_documents ( id TEXT PRIMARY KEY, @@ -58,7 +58,7 @@ async function createHybridSearchFixture(owner: KloPGliteOwnerProcess): Promise< `); } -async function seedHybridSearchFixture(owner: KloPGliteOwnerProcess): Promise { +async function seedHybridSearchFixture(owner: KtxPGliteOwnerProcess): Promise { await owner.query( ` INSERT INTO prototype_documents (id, search_text, metadata, embedding) @@ -92,13 +92,13 @@ async function seedHybridSearchFixture(owner: KloPGliteOwnerProcess): Promise { +describe('KtxPGliteOwnerProcess', () => { let tempDir: string; let dataDir: string; let port: number; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-owner-process-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-owner-process-')); dataDir = join(tempDir, 'pgdata'); port = await allocatePort(); }); @@ -122,7 +122,7 @@ describe('KloPGliteOwnerProcess', () => { }); it('starts a socket owner process and serves PostgreSQL clients', async () => { - const owner = await KloPGliteOwnerProcess.start({ + const owner = await KtxPGliteOwnerProcess.start({ dataDir, host: '127.0.0.1', port, @@ -163,7 +163,7 @@ describe('KloPGliteOwnerProcess', () => { }); it('runs lexical, semantic, and dictionary conformance probes through socket clients', async () => { - const owner = await KloPGliteOwnerProcess.start({ + const owner = await KtxPGliteOwnerProcess.start({ dataDir, host: '127.0.0.1', port, @@ -266,7 +266,7 @@ describe('KloPGliteOwnerProcess', () => { }); it('persists indexed rows after stopping and restarting the owner process', async () => { - const firstOwner = await KloPGliteOwnerProcess.start({ + const firstOwner = await KtxPGliteOwnerProcess.start({ dataDir, host: '127.0.0.1', port, @@ -279,7 +279,7 @@ describe('KloPGliteOwnerProcess', () => { await firstOwner.stop(); } - const secondOwner = await KloPGliteOwnerProcess.start({ + const secondOwner = await KtxPGliteOwnerProcess.start({ dataDir, host: '127.0.0.1', port, @@ -298,7 +298,7 @@ describe('KloPGliteOwnerProcess', () => { }); it('serves concurrent PostgreSQL clients through one owner process', async () => { - const owner = await KloPGliteOwnerProcess.start({ + const owner = await KtxPGliteOwnerProcess.start({ dataDir, host: '127.0.0.1', port, diff --git a/packages/context/src/search/pglite-owner-process.ts b/packages/context/src/search/pglite-owner-process.ts index 92abb220..373be59c 100644 --- a/packages/context/src/search/pglite-owner-process.ts +++ b/packages/context/src/search/pglite-owner-process.ts @@ -13,7 +13,7 @@ export const PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES = { arraySearch: false, } satisfies SearchBackendCapabilities; -export interface KloPGliteOwnerProcessOptions { +export interface KtxPGliteOwnerProcessOptions { dataDir: string; host: string; port: number; @@ -21,7 +21,7 @@ export interface KloPGliteOwnerProcessOptions { maxConnections?: number; } -export class KloPGliteOwnerProcess { +export class KtxPGliteOwnerProcess { readonly dataDir: string; readonly host: string; readonly port: number; @@ -30,7 +30,7 @@ export class KloPGliteOwnerProcess { #server: PGLiteSocketServer; #stopped = false; - private constructor(options: KloPGliteOwnerProcessOptions, db: PGliteInterface, server: PGLiteSocketServer) { + private constructor(options: KtxPGliteOwnerProcessOptions, db: PGliteInterface, server: PGLiteSocketServer) { this.dataDir = options.dataDir; this.host = options.host; this.port = options.port; @@ -38,7 +38,7 @@ export class KloPGliteOwnerProcess { this.#server = server; } - static async start(options: KloPGliteOwnerProcessOptions): Promise { + static async start(options: KtxPGliteOwnerProcessOptions): Promise { const db = await PGlite.create({ dataDir: options.dataDir, extensions: { @@ -65,7 +65,7 @@ export class KloPGliteOwnerProcess { await server.start(); - return new KloPGliteOwnerProcess(options, db, server); + return new KtxPGliteOwnerProcess(options, db, server); } catch (error) { await server?.stop().catch(() => undefined); await db.close().catch(() => undefined); @@ -79,7 +79,7 @@ export class KloPGliteOwnerProcess { port: this.port, user: 'postgres', database: 'postgres', - application_name: 'klo-pglite-owner-prototype', + application_name: 'ktx-pglite-owner-prototype', connectionTimeoutMillis: 5_000, }; } diff --git a/packages/context/src/search/pglite-runtime-boundary.test.ts b/packages/context/src/search/pglite-runtime-boundary.test.ts index 5eae9f47..ce2f5b7a 100644 --- a/packages/context/src/search/pglite-runtime-boundary.test.ts +++ b/packages/context/src/search/pglite-runtime-boundary.test.ts @@ -3,10 +3,10 @@ import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; -const kloRoot = fileURLToPath(new URL('../../../../', import.meta.url)); +const ktxRoot = fileURLToPath(new URL('../../../../', import.meta.url)); -function readKloFile(relativePath: string): string { - return readFileSync(join(kloRoot, relativePath), 'utf8'); +function readKtxFile(relativePath: string): string { + return readFileSync(join(ktxRoot, relativePath), 'utf8'); } function readContextPackageJson(): { @@ -15,7 +15,7 @@ function readContextPackageJson(): { exports?: Record; files?: string[]; } { - return JSON.parse(readKloFile('packages/context/package.json')); + return JSON.parse(readKtxFile('packages/context/package.json')); } describe('PGlite hybrid search runtime boundary', () => { @@ -42,7 +42,7 @@ describe('PGlite hybrid search runtime boundary', () => { ]; for (const relativePath of publicExportFiles) { - expect(readKloFile(relativePath), relativePath).not.toMatch(/pglite/i); + expect(readKtxFile(relativePath), relativePath).not.toMatch(/pglite/i); } const productionRoutingFiles = [ @@ -53,12 +53,12 @@ describe('PGlite hybrid search runtime boundary', () => { ]; for (const relativePath of productionRoutingFiles) { - expect(readKloFile(relativePath), relativePath).not.toMatch( + expect(readKtxFile(relativePath), relativePath).not.toMatch( /pglite-owner-prototype|pglite-sl-search-prototype|@electric-sql\/pglite/i, ); } - const localSlSource = readKloFile('packages/context/src/sl/local-sl.ts'); + const localSlSource = readKtxFile('packages/context/src/sl/local-sl.ts'); expect(localSlSource).toContain("input.backend === 'pglite-owner-prototype'"); expect(localSlSource).toContain('PGlite semantic-layer search prototype requires pglite owner-process options.'); expect(localSlSource).toContain("await import('./pglite-sl-search-prototype.js')"); diff --git a/packages/context/src/search/pglite-spike.test.ts b/packages/context/src/search/pglite-spike.test.ts index c3feb709..e8dff54e 100644 --- a/packages/context/src/search/pglite-spike.test.ts +++ b/packages/context/src/search/pglite-spike.test.ts @@ -120,7 +120,7 @@ describe('PGlite hybrid search spike', () => { let dataDir: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-search-spike-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-search-spike-')); dataDir = join(tempDir, 'pgdata'); }); diff --git a/packages/context/src/skills/skills-registry.service.ts b/packages/context/src/skills/skills-registry.service.ts index b4ff1a26..2f0e8de2 100644 --- a/packages/context/src/skills/skills-registry.service.ts +++ b/packages/context/src/skills/skills-registry.service.ts @@ -1,6 +1,6 @@ import { readFile, readdir, stat } from 'node:fs/promises'; import { join } from 'node:path'; -import { noopLogger, type KloLogger } from '../core/index.js'; +import { noopLogger, type KtxLogger } from '../core/index.js'; export type SkillCaller = 'research' | 'memory_agent'; @@ -20,14 +20,14 @@ export interface FrontmatterFields { export interface SkillsRegistryServiceOptions { skillsDir: string; additionalSkillDirs?: string[]; - logger?: KloLogger; + logger?: KtxLogger; } const SKILL_FILENAME = 'SKILL.md'; const VALID_CALLERS: ReadonlySet = new Set(['research', 'memory_agent']); export class SkillsRegistryService { - private readonly logger: KloLogger; + private readonly logger: KtxLogger; private readonly skillsDir: string; private readonly additionalSkillDirs: string[]; private catalogPromise: Promise> | null = null; diff --git a/packages/context/src/sl/index.ts b/packages/context/src/sl/index.ts index 7c34d7e6..1a0167cb 100644 --- a/packages/context/src/sl/index.ts +++ b/packages/context/src/sl/index.ts @@ -9,8 +9,8 @@ export type { SlSearchMetadata, } from './types.js'; export type { - KloConnectionInfo, - KloQueryResult, + KtxConnectionInfo, + KtxQueryResult, SlConnectionCatalogPort, SlPythonPort, SlSourcesIndexPort, diff --git a/packages/context/src/sl/local-query.test.ts b/packages/context/src/sl/local-query.test.ts index 334e5fc5..80209329 100644 --- a/packages/context/src/sl/local-query.test.ts +++ b/packages/context/src/sl/local-query.test.ts @@ -2,18 +2,18 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { compileLocalSlQuery } from './local-query.js'; describe('compileLocalSlQuery', () => { let tempDir: string; - let project: KloLocalProject; - let compute: KloSemanticLayerComputePort; + let project: KtxLocalProject; + let compute: KtxSemanticLayerComputePort; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-query-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-query-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); project.config.connections.warehouse = { driver: 'postgres', readonly: true }; await project.fileStore.writeFile( 'semantic-layer/warehouse/orders.yaml', @@ -31,8 +31,8 @@ measures: expr: count(*) joins: [] `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add orders source', ); await project.fileStore.writeFile( @@ -46,8 +46,8 @@ joins: [] measures: [] grain: [] `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add overlay source', ); @@ -130,8 +130,8 @@ grain: [] - name: amount type: number `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add manifest shard', ); diff --git a/packages/context/src/sl/local-query.ts b/packages/context/src/sl/local-query.ts index 4e7af76c..a6f224dd 100644 --- a/packages/context/src/sl/local-query.ts +++ b/packages/context/src/sl/local-query.ts @@ -1,6 +1,6 @@ -import type { KloSqlQueryExecutorPort } from '../connections/index.js'; -import type { KloSemanticLayerComputePort } from '../daemon/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import type { KtxSqlQueryExecutorPort } from '../connections/index.js'; +import type { KtxSemanticLayerComputePort } from '../daemon/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { loadLocalSlSourceRecords } from './local-sl.js'; import type { SemanticLayerQueryExecutionResult, SemanticLayerQueryInput } from './types.js'; @@ -10,10 +10,10 @@ const COMPILE_ONLY_REASON = export interface CompileLocalSlQueryOptions { connectionId?: string; query: SemanticLayerQueryInput; - compute: KloSemanticLayerComputePort; + compute: KtxSemanticLayerComputePort; execute?: boolean; maxRows?: number; - queryExecutor?: KloSqlQueryExecutorPort; + queryExecutor?: KtxSqlQueryExecutorPort; } export interface CompileLocalSlQueryResult extends SemanticLayerQueryExecutionResult { @@ -61,7 +61,7 @@ function dialectForDriver(driver: string | undefined): string { return map[normalized] ?? 'postgres'; } -function resolveLocalConnectionId(project: KloLocalProject, requested: string | undefined): string { +function resolveLocalConnectionId(project: KtxLocalProject, requested: string | undefined): string { if (requested) { return assertSafeConnectionId(requested); } @@ -73,7 +73,7 @@ function resolveLocalConnectionId(project: KloLocalProject, requested: string | } async function loadComputableSources( - project: KloLocalProject, + project: KtxLocalProject, connectionId: string, ): Promise[]> { return (await loadLocalSlSourceRecords(project, { connectionId: assertSafeConnectionId(connectionId) })) @@ -88,7 +88,7 @@ function headersFromColumns(columns: Array>): string[] { } export async function compileLocalSlQuery( - project: KloLocalProject, + project: KtxLocalProject, options: CompileLocalSlQueryOptions, ): Promise { const connectionId = resolveLocalConnectionId(project, options.connectionId); diff --git a/packages/context/src/sl/local-sl.test.ts b/packages/context/src/sl/local-sl.test.ts index 63feb061..9af28bca 100644 --- a/packages/context/src/sl/local-sl.test.ts +++ b/packages/context/src/sl/local-sl.test.ts @@ -2,7 +2,7 @@ import { access, mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { listLocalSlSources, readLocalSlSource, @@ -46,11 +46,11 @@ const SUPPORT_YAML = [ describe('local semantic-layer helpers', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-sl-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-sl-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); }); afterEach(async () => { @@ -102,8 +102,8 @@ describe('local semantic-layer helpers', () => { - name: amount type: number `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add manifest shard', ); @@ -144,8 +144,8 @@ describe('local semantic-layer helpers', () => { - name: amount type: number `, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Add manifest shard', ); @@ -184,7 +184,7 @@ describe('local semantic-layer helpers', () => { }), ]); expect(results[0]?.score).toBeGreaterThan(0); - await expect(access(join(project.projectDir, '.klo/db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx/db.sqlite'))).resolves.toBeUndefined(); }); it('searches all connections with one global hybrid ranking pass', async () => { @@ -260,8 +260,8 @@ describe('local semantic-layer helpers', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed dictionary profile', ); diff --git a/packages/context/src/sl/local-sl.ts b/packages/context/src/sl/local-sl.ts index ff32ee65..b8d29e87 100644 --- a/packages/context/src/sl/local-sl.ts +++ b/packages/context/src/sl/local-sl.ts @@ -1,8 +1,8 @@ import { join } from 'node:path'; import YAML from 'yaml'; import { z } from 'zod'; -import type { KloEmbeddingPort, KloFileWriteResult } from '../core/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import type { KtxEmbeddingPort, KtxFileWriteResult } from '../core/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { HybridSearchCore, type SearchCandidateGenerator } from '../search/index.js'; import { DEFAULT_PRIORITY, resolveDescription } from './descriptions.js'; import { sourceDefinitionSchema, sourceOverlaySchema } from './schemas.js'; @@ -33,7 +33,7 @@ export interface LocalSlSourceSearchResult extends LocalSlSourceSummary { export interface LocalSlSearchInput { connectionId?: string; query: string; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; limit?: number; backend?: 'pglite-owner-prototype'; pglite?: PgliteSlSearchPrototypeOwnerOptions; @@ -52,8 +52,8 @@ export interface LocalSlValidationResult { errors: string[]; } -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; function assertSafePathToken(kind: string, value: string): string { if ( @@ -191,7 +191,7 @@ function parsedStandaloneSource(parsed: Record, name: string): } export async function loadLocalSlSourceRecords( - project: KloLocalProject, + project: KtxLocalProject, input: { connectionId: string }, ): Promise { const connectionId = assertSafeConnectionId(input.connectionId); @@ -255,9 +255,9 @@ export async function validateLocalSlSource(rawYaml: string): Promise { +): Promise { const validation = await validateLocalSlSource(input.yaml); if (!validation.valid) { throw new Error(`Invalid semantic-layer source: ${validation.errors.join('; ')}`); @@ -279,7 +279,7 @@ export async function writeLocalSlSource( } export async function readLocalSlSource( - project: KloLocalProject, + project: KtxLocalProject, input: { connectionId: string; sourceName: string }, ): Promise { const path = slPath(input.connectionId, input.sourceName); @@ -299,7 +299,7 @@ export async function readLocalSlSource( } export async function listLocalSlSources( - project: KloLocalProject, + project: KtxLocalProject, input: { connectionId?: string } = {}, ): Promise { if (input.connectionId) { @@ -325,12 +325,12 @@ interface LocalSlSearchCandidate { searchText: string; } -function sqliteSlDbPath(project: KloLocalProject): string { - return join(project.projectDir, '.klo', 'db.sqlite'); +function sqliteSlDbPath(project: KtxLocalProject): string { + return join(project.projectDir, '.ktx', 'db.sqlite'); } async function loadLocalSlSearchCandidates( - project: KloLocalProject, + project: KtxLocalProject, input: { connectionId?: string } = {}, ): Promise { if (input.connectionId) { @@ -390,9 +390,9 @@ function tokenLaneCandidates(candidates: LocalSlSearchCandidate[], terms: readon async function refreshHybridSlIndexes(input: { index: SqliteSlSourcesIndex; - project: KloLocalProject; + project: KtxLocalProject; candidates: LocalSlSearchCandidate[]; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; }): Promise { const candidatesByConnection = new Map(); for (const candidate of input.candidates) { @@ -435,7 +435,7 @@ async function refreshHybridSlIndexes(input: { } export async function searchLocalSlSources( - project: KloLocalProject, + project: KtxLocalProject, input: LocalSlSearchInput, ): Promise { const query = input.query.trim(); diff --git a/packages/context/src/sl/pglite-sl-search-prototype.test.ts b/packages/context/src/sl/pglite-sl-search-prototype.test.ts index 5288eb2f..1d0ece25 100644 --- a/packages/context/src/sl/pglite-sl-search-prototype.test.ts +++ b/packages/context/src/sl/pglite-sl-search-prototype.test.ts @@ -3,7 +3,7 @@ import { createServer } from 'node:net'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { assertSearchBackendConformanceCase } from '../search/index.js'; import { searchLocalSlSources, writeLocalSlSource, type LocalSlSourceSearchResult } from './local-sl.js'; import { searchLocalSlSourcesWithPglitePrototype } from './pglite-sl-search-prototype.js'; @@ -103,7 +103,7 @@ function toConformanceResult(result: LocalSlSourceSearchResult) { }; } -async function seedSemanticLayerProject(project: KloLocalProject): Promise { +async function seedSemanticLayerProject(project: KtxLocalProject): Promise { await writeLocalSlSource(project, { connectionId: 'warehouse', sourceName: 'orders', yaml: ORDERS_YAML }); await writeLocalSlSource(project, { connectionId: 'finance', sourceName: 'orders', yaml: FINANCE_ORDERS_YAML }); await writeLocalSlSource(project, { connectionId: 'warehouse', sourceName: 'customers', yaml: CUSTOMERS_YAML }); @@ -152,21 +152,21 @@ async function seedSemanticLayerProject(project: KloLocalProject): Promise null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed PGlite dictionary profile', ); } describe('PGlite semantic-layer search prototype', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; let pgliteDataDir: string; let port: number; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-pglite-sl-prototype-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-pglite-sl-prototype-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); project.config.ingest.embeddings.dimensions = 3; pgliteDataDir = join(tempDir, 'pglite-search'); port = await allocatePort(); diff --git a/packages/context/src/sl/pglite-sl-search-prototype.ts b/packages/context/src/sl/pglite-sl-search-prototype.ts index 551ad5d8..77c8c7d5 100644 --- a/packages/context/src/sl/pglite-sl-search-prototype.ts +++ b/packages/context/src/sl/pglite-sl-search-prototype.ts @@ -1,9 +1,9 @@ import { mkdir } from 'node:fs/promises'; import { join } from 'node:path'; -import type { KloEmbeddingPort } from '../core/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import type { KtxEmbeddingPort } from '../core/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { HybridSearchCore, type SearchCandidateGenerator } from '../search/index.js'; -import { KloPGliteOwnerProcess } from '../search/pglite-owner-process.js'; +import { KtxPGliteOwnerProcess } from '../search/pglite-owner-process.js'; import { listLocalSlSources, loadLocalSlSourceRecords, @@ -23,7 +23,7 @@ export interface PgliteSlSearchPrototypeOwnerOptions { export interface PgliteSlSearchPrototypeInput { connectionId?: string; query: string; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; limit?: number; pglite: PgliteSlSearchPrototypeOwnerOptions; } @@ -50,11 +50,11 @@ function candidateKey(summary: LocalSlSourceSummary): string { return `${summary.connectionId}/${summary.name}`; } -function pgliteDataDir(project: KloLocalProject, input: PgliteSlSearchPrototypeOwnerOptions): string { - return input.dataDir ?? join(project.projectDir, '.klo', 'pglite-search-prototype'); +function pgliteDataDir(project: KtxLocalProject, input: PgliteSlSearchPrototypeOwnerOptions): string { + return input.dataDir ?? join(project.projectDir, '.ktx', 'pglite-search-prototype'); } -function vectorDimensions(project: KloLocalProject): number { +function vectorDimensions(project: KtxLocalProject): number { const dimensions = project.config.ingest.embeddings.dimensions; if (!Number.isInteger(dimensions) || dimensions <= 0) { throw new Error(`PGlite SL search prototype needs a positive embedding dimension, got ${String(dimensions)}.`); @@ -67,7 +67,7 @@ function connectionIdsForSearch(input: { connectionId?: string }): string[] | nu } async function loadCandidates( - project: KloLocalProject, + project: KtxLocalProject, input: { connectionId?: string } = {}, ): Promise { if (input.connectionId) { @@ -139,7 +139,7 @@ function postgresqlOrTsQuery(query: string): string { return [...new Set(terms)].join(' | '); } -async function resetPrototypeSchema(owner: KloPGliteOwnerProcess, dimensions: number): Promise { +async function resetPrototypeSchema(owner: KtxPGliteOwnerProcess, dimensions: number): Promise { await owner.query(` DROP TABLE IF EXISTS prototype_sl_dictionary_values; DROP TABLE IF EXISTS prototype_sl_sources; @@ -184,7 +184,7 @@ async function resetPrototypeSchema(owner: KloPGliteOwnerProcess, dimensions: nu async function sourceEmbeddings(input: { candidates: LocalSlSearchCandidate[]; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; dimensions: number; }): Promise | null> { if (!input.embeddingService) { @@ -209,7 +209,7 @@ async function sourceEmbeddings(input: { } async function insertSourceRows(input: { - owner: KloPGliteOwnerProcess; + owner: KtxPGliteOwnerProcess; candidates: LocalSlSearchCandidate[]; embeddings: Map | null; }): Promise { @@ -246,7 +246,7 @@ async function insertSourceRows(input: { } } -async function insertDictionaryRows(owner: KloPGliteOwnerProcess, entries: SlDictionaryEntry[]): Promise { +async function insertDictionaryRows(owner: KtxPGliteOwnerProcess, entries: SlDictionaryEntry[]): Promise { for (const entry of entries) { await owner.query( ` @@ -305,7 +305,7 @@ function groupDictionaryRows(rows: PgliteDictionaryRow[], limit: number) { } async function queryLexicalCandidates(input: { - owner: KloPGliteOwnerProcess; + owner: KtxPGliteOwnerProcess; queryText: string; connectionIds: string[] | null; limit: number; @@ -341,10 +341,10 @@ async function queryLexicalCandidates(input: { } async function querySemanticCandidates(input: { - owner: KloPGliteOwnerProcess; + owner: KtxPGliteOwnerProcess; queryText: string; connectionIds: string[] | null; - embeddingService?: KloEmbeddingPort | null; + embeddingService?: KtxEmbeddingPort | null; dimensions: number; limit: number; }) { @@ -397,7 +397,7 @@ async function querySemanticCandidates(input: { } async function queryDictionaryCandidates(input: { - owner: KloPGliteOwnerProcess; + owner: KtxPGliteOwnerProcess; queryText: string; connectionIds: string[] | null; limit: number; @@ -437,7 +437,7 @@ async function queryDictionaryCandidates(input: { } export async function searchLocalSlSourcesWithPglitePrototype( - project: KloLocalProject, + project: KtxLocalProject, input: PgliteSlSearchPrototypeInput, ): Promise { const query = input.query.trim(); @@ -453,7 +453,7 @@ export async function searchLocalSlSourcesWithPglitePrototype( const dataDir = pgliteDataDir(project, input.pglite); await mkdir(dataDir, { recursive: true }); - const owner = await KloPGliteOwnerProcess.start({ + const owner = await KtxPGliteOwnerProcess.start({ dataDir, host: input.pglite.host, port: input.pglite.port, diff --git a/packages/context/src/sl/ports.ts b/packages/context/src/sl/ports.ts index dbad7d02..d2426460 100644 --- a/packages/context/src/sl/ports.ts +++ b/packages/context/src/sl/ports.ts @@ -1,21 +1,21 @@ import type { SemanticLayerQueryInput, SemanticLayerSource } from './types.js'; -export interface KloConnectionInfo { +export interface KtxConnectionInfo { id: string; name: string; connectionType: string; } -export interface KloQueryResult { +export interface KtxQueryResult { headers?: string[]; rows?: unknown[][]; totalRows?: number; } export interface SlConnectionCatalogPort { - listEnabledConnections(ids: string[]): Promise; - getConnectionById(connectionId: string): Promise; - executeQuery(connectionId: string, sql: string): Promise; + listEnabledConnections(ids: string[]): Promise; + getConnectionById(connectionId: string): Promise; + executeQuery(connectionId: string, sql: string): Promise; } export interface SlPythonPort { diff --git a/packages/context/src/sl/schemas.ts b/packages/context/src/sl/schemas.ts index f5232b1d..55e07f22 100644 --- a/packages/context/src/sl/schemas.ts +++ b/packages/context/src/sl/schemas.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; // Literal vocabularies — kept in lockstep with the Python Pydantic model at -// python-service/klo-sl/semantic_layer/models.py (SourceColumn / ColumnRole / +// python-service/ktx-sl/semantic_layer/models.py (SourceColumn / ColumnRole / // ColumnVisibility / JoinDeclaration). If these diverge, YAMLs can pass // TypeScript validation at ingest time but fail Python loading at query time. const columnTypeValues = ['string', 'number', 'time', 'boolean'] as const; diff --git a/packages/context/src/sl/semantic-layer.service.ts b/packages/context/src/sl/semantic-layer.service.ts index a1039228..5d559a31 100644 --- a/packages/context/src/sl/semantic-layer.service.ts +++ b/packages/context/src/sl/semantic-layer.service.ts @@ -1,5 +1,5 @@ import YAML from 'yaml'; -import type { KloFileStorePort, KloLogger } from '../core/index.js'; +import type { KtxFileStorePort, KtxLogger } from '../core/index.js'; import { noopLogger } from '../core/index.js'; import type { SlConnectionCatalogPort, SlPythonPort } from './ports.js'; import { isOverlaySource, sourceDefinitionSchema, sourceOverlaySchema } from './schemas.js'; @@ -36,10 +36,10 @@ function formatPortError(error: unknown, fallback: string): string { export class SemanticLayerService { constructor( - private readonly configService: KloFileStorePort, + private readonly configService: KtxFileStorePort, private readonly connections: SlConnectionCatalogPort, private readonly python: SlPythonPort, - private readonly logger: KloLogger = noopLogger, + private readonly logger: KtxLogger = noopLogger, ) {} /** @@ -49,7 +49,7 @@ export class SemanticLayerService { */ forWorktree(workdir: string): SemanticLayerService { return new SemanticLayerService( - this.configService.forWorktree(workdir) as KloFileStorePort, + this.configService.forWorktree(workdir) as KtxFileStorePort, this.connections, this.python, this.logger, diff --git a/packages/context/src/sl/sl-dictionary-profile.test.ts b/packages/context/src/sl/sl-dictionary-profile.test.ts index 64b1e454..756cb81a 100644 --- a/packages/context/src/sl/sl-dictionary-profile.test.ts +++ b/packages/context/src/sl/sl-dictionary-profile.test.ts @@ -2,16 +2,16 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { loadLatestSlDictionaryEntries } from './sl-dictionary-profile.js'; describe('loadLatestSlDictionaryEntries', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-sl-dictionary-profile-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-sl-dictionary-profile-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); }); afterEach(async () => { @@ -63,8 +63,8 @@ describe('loadLatestSlDictionaryEntries', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed profile', ); @@ -98,8 +98,8 @@ describe('loadLatestSlDictionaryEntries', () => { null, 2, )}\n`, - 'klo', - 'klo@example.com', + 'ktx', + 'ktx@example.com', 'Seed newer profile', ); diff --git a/packages/context/src/sl/sl-dictionary-profile.ts b/packages/context/src/sl/sl-dictionary-profile.ts index f881a0f3..141d71ce 100644 --- a/packages/context/src/sl/sl-dictionary-profile.ts +++ b/packages/context/src/sl/sl-dictionary-profile.ts @@ -1,5 +1,5 @@ -import type { KloLocalProject } from '../project/index.js'; -import { defaultKloDataDictionarySettings, isKloDataDictionaryCandidate } from '../scan/index.js'; +import type { KtxLocalProject } from '../project/index.js'; +import { defaultKtxDataDictionarySettings, isKtxDataDictionaryCandidate } from '../scan/index.js'; export interface SlDictionaryEntry { connectionId: string; @@ -58,12 +58,12 @@ function columnEntries(connectionId: string, column: RelationshipProfileColumn): } const columnType = column.normalizedType ?? column.nativeType ?? ''; - if (!isKloDataDictionaryCandidate(columnType, columnName)) { + if (!isKtxDataDictionaryCandidate(columnType, columnName)) { return []; } const cardinality = typeof column.distinctCount === 'number' ? column.distinctCount : null; - if (cardinality !== null && cardinality > defaultKloDataDictionarySettings.cardinalityThreshold) { + if (cardinality !== null && cardinality > defaultKtxDataDictionarySettings.cardinalityThreshold) { return []; } @@ -76,7 +76,7 @@ function columnEntries(connectionId: string, column: RelationshipProfileColumn): })); } -async function latestProfilePath(project: KloLocalProject, connectionId: string): Promise { +async function latestProfilePath(project: KtxLocalProject, connectionId: string): Promise { const root = `raw-sources/${connectionId}/live-database`; let files: string[]; try { @@ -94,7 +94,7 @@ async function latestProfilePath(project: KloLocalProject, connectionId: string) } export async function loadLatestSlDictionaryEntries( - project: KloLocalProject, + project: KtxLocalProject, connectionIds: readonly string[], ): Promise { const entries: SlDictionaryEntry[] = []; diff --git a/packages/context/src/sl/sl-search.service.ts b/packages/context/src/sl/sl-search.service.ts index 648c4066..e351011f 100644 --- a/packages/context/src/sl/sl-search.service.ts +++ b/packages/context/src/sl/sl-search.service.ts @@ -1,4 +1,4 @@ -import type { KloEmbeddingPort, KloLogger } from '../core/index.js'; +import type { KtxEmbeddingPort, KtxLogger } from '../core/index.js'; import { noopLogger } from '../core/index.js'; import { DEFAULT_PRIORITY, resolveDescription } from './descriptions.js'; import type { SlSourcesIndexPort } from './ports.js'; @@ -74,9 +74,9 @@ export function buildSemanticLayerSourceSearchText( export class SlSearchService { constructor( - private readonly embeddingService: KloEmbeddingPort, + private readonly embeddingService: KtxEmbeddingPort, private readonly slSourcesRepository: SlSourcesIndexPort, - private readonly logger: KloLogger = noopLogger, + private readonly logger: KtxLogger = noopLogger, ) {} async indexSources(connectionId: string, sources: SemanticLayerSource[]): Promise { diff --git a/packages/context/src/sl/sqlite-sl-sources-index.test.ts b/packages/context/src/sl/sqlite-sl-sources-index.test.ts index 51b2fa95..01f1be37 100644 --- a/packages/context/src/sl/sqlite-sl-sources-index.test.ts +++ b/packages/context/src/sl/sqlite-sl-sources-index.test.ts @@ -9,7 +9,7 @@ describe('SqliteSlSourcesIndex', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-sqlite-sl-index-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-sqlite-sl-index-')); dbPath = join(tempDir, 'db.sqlite'); }); diff --git a/packages/context/src/sl/tools/sl-warehouse-validation.ts b/packages/context/src/sl/tools/sl-warehouse-validation.ts index cb16d60b..b2799974 100644 --- a/packages/context/src/sl/tools/sl-warehouse-validation.ts +++ b/packages/context/src/sl/tools/sl-warehouse-validation.ts @@ -1,5 +1,5 @@ import YAML from 'yaml'; -import type { GitService, KloFileStorePort } from '../../core/index.js'; +import type { GitService, KtxFileStorePort } from '../../core/index.js'; import { SYSTEM_GIT_AUTHOR } from '../../tools/index.js'; import type { SlConnectionCatalogPort, SlSourcesIndexPort } from '../ports.js'; import { sourceOverlaySchema } from '../schemas.js'; @@ -9,7 +9,7 @@ import { sourceDefinitionSchema } from './base-semantic-layer.tool.js'; export interface SlValidationDeps { semanticLayerService: SemanticLayerService; connections: SlConnectionCatalogPort; - configService: KloFileStorePort; + configService: KtxFileStorePort; gitService: GitService; slSourcesRepository: SlSourcesIndexPort; probeRowCount: number; diff --git a/packages/context/src/sql-analysis/http-sql-analysis-port.ts b/packages/context/src/sql-analysis/http-sql-analysis-port.ts index cc1e96a4..a26d69e4 100644 --- a/packages/context/src/sql-analysis/http-sql-analysis-port.ts +++ b/packages/context/src/sql-analysis/http-sql-analysis-port.ts @@ -9,14 +9,14 @@ import type { SqlAnalysisPort, } from './ports.js'; -export type KloSqlAnalysisHttpJsonRunner = ( +export type KtxSqlAnalysisHttpJsonRunner = ( path: string, payload: Record, ) => Promise>; export interface HttpSqlAnalysisPortOptions { baseUrl: string; - requestJson?: KloSqlAnalysisHttpJsonRunner; + requestJson?: KtxSqlAnalysisHttpJsonRunner; } function normalizedBaseUrl(baseUrl: string): string { @@ -31,7 +31,7 @@ function parseJsonObject(raw: string, path: string): Record { return parsed as Record; } -function postJson(baseUrl: string): KloSqlAnalysisHttpJsonRunner { +function postJson(baseUrl: string): KtxSqlAnalysisHttpJsonRunner { return async (path, payload) => new Promise((resolve, reject) => { const target = new URL(path.replace(/^\//, ''), normalizedBaseUrl(baseUrl)); diff --git a/packages/context/src/sql-analysis/index.ts b/packages/context/src/sql-analysis/index.ts index a4f6315f..89e3ada9 100644 --- a/packages/context/src/sql-analysis/index.ts +++ b/packages/context/src/sql-analysis/index.ts @@ -1,5 +1,5 @@ export { createHttpSqlAnalysisPort } from './http-sql-analysis-port.js'; -export type { HttpSqlAnalysisPortOptions, KloSqlAnalysisHttpJsonRunner } from './http-sql-analysis-port.js'; +export type { HttpSqlAnalysisPortOptions, KtxSqlAnalysisHttpJsonRunner } from './http-sql-analysis-port.js'; export type { SqlAnalysisDialect, SqlAnalysisFingerprintResult, diff --git a/packages/context/src/test/make-local-git-repo.ts b/packages/context/src/test/make-local-git-repo.ts index 364c99db..a7b4c662 100644 --- a/packages/context/src/test/make-local-git-repo.ts +++ b/packages/context/src/test/make-local-git-repo.ts @@ -19,8 +19,8 @@ export async function makeLocalGitRepo(fixtureDir: string, destRoot: string): Pr const git = createSimpleGit(repoDir); await git.init(); await git.raw(['checkout', '-B', 'main']); - await git.addConfig('user.email', 'test@klo.local'); - await git.addConfig('user.name', 'KLO Test'); + await git.addConfig('user.email', 'test@ktx.local'); + await git.addConfig('user.name', 'KTX Test'); await git.add('.'); await git.commit('initial'); const commit = async (message: string): Promise => { diff --git a/packages/context/src/tools/base-tool.ts b/packages/context/src/tools/base-tool.ts index 526e26c3..37da69a0 100644 --- a/packages/context/src/tools/base-tool.ts +++ b/packages/context/src/tools/base-tool.ts @@ -1,6 +1,6 @@ import { tool } from 'ai'; import { z, type ZodType } from 'zod'; -import { noopLogger, type KloLogger } from '../core/index.js'; +import { noopLogger, type KtxLogger } from '../core/index.js'; import type { IngestToolMetadata, ToolSession } from './tool-session.js'; export interface ToolOutput { @@ -72,11 +72,11 @@ export interface MethodologyEntry { * SECURITY: All tools require authentication. userId must always be provided in ToolContext. */ export abstract class BaseTool { - protected readonly logger: KloLogger; + protected readonly logger: KtxLogger; abstract readonly name: string; - constructor(logger: KloLogger = noopLogger) { + constructor(logger: KtxLogger = noopLogger) { this.logger = logger; } diff --git a/packages/context/src/tools/context-candidate-write.tool.ts b/packages/context/src/tools/context-candidate-write.tool.ts index 5152503e..16a28707 100644 --- a/packages/context/src/tools/context-candidate-write.tool.ts +++ b/packages/context/src/tools/context-candidate-write.tool.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; import { z } from 'zod'; -import type { KloEmbeddingPort } from '../core/index.js'; +import type { KtxEmbeddingPort } from '../core/index.js'; import { buildContextCandidateEmbeddingText } from '../ingest/context-candidates/index.js'; import { BaseTool, type ToolContext, type ToolOutput } from './base-tool.js'; import { chunkIdSchema } from './context-evidence-ids.js'; @@ -40,7 +40,7 @@ export class ContextCandidateWriteTool extends BaseTool, + private readonly embeddingService: Pick, ) { super(); } diff --git a/packages/context/src/tools/context-evidence-search.tool.ts b/packages/context/src/tools/context-evidence-search.tool.ts index caeb8f4f..3ca09451 100644 --- a/packages/context/src/tools/context-evidence-search.tool.ts +++ b/packages/context/src/tools/context-evidence-search.tool.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import type { KloEmbeddingPort } from '../core/index.js'; +import type { KtxEmbeddingPort } from '../core/index.js'; import { BaseTool, type ToolContext, type ToolOutput } from './base-tool.js'; import type { ContextEvidenceToolStorePort } from './context-evidence-tool-store.js'; import { ingestMetadataRequired, resolveIngestMetadata, type ToolFailure } from './context-ingest-metadata.js'; @@ -48,7 +48,7 @@ export class ContextEvidenceSearchTool extends BaseTool, + private readonly embeddingService: Pick, ) { super(); } diff --git a/packages/context/src/tools/context-evidence-tools.test.ts b/packages/context/src/tools/context-evidence-tools.test.ts index d3f11fea..2a76f924 100644 --- a/packages/context/src/tools/context-evidence-tools.test.ts +++ b/packages/context/src/tools/context-evidence-tools.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { KloEmbeddingPort } from '../core/index.js'; +import type { KtxEmbeddingPort } from '../core/index.js'; import { SqliteContextEvidenceStore } from '../ingest/context-evidence/sqlite-context-evidence-store.js'; import { ContextCandidateMarkTool } from './context-candidate-mark.tool.js'; import { ContextCandidateWriteTool } from './context-candidate-write.tool.js'; @@ -39,11 +39,11 @@ const ingestContext = (): ToolContext => ({ } as unknown as ToolSession, }); -const makeEmbeddingService = (overrides: Partial = {}) => +const makeEmbeddingService = (overrides: Partial = {}) => ({ computeEmbedding: vi.fn().mockResolvedValue([0.25, 0.5, 0.75]), ...overrides, - }) as Partial as KloEmbeddingPort; + }) as Partial as KtxEmbeddingPort; describe('context evidence tools', () => { it('searches context evidence with ingest defaults', async () => { @@ -86,7 +86,7 @@ describe('context evidence tools', () => { } as Partial as ContextEvidenceToolStorePort; const embeddings = { computeEmbedding: vi.fn().mockResolvedValue([0.1, ...Array.from({ length: 383 }, () => 0)]), - } as Partial as KloEmbeddingPort; + } as Partial as KtxEmbeddingPort; const tool = new ContextEvidenceSearchTool(repository, embeddings); const result = await tool.call({ query: 'revenue refunds', limit: 5, includeDeleted: false }, ingestContext()); @@ -116,7 +116,7 @@ describe('context evidence tools', () => { it('returns a structured ingest metadata error outside ingest sessions', async () => { const tool = new ContextEvidenceSearchTool( { searchRRF: vi.fn() } as Partial as ContextEvidenceToolStorePort, - { computeEmbedding: vi.fn() } as Partial as KloEmbeddingPort, + { computeEmbedding: vi.fn() } as Partial as KtxEmbeddingPort, ); const result = await tool.call( @@ -446,8 +446,8 @@ describe('context evidence tools against real SqliteContextEvidenceStore', () => let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-context-tools-sqlite-')); - dbPath = join(tempDir, '.klo', 'db.sqlite'); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-context-tools-sqlite-')); + dbPath = join(tempDir, '.ktx', 'db.sqlite'); }); afterEach(async () => { @@ -530,7 +530,7 @@ describe('context evidence tools against real SqliteContextEvidenceStore', () => const tool = new ContextCandidateWriteTool(store, { computeEmbedding: vi.fn().mockResolvedValue([0.1, 0.2, 0.3]), - } as Partial as KloEmbeddingPort); + } as Partial as KtxEmbeddingPort); const parsed = tool.parseInput({ candidateKey: 'revenue-definition', @@ -558,7 +558,7 @@ describe('context evidence tools against real SqliteContextEvidenceStore', () => it('candidate write schema rejects a bare UUID without the ctxchunk- prefix', () => { const tool = new ContextCandidateWriteTool( {} as ContextEvidenceToolStorePort, - { computeEmbedding: vi.fn() } as Partial as KloEmbeddingPort, + { computeEmbedding: vi.fn() } as Partial as KtxEmbeddingPort, ); expect(() => diff --git a/packages/context/src/tools/tool-session.ts b/packages/context/src/tools/tool-session.ts index ec03eb4f..8849b673 100644 --- a/packages/context/src/tools/tool-session.ts +++ b/packages/context/src/tools/tool-session.ts @@ -1,4 +1,4 @@ -import type { GitService, KloFileStorePort } from '../core/index.js'; +import type { GitService, KtxFileStorePort } from '../core/index.js'; import type { SemanticLayerService } from '../sl/index.js'; import type { KnowledgeWikiService } from '../wiki/index.js'; import type { TouchedSlSourceSet } from './touched-sl-sources.js'; @@ -47,7 +47,7 @@ export interface ToolSession { actions: MemoryAction[]; semanticLayerService: SemanticLayerService; wikiService: KnowledgeWikiService; - configService: KloFileStorePort; + configService: KtxFileStorePort; gitService: GitService; ingest?: IngestToolMetadata; evictionDecisions?: EvictionDecisionRecord[]; diff --git a/packages/context/src/wiki/knowledge-wiki.service.ts b/packages/context/src/wiki/knowledge-wiki.service.ts index 467f8df7..6234c729 100644 --- a/packages/context/src/wiki/knowledge-wiki.service.ts +++ b/packages/context/src/wiki/knowledge-wiki.service.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; import YAML from 'yaml'; -import type { KloEmbeddingPort, KloFileStorePort, KloLogger } from '../core/index.js'; +import type { KtxEmbeddingPort, KtxFileStorePort, KtxLogger } from '../core/index.js'; import { noopLogger } from '../core/index.js'; import { buildKnowledgeSearchText } from './knowledge-search-text.js'; import type { KnowledgeGitDiffPort, KnowledgeIndexPort, UpsertPageParams } from './ports.js'; @@ -14,11 +14,11 @@ export class KnowledgeWikiService { private isWorktreeScoped = false; constructor( - private readonly configService: KloFileStorePort, - private readonly embeddingService: KloEmbeddingPort, + private readonly configService: KtxFileStorePort, + private readonly embeddingService: KtxEmbeddingPort, private readonly pagesRepository: KnowledgeIndexPort, private readonly gitService: KnowledgeGitDiffPort, - private readonly logger: KloLogger = noopLogger, + private readonly logger: KtxLogger = noopLogger, ) {} /** @@ -30,7 +30,7 @@ export class KnowledgeWikiService { */ forWorktree(workdir: string): KnowledgeWikiService { return new KnowledgeWikiService( - this.configService.forWorktree(workdir) as KloFileStorePort, + this.configService.forWorktree(workdir) as KtxFileStorePort, this.embeddingService, this.pagesRepository, this.gitService, diff --git a/packages/context/src/wiki/local-knowledge.test.ts b/packages/context/src/wiki/local-knowledge.test.ts index f8e8a655..1b979cef 100644 --- a/packages/context/src/wiki/local-knowledge.test.ts +++ b/packages/context/src/wiki/local-knowledge.test.ts @@ -2,7 +2,7 @@ import { access, mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { initKloProject, type KloLocalProject } from '../project/index.js'; +import { initKtxProject, type KtxLocalProject } from '../project/index.js'; import { listLocalKnowledgePages, readLocalKnowledgePage, @@ -24,11 +24,11 @@ class FakeEmbeddingPort { describe('local knowledge helpers', () => { let tempDir: string; - let project: KloLocalProject; + let project: KtxLocalProject; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-local-knowledge-')); - project = await initKloProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-knowledge-')); + project = await initKtxProject({ projectDir: join(tempDir, 'project'), projectName: 'warehouse' }); }); afterEach(async () => { @@ -80,7 +80,7 @@ describe('local knowledge helpers', () => { }), ]); expect(search[0]?.score).toBeGreaterThan(0); - await expect(access(join(project.projectDir, '.klo', 'db.sqlite'))).resolves.toBeUndefined(); + await expect(access(join(project.projectDir, '.ktx', 'db.sqlite'))).resolves.toBeUndefined(); }); it('adds the token lane alongside lexical wiki matches', async () => { diff --git a/packages/context/src/wiki/local-knowledge.ts b/packages/context/src/wiki/local-knowledge.ts index 95a17ea1..11cc1cc1 100644 --- a/packages/context/src/wiki/local-knowledge.ts +++ b/packages/context/src/wiki/local-knowledge.ts @@ -1,7 +1,7 @@ import { join } from 'node:path'; import YAML from 'yaml'; -import type { KloEmbeddingPort, KloFileWriteResult } from '../core/index.js'; -import type { KloLocalProject } from '../project/index.js'; +import type { KtxEmbeddingPort, KtxFileWriteResult } from '../core/index.js'; +import type { KtxLocalProject } from '../project/index.js'; import { HybridSearchCore, type SearchCandidateGenerator } from '../search/index.js'; import { buildKnowledgeSearchText } from './knowledge-search-text.js'; import { SqliteKnowledgeIndex, type SqliteKnowledgeIndexPage } from './sqlite-knowledge-index.js'; @@ -50,8 +50,8 @@ export interface WriteLocalKnowledgePageInput { fingerprints?: string[]; } -const LOCAL_AUTHOR = 'klo'; -const LOCAL_AUTHOR_EMAIL = 'klo@example.com'; +const LOCAL_AUTHOR = 'ktx'; +const LOCAL_AUTHOR_EMAIL = 'ktx@example.com'; function assertSafePathToken(kind: string, value: string): string { if ( @@ -137,7 +137,7 @@ function serializeKnowledgePage(input: WriteLocalKnowledgePageInput): string { } async function readPageAtPath( - project: KloLocalProject, + project: KtxLocalProject, key: string, path: string, scope: LocalKnowledgeScope, @@ -151,9 +151,9 @@ async function readPageAtPath( } export async function writeLocalKnowledgePage( - project: KloLocalProject, + project: KtxLocalProject, input: WriteLocalKnowledgePageInput, -): Promise { +): Promise { const path = knowledgePath(input.scope, input.userId, input.key); return project.fileStore.writeFile( path, @@ -165,7 +165,7 @@ export async function writeLocalKnowledgePage( } export async function readLocalKnowledgePage( - project: KloLocalProject, + project: KtxLocalProject, input: { key: string; userId?: string }, ): Promise { const userPath = knowledgePath('USER', input.userId, input.key); @@ -177,7 +177,7 @@ export async function readLocalKnowledgePage( } export async function listLocalKnowledgePages( - project: KloLocalProject, + project: KtxLocalProject, input: { userId?: string } = {}, ): Promise { const userId = input.userId ?? 'local'; @@ -201,8 +201,8 @@ function scorePage(page: LocalKnowledgePage, terms: string[]): number { return terms.some((term) => haystack.includes(term)) ? 3 : 0; } -function sqliteKnowledgeDbPath(project: KloLocalProject): string { - return join(project.projectDir, '.klo', 'db.sqlite'); +function sqliteKnowledgeDbPath(project: KtxLocalProject): string { + return join(project.projectDir, '.ktx', 'db.sqlite'); } function pageSearchText(page: LocalKnowledgePage): string { @@ -211,7 +211,7 @@ function pageSearchText(page: LocalKnowledgePage): string { async function embeddingForPageSearchText( searchText: string, - embeddingService: KloEmbeddingPort | null, + embeddingService: KtxEmbeddingPort | null, ): Promise { if (!embeddingService) { return null; @@ -234,7 +234,7 @@ function tokenLaneCandidates(pages: LocalKnowledgePage[], terms: string[]) { } async function loadAllKnowledgePages( - project: KloLocalProject, + project: KtxLocalProject, input: { userId?: string } = {}, ): Promise { const summaries = await listLocalKnowledgePages(project, { userId: input.userId }); @@ -249,8 +249,8 @@ async function loadAllKnowledgePages( } async function searchLocalKnowledgePagesWithSqlite( - project: KloLocalProject, - input: { query: string; userId?: string; embeddingService?: KloEmbeddingPort | null; limit?: number }, + project: KtxLocalProject, + input: { query: string; userId?: string; embeddingService?: KtxEmbeddingPort | null; limit?: number }, ): Promise { const pages = await loadAllKnowledgePages(project, { userId: input.userId }); const byPath = new Map(pages.map((page) => [page.path, page])); @@ -352,7 +352,7 @@ async function searchLocalKnowledgePagesWithSqlite( } async function searchLocalKnowledgePagesWithScan( - project: KloLocalProject, + project: KtxLocalProject, input: { query: string; userId?: string; limit?: number }, ): Promise { const terms = input.query @@ -381,8 +381,8 @@ async function searchLocalKnowledgePagesWithScan( } export async function searchLocalKnowledgePages( - project: KloLocalProject, - input: { query: string; userId?: string; embeddingService?: KloEmbeddingPort | null; limit?: number }, + project: KtxLocalProject, + input: { query: string; userId?: string; embeddingService?: KtxEmbeddingPort | null; limit?: number }, ): Promise { if (project.config.storage.search === 'sqlite-fts5') { return searchLocalKnowledgePagesWithSqlite(project, input); diff --git a/packages/context/src/wiki/ports.ts b/packages/context/src/wiki/ports.ts index 21a2fa87..7fa48c29 100644 --- a/packages/context/src/wiki/ports.ts +++ b/packages/context/src/wiki/ports.ts @@ -1,4 +1,4 @@ -import type { KloFileStorePort } from '../core/file-store.js'; +import type { KtxFileStorePort } from '../core/file-store.js'; export interface UpsertPageParams { scope: string; @@ -65,4 +65,4 @@ export interface KnowledgeGitDiffPort { getFileAtCommit(path: string, sha: string): Promise; } -export type WikiFileStorePort = KloFileStorePort; +export type WikiFileStorePort = KtxFileStorePort; diff --git a/packages/context/src/wiki/sqlite-knowledge-index.test.ts b/packages/context/src/wiki/sqlite-knowledge-index.test.ts index 05509e12..9bb0e6f0 100644 --- a/packages/context/src/wiki/sqlite-knowledge-index.test.ts +++ b/packages/context/src/wiki/sqlite-knowledge-index.test.ts @@ -9,7 +9,7 @@ describe('SqliteKnowledgeIndex', () => { let dbPath: string; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'klo-sqlite-knowledge-index-')); + tempDir = await mkdtemp(join(tmpdir(), 'ktx-sqlite-knowledge-index-')); dbPath = join(tempDir, 'db.sqlite'); }); diff --git a/packages/llm/package.json b/packages/llm/package.json index 54920022..fc7deeba 100644 --- a/packages/llm/package.json +++ b/packages/llm/package.json @@ -1,7 +1,7 @@ { - "name": "@klo/llm", + "name": "@ktx/llm", "version": "0.0.0-private", - "description": "Canonical KLO LLM and embedding provider package", + "description": "Canonical KTX LLM and embedding provider package", "private": true, "type": "module", "engines": { diff --git a/packages/llm/src/embedding-health.test.ts b/packages/llm/src/embedding-health.test.ts index 70e15515..ca998aa9 100644 --- a/packages/llm/src/embedding-health.test.ts +++ b/packages/llm/src/embedding-health.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -import { runKloEmbeddingHealthCheck } from './embedding-health.js'; +import { runKtxEmbeddingHealthCheck } from './embedding-health.js'; -describe('KLO embedding health check', () => { +describe('KTX embedding health check', () => { it('runs a one-shot OpenAI embedding check through the configured provider', async () => { const createOpenAIClient = vi.fn(() => ({ embeddings: { @@ -12,7 +12,7 @@ describe('KLO embedding health check', () => { })); await expect( - runKloEmbeddingHealthCheck( + runKtxEmbeddingHealthCheck( { backend: 'openai', model: 'text-embedding-3-small', @@ -36,7 +36,7 @@ describe('KLO embedding health check', () => { })); await expect( - runKloEmbeddingHealthCheck( + runKtxEmbeddingHealthCheck( { backend: 'openai', model: 'text-embedding-3-small', @@ -61,7 +61,7 @@ describe('KLO embedding health check', () => { })); await expect( - runKloEmbeddingHealthCheck( + runKtxEmbeddingHealthCheck( { backend: 'openai', model: 'text-embedding-3-small', @@ -89,7 +89,7 @@ describe('KLO embedding health check', () => { })); await expect( - runKloEmbeddingHealthCheck( + runKtxEmbeddingHealthCheck( { backend: 'openai', model: 'text-embedding-3-small', diff --git a/packages/llm/src/embedding-health.ts b/packages/llm/src/embedding-health.ts index 4662169e..2c8ac53d 100644 --- a/packages/llm/src/embedding-health.ts +++ b/packages/llm/src/embedding-health.ts @@ -1,15 +1,15 @@ -import { createKloEmbeddingProvider, type KloEmbeddingProviderDeps } from './embedding-provider.js'; -import type { KloEmbeddingConfig } from './types.js'; +import { createKtxEmbeddingProvider, type KtxEmbeddingProviderDeps } from './embedding-provider.js'; +import type { KtxEmbeddingConfig } from './types.js'; -export type KloEmbeddingHealthCheckResult = { ok: true } | { ok: false; message: string }; +export type KtxEmbeddingHealthCheckResult = { ok: true } | { ok: false; message: string }; -export interface KloEmbeddingHealthCheckOptions { +export interface KtxEmbeddingHealthCheckOptions { text?: string; timeoutMs?: number; - deps?: KloEmbeddingProviderDeps; + deps?: KtxEmbeddingProviderDeps; } -function redactHealthCheckMessage(message: string, config: KloEmbeddingConfig): string { +function redactHealthCheckMessage(message: string, config: KtxEmbeddingConfig): string { const secrets = [config.openai?.apiKey].filter( (value): value is string => typeof value === 'string' && value.length > 0, ); @@ -30,14 +30,14 @@ async function withTimeout(promise: Promise, timeoutMs: number): Promise { +export async function runKtxEmbeddingHealthCheck( + config: KtxEmbeddingConfig, + options: KtxEmbeddingHealthCheckOptions = {}, +): Promise { try { - const provider = createKloEmbeddingProvider(config, options.deps); + const provider = createKtxEmbeddingProvider(config, options.deps); const embedding = await withTimeout( - provider.embed(options.text ?? 'KLO embedding health check'), + provider.embed(options.text ?? 'KTX embedding health check'), options.timeoutMs ?? 15_000, ); if (embedding.length !== config.dimensions) { diff --git a/packages/llm/src/embedding-provider.test.ts b/packages/llm/src/embedding-provider.test.ts index f6f63522..3a0a6f9a 100644 --- a/packages/llm/src/embedding-provider.test.ts +++ b/packages/llm/src/embedding-provider.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it, vi } from 'vitest'; -import { createKloEmbeddingProvider } from './embedding-provider.js'; -import type { KloEmbeddingConfig } from './types.js'; +import { createKtxEmbeddingProvider } from './embedding-provider.js'; +import type { KtxEmbeddingConfig } from './types.js'; -describe('createKloEmbeddingProvider', () => { +describe('createKtxEmbeddingProvider', () => { it('creates deterministic embeddings with stable dimensions', async () => { - const provider = createKloEmbeddingProvider({ + const provider = createKtxEmbeddingProvider({ backend: 'deterministic', model: 'sha256', dimensions: 6, @@ -26,9 +26,9 @@ describe('createKloEmbeddingProvider', () => { dimensions: 2, gateway: { apiKey: 'gateway-key' }, // pragma: allowlist secret }), - ) as KloEmbeddingConfig; + ) as KtxEmbeddingConfig; - expect(() => createKloEmbeddingProvider(config)).toThrow('Unsupported KLO embedding backend: gateway'); + expect(() => createKtxEmbeddingProvider(config)).toThrow('Unsupported KTX embedding backend: gateway'); }); it('uses OpenAI embeddings with configured dimensions', async () => { @@ -41,7 +41,7 @@ describe('createKloEmbeddingProvider', () => { }, })); - const provider = createKloEmbeddingProvider( + const provider = createKtxEmbeddingProvider( { backend: 'openai', model: 'text-embedding-3-small', @@ -64,7 +64,7 @@ describe('createKloEmbeddingProvider', () => { .mockResolvedValueOnce(new Response(JSON.stringify({ embedding: [0.1, 0.2] }), { status: 200 })) .mockResolvedValueOnce(new Response(JSON.stringify({ embedding: [0.3, 0.4] }), { status: 200 })); - const provider = createKloEmbeddingProvider( + const provider = createKtxEmbeddingProvider( { backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', @@ -91,7 +91,7 @@ describe('createKloEmbeddingProvider', () => { .mockResolvedValueOnce(new Response(JSON.stringify({ embedding: [0.1, 0.2] }), { status: 200 })) .mockResolvedValueOnce(new Response(JSON.stringify({ embeddings: [[0.5, 0.6]] }), { status: 200 })); - const daemonProvider = createKloEmbeddingProvider( + const daemonProvider = createKtxEmbeddingProvider( { backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', @@ -114,14 +114,14 @@ describe('createKloEmbeddingProvider', () => { ); }); - it('falls back to one-shot klo-daemon inference when the local HTTP daemon is unavailable', async () => { + it('falls back to one-shot ktx-daemon inference when the local HTTP daemon is unavailable', async () => { const fetch = vi.fn().mockRejectedValue(new TypeError('fetch failed')); const runSentenceTransformersJson = vi .fn() .mockResolvedValueOnce({ embedding: [0.1, 0.2] }) .mockResolvedValueOnce({ embeddings: [[0.3, 0.4], [0.5, 0.6]] }); - const provider = createKloEmbeddingProvider( + const provider = createKtxEmbeddingProvider( { backend: 'sentence-transformers', model: 'all-MiniLM-L6-v2', @@ -137,7 +137,7 @@ describe('createKloEmbeddingProvider', () => { ]); expect(fetch).toHaveBeenCalledTimes(1); expect(runSentenceTransformersJson).toHaveBeenNthCalledWith(1, 'embedding-compute', { - text: '__klo_embedding_probe__', + text: '__ktx_embedding_probe__', }); expect(runSentenceTransformersJson).toHaveBeenNthCalledWith(2, 'embedding-compute-bulk', { texts: ['hello', 'world'], diff --git a/packages/llm/src/embedding-provider.ts b/packages/llm/src/embedding-provider.ts index 804dbdbc..fa16b561 100644 --- a/packages/llm/src/embedding-provider.ts +++ b/packages/llm/src/embedding-provider.ts @@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'; import { spawn } from 'node:child_process'; import { join } from 'node:path'; import OpenAI from 'openai'; -import type { KloEmbeddingConfig, KloEmbeddingProvider } from './types.js'; +import type { KtxEmbeddingConfig, KtxEmbeddingProvider } from './types.js'; type FetchFn = typeof fetch; type SentenceTransformersCommand = 'embedding-compute' | 'embedding-compute-bulk'; @@ -12,7 +12,7 @@ type SentenceTransformersJsonRunner = ( ) => Promise>; type SentenceTransformersProcessCommand = { command: string; args: string[] }; -export interface KloEmbeddingProviderDeps { +export interface KtxEmbeddingProviderDeps { createOpenAIClient?: (options: { apiKey?: string; baseURL?: string }) => { embeddings: { create(input: { @@ -90,7 +90,7 @@ function errorText(error: unknown): string { function parseJsonObject(raw: string, subcommand: string): Record { const parsed = JSON.parse(raw) as unknown; if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new Error(`klo-daemon ${subcommand} returned non-object JSON`); + throw new Error(`ktx-daemon ${subcommand} returned non-object JSON`); } return parsed as Record; } @@ -105,13 +105,13 @@ function isCommandNotFound(error: unknown): boolean { function defaultSentenceTransformersProcessCommands(): SentenceTransformersProcessCommand[] { const venvBin = - process.platform === 'win32' ? join('.venv', 'Scripts', 'klo-daemon.exe') : join('.venv', 'bin', 'klo-daemon'); + process.platform === 'win32' ? join('.venv', 'Scripts', 'ktx-daemon.exe') : join('.venv', 'bin', 'ktx-daemon'); const repoVenvBin = process.platform === 'win32' - ? join('klo', '.venv', 'Scripts', 'klo-daemon.exe') - : join('klo', '.venv', 'bin', 'klo-daemon'); + ? join('ktx', '.venv', 'Scripts', 'ktx-daemon.exe') + : join('ktx', '.venv', 'bin', 'ktx-daemon'); return [ - { command: 'klo-daemon', args: [] }, + { command: 'ktx-daemon', args: [] }, { command: venvBin, args: [] }, { command: repoVenvBin, args: [] }, ]; @@ -143,7 +143,7 @@ function runSentenceTransformersProcessCommand( const stdoutText = Buffer.concat(stdout).toString('utf8').trim(); const stderrText = Buffer.concat(stderr).toString('utf8').trim(); if (code !== 0) { - reject(new Error(`klo-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); + reject(new Error(`ktx-daemon ${subcommand} failed: ${stderrText || `exit code ${code}`}`)); return; } try { @@ -180,11 +180,11 @@ function runSentenceTransformersProcessJson(options: { } } } - throw new Error(`klo-daemon ${subcommand} failed: ${errors.join('; ')}`); + throw new Error(`ktx-daemon ${subcommand} failed: ${errors.join('; ')}`); }; } -class DeterministicEmbeddingProvider implements KloEmbeddingProvider { +class DeterministicEmbeddingProvider implements KtxEmbeddingProvider { readonly maxBatchSize: number; constructor(readonly dimensions: number, batchSize = DEFAULT_BATCH_SIZE) { @@ -202,19 +202,19 @@ class DeterministicEmbeddingProvider implements KloEmbeddingProvider { } } -class OpenAIEmbeddingProvider implements KloEmbeddingProvider { +class OpenAIEmbeddingProvider implements KtxEmbeddingProvider { readonly dimensions: number; readonly maxBatchSize: number; - private readonly client: ReturnType>; + private readonly client: ReturnType>; constructor( - private readonly config: KloEmbeddingConfig, - deps: KloEmbeddingProviderDeps, + private readonly config: KtxEmbeddingConfig, + deps: KtxEmbeddingProviderDeps, ) { this.dimensions = config.dimensions; this.maxBatchSize = config.batchSize ?? DEFAULT_BATCH_SIZE; if (!config.openai?.apiKey) { - throw new Error('openai.apiKey is required when KLO embedding backend is openai'); + throw new Error('openai.apiKey is required when KTX embedding backend is openai'); } this.client = deps.createOpenAIClient ? deps.createOpenAIClient({ apiKey: config.openai.apiKey, baseURL: config.openai.baseURL }) @@ -249,7 +249,7 @@ class OpenAIEmbeddingProvider implements KloEmbeddingProvider { } } -class SentenceTransformersEmbeddingProvider implements KloEmbeddingProvider { +class SentenceTransformersEmbeddingProvider implements KtxEmbeddingProvider { readonly dimensions: number; readonly maxBatchSize: number; private readonly fetch: FetchFn; @@ -259,9 +259,9 @@ class SentenceTransformersEmbeddingProvider implements KloEmbeddingProvider { private readonly startupProbe: Promise; private useProcessRunner = false; - constructor(config: KloEmbeddingConfig, deps: KloEmbeddingProviderDeps) { + constructor(config: KtxEmbeddingConfig, deps: KtxEmbeddingProviderDeps) { if (!config.sentenceTransformers?.baseURL) { - throw new Error('sentenceTransformers.baseURL is required when KLO embedding backend is sentence-transformers'); + throw new Error('sentenceTransformers.baseURL is required when KTX embedding backend is sentence-transformers'); } this.dimensions = config.dimensions; this.maxBatchSize = config.batchSize ?? DEFAULT_BATCH_SIZE; @@ -277,7 +277,7 @@ class SentenceTransformersEmbeddingProvider implements KloEmbeddingProvider { cwd: deps.sentenceTransformersCwd, env: deps.sentenceTransformersEnv, }); - this.startupProbe = this.requestSingle('__klo_embedding_probe__').then((embedding) => { + this.startupProbe = this.requestSingle('__ktx_embedding_probe__').then((embedding) => { assertVectorDimensions(embedding, this.dimensions, 'sentence-transformers'); }); } @@ -339,7 +339,7 @@ class SentenceTransformersEmbeddingProvider implements KloEmbeddingProvider { throw new Error( `Embedding provider sentence-transformers local HTTP request failed (${errorText( httpError, - )}) and klo-daemon fallback failed (${errorText(processError)})`, + )}) and ktx-daemon fallback failed (${errorText(processError)})`, ); } } @@ -362,10 +362,10 @@ class SentenceTransformersEmbeddingProvider implements KloEmbeddingProvider { } } -export function createKloEmbeddingProvider( - config: KloEmbeddingConfig, - deps: KloEmbeddingProviderDeps = {}, -): KloEmbeddingProvider { +export function createKtxEmbeddingProvider( + config: KtxEmbeddingConfig, + deps: KtxEmbeddingProviderDeps = {}, +): KtxEmbeddingProvider { switch (config.backend) { case 'deterministic': return new DeterministicEmbeddingProvider(config.dimensions, config.batchSize); @@ -374,6 +374,6 @@ export function createKloEmbeddingProvider( case 'sentence-transformers': return new SentenceTransformersEmbeddingProvider(config, deps); default: - throw new Error(`Unsupported KLO embedding backend: ${String((config as { backend?: string }).backend)}`); + throw new Error(`Unsupported KTX embedding backend: ${String((config as { backend?: string }).backend)}`); } } diff --git a/packages/llm/src/index.ts b/packages/llm/src/index.ts index bcd9e8e4..164ba183 100644 --- a/packages/llm/src/index.ts +++ b/packages/llm/src/index.ts @@ -1,30 +1,30 @@ -export { createKloEmbeddingProvider } from './embedding-provider.js'; -export { runKloEmbeddingHealthCheck } from './embedding-health.js'; -export { KloMessageBuilder } from './message-builder.js'; -export type { KloEmbeddingHealthCheckOptions, KloEmbeddingHealthCheckResult } from './embedding-health.js'; -export type { KloEmbeddingProviderDeps } from './embedding-provider.js'; -export type { KloLlmHealthCheckDeps, KloLlmHealthCheckOptions, KloLlmHealthCheckResult } from './model-health.js'; -export { runKloLlmHealthCheck } from './model-health.js'; +export { createKtxEmbeddingProvider } from './embedding-provider.js'; +export { runKtxEmbeddingHealthCheck } from './embedding-health.js'; +export { KtxMessageBuilder } from './message-builder.js'; +export type { KtxEmbeddingHealthCheckOptions, KtxEmbeddingHealthCheckResult } from './embedding-health.js'; +export type { KtxEmbeddingProviderDeps } from './embedding-provider.js'; +export type { KtxLlmHealthCheckDeps, KtxLlmHealthCheckOptions, KtxLlmHealthCheckResult } from './model-health.js'; +export { runKtxLlmHealthCheck } from './model-health.js'; export { - createKloLlmProvider, + createKtxLlmProvider, isAnthropicProtocolModel, modelIdFromLanguageModel, - type KloLlmProviderFactoryDeps, + type KtxLlmProviderFactoryDeps, } from './model-provider.js'; export type { - KloEmbeddingBackend, - KloEmbeddingConfig, - KloEmbeddingProvider, - KloEmbeddingTokenUsageEvent, - KloJsonValue, - KloLlmBackend, - KloLlmConfig, - KloLlmProvider, - KloModelRole, - KloPromptCacheTtl, - KloPromptCachingConfig, - KloPromptParts, - KloProviderOptions, - KloTokenUsageEvent, + KtxEmbeddingBackend, + KtxEmbeddingConfig, + KtxEmbeddingProvider, + KtxEmbeddingTokenUsageEvent, + KtxJsonValue, + KtxLlmBackend, + KtxLlmConfig, + KtxLlmProvider, + KtxModelRole, + KtxPromptCacheTtl, + KtxPromptCachingConfig, + KtxPromptParts, + KtxProviderOptions, + KtxTokenUsageEvent, } from './types.js'; -export { KLO_MODEL_ROLES } from './types.js'; +export { KTX_MODEL_ROLES } from './types.js'; diff --git a/packages/llm/src/message-builder.test.ts b/packages/llm/src/message-builder.test.ts index d5c2e002..bc13a7e1 100644 --- a/packages/llm/src/message-builder.test.ts +++ b/packages/llm/src/message-builder.test.ts @@ -1,19 +1,19 @@ import type { ModelMessage } from 'ai'; import { describe, expect, it } from 'vitest'; -import { KloMessageBuilder } from './message-builder.js'; -import { createKloLlmProvider } from './model-provider.js'; +import { KtxMessageBuilder } from './message-builder.js'; +import { createKtxLlmProvider } from './model-provider.js'; -function makeBuilder(overrides: Parameters[0]['promptCaching'] = {}) { - const provider = createKloLlmProvider({ +function makeBuilder(overrides: Parameters[0]['promptCaching'] = {}) { + const provider = createKtxLlmProvider({ backend: 'gateway', gateway: { baseURL: 'https://gateway.test' }, modelSlots: { default: 'anthropic/claude-sonnet-4-6' }, promptCaching: { enabled: true, ...overrides }, }); - return new KloMessageBuilder(provider); + return new KtxMessageBuilder(provider); } -describe('KloMessageBuilder.build', () => { +describe('KtxMessageBuilder.build', () => { it('caches static system, last sorted tool, and last history message', () => { const builder = makeBuilder(); @@ -82,9 +82,9 @@ describe('KloMessageBuilder.build', () => { }); it('clamps every TTL to 5m for Vertex when vertexFallbackTo5m is enabled', () => { - const provider = createKloLlmProvider({ + const provider = createKtxLlmProvider({ backend: 'vertex', - vertex: { project: 'klo-test', location: 'us-east5' }, + vertex: { project: 'ktx-test', location: 'us-east5' }, modelSlots: { default: 'claude-sonnet-4-6' }, promptCaching: { enabled: true, @@ -94,7 +94,7 @@ describe('KloMessageBuilder.build', () => { vertexFallbackTo5m: true, }, }); - const builder = new KloMessageBuilder(provider); + const builder = new KtxMessageBuilder(provider); const out = builder.build({ parts: { staticSystem: 'STATIC' }, diff --git a/packages/llm/src/message-builder.ts b/packages/llm/src/message-builder.ts index 920e647e..a98a0375 100644 --- a/packages/llm/src/message-builder.ts +++ b/packages/llm/src/message-builder.ts @@ -1,42 +1,42 @@ import type { LanguageModel, ModelMessage, ToolSet } from 'ai'; import { isAnthropicProtocolModel } from './model-provider.js'; -import type { KloLlmProvider, KloPromptCacheTtl, KloPromptParts } from './types.js'; +import type { KtxLlmProvider, KtxPromptCacheTtl, KtxPromptParts } from './types.js'; type ToolMap = ToolSet | Record>; -interface KloMessageBuilderOptions { +interface KtxMessageBuilderOptions { cacheSystem?: boolean; cacheTools?: boolean; cacheLastHistory?: boolean; } -interface KloBuildInput { - parts: KloPromptParts; +interface KtxBuildInput { + parts: KtxPromptParts; history: ModelMessage[]; currentMessage: ModelMessage; tools: ToolMap; model: LanguageModel | string; } -interface KloWrapSimpleInput { +interface KtxWrapSimpleInput { system?: string; messages?: ModelMessage[]; tools?: ToolMap; model: LanguageModel | string; } -interface KloBuildOutput { +interface KtxBuildOutput { messages: ModelMessage[]; tools: ToolMap; } -export class KloMessageBuilder { +export class KtxMessageBuilder { constructor( - private readonly provider: KloLlmProvider, - private readonly options: KloMessageBuilderOptions = {}, + private readonly provider: KtxLlmProvider, + private readonly options: KtxMessageBuilderOptions = {}, ) {} - build(input: KloBuildInput): KloBuildOutput { + build(input: KtxBuildInput): KtxBuildOutput { const cfg = this.provider.promptCachingConfig(); const cachingActive = cfg.enabled && isAnthropicProtocolModel(input.model); const ttls = this.resolveTtls(input.model); @@ -68,7 +68,7 @@ export class KloMessageBuilder { }; } - wrapSimple(input: KloWrapSimpleInput): KloBuildOutput { + wrapSimple(input: KtxWrapSimpleInput): KtxBuildOutput { const cfg = this.provider.promptCachingConfig(); const cachingActive = cfg.enabled && isAnthropicProtocolModel(input.model); const ttls = this.resolveTtls(input.model); @@ -112,9 +112,9 @@ export class KloMessageBuilder { } private resolveTtls(model: LanguageModel | string): { - systemTtl: KloPromptCacheTtl; - toolsTtl: KloPromptCacheTtl; - historyTtl: KloPromptCacheTtl; + systemTtl: KtxPromptCacheTtl; + toolsTtl: KtxPromptCacheTtl; + historyTtl: KtxPromptCacheTtl; } { const cfg = this.provider.promptCachingConfig(); if (cfg.vertexFallbackTo5m && this.provider.activeBackend() === 'vertex' && isAnthropicProtocolModel(model)) { @@ -145,7 +145,7 @@ export class KloMessageBuilder { private markLastHistoryMessage( history: ModelMessage[], - ttl: KloPromptCacheTtl, + ttl: KtxPromptCacheTtl, model: LanguageModel | string, ): ModelMessage[] { if (history.length === 0) { @@ -177,7 +177,7 @@ export class KloMessageBuilder { tools: ToolMap, cachingActive: boolean, cacheTools: boolean, - ttl: KloPromptCacheTtl, + ttl: KtxPromptCacheTtl, model: LanguageModel | string, ): ToolMap { const keys = Object.keys(tools).sort(); diff --git a/packages/llm/src/model-health.test.ts b/packages/llm/src/model-health.test.ts index 3305886d..003c12d5 100644 --- a/packages/llm/src/model-health.test.ts +++ b/packages/llm/src/model-health.test.ts @@ -1,15 +1,15 @@ import { describe, expect, it, vi } from 'vitest'; -import { runKloLlmHealthCheck } from './model-health.js'; +import { runKtxLlmHealthCheck } from './model-health.js'; const anthropicModel = { modelId: 'claude-sonnet-4-6' } as never; -describe('KLO LLM health check', () => { +describe('KTX LLM health check', () => { it('runs a minimal non-streaming model call through the configured provider', async () => { const generateText = vi.fn(async () => ({ text: 'ok' })); const createAnthropic = vi.fn(() => vi.fn(() => anthropicModel)); await expect( - runKloLlmHealthCheck( + runKtxLlmHealthCheck( { backend: 'anthropic', anthropic: { apiKey: 'sk-ant-test' }, @@ -40,7 +40,7 @@ describe('KLO LLM health check', () => { }); await expect( - runKloLlmHealthCheck( + runKtxLlmHealthCheck( { backend: 'anthropic', anthropic: { apiKey: 'sk-ant-secret' }, diff --git a/packages/llm/src/model-health.ts b/packages/llm/src/model-health.ts index 78454b17..131822b6 100644 --- a/packages/llm/src/model-health.ts +++ b/packages/llm/src/model-health.ts @@ -1,20 +1,20 @@ import { generateText } from 'ai'; -import { createKloLlmProvider, type KloLlmProviderFactoryDeps } from './model-provider.js'; -import type { KloLlmConfig } from './types.js'; +import { createKtxLlmProvider, type KtxLlmProviderFactoryDeps } from './model-provider.js'; +import type { KtxLlmConfig } from './types.js'; -export type KloLlmHealthCheckResult = { ok: true } | { ok: false; message: string }; +export type KtxLlmHealthCheckResult = { ok: true } | { ok: false; message: string }; -export interface KloLlmHealthCheckDeps extends Omit { +export interface KtxLlmHealthCheckDeps extends Omit { generateText?: (options: Parameters[0]) => Promise; } -export interface KloLlmHealthCheckOptions { +export interface KtxLlmHealthCheckOptions { prompt?: string; timeoutMs?: number; - deps?: KloLlmHealthCheckDeps; + deps?: KtxLlmHealthCheckDeps; } -function redactHealthCheckMessage(message: string, config: KloLlmConfig): string { +function redactHealthCheckMessage(message: string, config: KtxLlmConfig): string { const secrets = [config.anthropic?.apiKey, config.gateway?.apiKey].filter( (value): value is string => typeof value === 'string' && value.length > 0, ); @@ -35,13 +35,13 @@ async function withTimeout(promise: Promise, timeoutMs: number): Promise { +export async function runKtxLlmHealthCheck( + config: KtxLlmConfig, + options: KtxLlmHealthCheckOptions = {}, +): Promise { try { const { generateText: runGenerateTextOverride, ...providerDeps } = options.deps ?? {}; - const provider = createKloLlmProvider(config, providerDeps); + const provider = createKtxLlmProvider(config, providerDeps); const runGenerateText = runGenerateTextOverride ?? generateText; await withTimeout( runGenerateText({ diff --git a/packages/llm/src/model-provider.test.ts b/packages/llm/src/model-provider.test.ts index 2af58f4c..55dd5da9 100644 --- a/packages/llm/src/model-provider.test.ts +++ b/packages/llm/src/model-provider.test.ts @@ -1,16 +1,16 @@ import type { LanguageModel } from 'ai'; import { describe, expect, it, vi } from 'vitest'; -import { createKloLlmProvider } from './model-provider.js'; +import { createKtxLlmProvider } from './model-provider.js'; const languageModel = (modelId: string, provider = 'test'): LanguageModel => ({ modelId, provider }) as LanguageModel; -describe('createKloLlmProvider', () => { +describe('createKtxLlmProvider', () => { it('uses direct Anthropic with both beta headers', () => { const anthropicModel = languageModel('claude-sonnet-4-6', 'anthropic'); const anthropic = vi.fn(() => anthropicModel); const createAnthropic = vi.fn(() => anthropic); - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'anthropic', anthropic: { apiKey: 'test-anthropic-key', baseURL: 'https://anthropic.test' }, // pragma: allowlist secret @@ -36,10 +36,10 @@ describe('createKloLlmProvider', () => { const vertex = vi.fn(() => vertexModel); const createVertexAnthropic = vi.fn(() => vertex); - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'vertex', - vertex: { project: 'klo-test', location: 'us-east5' }, + vertex: { project: 'ktx-test', location: 'us-east5' }, modelSlots: { default: 'claude-sonnet-4-6' }, promptCaching: { enabled: false }, }, @@ -47,7 +47,7 @@ describe('createKloLlmProvider', () => { ); expect(provider.getModel('default')).toBe(vertexModel); - expect(createVertexAnthropic).toHaveBeenCalledWith({ project: 'klo-test', location: 'us-east5' }); + expect(createVertexAnthropic).toHaveBeenCalledWith({ project: 'ktx-test', location: 'us-east5' }); expect(vertex).toHaveBeenCalledWith('claude-sonnet-4-6'); }); @@ -56,7 +56,7 @@ describe('createKloLlmProvider', () => { const gateway = vi.fn(() => gatewayModel); const createGateway = vi.fn(() => gateway); - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'gateway', gateway: { apiKey: 'gateway-key', baseURL: 'https://gateway.test/v1' }, // pragma: allowlist secret @@ -77,7 +77,7 @@ describe('createKloLlmProvider', () => { it('uses explicit role overrides before default', () => { const anthropic = vi.fn((modelId: string) => languageModel(modelId, 'anthropic')); - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'anthropic', anthropic: { apiKey: 'test-anthropic-key' }, // pragma: allowlist secret @@ -97,7 +97,7 @@ describe('createKloLlmProvider', () => { }); it('emits cache markers only when enabled and the model speaks Anthropic protocol', () => { - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'gateway', gateway: { baseURL: 'https://gateway.test/v1' }, @@ -114,7 +114,7 @@ describe('createKloLlmProvider', () => { }); it('returns Anthropic thinking provider options', () => { - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'anthropic', anthropic: { apiKey: 'test-anthropic-key' }, // pragma: allowlist secret @@ -132,7 +132,7 @@ describe('createKloLlmProvider', () => { }); it('defaults prompt caching to enabled with canonical TTLs', () => { - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'gateway', gateway: { baseURL: 'https://gateway.test/v1' }, @@ -157,7 +157,7 @@ describe('createKloLlmProvider', () => { }); it('preserves explicit prompt caching opt-out', () => { - const provider = createKloLlmProvider( + const provider = createKtxLlmProvider( { backend: 'anthropic', anthropic: { apiKey: 'test-anthropic-key' }, // pragma: allowlist secret diff --git a/packages/llm/src/model-provider.ts b/packages/llm/src/model-provider.ts index a240480d..66a9fddd 100644 --- a/packages/llm/src/model-provider.ts +++ b/packages/llm/src/model-provider.ts @@ -1,14 +1,14 @@ import { createAnthropic } from '@ai-sdk/anthropic'; import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; import { createGateway, generateText, type LanguageModel } from 'ai'; -import { createKloToolCallRepairHandler } from './repair.js'; +import { createKtxToolCallRepairHandler } from './repair.js'; import type { - KloLlmConfig, - KloLlmProvider, - KloModelRole, - KloPromptCacheTtl, - KloPromptCachingConfig, - KloProviderOptions, + KtxLlmConfig, + KtxLlmProvider, + KtxModelRole, + KtxPromptCacheTtl, + KtxPromptCachingConfig, + KtxProviderOptions, } from './types.js'; type AnthropicFactory = typeof createAnthropic; @@ -16,14 +16,14 @@ type AnthropicModelFactory = (modelId: string) => LanguageModel; type VertexAnthropicFactory = (options?: Parameters[0]) => AnthropicModelFactory; type GatewayFactory = (options?: Parameters[0]) => AnthropicModelFactory; -export interface KloLlmProviderFactoryDeps { +export interface KtxLlmProviderFactoryDeps { createAnthropic?: (options?: Parameters[0]) => AnthropicModelFactory; createVertexAnthropic?: VertexAnthropicFactory; createGateway?: GatewayFactory; generateText?: typeof generateText; } -const DEFAULT_PROMPT_CACHING: KloPromptCachingConfig = { +const DEFAULT_PROMPT_CACHING: KtxPromptCachingConfig = { enabled: true, systemTtl: '1h', toolsTtl: '1h', @@ -36,7 +36,7 @@ const DEFAULT_PROMPT_CACHING: KloPromptCachingConfig = { const DIRECT_ANTHROPIC_BETA_HEADER = 'interleaved-thinking-2025-05-14,extended-cache-ttl-2025-04-11'; -function resolvePromptCaching(config: KloLlmConfig): KloPromptCachingConfig { +function resolvePromptCaching(config: KtxLlmConfig): KtxPromptCachingConfig { return { ...DEFAULT_PROMPT_CACHING, ...config.promptCaching }; } @@ -49,21 +49,21 @@ export function isAnthropicProtocolModel(model: LanguageModel | string): boolean return modelId.startsWith('claude-') || modelId.startsWith('anthropic/') || modelId.includes('/claude-'); } -class DefaultKloLlmProvider implements KloLlmProvider { - private readonly promptCaching: KloPromptCachingConfig; +class DefaultKtxLlmProvider implements KtxLlmProvider { + private readonly promptCaching: KtxPromptCachingConfig; private readonly getModelByResolvedName: (modelId: string) => LanguageModel; private readonly runGenerateText: typeof generateText; constructor( - private readonly config: KloLlmConfig, - deps: KloLlmProviderFactoryDeps, + private readonly config: KtxLlmConfig, + deps: KtxLlmProviderFactoryDeps, ) { this.promptCaching = resolvePromptCaching(config); this.runGenerateText = deps.generateText ?? generateText; this.getModelByResolvedName = this.createModelFactory(config, deps); } - getModel(role: KloModelRole): LanguageModel { + getModel(role: KtxModelRole): LanguageModel { return this.getModelByName(this.resolveRole(role)); } @@ -71,7 +71,7 @@ class DefaultKloLlmProvider implements KloLlmProvider { return this.getModelByResolvedName(modelId); } - cacheMarker(ttl: KloPromptCacheTtl, model?: LanguageModel | string) { + cacheMarker(ttl: KtxPromptCacheTtl, model?: LanguageModel | string) { if (!this.promptCaching.enabled) { return undefined; } @@ -82,14 +82,14 @@ class DefaultKloLlmProvider implements KloLlmProvider { } repairToolCallHandler(options: { source?: string } = {}) { - return createKloToolCallRepairHandler({ - source: options.source ?? 'klo-llm', + return createKtxToolCallRepairHandler({ + source: options.source ?? 'ktx-llm', getRepairModel: () => this.getModel('repair'), generateText: this.runGenerateText, }); } - thinkingProviderOptions(_role: KloModelRole, budgetTokens: number): KloProviderOptions { + thinkingProviderOptions(_role: KtxModelRole, budgetTokens: number): KtxProviderOptions { return { anthropic: { thinking: { type: 'enabled', budgetTokens }, @@ -101,7 +101,7 @@ class DefaultKloLlmProvider implements KloLlmProvider { return this.config.telemetry?.experimentalTelemetry; } - promptCachingConfig(): KloPromptCachingConfig { + promptCachingConfig(): KtxPromptCachingConfig { return this.promptCaching; } @@ -109,11 +109,11 @@ class DefaultKloLlmProvider implements KloLlmProvider { return this.config.backend; } - private resolveRole(role: KloModelRole): string { + private resolveRole(role: KtxModelRole): string { return this.config.modelSlots[role] ?? this.config.modelSlots.default; } - private createModelFactory(config: KloLlmConfig, deps: KloLlmProviderFactoryDeps): (modelId: string) => LanguageModel { + private createModelFactory(config: KtxLlmConfig, deps: KtxLlmProviderFactoryDeps): (modelId: string) => LanguageModel { if (config.backend === 'anthropic') { const anthropic = (deps.createAnthropic ?? createAnthropic)({ ...(config.anthropic?.apiKey ? { apiKey: config.anthropic.apiKey } : {}), @@ -127,7 +127,7 @@ class DefaultKloLlmProvider implements KloLlmProvider { if (config.backend === 'vertex') { if (!config.vertex?.location) { - throw new Error('vertex.location is required when KLO LLM backend is vertex'); + throw new Error('vertex.location is required when KTX LLM backend is vertex'); } const vertex = (deps.createVertexAnthropic ?? createVertexAnthropic)({ ...(config.vertex.project ? { project: config.vertex.project } : {}), @@ -144,9 +144,9 @@ class DefaultKloLlmProvider implements KloLlmProvider { } } -export function createKloLlmProvider(config: KloLlmConfig, deps: KloLlmProviderFactoryDeps = {}): KloLlmProvider { +export function createKtxLlmProvider(config: KtxLlmConfig, deps: KtxLlmProviderFactoryDeps = {}): KtxLlmProvider { if (!config.modelSlots.default) { throw new Error('modelSlots.default is required'); } - return new DefaultKloLlmProvider(config, deps); + return new DefaultKtxLlmProvider(config, deps); } diff --git a/packages/llm/src/package-exports.test.ts b/packages/llm/src/package-exports.test.ts index 37485e1a..cf080325 100644 --- a/packages/llm/src/package-exports.test.ts +++ b/packages/llm/src/package-exports.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from 'vitest'; -describe('@klo/llm package exports', () => { +describe('@ktx/llm package exports', () => { it('exports the canonical LLM and embedding surfaces', async () => { const llm = await import('./index.js'); - expect(llm.KLO_MODEL_ROLES).toEqual([ + expect(llm.KTX_MODEL_ROLES).toEqual([ 'default', 'triage', 'candidateExtraction', @@ -12,8 +12,8 @@ describe('@klo/llm package exports', () => { 'reconcile', 'repair', ]); - expect(llm.createKloLlmProvider).toBeTypeOf('function'); - expect(llm.KloMessageBuilder).toBeTypeOf('function'); - expect(llm.createKloEmbeddingProvider).toBeTypeOf('function'); + expect(llm.createKtxLlmProvider).toBeTypeOf('function'); + expect(llm.KtxMessageBuilder).toBeTypeOf('function'); + expect(llm.createKtxEmbeddingProvider).toBeTypeOf('function'); }); }); diff --git a/packages/llm/src/repair.test.ts b/packages/llm/src/repair.test.ts index 6b0ad850..bef53a46 100644 --- a/packages/llm/src/repair.test.ts +++ b/packages/llm/src/repair.test.ts @@ -1,12 +1,12 @@ import { NoSuchToolError, type LanguageModel } from 'ai'; import { describe, expect, it, vi } from 'vitest'; -import { createKloToolCallRepairHandler } from './repair.js'; +import { createKtxToolCallRepairHandler } from './repair.js'; const repairModel = { modelId: 'claude-repair', provider: 'anthropic' } as LanguageModel; -describe('createKloToolCallRepairHandler', () => { +describe('createKtxToolCallRepairHandler', () => { it('returns null for NoSuchToolError', async () => { - const handler = createKloToolCallRepairHandler({ + const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText: vi.fn(), @@ -26,7 +26,7 @@ describe('createKloToolCallRepairHandler', () => { it('repairs string input by local JSON extraction without an LLM call', async () => { const generateText = vi.fn(); - const handler = createKloToolCallRepairHandler({ + const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText, @@ -57,7 +57,7 @@ describe('createKloToolCallRepairHandler', () => { it('falls back to the repair model when local extraction fails', async () => { const generateText = vi.fn().mockResolvedValue({ text: '{"path":"customers.yaml"}' }); - const handler = createKloToolCallRepairHandler({ + const handler = createKtxToolCallRepairHandler({ source: 'unit', getRepairModel: () => repairModel, generateText, diff --git a/packages/llm/src/repair.ts b/packages/llm/src/repair.ts index 4d0cd0ab..59b058c3 100644 --- a/packages/llm/src/repair.ts +++ b/packages/llm/src/repair.ts @@ -1,6 +1,6 @@ import { NoSuchToolError, type LanguageModel, type ToolCallRepairFunction, type ToolSet, generateText } from 'ai'; -interface KloToolCallRepairHandlerInput { +interface KtxToolCallRepairHandlerInput { source: string; getRepairModel: () => LanguageModel; generateText?: typeof generateText; @@ -32,8 +32,8 @@ function extractJsonFromText(text: string): string | null { return null; } -export function createKloToolCallRepairHandler( - input: KloToolCallRepairHandlerInput, +export function createKtxToolCallRepairHandler( + input: KtxToolCallRepairHandlerInput, ): ToolCallRepairFunction { const runGenerateText = input.generateText ?? generateText; diff --git a/packages/llm/src/types.ts b/packages/llm/src/types.ts index 09486643..a477b2f2 100644 --- a/packages/llm/src/types.ts +++ b/packages/llm/src/types.ts @@ -1,33 +1,33 @@ import type { LanguageModel, TelemetrySettings, ToolCallRepairFunction, ToolSet } from 'ai'; -export const KLO_MODEL_ROLES = ['default', 'triage', 'candidateExtraction', 'curator', 'reconcile', 'repair'] as const; +export const KTX_MODEL_ROLES = ['default', 'triage', 'candidateExtraction', 'curator', 'reconcile', 'repair'] as const; -export type KloModelRole = (typeof KLO_MODEL_ROLES)[number]; -export type KloLlmBackend = 'anthropic' | 'vertex' | 'gateway'; -export type KloPromptCacheTtl = '5m' | '1h'; +export type KtxModelRole = (typeof KTX_MODEL_ROLES)[number]; +export type KtxLlmBackend = 'anthropic' | 'vertex' | 'gateway'; +export type KtxPromptCacheTtl = '5m' | '1h'; -export type KloJsonValue = +export type KtxJsonValue = | null | string | number | boolean - | KloJsonValue[] - | { [key: string]: KloJsonValue | undefined }; + | KtxJsonValue[] + | { [key: string]: KtxJsonValue | undefined }; -export type KloProviderOptions = Record; +export type KtxProviderOptions = Record; -export interface KloPromptCachingConfig { +export interface KtxPromptCachingConfig { enabled: boolean; - systemTtl: KloPromptCacheTtl; - toolsTtl: KloPromptCacheTtl; - historyTtl: KloPromptCacheTtl; + systemTtl: KtxPromptCacheTtl; + toolsTtl: KtxPromptCacheTtl; + historyTtl: KtxPromptCacheTtl; cacheSystem: boolean; cacheTools: boolean; cacheHistory: boolean; vertexFallbackTo5m: boolean; } -export interface KloTokenUsageEvent { +export interface KtxTokenUsageEvent { source?: string; modelId?: string; inputTokens?: number; @@ -35,60 +35,60 @@ export interface KloTokenUsageEvent { totalTokens?: number; } -export interface KloLlmConfig { - backend: KloLlmBackend; +export interface KtxLlmConfig { + backend: KtxLlmBackend; vertex?: { project?: string; location: string }; anthropic?: { apiKey?: string; baseURL?: string }; gateway?: { baseURL?: string; apiKey?: string }; - modelSlots: { default: string } & Partial>; - promptCaching?: Partial; + modelSlots: { default: string } & Partial>; + promptCaching?: Partial; telemetry?: { experimentalTelemetry?: TelemetrySettings; - onTokenUsage?: (event: KloTokenUsageEvent) => void; + onTokenUsage?: (event: KtxTokenUsageEvent) => void; }; } -export interface KloLlmProvider { - getModel(role: KloModelRole): LanguageModel; +export interface KtxLlmProvider { + getModel(role: KtxModelRole): LanguageModel; getModelByName(modelId: string): LanguageModel; cacheMarker( - ttl: KloPromptCacheTtl, + ttl: KtxPromptCacheTtl, model?: LanguageModel | string, - ): { anthropic: { cacheControl: { type: 'ephemeral'; ttl: KloPromptCacheTtl } } } | undefined; + ): { anthropic: { cacheControl: { type: 'ephemeral'; ttl: KtxPromptCacheTtl } } } | undefined; repairToolCallHandler(options?: { source?: string }): ToolCallRepairFunction; - thinkingProviderOptions(role: KloModelRole, budgetTokens: number): KloProviderOptions; + thinkingProviderOptions(role: KtxModelRole, budgetTokens: number): KtxProviderOptions; telemetryConfig(): TelemetrySettings | undefined; - promptCachingConfig(): KloPromptCachingConfig; - activeBackend(): KloLlmBackend; + promptCachingConfig(): KtxPromptCachingConfig; + activeBackend(): KtxLlmBackend; } -export type KloEmbeddingBackend = 'openai' | 'deterministic' | 'sentence-transformers'; +export type KtxEmbeddingBackend = 'openai' | 'deterministic' | 'sentence-transformers'; -export interface KloEmbeddingTokenUsageEvent { - backend: KloEmbeddingBackend; +export interface KtxEmbeddingTokenUsageEvent { + backend: KtxEmbeddingBackend; model: string; inputCount: number; totalTokens?: number; } -export interface KloEmbeddingConfig { - backend: KloEmbeddingBackend; +export interface KtxEmbeddingConfig { + backend: KtxEmbeddingBackend; model: string; dimensions: number; openai?: { apiKey?: string; baseURL?: string }; sentenceTransformers?: { baseURL: string; pathPrefix?: string }; batchSize?: number; - telemetry?: { onTokenUsage?: (event: KloEmbeddingTokenUsageEvent) => void }; + telemetry?: { onTokenUsage?: (event: KtxEmbeddingTokenUsageEvent) => void }; } -export interface KloEmbeddingProvider { +export interface KtxEmbeddingProvider { readonly dimensions: number; readonly maxBatchSize: number; embed(text: string): Promise; embedMany(texts: string[]): Promise; } -export interface KloPromptParts { +export interface KtxPromptParts { staticSystem: string; dynamicSystem?: string; leadingUserContext?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db51390e..b900b9ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,34 +30,34 @@ importers: '@commander-js/extra-typings': specifier: 14.0.0 version: 14.0.0(commander@14.0.3) - '@klo/connector-bigquery': + '@ktx/connector-bigquery': specifier: workspace:* version: file:packages/connector-bigquery(ws@8.20.0) - '@klo/connector-clickhouse': + '@ktx/connector-clickhouse': specifier: workspace:* version: file:packages/connector-clickhouse(ws@8.20.0) - '@klo/connector-mysql': + '@ktx/connector-mysql': specifier: workspace:* version: file:packages/connector-mysql(@types/node@24.12.2)(ws@8.20.0) - '@klo/connector-postgres': + '@ktx/connector-postgres': specifier: workspace:* version: file:packages/connector-postgres(ws@8.20.0) - '@klo/connector-posthog': + '@ktx/connector-posthog': specifier: workspace:* version: file:packages/connector-posthog(ws@8.20.0) - '@klo/connector-snowflake': + '@ktx/connector-snowflake': specifier: workspace:* version: file:packages/connector-snowflake(asn1.js@5.4.1)(ws@8.20.0) - '@klo/connector-sqlite': + '@ktx/connector-sqlite': specifier: workspace:* version: file:packages/connector-sqlite(ws@8.20.0) - '@klo/connector-sqlserver': + '@ktx/connector-sqlserver': specifier: workspace:* - version: file:packages/connector-sqlserver(ws@8.20.0) - '@klo/context': + version: file:packages/connector-sqlserver(@azure/core-client@1.10.1)(ws@8.20.0) + '@ktx/context': specifier: workspace:* version: file:packages/context(ws@8.20.0) - '@klo/llm': + '@ktx/llm': specifier: workspace:* version: link:../llm '@modelcontextprotocol/sdk': @@ -103,7 +103,7 @@ importers: '@google-cloud/bigquery': specifier: ^8.1.1 version: 8.3.0 - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context devDependencies: @@ -122,7 +122,7 @@ importers: '@clickhouse/client': specifier: ^1.18.2 version: 1.18.3 - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context devDependencies: @@ -138,7 +138,7 @@ importers: packages/connector-mysql: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context mysql2: @@ -157,7 +157,7 @@ importers: packages/connector-postgres: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context pg: @@ -179,7 +179,7 @@ importers: packages/connector-posthog: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context devDependencies: @@ -195,7 +195,7 @@ importers: packages/connector-snowflake: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context snowflake-sdk: @@ -214,7 +214,7 @@ importers: packages/connector-sqlite: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context better-sqlite3: @@ -236,7 +236,7 @@ importers: packages/connector-sqlserver: dependencies: - '@klo/context': + '@ktx/context': specifier: workspace:* version: file:packages/context mssql: @@ -258,7 +258,7 @@ importers: packages/context: dependencies: - '@klo/llm': + '@ktx/llm': specifier: workspace:* version: file:packages/llm(ws@8.20.0)(zod@4.3.6) '@looker/sdk': @@ -903,43 +903,43 @@ packages: '@js-joda/core@5.7.0': resolution: {integrity: sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==} - '@klo/connector-bigquery@file:packages/connector-bigquery': + '@ktx/connector-bigquery@file:packages/connector-bigquery': resolution: {directory: packages/connector-bigquery, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-clickhouse@file:packages/connector-clickhouse': + '@ktx/connector-clickhouse@file:packages/connector-clickhouse': resolution: {directory: packages/connector-clickhouse, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-mysql@file:packages/connector-mysql': + '@ktx/connector-mysql@file:packages/connector-mysql': resolution: {directory: packages/connector-mysql, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-postgres@file:packages/connector-postgres': + '@ktx/connector-postgres@file:packages/connector-postgres': resolution: {directory: packages/connector-postgres, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-posthog@file:packages/connector-posthog': + '@ktx/connector-posthog@file:packages/connector-posthog': resolution: {directory: packages/connector-posthog, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-snowflake@file:packages/connector-snowflake': + '@ktx/connector-snowflake@file:packages/connector-snowflake': resolution: {directory: packages/connector-snowflake, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-sqlite@file:packages/connector-sqlite': + '@ktx/connector-sqlite@file:packages/connector-sqlite': resolution: {directory: packages/connector-sqlite, type: directory} engines: {node: '>=22.0.0'} - '@klo/connector-sqlserver@file:packages/connector-sqlserver': + '@ktx/connector-sqlserver@file:packages/connector-sqlserver': resolution: {directory: packages/connector-sqlserver, type: directory} engines: {node: '>=22.0.0'} - '@klo/context@file:packages/context': + '@ktx/context@file:packages/context': resolution: {directory: packages/context, type: directory} engines: {node: '>=22.0.0'} - '@klo/llm@file:packages/llm': + '@ktx/llm@file:packages/llm': resolution: {directory: packages/llm, type: directory} engines: {node: '>=22.0.0'} @@ -3632,11 +3632,6 @@ snapshots: '@azure/core-client': 1.10.1 '@azure/core-rest-pipeline': 1.23.0 - '@azure/core-http-compat@2.4.0(@azure/core-rest-pipeline@1.23.0)': - dependencies: - '@azure/abort-controller': 2.1.2 - '@azure/core-rest-pipeline': 1.23.0 - '@azure/core-lro@2.7.2': dependencies: '@azure/abort-controller': 2.1.2 @@ -3708,24 +3703,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@azure/keyvault-keys@4.10.0': - dependencies: - '@azure-rest/core-client': 2.6.0 - '@azure/abort-controller': 2.1.2 - '@azure/core-auth': 1.10.1 - '@azure/core-http-compat': 2.4.0(@azure/core-rest-pipeline@1.23.0) - '@azure/core-lro': 2.7.2 - '@azure/core-paging': 1.6.2 - '@azure/core-rest-pipeline': 1.23.0 - '@azure/core-tracing': 1.3.1 - '@azure/core-util': 1.13.1 - '@azure/keyvault-common': 2.1.0 - '@azure/logger': 1.3.0 - tslib: 2.8.1 - transitivePeerDependencies: - - '@azure/core-client' - - supports-color - '@azure/keyvault-keys@4.10.0(@azure/core-client@1.10.1)': dependencies: '@azure-rest/core-client': 2.6.0 @@ -3959,10 +3936,10 @@ snapshots: '@js-joda/core@5.7.0': {} - '@klo/connector-bigquery@file:packages/connector-bigquery(ws@8.20.0)': + '@ktx/connector-bigquery@file:packages/connector-bigquery(ws@8.20.0)': dependencies: '@google-cloud/bigquery': 8.3.0 - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) transitivePeerDependencies: - '@cfworker/json-schema' - js-yaml @@ -3970,10 +3947,10 @@ snapshots: - supports-color - ws - '@klo/connector-clickhouse@file:packages/connector-clickhouse(ws@8.20.0)': + '@ktx/connector-clickhouse@file:packages/connector-clickhouse(ws@8.20.0)': dependencies: '@clickhouse/client': 1.18.3 - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) transitivePeerDependencies: - '@cfworker/json-schema' - js-yaml @@ -3981,9 +3958,9 @@ snapshots: - supports-color - ws - '@klo/connector-mysql@file:packages/connector-mysql(@types/node@24.12.2)(ws@8.20.0)': + '@ktx/connector-mysql@file:packages/connector-mysql(@types/node@24.12.2)(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) mysql2: 3.22.3(@types/node@24.12.2) transitivePeerDependencies: - '@cfworker/json-schema' @@ -3993,9 +3970,9 @@ snapshots: - supports-color - ws - '@klo/connector-postgres@file:packages/connector-postgres(ws@8.20.0)': + '@ktx/connector-postgres@file:packages/connector-postgres(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) pg: 8.20.0 transitivePeerDependencies: - '@cfworker/json-schema' @@ -4004,9 +3981,9 @@ snapshots: - supports-color - ws - '@klo/connector-posthog@file:packages/connector-posthog(ws@8.20.0)': + '@ktx/connector-posthog@file:packages/connector-posthog(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) transitivePeerDependencies: - '@cfworker/json-schema' - js-yaml @@ -4014,9 +3991,9 @@ snapshots: - supports-color - ws - '@klo/connector-snowflake@file:packages/connector-snowflake(asn1.js@5.4.1)(ws@8.20.0)': + '@ktx/connector-snowflake@file:packages/connector-snowflake(asn1.js@5.4.1)(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) snowflake-sdk: 2.4.0(asn1.js@5.4.1) transitivePeerDependencies: - '@cfworker/json-schema' @@ -4028,9 +4005,9 @@ snapshots: - supports-color - ws - '@klo/connector-sqlite@file:packages/connector-sqlite(ws@8.20.0)': + '@ktx/connector-sqlite@file:packages/connector-sqlite(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) + '@ktx/context': file:packages/context(ws@8.20.0) better-sqlite3: 12.9.0 transitivePeerDependencies: - '@cfworker/json-schema' @@ -4039,10 +4016,10 @@ snapshots: - supports-color - ws - '@klo/connector-sqlserver@file:packages/connector-sqlserver(ws@8.20.0)': + '@ktx/connector-sqlserver@file:packages/connector-sqlserver(@azure/core-client@1.10.1)(ws@8.20.0)': dependencies: - '@klo/context': file:packages/context(ws@8.20.0) - mssql: 12.5.0 + '@ktx/context': file:packages/context(ws@8.20.0) + mssql: 12.5.0(@azure/core-client@1.10.1) transitivePeerDependencies: - '@azure/core-client' - '@cfworker/json-schema' @@ -4051,9 +4028,9 @@ snapshots: - supports-color - ws - '@klo/context@file:packages/context': + '@ktx/context@file:packages/context': dependencies: - '@klo/llm': file:packages/llm(zod@4.4.3) + '@ktx/llm': file:packages/llm(zod@4.4.3) '@looker/sdk': 26.6.1 '@looker/sdk-node': 26.6.1 '@looker/sdk-rtl': 21.6.5 @@ -4076,9 +4053,9 @@ snapshots: - supports-color - ws - '@klo/context@file:packages/context(ws@8.20.0)': + '@ktx/context@file:packages/context(ws@8.20.0)': dependencies: - '@klo/llm': file:packages/llm(ws@8.20.0)(zod@4.4.3) + '@ktx/llm': file:packages/llm(ws@8.20.0)(zod@4.4.3) '@looker/sdk': 26.6.1 '@looker/sdk-node': 26.6.1 '@looker/sdk-rtl': 21.6.5 @@ -4101,7 +4078,7 @@ snapshots: - supports-color - ws - '@klo/llm@file:packages/llm(ws@8.20.0)(zod@4.3.6)': + '@ktx/llm@file:packages/llm(ws@8.20.0)(zod@4.3.6)': dependencies: '@ai-sdk/anthropic': 3.0.71(zod@4.3.6) '@ai-sdk/google-vertex': 4.0.118(zod@4.3.6) @@ -4112,7 +4089,7 @@ snapshots: - ws - zod - '@klo/llm@file:packages/llm(ws@8.20.0)(zod@4.4.3)': + '@ktx/llm@file:packages/llm(ws@8.20.0)(zod@4.4.3)': dependencies: '@ai-sdk/anthropic': 3.0.71(zod@4.4.3) '@ai-sdk/google-vertex': 4.0.118(zod@4.4.3) @@ -4123,7 +4100,7 @@ snapshots: - ws - zod - '@klo/llm@file:packages/llm(zod@4.4.3)': + '@ktx/llm@file:packages/llm(zod@4.4.3)': dependencies: '@ai-sdk/anthropic': 3.0.71(zod@4.4.3) '@ai-sdk/google-vertex': 4.0.118(zod@4.4.3) @@ -5594,17 +5571,6 @@ snapshots: ms@2.1.3: {} - mssql@12.5.0: - dependencies: - '@tediousjs/connection-string': 1.1.0 - commander: 11.1.0 - debug: 4.4.3 - tarn: 3.0.2 - tedious: 19.2.1 - transitivePeerDependencies: - - '@azure/core-client' - - supports-color - mssql@12.5.0(@azure/core-client@1.10.1): dependencies: '@tediousjs/connection-string': 1.1.0 @@ -6107,22 +6073,6 @@ snapshots: tarn@3.0.2: {} - tedious@19.2.1: - dependencies: - '@azure/core-auth': 1.10.1 - '@azure/identity': 4.13.1 - '@azure/keyvault-keys': 4.10.0 - '@js-joda/core': 5.7.0 - '@types/node': 24.12.2 - bl: 6.1.6 - iconv-lite: 0.7.2 - js-md4: 0.3.2 - native-duplexpair: 1.0.0 - sprintf-js: 1.1.3 - transitivePeerDependencies: - - '@azure/core-client' - - supports-color - tedious@19.2.1(@azure/core-client@1.10.1): dependencies: '@azure/core-auth': 1.10.1 diff --git a/pyproject.toml b/pyproject.toml index 9987127d..1c4816b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "klo-workspace" +name = "ktx-workspace" version = "0.0.0" -description = "Workspace root for klo Python packages" +description = "Workspace root for ktx Python packages" requires-python = ">=3.13" license = "Apache-2.0" dependencies = [] @@ -30,14 +30,14 @@ torch = { index = "pytorch-cpu" } [tool.uv.workspace] members = [ - "python/klo-sl", - "python/klo-daemon", + "python/ktx-sl", + "python/ktx-daemon", ] [tool.pytest.ini_options] addopts = ["--import-mode=importlib"] -pythonpath = ["python/klo-sl/tests"] +pythonpath = ["python/ktx-sl/tests"] testpaths = [ - "python/klo-sl/tests", - "python/klo-daemon/tests", + "python/ktx-sl/tests", + "python/ktx-daemon/tests", ] diff --git a/python/klo-daemon/src/klo_daemon/__init__.py b/python/klo-daemon/src/klo_daemon/__init__.py deleted file mode 100644 index 5fa3a0a8..00000000 --- a/python/klo-daemon/src/klo_daemon/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Portable compute package for KLO.""" - -PACKAGE_NAME = "klo-daemon" -VERSION = "0.1.0" - -__all__ = ["PACKAGE_NAME", "VERSION"] diff --git a/python/klo-daemon/tests/test_package.py b/python/klo-daemon/tests/test_package.py deleted file mode 100644 index 790c59b0..00000000 --- a/python/klo-daemon/tests/test_package.py +++ /dev/null @@ -1,6 +0,0 @@ -from klo_daemon import PACKAGE_NAME, VERSION - - -def test_package_metadata() -> None: - assert PACKAGE_NAME == "klo-daemon" - assert VERSION == "0.1.0" diff --git a/python/klo-daemon/README.md b/python/ktx-daemon/README.md similarity index 81% rename from python/klo-daemon/README.md rename to python/ktx-daemon/README.md index f886bde1..bb429c5e 100644 --- a/python/klo-daemon/README.md +++ b/python/ktx-daemon/README.md @@ -1,17 +1,17 @@ -# klo-daemon +# ktx-daemon -`klo-daemon` is the portable Python compute package for KLO. +`ktx-daemon` is the portable Python compute package for KTX. It supports portable compute in two modes: -- One-shot commands, used by default by `@klo/context`. +- One-shot commands, used by default by `@ktx/context`. - An explicit HTTP server for long-running local MCP sessions. ## One-shot semantic query ```bash printf '%s\n' '{"sources":[],"query":{"measures":[],"dimensions":[]},"dialect":"postgres"}' \ - | klo-daemon semantic-query + | ktx-daemon semantic-query ``` ## One-shot source generation @@ -20,7 +20,7 @@ Generate semantic-layer sources from schema scan data: ```bash printf '%s\n' '{"tables":[{"name":"orders","db":"public","columns":[{"name":"id","type":"integer","primary_key":true}]}],"links":[],"dialect":"postgres"}' \ - | klo-daemon semantic-generate-sources + | ktx-daemon semantic-generate-sources ``` ## One-shot database introspection @@ -29,7 +29,7 @@ Introspect a Postgres database schema: ```bash printf '%s\n' '{"connection_id":"warehouse","driver":"postgres","url":"postgresql://readonly@example.test/warehouse","schemas":["public"]}' \ - | klo-daemon database-introspect + | ktx-daemon database-introspect ``` ## One-shot LookML parsing @@ -38,7 +38,7 @@ Parse LookML projects into resolved, KSL-ready structures: ```bash printf '%s\n' '{"files":[{"path":"views/orders.view.lkml","content":"view: orders { sql_table_name: public.orders ;; measure: order_count { type: count } }"}],"dialect":"postgres"}' \ - | klo-daemon lookml-parse + | ktx-daemon lookml-parse ``` ## One-shot embeddings @@ -47,14 +47,14 @@ Compute text embeddings locally: ```bash printf '%s\n' '{"text":"hello"}' \ - | klo-daemon embedding-compute + | ktx-daemon embedding-compute ``` Compute text embeddings locally in bulk: ```bash printf '%s\n' '{"texts":["hello","world"]}' \ - | klo-daemon embedding-compute-bulk + | ktx-daemon embedding-compute-bulk ``` ## One-shot code execution @@ -63,7 +63,7 @@ Execute Python code with the current in-process boundary: ```bash printf '%s\n' '{"code":"result = 1 + 2"}' \ - | klo-daemon code-execute + | ktx-daemon code-execute ``` ## HTTP compute server @@ -71,13 +71,13 @@ printf '%s\n' '{"code":"result = 1 + 2"}' \ Start the HTTP compute server with code execution disabled: ```bash -klo-daemon serve-http --host 127.0.0.1 --port 8765 +ktx-daemon serve-http --host 127.0.0.1 --port 8765 ``` Enable HTTP code execution explicitly: ```bash -klo-daemon serve-http --host 127.0.0.1 --port 8765 --enable-code-execution +ktx-daemon serve-http --host 127.0.0.1 --port 8765 --enable-code-execution ``` Available HTTP endpoints: @@ -99,6 +99,6 @@ Code execution is off by default. When enabled, it runs Python `exec` in the daemon process with the same in-process boundary as the one-shot `code-execute` command and does not provide OS-level sandboxing. -HTTP code execution uses the standalone KLO boundary. It does not forward +HTTP code execution uses the standalone KTX boundary. It does not forward caller authorization headers to a host app and does not connect scratchpad or visualization helpers to host application APIs. diff --git a/python/klo-daemon/pyproject.toml b/python/ktx-daemon/pyproject.toml similarity index 81% rename from python/klo-daemon/pyproject.toml rename to python/ktx-daemon/pyproject.toml index 10ed78d6..57a2760a 100644 --- a/python/klo-daemon/pyproject.toml +++ b/python/ktx-daemon/pyproject.toml @@ -1,13 +1,13 @@ [project] -name = "klo-daemon" +name = "ktx-daemon" version = "0.1.0" -description = "Portable compute package for KLO semantic-layer operations" +description = "Portable compute package for KTX semantic-layer operations" readme = "README.md" requires-python = ">=3.13" license = "Apache-2.0" dependencies = [ "fastapi>=0.115.0", - "klo-sl", + "ktx-sl", "lkml>=1.3.7", "numpy>=2.2.6", "orjson>=3.11.4", @@ -22,7 +22,7 @@ dependencies = [ ] [project.scripts] -klo-daemon = "klo_daemon.__main__:main" +ktx-daemon = "ktx_daemon.__main__:main" [project.urls] Homepage = "https://github.com/kaelio/ktx" @@ -34,7 +34,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/klo_daemon"] +packages = ["src/ktx_daemon"] [dependency-groups] dev = [ @@ -43,7 +43,7 @@ dev = [ ] [tool.uv.sources] -klo-sl = { workspace = true } +ktx-sl = { workspace = true } [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/python/ktx-daemon/src/ktx_daemon/__init__.py b/python/ktx-daemon/src/ktx_daemon/__init__.py new file mode 100644 index 00000000..ddc5f6d9 --- /dev/null +++ b/python/ktx-daemon/src/ktx_daemon/__init__.py @@ -0,0 +1,6 @@ +"""Portable compute package for KTX.""" + +PACKAGE_NAME = "ktx-daemon" +VERSION = "0.1.0" + +__all__ = ["PACKAGE_NAME", "VERSION"] diff --git a/python/klo-daemon/src/klo_daemon/__main__.py b/python/ktx-daemon/src/ktx_daemon/__main__.py similarity index 90% rename from python/klo-daemon/src/klo_daemon/__main__.py rename to python/ktx-daemon/src/ktx_daemon/__main__.py index a6bac682..e7ae779a 100644 --- a/python/klo-daemon/src/klo_daemon/__main__.py +++ b/python/ktx-daemon/src/ktx_daemon/__main__.py @@ -1,4 +1,4 @@ -"""Command entry point for one-shot KLO daemon compute operations.""" +"""Command entry point for one-shot KTX daemon compute operations.""" from __future__ import annotations @@ -9,32 +9,32 @@ from typing import Any from pydantic import ValidationError -from klo_daemon.code_execution import ExecuteCodeRequest, execute_code_response -from klo_daemon.database_introspection import ( +from ktx_daemon.code_execution import ExecuteCodeRequest, execute_code_response +from ktx_daemon.database_introspection import ( DatabaseIntrospectionRequest, introspect_database_response, ) -from klo_daemon.embeddings import ( +from ktx_daemon.embeddings import ( ComputeEmbeddingBulkRequest, ComputeEmbeddingRequest, compute_embedding_bulk_response, compute_embedding_response, ) -from klo_daemon.lookml import ParseLookMLRequest, parse_lookml_project -from klo_daemon.semantic_layer import ( +from ktx_daemon.lookml import ParseLookMLRequest, parse_lookml_project +from ktx_daemon.semantic_layer import ( SemanticLayerQueryRequest, ValidateSourcesRequest, query_semantic_layer, validate_semantic_layer, ) -from klo_daemon.source_generation import ( +from ktx_daemon.source_generation import ( GenerateSourcesRequest, generate_sources_response, ) def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(prog="klo-daemon") + parser = argparse.ArgumentParser(prog="ktx-daemon") subcommands = parser.add_subparsers(dest="command", required=True) subcommands.add_parser("semantic-query", help="Compile a semantic-layer query") subcommands.add_parser("semantic-validate", help="Validate semantic-layer sources") @@ -64,7 +64,7 @@ def build_parser() -> argparse.ArgumentParser: ) serve_http = subcommands.add_parser( "serve-http", - help="Run the KLO daemon portable compute HTTP server", + help="Run the KTX daemon portable compute HTTP server", ) serve_http.add_argument("--host", default="127.0.0.1") serve_http.add_argument("--port", type=int, default=8765) @@ -98,7 +98,7 @@ def run_http_server( ) -> None: import uvicorn - from klo_daemon.app import create_app + from ktx_daemon.app import create_app uvicorn.run( create_app(enable_code_execution=enable_code_execution), diff --git a/python/klo-daemon/src/klo_daemon/app.py b/python/ktx-daemon/src/ktx_daemon/app.py similarity index 94% rename from python/klo-daemon/src/klo_daemon/app.py rename to python/ktx-daemon/src/ktx_daemon/app.py index fcf7a298..73220227 100644 --- a/python/klo-daemon/src/klo_daemon/app.py +++ b/python/ktx-daemon/src/ktx_daemon/app.py @@ -1,4 +1,4 @@ -"""FastAPI app factory for the KLO daemon semantic compute server.""" +"""FastAPI app factory for the KTX daemon semantic compute server.""" from __future__ import annotations @@ -9,18 +9,18 @@ from typing import Any from fastapi import FastAPI, HTTPException from fastapi.responses import Response -from klo_daemon.code_execution import ( +from ktx_daemon.code_execution import ( ExecuteCodeRequest, ExecuteCodeResponse, dumps_numpy_json, execute_code_response, ) -from klo_daemon.database_introspection import ( +from ktx_daemon.database_introspection import ( DatabaseIntrospectionRequest, DatabaseIntrospectionResponse, introspect_database_response, ) -from klo_daemon.embeddings import ( +from ktx_daemon.embeddings import ( ComputeEmbeddingBulkRequest, ComputeEmbeddingBulkResponse, ComputeEmbeddingRequest, @@ -29,12 +29,12 @@ from klo_daemon.embeddings import ( compute_embedding_bulk_response, compute_embedding_response, ) -from klo_daemon.lookml import ( +from ktx_daemon.lookml import ( ParseLookMLRequest, ParseLookMLResponse, parse_lookml_project, ) -from klo_daemon.semantic_layer import ( +from ktx_daemon.semantic_layer import ( SemanticLayerQueryRequest, SemanticLayerQueryResponse, ValidateSourcesRequest, @@ -42,12 +42,12 @@ from klo_daemon.semantic_layer import ( query_semantic_layer, validate_semantic_layer, ) -from klo_daemon.source_generation import ( +from ktx_daemon.source_generation import ( GenerateSourcesRequest, GenerateSourcesResponse, generate_sources_response, ) -from klo_daemon.table_identifier import ( +from ktx_daemon.table_identifier import ( ParseTableIdentifierBatchRequest, ParseTableIdentifierBatchResponse, parse_table_identifier_response, @@ -73,8 +73,8 @@ def create_app( enable_code_execution: bool = False, ) -> FastAPI: app = FastAPI( - title="KLO Daemon", - description="Stateless portable compute server for KLO.", + title="KTX Daemon", + description="Stateless portable compute server for KTX.", version="0.1.0", ) diff --git a/python/klo-daemon/src/klo_daemon/code_execution.py b/python/ktx-daemon/src/ktx_daemon/code_execution.py similarity index 99% rename from python/klo-daemon/src/klo_daemon/code_execution.py rename to python/ktx-daemon/src/ktx_daemon/code_execution.py index f8d1a425..7c793f4f 100644 --- a/python/klo-daemon/src/klo_daemon/code_execution.py +++ b/python/ktx-daemon/src/ktx_daemon/code_execution.py @@ -1,4 +1,4 @@ -"""Portable in-process code execution helpers for KLO daemon. +"""Portable in-process code execution helpers for KTX daemon. This module preserves the host application's current Python execution behavior. It runs code with Python ``exec`` in the current process and does not provide diff --git a/python/klo-daemon/src/klo_daemon/database_introspection.py b/python/ktx-daemon/src/ktx_daemon/database_introspection.py similarity index 98% rename from python/klo-daemon/src/klo_daemon/database_introspection.py rename to python/ktx-daemon/src/ktx_daemon/database_introspection.py index abb58e37..ba9fa1d8 100644 --- a/python/klo-daemon/src/klo_daemon/database_introspection.py +++ b/python/ktx-daemon/src/ktx_daemon/database_introspection.py @@ -1,4 +1,4 @@ -"""Portable database introspection helpers for KLO daemon.""" +"""Portable database introspection helpers for KTX daemon.""" from __future__ import annotations @@ -183,7 +183,7 @@ def _load_postgres_rows( connection = psycopg.connect( request.url, connect_timeout=request.connection_timeout_seconds, - application_name="klo-daemon-database-introspection", + application_name="ktx-daemon-database-introspection", row_factory=dict_row, ) try: diff --git a/python/klo-daemon/src/klo_daemon/embeddings.py b/python/ktx-daemon/src/ktx_daemon/embeddings.py similarity index 98% rename from python/klo-daemon/src/klo_daemon/embeddings.py rename to python/ktx-daemon/src/ktx_daemon/embeddings.py index a105cf11..edf398e4 100644 --- a/python/klo-daemon/src/klo_daemon/embeddings.py +++ b/python/ktx-daemon/src/ktx_daemon/embeddings.py @@ -1,4 +1,4 @@ -"""Portable embedding compute helpers for KLO daemon.""" +"""Portable embedding compute helpers for KTX daemon.""" from __future__ import annotations diff --git a/python/klo-daemon/src/klo_daemon/lookml.py b/python/ktx-daemon/src/ktx_daemon/lookml.py similarity index 100% rename from python/klo-daemon/src/klo_daemon/lookml.py rename to python/ktx-daemon/src/ktx_daemon/lookml.py diff --git a/python/klo-daemon/src/klo_daemon/semantic_layer.py b/python/ktx-daemon/src/ktx_daemon/semantic_layer.py similarity index 98% rename from python/klo-daemon/src/klo_daemon/semantic_layer.py rename to python/ktx-daemon/src/ktx_daemon/semantic_layer.py index 56b25886..d6c15cd0 100644 --- a/python/klo-daemon/src/klo_daemon/semantic_layer.py +++ b/python/ktx-daemon/src/ktx_daemon/semantic_layer.py @@ -1,4 +1,4 @@ -"""Semantic-layer compute helpers for the KLO daemon package.""" +"""Semantic-layer compute helpers for the KTX daemon package.""" from __future__ import annotations diff --git a/python/klo-daemon/src/klo_daemon/source_generation.py b/python/ktx-daemon/src/ktx_daemon/source_generation.py similarity index 98% rename from python/klo-daemon/src/klo_daemon/source_generation.py rename to python/ktx-daemon/src/ktx_daemon/source_generation.py index 9a5af5a4..8831398a 100644 --- a/python/klo-daemon/src/klo_daemon/source_generation.py +++ b/python/ktx-daemon/src/ktx_daemon/source_generation.py @@ -1,4 +1,4 @@ -"""Generate klo-sl YAML source definitions from database schema scan data.""" +"""Generate ktx-sl YAML source definitions from database schema scan data.""" from __future__ import annotations @@ -243,7 +243,7 @@ def generate_sources(request: GenerateSourcesRequest) -> list[dict[str, Any]]: ) sources.append(source.model_dump(exclude_none=True)) - logger.info("Generated %d klo-sl source definitions", len(sources)) + logger.info("Generated %d ktx-sl source definitions", len(sources)) return sources diff --git a/python/klo-daemon/src/klo_daemon/table_identifier.py b/python/ktx-daemon/src/ktx_daemon/table_identifier.py similarity index 100% rename from python/klo-daemon/src/klo_daemon/table_identifier.py rename to python/ktx-daemon/src/ktx_daemon/table_identifier.py diff --git a/python/klo-daemon/tests/test_app.py b/python/ktx-daemon/tests/test_app.py similarity index 99% rename from python/klo-daemon/tests/test_app.py rename to python/ktx-daemon/tests/test_app.py index 59c2982e..fe3c1e4d 100644 --- a/python/klo-daemon/tests/test_app.py +++ b/python/ktx-daemon/tests/test_app.py @@ -2,8 +2,8 @@ from __future__ import annotations from fastapi.testclient import TestClient -from klo_daemon.app import create_app -from klo_daemon.database_introspection import ( +from ktx_daemon.app import create_app +from ktx_daemon.database_introspection import ( DatabaseIntrospectionResponse, LiveDatabaseColumn, LiveDatabaseTable, diff --git a/python/klo-daemon/tests/test_cli.py b/python/ktx-daemon/tests/test_cli.py similarity index 94% rename from python/klo-daemon/tests/test_cli.py rename to python/ktx-daemon/tests/test_cli.py index 44dd76a3..88e06de7 100644 --- a/python/klo-daemon/tests/test_cli.py +++ b/python/ktx-daemon/tests/test_cli.py @@ -30,7 +30,7 @@ def run_daemon_command( src_path = str(Path(__file__).resolve().parents[1] / "src") env["PYTHONPATH"] = src_path + os.pathsep + env.get("PYTHONPATH", "") return subprocess.run( - [sys.executable, "-m", "klo_daemon", command], + [sys.executable, "-m", "ktx_daemon", command], input=json.dumps(payload), text=True, capture_output=True, @@ -79,7 +79,7 @@ def test_command_returns_nonzero_for_invalid_json() -> None: src_path = str(Path(__file__).resolve().parents[1] / "src") env["PYTHONPATH"] = src_path + os.pathsep + env.get("PYTHONPATH", "") result = subprocess.run( - [sys.executable, "-m", "klo_daemon", "semantic-query"], + [sys.executable, "-m", "ktx_daemon", "semantic-query"], input="{", text=True, capture_output=True, @@ -94,7 +94,7 @@ def test_command_returns_nonzero_for_invalid_json() -> None: def test_serve_http_command_starts_uvicorn_without_reading_stdin( monkeypatch, ) -> None: - from klo_daemon import __main__ as daemon_main + from ktx_daemon import __main__ as daemon_main calls: list[dict[str, object]] = [] @@ -146,7 +146,7 @@ def test_serve_http_command_starts_uvicorn_without_reading_stdin( def test_serve_http_command_defaults_to_loopback(monkeypatch) -> None: - from klo_daemon import __main__ as daemon_main + from ktx_daemon import __main__ as daemon_main calls: list[dict[str, object]] = [] @@ -180,7 +180,7 @@ def test_serve_http_command_defaults_to_loopback(monkeypatch) -> None: def test_serve_http_command_can_enable_code_execution(monkeypatch) -> None: - from klo_daemon import __main__ as daemon_main + from ktx_daemon import __main__ as daemon_main calls: list[dict[str, object]] = [] @@ -300,8 +300,8 @@ def test_semantic_generate_sources_command_reads_stdin_and_writes_json() -> None def test_database_introspect_command_reads_stdin_and_writes_json( monkeypatch, capsys ) -> None: - from klo_daemon import __main__ as daemon_main - from klo_daemon.database_introspection import ( + from ktx_daemon import __main__ as daemon_main + from ktx_daemon.database_introspection import ( DatabaseIntrospectionResponse, LiveDatabaseColumn, LiveDatabaseTable, @@ -353,8 +353,8 @@ def test_database_introspect_command_reads_stdin_and_writes_json( def test_embedding_compute_command_reads_stdin_and_writes_json( monkeypatch, capsys ) -> None: - from klo_daemon import __main__ as daemon_main - from klo_daemon.embeddings import ComputeEmbeddingResponse + from ktx_daemon import __main__ as daemon_main + from ktx_daemon.embeddings import ComputeEmbeddingResponse def fake_compute(request): assert request.text == "hello" @@ -372,8 +372,8 @@ def test_embedding_compute_command_reads_stdin_and_writes_json( def test_embedding_compute_bulk_command_reads_stdin_and_writes_json( monkeypatch, capsys ) -> None: - from klo_daemon import __main__ as daemon_main - from klo_daemon.embeddings import ComputeEmbeddingBulkResponse + from ktx_daemon import __main__ as daemon_main + from ktx_daemon.embeddings import ComputeEmbeddingBulkResponse def fake_compute(request): assert request.texts == ["hello", "world"] @@ -389,8 +389,8 @@ def test_embedding_compute_bulk_command_reads_stdin_and_writes_json( def test_code_execute_command_reads_stdin_and_writes_json(monkeypatch, capsys) -> None: - from klo_daemon import __main__ as daemon_main - from klo_daemon.code_execution import ExecuteCodeResponse + from ktx_daemon import __main__ as daemon_main + from ktx_daemon.code_execution import ExecuteCodeResponse calls: list[dict[str, Any]] = [] diff --git a/python/klo-daemon/tests/test_code_execution.py b/python/ktx-daemon/tests/test_code_execution.py similarity index 99% rename from python/klo-daemon/tests/test_code_execution.py rename to python/ktx-daemon/tests/test_code_execution.py index d50eaa26..6353afef 100644 --- a/python/klo-daemon/tests/test_code_execution.py +++ b/python/ktx-daemon/tests/test_code_execution.py @@ -9,7 +9,7 @@ import orjson import pandas as pd import pytest -from klo_daemon.code_execution import ( +from ktx_daemon.code_execution import ( ExecuteCodeRequest, create_scratchpad_helpers, detect_visualizations, diff --git a/python/klo-daemon/tests/test_database_introspection.py b/python/ktx-daemon/tests/test_database_introspection.py similarity index 99% rename from python/klo-daemon/tests/test_database_introspection.py rename to python/ktx-daemon/tests/test_database_introspection.py index 3d51b698..7dd2f3f9 100644 --- a/python/klo-daemon/tests/test_database_introspection.py +++ b/python/ktx-daemon/tests/test_database_introspection.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from klo_daemon.database_introspection import ( +from ktx_daemon.database_introspection import ( DatabaseIntrospectionRequest, DatabaseIntrospectionRows, _statement_timeout_config, diff --git a/python/klo-daemon/tests/test_embeddings.py b/python/ktx-daemon/tests/test_embeddings.py similarity index 98% rename from python/klo-daemon/tests/test_embeddings.py rename to python/ktx-daemon/tests/test_embeddings.py index f923c997..6616c804 100644 --- a/python/klo-daemon/tests/test_embeddings.py +++ b/python/ktx-daemon/tests/test_embeddings.py @@ -2,7 +2,7 @@ from __future__ import annotations import pytest -from klo_daemon.embeddings import ( +from ktx_daemon.embeddings import ( ComputeEmbeddingBulkRequest, ComputeEmbeddingRequest, SentenceTransformersEmbeddingProvider, diff --git a/python/klo-daemon/tests/test_lookml.py b/python/ktx-daemon/tests/test_lookml.py similarity index 98% rename from python/klo-daemon/tests/test_lookml.py rename to python/ktx-daemon/tests/test_lookml.py index cb41664a..3aa69c75 100644 --- a/python/klo-daemon/tests/test_lookml.py +++ b/python/ktx-daemon/tests/test_lookml.py @@ -1,6 +1,6 @@ from __future__ import annotations -from klo_daemon.lookml import ( +from ktx_daemon.lookml import ( LookMLFileInput, ParseLookMLRequest, parse_lookml_project, diff --git a/python/ktx-daemon/tests/test_package.py b/python/ktx-daemon/tests/test_package.py new file mode 100644 index 00000000..3812b448 --- /dev/null +++ b/python/ktx-daemon/tests/test_package.py @@ -0,0 +1,6 @@ +from ktx_daemon import PACKAGE_NAME, VERSION + + +def test_package_metadata() -> None: + assert PACKAGE_NAME == "ktx-daemon" + assert VERSION == "0.1.0" diff --git a/python/klo-daemon/tests/test_semantic_layer.py b/python/ktx-daemon/tests/test_semantic_layer.py similarity index 97% rename from python/klo-daemon/tests/test_semantic_layer.py rename to python/ktx-daemon/tests/test_semantic_layer.py index cd51e197..7a75c019 100644 --- a/python/klo-daemon/tests/test_semantic_layer.py +++ b/python/ktx-daemon/tests/test_semantic_layer.py @@ -1,6 +1,6 @@ from __future__ import annotations -from klo_daemon.semantic_layer import ( +from ktx_daemon.semantic_layer import ( SemanticLayerQueryRequest, ValidateSourcesRequest, query_semantic_layer, diff --git a/python/klo-daemon/tests/test_source_generation.py b/python/ktx-daemon/tests/test_source_generation.py similarity index 99% rename from python/klo-daemon/tests/test_source_generation.py rename to python/ktx-daemon/tests/test_source_generation.py index 7f4ed877..6f3c3977 100644 --- a/python/klo-daemon/tests/test_source_generation.py +++ b/python/ktx-daemon/tests/test_source_generation.py @@ -1,6 +1,6 @@ from __future__ import annotations -from klo_daemon.source_generation import ( +from ktx_daemon.source_generation import ( ColumnInput, GenerateSourcesRequest, LinkInput, diff --git a/python/klo-sl/AGENTS.md b/python/ktx-sl/AGENTS.md similarity index 100% rename from python/klo-sl/AGENTS.md rename to python/ktx-sl/AGENTS.md diff --git a/python/klo-sl/CLAUDE.md b/python/ktx-sl/CLAUDE.md similarity index 100% rename from python/klo-sl/CLAUDE.md rename to python/ktx-sl/CLAUDE.md diff --git a/python/klo-sl/README.md b/python/ktx-sl/README.md similarity index 100% rename from python/klo-sl/README.md rename to python/ktx-sl/README.md diff --git a/python/klo-sl/demos/complex_cte_join.yaml b/python/ktx-sl/demos/complex_cte_join.yaml similarity index 100% rename from python/klo-sl/demos/complex_cte_join.yaml rename to python/ktx-sl/demos/complex_cte_join.yaml diff --git a/python/klo-sl/demos/run_complex_cte_join.sh b/python/ktx-sl/demos/run_complex_cte_join.sh similarity index 100% rename from python/klo-sl/demos/run_complex_cte_join.sh rename to python/ktx-sl/demos/run_complex_cte_join.sh diff --git a/python/klo-sl/pyproject.toml b/python/ktx-sl/pyproject.toml similarity index 98% rename from python/klo-sl/pyproject.toml rename to python/ktx-sl/pyproject.toml index ccd46e25..f3081327 100644 --- a/python/klo-sl/pyproject.toml +++ b/python/ktx-sl/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "klo-sl" +name = "ktx-sl" version = "0.1.0" description = "Agent-first semantic layer engine with aggregate locality" readme = "README.md" diff --git a/python/klo-sl/scripts/gen_b2b_saas_model.py b/python/ktx-sl/scripts/gen_b2b_saas_model.py similarity index 93% rename from python/klo-sl/scripts/gen_b2b_saas_model.py rename to python/ktx-sl/scripts/gen_b2b_saas_model.py index 5a6721d4..bf8ed048 100644 --- a/python/klo-sl/scripts/gen_b2b_saas_model.py +++ b/python/ktx-sl/scripts/gen_b2b_saas_model.py @@ -2,8 +2,8 @@ """Generate semantic layer YAML sources from demo DB metadata. Usage: - kubectl port-forward -n klo-demo deployment/klo-demo-db 5433:5432 & - KLO_DEMO_DB_PASSWORD=local-demo-password python scripts/gen_b2b_saas_model.py + kubectl port-forward -n ktx-demo deployment/ktx-demo-db 5433:5432 & + KTX_DEMO_DB_PASSWORD=local-demo-password python scripts/gen_b2b_saas_model.py """ import os @@ -14,11 +14,11 @@ CONNECTION_ID = "256bc76b-cc47-4d5d-a9fc-5bcfb0364d44" OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "..", "sources", "b2b_saas") DB_PARAMS = { - "host": os.environ.get("KLO_DEMO_DB_HOST", "127.0.0.1"), - "port": int(os.environ.get("KLO_DEMO_DB_PORT", "5433")), - "user": os.environ.get("KLO_DEMO_DB_USER", "klo-demo-user"), - "password": os.environ.get("KLO_DEMO_DB_PASSWORD", ""), - "dbname": os.environ.get("KLO_DEMO_DB_NAME", "klo-demo-db"), + "host": os.environ.get("KTX_DEMO_DB_HOST", "127.0.0.1"), + "port": int(os.environ.get("KTX_DEMO_DB_PORT", "5433")), + "user": os.environ.get("KTX_DEMO_DB_USER", "ktx-demo-user"), + "password": os.environ.get("KTX_DEMO_DB_PASSWORD", ""), + "dbname": os.environ.get("KTX_DEMO_DB_NAME", "ktx-demo-db"), } # Map DB types to semantic layer types diff --git a/python/klo-sl/scripts/slquery.py b/python/ktx-sl/scripts/slquery.py similarity index 98% rename from python/klo-sl/scripts/slquery.py rename to python/ktx-sl/scripts/slquery.py index 70c92442..6bf07ee7 100644 --- a/python/klo-sl/scripts/slquery.py +++ b/python/ktx-sl/scripts/slquery.py @@ -21,7 +21,7 @@ from semantic_layer.engine import SemanticEngine SOURCES_DIR = Path(__file__).resolve().parent.parent / "sources" / "b2b_saas" DB_PATH = Path( - os.environ.get("KLO_B2B_SQLITE_DB", "sample-data-generator/b2b_data.db") + os.environ.get("KTX_B2B_SQLITE_DB", "sample-data-generator/b2b_data.db") ).expanduser() # sqlglot's sqlite dialect handles most transpilation, but has a few gaps. diff --git a/python/klo-sl/scripts/tpch_runner.py b/python/ktx-sl/scripts/tpch_runner.py similarity index 100% rename from python/klo-sl/scripts/tpch_runner.py rename to python/ktx-sl/scripts/tpch_runner.py diff --git a/python/klo-sl/semantic_layer/__init__.py b/python/ktx-sl/semantic_layer/__init__.py similarity index 100% rename from python/klo-sl/semantic_layer/__init__.py rename to python/ktx-sl/semantic_layer/__init__.py diff --git a/python/klo-sl/semantic_layer/__main__.py b/python/ktx-sl/semantic_layer/__main__.py similarity index 100% rename from python/klo-sl/semantic_layer/__main__.py rename to python/ktx-sl/semantic_layer/__main__.py diff --git a/python/klo-sl/semantic_layer/cli.py b/python/ktx-sl/semantic_layer/cli.py similarity index 100% rename from python/klo-sl/semantic_layer/cli.py rename to python/ktx-sl/semantic_layer/cli.py diff --git a/python/klo-sl/semantic_layer/duplicate_check.py b/python/ktx-sl/semantic_layer/duplicate_check.py similarity index 100% rename from python/klo-sl/semantic_layer/duplicate_check.py rename to python/ktx-sl/semantic_layer/duplicate_check.py diff --git a/python/klo-sl/semantic_layer/engine.py b/python/ktx-sl/semantic_layer/engine.py similarity index 100% rename from python/klo-sl/semantic_layer/engine.py rename to python/ktx-sl/semantic_layer/engine.py diff --git a/python/klo-sl/semantic_layer/generator.py b/python/ktx-sl/semantic_layer/generator.py similarity index 100% rename from python/klo-sl/semantic_layer/generator.py rename to python/ktx-sl/semantic_layer/generator.py diff --git a/python/klo-sl/semantic_layer/graph.py b/python/ktx-sl/semantic_layer/graph.py similarity index 100% rename from python/klo-sl/semantic_layer/graph.py rename to python/ktx-sl/semantic_layer/graph.py diff --git a/python/klo-sl/semantic_layer/loader.py b/python/ktx-sl/semantic_layer/loader.py similarity index 100% rename from python/klo-sl/semantic_layer/loader.py rename to python/ktx-sl/semantic_layer/loader.py diff --git a/python/klo-sl/semantic_layer/manifest.py b/python/ktx-sl/semantic_layer/manifest.py similarity index 100% rename from python/klo-sl/semantic_layer/manifest.py rename to python/ktx-sl/semantic_layer/manifest.py diff --git a/python/klo-sl/semantic_layer/models.py b/python/ktx-sl/semantic_layer/models.py similarity index 100% rename from python/klo-sl/semantic_layer/models.py rename to python/ktx-sl/semantic_layer/models.py diff --git a/python/klo-sl/semantic_layer/parser.py b/python/ktx-sl/semantic_layer/parser.py similarity index 100% rename from python/klo-sl/semantic_layer/parser.py rename to python/ktx-sl/semantic_layer/parser.py diff --git a/python/klo-sl/semantic_layer/planner.py b/python/ktx-sl/semantic_layer/planner.py similarity index 100% rename from python/klo-sl/semantic_layer/planner.py rename to python/ktx-sl/semantic_layer/planner.py diff --git a/python/klo-sl/semantic_layer/sql_table_extractor.py b/python/ktx-sl/semantic_layer/sql_table_extractor.py similarity index 100% rename from python/klo-sl/semantic_layer/sql_table_extractor.py rename to python/ktx-sl/semantic_layer/sql_table_extractor.py diff --git a/python/klo-sl/semantic_layer/table_identifier_parser.py b/python/ktx-sl/semantic_layer/table_identifier_parser.py similarity index 100% rename from python/klo-sl/semantic_layer/table_identifier_parser.py rename to python/ktx-sl/semantic_layer/table_identifier_parser.py diff --git a/python/klo-sl/sources/b2b_saas/abm_engagements.yaml b/python/ktx-sl/sources/b2b_saas/abm_engagements.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/abm_engagements.yaml rename to python/ktx-sl/sources/b2b_saas/abm_engagements.yaml diff --git a/python/klo-sl/sources/b2b_saas/account_intent_signals.yaml b/python/ktx-sl/sources/b2b_saas/account_intent_signals.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/account_intent_signals.yaml rename to python/ktx-sl/sources/b2b_saas/account_intent_signals.yaml diff --git a/python/klo-sl/sources/b2b_saas/accounts.yaml b/python/ktx-sl/sources/b2b_saas/accounts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/accounts.yaml rename to python/ktx-sl/sources/b2b_saas/accounts.yaml diff --git a/python/klo-sl/sources/b2b_saas/activities.yaml b/python/ktx-sl/sources/b2b_saas/activities.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/activities.yaml rename to python/ktx-sl/sources/b2b_saas/activities.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_accounts.yaml b/python/ktx-sl/sources/b2b_saas/ad_accounts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_accounts.yaml rename to python/ktx-sl/sources/b2b_saas/ad_accounts.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_ad_stats.yaml b/python/ktx-sl/sources/b2b_saas/ad_ad_stats.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_ad_stats.yaml rename to python/ktx-sl/sources/b2b_saas/ad_ad_stats.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_campaigns.yaml b/python/ktx-sl/sources/b2b_saas/ad_campaigns.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_campaigns.yaml rename to python/ktx-sl/sources/b2b_saas/ad_campaigns.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_creative_stats.yaml b/python/ktx-sl/sources/b2b_saas/ad_creative_stats.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_creative_stats.yaml rename to python/ktx-sl/sources/b2b_saas/ad_creative_stats.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_creatives.yaml b/python/ktx-sl/sources/b2b_saas/ad_creatives.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_creatives.yaml rename to python/ktx-sl/sources/b2b_saas/ad_creatives.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_groups.yaml b/python/ktx-sl/sources/b2b_saas/ad_groups.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_groups.yaml rename to python/ktx-sl/sources/b2b_saas/ad_groups.yaml diff --git a/python/klo-sl/sources/b2b_saas/ad_stats.yaml b/python/ktx-sl/sources/b2b_saas/ad_stats.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ad_stats.yaml rename to python/ktx-sl/sources/b2b_saas/ad_stats.yaml diff --git a/python/klo-sl/sources/b2b_saas/ads.yaml b/python/ktx-sl/sources/b2b_saas/ads.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ads.yaml rename to python/ktx-sl/sources/b2b_saas/ads.yaml diff --git a/python/klo-sl/sources/b2b_saas/ap_bills.yaml b/python/ktx-sl/sources/b2b_saas/ap_bills.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ap_bills.yaml rename to python/ktx-sl/sources/b2b_saas/ap_bills.yaml diff --git a/python/klo-sl/sources/b2b_saas/approvals.yaml b/python/ktx-sl/sources/b2b_saas/approvals.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/approvals.yaml rename to python/ktx-sl/sources/b2b_saas/approvals.yaml diff --git a/python/klo-sl/sources/b2b_saas/attribution_credits.yaml b/python/ktx-sl/sources/b2b_saas/attribution_credits.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/attribution_credits.yaml rename to python/ktx-sl/sources/b2b_saas/attribution_credits.yaml diff --git a/python/klo-sl/sources/b2b_saas/budgets.yaml b/python/ktx-sl/sources/b2b_saas/budgets.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/budgets.yaml rename to python/ktx-sl/sources/b2b_saas/budgets.yaml diff --git a/python/klo-sl/sources/b2b_saas/calls.yaml b/python/ktx-sl/sources/b2b_saas/calls.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/calls.yaml rename to python/ktx-sl/sources/b2b_saas/calls.yaml diff --git a/python/klo-sl/sources/b2b_saas/campaign_members.yaml b/python/ktx-sl/sources/b2b_saas/campaign_members.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/campaign_members.yaml rename to python/ktx-sl/sources/b2b_saas/campaign_members.yaml diff --git a/python/klo-sl/sources/b2b_saas/campaigns.yaml b/python/ktx-sl/sources/b2b_saas/campaigns.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/campaigns.yaml rename to python/ktx-sl/sources/b2b_saas/campaigns.yaml diff --git a/python/klo-sl/sources/b2b_saas/card_transactions.yaml b/python/ktx-sl/sources/b2b_saas/card_transactions.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/card_transactions.yaml rename to python/ktx-sl/sources/b2b_saas/card_transactions.yaml diff --git a/python/klo-sl/sources/b2b_saas/cash_balances.yaml b/python/ktx-sl/sources/b2b_saas/cash_balances.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/cash_balances.yaml rename to python/ktx-sl/sources/b2b_saas/cash_balances.yaml diff --git a/python/klo-sl/sources/b2b_saas/charges.yaml b/python/ktx-sl/sources/b2b_saas/charges.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/charges.yaml rename to python/ktx-sl/sources/b2b_saas/charges.yaml diff --git a/python/klo-sl/sources/b2b_saas/churn_risk.yaml b/python/ktx-sl/sources/b2b_saas/churn_risk.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/churn_risk.yaml rename to python/ktx-sl/sources/b2b_saas/churn_risk.yaml diff --git a/python/klo-sl/sources/b2b_saas/contacts.yaml b/python/ktx-sl/sources/b2b_saas/contacts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/contacts.yaml rename to python/ktx-sl/sources/b2b_saas/contacts.yaml diff --git a/python/klo-sl/sources/b2b_saas/content_assets.yaml b/python/ktx-sl/sources/b2b_saas/content_assets.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/content_assets.yaml rename to python/ktx-sl/sources/b2b_saas/content_assets.yaml diff --git a/python/klo-sl/sources/b2b_saas/content_touches.yaml b/python/ktx-sl/sources/b2b_saas/content_touches.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/content_touches.yaml rename to python/ktx-sl/sources/b2b_saas/content_touches.yaml diff --git a/python/klo-sl/sources/b2b_saas/contracts.yaml b/python/ktx-sl/sources/b2b_saas/contracts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/contracts.yaml rename to python/ktx-sl/sources/b2b_saas/contracts.yaml diff --git a/python/klo-sl/sources/b2b_saas/crm_notes.yaml b/python/ktx-sl/sources/b2b_saas/crm_notes.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/crm_notes.yaml rename to python/ktx-sl/sources/b2b_saas/crm_notes.yaml diff --git a/python/klo-sl/sources/b2b_saas/currencies.yaml b/python/ktx-sl/sources/b2b_saas/currencies.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/currencies.yaml rename to python/ktx-sl/sources/b2b_saas/currencies.yaml diff --git a/python/klo-sl/sources/b2b_saas/departments_hr.yaml b/python/ktx-sl/sources/b2b_saas/departments_hr.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/departments_hr.yaml rename to python/ktx-sl/sources/b2b_saas/departments_hr.yaml diff --git a/python/klo-sl/sources/b2b_saas/disputes.yaml b/python/ktx-sl/sources/b2b_saas/disputes.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/disputes.yaml rename to python/ktx-sl/sources/b2b_saas/disputes.yaml diff --git a/python/klo-sl/sources/b2b_saas/email_events.yaml b/python/ktx-sl/sources/b2b_saas/email_events.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/email_events.yaml rename to python/ktx-sl/sources/b2b_saas/email_events.yaml diff --git a/python/klo-sl/sources/b2b_saas/email_sends.yaml b/python/ktx-sl/sources/b2b_saas/email_sends.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/email_sends.yaml rename to python/ktx-sl/sources/b2b_saas/email_sends.yaml diff --git a/python/klo-sl/sources/b2b_saas/employees.yaml b/python/ktx-sl/sources/b2b_saas/employees.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/employees.yaml rename to python/ktx-sl/sources/b2b_saas/employees.yaml diff --git a/python/klo-sl/sources/b2b_saas/etl_runs.yaml b/python/ktx-sl/sources/b2b_saas/etl_runs.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/etl_runs.yaml rename to python/ktx-sl/sources/b2b_saas/etl_runs.yaml diff --git a/python/klo-sl/sources/b2b_saas/fiscal_calendar.yaml b/python/ktx-sl/sources/b2b_saas/fiscal_calendar.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/fiscal_calendar.yaml rename to python/ktx-sl/sources/b2b_saas/fiscal_calendar.yaml diff --git a/python/klo-sl/sources/b2b_saas/forecast_snapshots.yaml b/python/ktx-sl/sources/b2b_saas/forecast_snapshots.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/forecast_snapshots.yaml rename to python/ktx-sl/sources/b2b_saas/forecast_snapshots.yaml diff --git a/python/klo-sl/sources/b2b_saas/fx_rates.yaml b/python/ktx-sl/sources/b2b_saas/fx_rates.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/fx_rates.yaml rename to python/ktx-sl/sources/b2b_saas/fx_rates.yaml diff --git a/python/klo-sl/sources/b2b_saas/ga4_event_params.yaml b/python/ktx-sl/sources/b2b_saas/ga4_event_params.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ga4_event_params.yaml rename to python/ktx-sl/sources/b2b_saas/ga4_event_params.yaml diff --git a/python/klo-sl/sources/b2b_saas/ga4_events.yaml b/python/ktx-sl/sources/b2b_saas/ga4_events.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/ga4_events.yaml rename to python/ktx-sl/sources/b2b_saas/ga4_events.yaml diff --git a/python/klo-sl/sources/b2b_saas/gl_accounts.yaml b/python/ktx-sl/sources/b2b_saas/gl_accounts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/gl_accounts.yaml rename to python/ktx-sl/sources/b2b_saas/gl_accounts.yaml diff --git a/python/klo-sl/sources/b2b_saas/identities.yaml b/python/ktx-sl/sources/b2b_saas/identities.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/identities.yaml rename to python/ktx-sl/sources/b2b_saas/identities.yaml diff --git a/python/klo-sl/sources/b2b_saas/identity_links.yaml b/python/ktx-sl/sources/b2b_saas/identity_links.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/identity_links.yaml rename to python/ktx-sl/sources/b2b_saas/identity_links.yaml diff --git a/python/klo-sl/sources/b2b_saas/invoice_lines.yaml b/python/ktx-sl/sources/b2b_saas/invoice_lines.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/invoice_lines.yaml rename to python/ktx-sl/sources/b2b_saas/invoice_lines.yaml diff --git a/python/klo-sl/sources/b2b_saas/invoices.yaml b/python/ktx-sl/sources/b2b_saas/invoices.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/invoices.yaml rename to python/ktx-sl/sources/b2b_saas/invoices.yaml diff --git a/python/klo-sl/sources/b2b_saas/journal_entries.yaml b/python/ktx-sl/sources/b2b_saas/journal_entries.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/journal_entries.yaml rename to python/ktx-sl/sources/b2b_saas/journal_entries.yaml diff --git a/python/klo-sl/sources/b2b_saas/journal_lines.yaml b/python/ktx-sl/sources/b2b_saas/journal_lines.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/journal_lines.yaml rename to python/ktx-sl/sources/b2b_saas/journal_lines.yaml diff --git a/python/klo-sl/sources/b2b_saas/keyword_rankings.yaml b/python/ktx-sl/sources/b2b_saas/keyword_rankings.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/keyword_rankings.yaml rename to python/ktx-sl/sources/b2b_saas/keyword_rankings.yaml diff --git a/python/klo-sl/sources/b2b_saas/lead_status_history.yaml b/python/ktx-sl/sources/b2b_saas/lead_status_history.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/lead_status_history.yaml rename to python/ktx-sl/sources/b2b_saas/lead_status_history.yaml diff --git a/python/klo-sl/sources/b2b_saas/leads.yaml b/python/ktx-sl/sources/b2b_saas/leads.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/leads.yaml rename to python/ktx-sl/sources/b2b_saas/leads.yaml diff --git a/python/klo-sl/sources/b2b_saas/meeting_bookings.yaml b/python/ktx-sl/sources/b2b_saas/meeting_bookings.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/meeting_bookings.yaml rename to python/ktx-sl/sources/b2b_saas/meeting_bookings.yaml diff --git a/python/klo-sl/sources/b2b_saas/open_roles.yaml b/python/ktx-sl/sources/b2b_saas/open_roles.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/open_roles.yaml rename to python/ktx-sl/sources/b2b_saas/open_roles.yaml diff --git a/python/klo-sl/sources/b2b_saas/opportunities.yaml b/python/ktx-sl/sources/b2b_saas/opportunities.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/opportunities.yaml rename to python/ktx-sl/sources/b2b_saas/opportunities.yaml diff --git a/python/klo-sl/sources/b2b_saas/opportunity_contact_roles.yaml b/python/ktx-sl/sources/b2b_saas/opportunity_contact_roles.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/opportunity_contact_roles.yaml rename to python/ktx-sl/sources/b2b_saas/opportunity_contact_roles.yaml diff --git a/python/klo-sl/sources/b2b_saas/opportunity_line_items.yaml b/python/ktx-sl/sources/b2b_saas/opportunity_line_items.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/opportunity_line_items.yaml rename to python/ktx-sl/sources/b2b_saas/opportunity_line_items.yaml diff --git a/python/klo-sl/sources/b2b_saas/opportunity_stage_history.yaml b/python/ktx-sl/sources/b2b_saas/opportunity_stage_history.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/opportunity_stage_history.yaml rename to python/ktx-sl/sources/b2b_saas/opportunity_stage_history.yaml diff --git a/python/klo-sl/sources/b2b_saas/payment_intents.yaml b/python/ktx-sl/sources/b2b_saas/payment_intents.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/payment_intents.yaml rename to python/ktx-sl/sources/b2b_saas/payment_intents.yaml diff --git a/python/klo-sl/sources/b2b_saas/payments.yaml b/python/ktx-sl/sources/b2b_saas/payments.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/payments.yaml rename to python/ktx-sl/sources/b2b_saas/payments.yaml diff --git a/python/klo-sl/sources/b2b_saas/payroll_runs.yaml b/python/ktx-sl/sources/b2b_saas/payroll_runs.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/payroll_runs.yaml rename to python/ktx-sl/sources/b2b_saas/payroll_runs.yaml diff --git a/python/klo-sl/sources/b2b_saas/pricebook_entries.yaml b/python/ktx-sl/sources/b2b_saas/pricebook_entries.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/pricebook_entries.yaml rename to python/ktx-sl/sources/b2b_saas/pricebook_entries.yaml diff --git a/python/klo-sl/sources/b2b_saas/pricebooks.yaml b/python/ktx-sl/sources/b2b_saas/pricebooks.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/pricebooks.yaml rename to python/ktx-sl/sources/b2b_saas/pricebooks.yaml diff --git a/python/klo-sl/sources/b2b_saas/product_costs.yaml b/python/ktx-sl/sources/b2b_saas/product_costs.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/product_costs.yaml rename to python/ktx-sl/sources/b2b_saas/product_costs.yaml diff --git a/python/klo-sl/sources/b2b_saas/product_usage.yaml b/python/ktx-sl/sources/b2b_saas/product_usage.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/product_usage.yaml rename to python/ktx-sl/sources/b2b_saas/product_usage.yaml diff --git a/python/klo-sl/sources/b2b_saas/products.yaml b/python/ktx-sl/sources/b2b_saas/products.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/products.yaml rename to python/ktx-sl/sources/b2b_saas/products.yaml diff --git a/python/klo-sl/sources/b2b_saas/quotas.yaml b/python/ktx-sl/sources/b2b_saas/quotas.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/quotas.yaml rename to python/ktx-sl/sources/b2b_saas/quotas.yaml diff --git a/python/klo-sl/sources/b2b_saas/quote_line_items.yaml b/python/ktx-sl/sources/b2b_saas/quote_line_items.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/quote_line_items.yaml rename to python/ktx-sl/sources/b2b_saas/quote_line_items.yaml diff --git a/python/klo-sl/sources/b2b_saas/quotes.yaml b/python/ktx-sl/sources/b2b_saas/quotes.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/quotes.yaml rename to python/ktx-sl/sources/b2b_saas/quotes.yaml diff --git a/python/klo-sl/sources/b2b_saas/refunds.yaml b/python/ktx-sl/sources/b2b_saas/refunds.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/refunds.yaml rename to python/ktx-sl/sources/b2b_saas/refunds.yaml diff --git a/python/klo-sl/sources/b2b_saas/revenue_schedules.yaml b/python/ktx-sl/sources/b2b_saas/revenue_schedules.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/revenue_schedules.yaml rename to python/ktx-sl/sources/b2b_saas/revenue_schedules.yaml diff --git a/python/klo-sl/sources/b2b_saas/reverse_etl_jobs.yaml b/python/ktx-sl/sources/b2b_saas/reverse_etl_jobs.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/reverse_etl_jobs.yaml rename to python/ktx-sl/sources/b2b_saas/reverse_etl_jobs.yaml diff --git a/python/klo-sl/sources/b2b_saas/sales_reps.yaml b/python/ktx-sl/sources/b2b_saas/sales_reps.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sales_reps.yaml rename to python/ktx-sl/sources/b2b_saas/sales_reps.yaml diff --git a/python/klo-sl/sources/b2b_saas/sales_teams.yaml b/python/ktx-sl/sources/b2b_saas/sales_teams.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sales_teams.yaml rename to python/ktx-sl/sources/b2b_saas/sales_teams.yaml diff --git a/python/klo-sl/sources/b2b_saas/search_console_stats.yaml b/python/ktx-sl/sources/b2b_saas/search_console_stats.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/search_console_stats.yaml rename to python/ktx-sl/sources/b2b_saas/search_console_stats.yaml diff --git a/python/klo-sl/sources/b2b_saas/sequence_enrollments.yaml b/python/ktx-sl/sources/b2b_saas/sequence_enrollments.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sequence_enrollments.yaml rename to python/ktx-sl/sources/b2b_saas/sequence_enrollments.yaml diff --git a/python/klo-sl/sources/b2b_saas/sequence_steps.yaml b/python/ktx-sl/sources/b2b_saas/sequence_steps.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sequence_steps.yaml rename to python/ktx-sl/sources/b2b_saas/sequence_steps.yaml diff --git a/python/klo-sl/sources/b2b_saas/sequence_touches.yaml b/python/ktx-sl/sources/b2b_saas/sequence_touches.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sequence_touches.yaml rename to python/ktx-sl/sources/b2b_saas/sequence_touches.yaml diff --git a/python/klo-sl/sources/b2b_saas/sequences.yaml b/python/ktx-sl/sources/b2b_saas/sequences.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/sequences.yaml rename to python/ktx-sl/sources/b2b_saas/sequences.yaml diff --git a/python/klo-sl/sources/b2b_saas/stage_weights.yaml b/python/ktx-sl/sources/b2b_saas/stage_weights.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/stage_weights.yaml rename to python/ktx-sl/sources/b2b_saas/stage_weights.yaml diff --git a/python/klo-sl/sources/b2b_saas/subscription_items.yaml b/python/ktx-sl/sources/b2b_saas/subscription_items.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/subscription_items.yaml rename to python/ktx-sl/sources/b2b_saas/subscription_items.yaml diff --git a/python/klo-sl/sources/b2b_saas/subscriptions.yaml b/python/ktx-sl/sources/b2b_saas/subscriptions.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/subscriptions.yaml rename to python/ktx-sl/sources/b2b_saas/subscriptions.yaml diff --git a/python/klo-sl/sources/b2b_saas/support_tickets.yaml b/python/ktx-sl/sources/b2b_saas/support_tickets.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/support_tickets.yaml rename to python/ktx-sl/sources/b2b_saas/support_tickets.yaml diff --git a/python/klo-sl/sources/b2b_saas/target_accounts.yaml b/python/ktx-sl/sources/b2b_saas/target_accounts.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/target_accounts.yaml rename to python/ktx-sl/sources/b2b_saas/target_accounts.yaml diff --git a/python/klo-sl/sources/b2b_saas/touchpoints.yaml b/python/ktx-sl/sources/b2b_saas/touchpoints.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/touchpoints.yaml rename to python/ktx-sl/sources/b2b_saas/touchpoints.yaml diff --git a/python/klo-sl/sources/b2b_saas/vendors.yaml b/python/ktx-sl/sources/b2b_saas/vendors.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/vendors.yaml rename to python/ktx-sl/sources/b2b_saas/vendors.yaml diff --git a/python/klo-sl/sources/b2b_saas/web_events.yaml b/python/ktx-sl/sources/b2b_saas/web_events.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/web_events.yaml rename to python/ktx-sl/sources/b2b_saas/web_events.yaml diff --git a/python/klo-sl/sources/b2b_saas/web_sessions.yaml b/python/ktx-sl/sources/b2b_saas/web_sessions.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/web_sessions.yaml rename to python/ktx-sl/sources/b2b_saas/web_sessions.yaml diff --git a/python/klo-sl/sources/b2b_saas/webinar_attendance.yaml b/python/ktx-sl/sources/b2b_saas/webinar_attendance.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/webinar_attendance.yaml rename to python/ktx-sl/sources/b2b_saas/webinar_attendance.yaml diff --git a/python/klo-sl/sources/b2b_saas/webinar_registrations.yaml b/python/ktx-sl/sources/b2b_saas/webinar_registrations.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/webinar_registrations.yaml rename to python/ktx-sl/sources/b2b_saas/webinar_registrations.yaml diff --git a/python/klo-sl/sources/b2b_saas/webinars.yaml b/python/ktx-sl/sources/b2b_saas/webinars.yaml similarity index 100% rename from python/klo-sl/sources/b2b_saas/webinars.yaml rename to python/ktx-sl/sources/b2b_saas/webinars.yaml diff --git a/python/klo-sl/sources/ecommerce/churn_risk.yaml b/python/ktx-sl/sources/ecommerce/churn_risk.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/churn_risk.yaml rename to python/ktx-sl/sources/ecommerce/churn_risk.yaml diff --git a/python/klo-sl/sources/ecommerce/customers.yaml b/python/ktx-sl/sources/ecommerce/customers.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/customers.yaml rename to python/ktx-sl/sources/ecommerce/customers.yaml diff --git a/python/klo-sl/sources/ecommerce/order_items.yaml b/python/ktx-sl/sources/ecommerce/order_items.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/order_items.yaml rename to python/ktx-sl/sources/ecommerce/order_items.yaml diff --git a/python/klo-sl/sources/ecommerce/orders.yaml b/python/ktx-sl/sources/ecommerce/orders.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/orders.yaml rename to python/ktx-sl/sources/ecommerce/orders.yaml diff --git a/python/klo-sl/sources/ecommerce/products.yaml b/python/ktx-sl/sources/ecommerce/products.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/products.yaml rename to python/ktx-sl/sources/ecommerce/products.yaml diff --git a/python/klo-sl/sources/ecommerce/regions.yaml b/python/ktx-sl/sources/ecommerce/regions.yaml similarity index 100% rename from python/klo-sl/sources/ecommerce/regions.yaml rename to python/ktx-sl/sources/ecommerce/regions.yaml diff --git a/python/klo-sl/sources/tpch/customer.yaml b/python/ktx-sl/sources/tpch/customer.yaml similarity index 100% rename from python/klo-sl/sources/tpch/customer.yaml rename to python/ktx-sl/sources/tpch/customer.yaml diff --git a/python/klo-sl/sources/tpch/lineitem.yaml b/python/ktx-sl/sources/tpch/lineitem.yaml similarity index 100% rename from python/klo-sl/sources/tpch/lineitem.yaml rename to python/ktx-sl/sources/tpch/lineitem.yaml diff --git a/python/klo-sl/sources/tpch/nation.yaml b/python/ktx-sl/sources/tpch/nation.yaml similarity index 100% rename from python/klo-sl/sources/tpch/nation.yaml rename to python/ktx-sl/sources/tpch/nation.yaml diff --git a/python/klo-sl/sources/tpch/orders.yaml b/python/ktx-sl/sources/tpch/orders.yaml similarity index 100% rename from python/klo-sl/sources/tpch/orders.yaml rename to python/ktx-sl/sources/tpch/orders.yaml diff --git a/python/klo-sl/sources/tpch/part.yaml b/python/ktx-sl/sources/tpch/part.yaml similarity index 100% rename from python/klo-sl/sources/tpch/part.yaml rename to python/ktx-sl/sources/tpch/part.yaml diff --git a/python/klo-sl/sources/tpch/partsupp.yaml b/python/ktx-sl/sources/tpch/partsupp.yaml similarity index 100% rename from python/klo-sl/sources/tpch/partsupp.yaml rename to python/ktx-sl/sources/tpch/partsupp.yaml diff --git a/python/klo-sl/sources/tpch/region.yaml b/python/ktx-sl/sources/tpch/region.yaml similarity index 100% rename from python/klo-sl/sources/tpch/region.yaml rename to python/ktx-sl/sources/tpch/region.yaml diff --git a/python/klo-sl/sources/tpch/supplier.yaml b/python/ktx-sl/sources/tpch/supplier.yaml similarity index 100% rename from python/klo-sl/sources/tpch/supplier.yaml rename to python/ktx-sl/sources/tpch/supplier.yaml diff --git a/python/klo-sl/tests/__init__.py b/python/ktx-sl/tests/__init__.py similarity index 100% rename from python/klo-sl/tests/__init__.py rename to python/ktx-sl/tests/__init__.py diff --git a/python/klo-sl/tests/conftest.py b/python/ktx-sl/tests/conftest.py similarity index 100% rename from python/klo-sl/tests/conftest.py rename to python/ktx-sl/tests/conftest.py diff --git a/python/klo-sl/tests/test_aggregate_locality.py b/python/ktx-sl/tests/test_aggregate_locality.py similarity index 100% rename from python/klo-sl/tests/test_aggregate_locality.py rename to python/ktx-sl/tests/test_aggregate_locality.py diff --git a/python/klo-sl/tests/test_cli.py b/python/ktx-sl/tests/test_cli.py similarity index 100% rename from python/klo-sl/tests/test_cli.py rename to python/ktx-sl/tests/test_cli.py diff --git a/python/klo-sl/tests/test_computed_columns.py b/python/ktx-sl/tests/test_computed_columns.py similarity index 100% rename from python/klo-sl/tests/test_computed_columns.py rename to python/ktx-sl/tests/test_computed_columns.py diff --git a/python/klo-sl/tests/test_corner_case_regressions.py b/python/ktx-sl/tests/test_corner_case_regressions.py similarity index 100% rename from python/klo-sl/tests/test_corner_case_regressions.py rename to python/ktx-sl/tests/test_corner_case_regressions.py diff --git a/python/klo-sl/tests/test_coverage_gaps.py b/python/ktx-sl/tests/test_coverage_gaps.py similarity index 100% rename from python/klo-sl/tests/test_coverage_gaps.py rename to python/ktx-sl/tests/test_coverage_gaps.py diff --git a/python/klo-sl/tests/test_duplicate_check.py b/python/ktx-sl/tests/test_duplicate_check.py similarity index 100% rename from python/klo-sl/tests/test_duplicate_check.py rename to python/ktx-sl/tests/test_duplicate_check.py diff --git a/python/klo-sl/tests/test_engine.py b/python/ktx-sl/tests/test_engine.py similarity index 100% rename from python/klo-sl/tests/test_engine.py rename to python/ktx-sl/tests/test_engine.py diff --git a/python/klo-sl/tests/test_generator.py b/python/ktx-sl/tests/test_generator.py similarity index 100% rename from python/klo-sl/tests/test_generator.py rename to python/ktx-sl/tests/test_generator.py diff --git a/python/klo-sl/tests/test_graph.py b/python/ktx-sl/tests/test_graph.py similarity index 100% rename from python/klo-sl/tests/test_graph.py rename to python/ktx-sl/tests/test_graph.py diff --git a/python/klo-sl/tests/test_loader.py b/python/ktx-sl/tests/test_loader.py similarity index 100% rename from python/klo-sl/tests/test_loader.py rename to python/ktx-sl/tests/test_loader.py diff --git a/python/klo-sl/tests/test_manifest.py b/python/ktx-sl/tests/test_manifest.py similarity index 100% rename from python/klo-sl/tests/test_manifest.py rename to python/ktx-sl/tests/test_manifest.py diff --git a/python/klo-sl/tests/test_models.py b/python/ktx-sl/tests/test_models.py similarity index 100% rename from python/klo-sl/tests/test_models.py rename to python/ktx-sl/tests/test_models.py diff --git a/python/klo-sl/tests/test_parser.py b/python/ktx-sl/tests/test_parser.py similarity index 100% rename from python/klo-sl/tests/test_parser.py rename to python/ktx-sl/tests/test_parser.py diff --git a/python/klo-sl/tests/test_planner.py b/python/ktx-sl/tests/test_planner.py similarity index 100% rename from python/klo-sl/tests/test_planner.py rename to python/ktx-sl/tests/test_planner.py diff --git a/python/klo-sl/tests/test_segments.py b/python/ktx-sl/tests/test_segments.py similarity index 100% rename from python/klo-sl/tests/test_segments.py rename to python/ktx-sl/tests/test_segments.py diff --git a/python/klo-sl/tests/test_snowflake.py b/python/ktx-sl/tests/test_snowflake.py similarity index 100% rename from python/klo-sl/tests/test_snowflake.py rename to python/ktx-sl/tests/test_snowflake.py diff --git a/python/klo-sl/tests/test_sql_join_coverage.py b/python/ktx-sl/tests/test_sql_join_coverage.py similarity index 100% rename from python/klo-sl/tests/test_sql_join_coverage.py rename to python/ktx-sl/tests/test_sql_join_coverage.py diff --git a/python/klo-sl/tests/test_table_identifier_parser.py b/python/ktx-sl/tests/test_table_identifier_parser.py similarity index 100% rename from python/klo-sl/tests/test_table_identifier_parser.py rename to python/ktx-sl/tests/test_table_identifier_parser.py diff --git a/python/klo-sl/tests/test_tpch.py b/python/ktx-sl/tests/test_tpch.py similarity index 100% rename from python/klo-sl/tests/test_tpch.py rename to python/ktx-sl/tests/test_tpch.py diff --git a/python/klo-sl/tests/test_validator.py b/python/ktx-sl/tests/test_validator.py similarity index 100% rename from python/klo-sl/tests/test_validator.py rename to python/ktx-sl/tests/test_validator.py diff --git a/release-policy.json b/release-policy.json index 0f0a7c73..0ba6297f 100644 --- a/release-policy.json +++ b/release-policy.json @@ -5,23 +5,23 @@ "publish": false, "registry": null, "packages": [ - "@klo/cli", - "@klo/connector-bigquery", - "@klo/connector-clickhouse", - "@klo/connector-mysql", - "@klo/connector-postgres", - "@klo/connector-posthog", - "@klo/connector-snowflake", - "@klo/connector-sqlite", - "@klo/connector-sqlserver", - "@klo/context", - "@klo/llm" + "@ktx/cli", + "@ktx/connector-bigquery", + "@ktx/connector-clickhouse", + "@ktx/connector-mysql", + "@ktx/connector-postgres", + "@ktx/connector-posthog", + "@ktx/connector-snowflake", + "@ktx/connector-sqlite", + "@ktx/connector-sqlserver", + "@ktx/context", + "@ktx/llm" ] }, "python": { "publish": false, "repository": null, - "packages": ["klo-sl", "klo-daemon"] + "packages": ["ktx-sl", "ktx-daemon"] }, "publishedPackageSmoke": { "packageName": null, diff --git a/scripts/acquire-public-benchmark-fixtures.test.mjs b/scripts/acquire-public-benchmark-fixtures.test.mjs index 8b8d871a..4a3362b4 100644 --- a/scripts/acquire-public-benchmark-fixtures.test.mjs +++ b/scripts/acquire-public-benchmark-fixtures.test.mjs @@ -6,7 +6,7 @@ import { describe, it } from 'node:test'; import { acquirePublicBenchmarkFixtures } from './acquire-public-benchmark-fixtures.mjs'; function tempRoot() { - return mkdtempSync(path.join(tmpdir(), 'klo-acquire-')); + return mkdtempSync(path.join(tmpdir(), 'ktx-acquire-')); } function writeManifest(dir, fixtures) { diff --git a/scripts/anti-fixture-conditional.test.mjs b/scripts/anti-fixture-conditional.test.mjs index d7d678a9..1ab4da1e 100644 --- a/scripts/anti-fixture-conditional.test.mjs +++ b/scripts/anti-fixture-conditional.test.mjs @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import { readdir, readFile } from 'node:fs/promises'; import { describe, it } from 'node:test'; -const KLO_ROOT = new URL('../', import.meta.url); +const KTX_ROOT = new URL('../', import.meta.url); const RELATIONSHIP_RUNTIME_SOURCES = Object.freeze([ 'packages/context/src/scan/relationship-benchmarks.ts', @@ -19,7 +19,7 @@ const RELATIONSHIP_RUNTIME_SOURCES = Object.freeze([ ]); async function checkedInFixtureIds() { - const fixtureRoot = new URL('packages/context/test/fixtures/relationship-benchmarks/', KLO_ROOT); + const fixtureRoot = new URL('packages/context/test/fixtures/relationship-benchmarks/', KTX_ROOT); const entries = await readdir(fixtureRoot, { withFileTypes: true }); return entries .filter((entry) => entry.isDirectory()) @@ -31,7 +31,7 @@ async function readRuntimeSources() { return Promise.all( RELATIONSHIP_RUNTIME_SOURCES.map(async (relativePath) => ({ relativePath, - source: await readFile(new URL(relativePath, KLO_ROOT), 'utf8'), + source: await readFile(new URL(relativePath, KTX_ROOT), 'utf8'), })), ); } diff --git a/scripts/build-adventureworks-oltp-fixture.mjs b/scripts/build-adventureworks-oltp-fixture.mjs index 52c0f3db..4ac2e5ba 100644 --- a/scripts/build-adventureworks-oltp-fixture.mjs +++ b/scripts/build-adventureworks-oltp-fixture.mjs @@ -216,16 +216,16 @@ export async function buildAdventureWorksOltpFixture(input) { } async function main() { - const url = process.env.KLO_ADVENTUREWORKS_SQLSERVER_URL; + const url = process.env.KTX_ADVENTUREWORKS_SQLSERVER_URL; if (!url) { throw new Error( - 'Set KLO_ADVENTUREWORKS_SQLSERVER_URL to a read-only SQL Server URL for a full AdventureWorks OLTP database before running this script.', + 'Set KTX_ADVENTUREWORKS_SQLSERVER_URL to a read-only SQL Server URL for a full AdventureWorks OLTP database before running this script.', ); } const source = JSON.parse(readFileSync(path.join(scriptDir, 'adventureworks-oltp-source.json'), 'utf8')); - const { KloSqlServerScanConnector } = await import('../packages/connector-sqlserver/dist/index.js'); - const connector = new KloSqlServerScanConnector({ + const { KtxSqlServerScanConnector } = await import('../packages/connector-sqlserver/dist/index.js'); + const connector = new KtxSqlServerScanConnector({ connectionId: fixtureId, connection: { driver: 'sqlserver', diff --git a/scripts/build-benchmark-snapshot.test.mjs b/scripts/build-benchmark-snapshot.test.mjs index 818d1489..adc30173 100644 --- a/scripts/build-benchmark-snapshot.test.mjs +++ b/scripts/build-benchmark-snapshot.test.mjs @@ -7,7 +7,7 @@ const require = createRequire(new URL('../packages/context/package.json', import const Database = require('better-sqlite3'); describe('buildBenchmarkSnapshot', () => { - it('emits a KloSchemaSnapshot-shaped object plus expected-links from declared FKs', () => { + it('emits a KtxSchemaSnapshot-shaped object plus expected-links from declared FKs', () => { const db = new Database(':memory:'); db.exec(` PRAGMA foreign_keys = ON; diff --git a/scripts/build-evidence-fusion-adversarial-fixtures.mjs b/scripts/build-evidence-fusion-adversarial-fixtures.mjs index cdd60545..6994d5c2 100644 --- a/scripts/build-evidence-fusion-adversarial-fixtures.mjs +++ b/scripts/build-evidence-fusion-adversarial-fixtures.mjs @@ -7,8 +7,8 @@ import { gzipSync } from 'node:zlib'; import { buildBenchmarkSnapshot, writeFixtureFiles } from './build-benchmark-snapshot.mjs'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); -const kloRoot = path.resolve(scriptDir, '..'); -const fixtureRoot = path.join(kloRoot, 'packages', 'context', 'test', 'fixtures', 'relationship-benchmarks'); +const ktxRoot = path.resolve(scriptDir, '..'); +const fixtureRoot = path.join(ktxRoot, 'packages', 'context', 'test', 'fixtures', 'relationship-benchmarks'); const require = createRequire(new URL('../packages/context/package.json', import.meta.url)); const Database = require('better-sqlite3'); const { stringify: yamlStringify } = require('yaml'); diff --git a/scripts/check-boundaries.mjs b/scripts/check-boundaries.mjs index 3099840c..5c43c575 100644 --- a/scripts/check-boundaries.mjs +++ b/scripts/check-boundaries.mjs @@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; const codeExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py']); const runtimeAssetPatterns = [/^packages\/[^/]+\/prompts\/.+\.md$/, /^packages\/[^/]+\/skills\/.+\.md$/]; -const identifierSkipPrefixes = ['docs/', 'examples/', 'python/klo-sl/plans/', 'python/klo-sl/openspec/']; +const identifierSkipPrefixes = ['docs/', 'examples/', 'python/ktx-sl/plans/', 'python/ktx-sl/openspec/']; const forbiddenIdentifierTerms = ['kae' + 'lio', 'Kae' + 'lio', 'KAE' + 'LIO_']; const appImportPatterns = [ @@ -47,7 +47,7 @@ const llmBoundaryPatterns = [ }, { label: 'legacy scan LLM provider port', - pattern: /\bKloScanLlmPort\b/, + pattern: /\bKtxScanLlmPort\b/, }, { label: 'legacy gateway LLM provider helper', @@ -120,7 +120,7 @@ export function scanFileContent(relativePath, content) { violations.push({ file: normalizedPath, kind: 'llm-boundary', - message: `Forbidden ${llmBoundaryPattern.label}; use @klo/llm`, + message: `Forbidden ${llmBoundaryPattern.label}; use @ktx/llm`, }); } } @@ -132,7 +132,7 @@ export function scanFileContent(relativePath, content) { violations.push({ file: normalizedPath, kind: 'llm-boundary', - message: `Forbidden ${llmBoundaryPattern.label}; use getModel(role) inside @klo/context`, + message: `Forbidden ${llmBoundaryPattern.label}; use getModel(role) inside @ktx/context`, }); } } @@ -197,7 +197,7 @@ async function main() { const violations = await collectViolations(rootDir); if (violations.length === 0) { - process.stdout.write('klo boundary check passed\n'); + process.stdout.write('ktx boundary check passed\n'); return; } diff --git a/scripts/check-boundaries.test.mjs b/scripts/check-boundaries.test.mjs index 0c1daae2..fab2b87e 100644 --- a/scripts/check-boundaries.test.mjs +++ b/scripts/check-boundaries.test.mjs @@ -61,22 +61,22 @@ describe('scanFileContent', () => { assert.equal(scanFileContent('docs/transition.md', name).length, 0); assert.equal(scanFileContent('examples/transition.md', name).length, 0); - assert.equal(scanFileContent('python/klo-sl/plans/brainstorm.md', name).length, 0); - assert.equal(scanFileContent('python/klo-sl/openspec/specs/semantic-layer/spec.md', name).length, 0); + assert.equal(scanFileContent('python/ktx-sl/plans/brainstorm.md', name).length, 0); + assert.equal(scanFileContent('python/ktx-sl/openspec/specs/semantic-layer/spec.md', name).length, 0); }); it('allows clean source files and clean runtime prompt assets', () => { assert.deepEqual( - scanFileContent('packages/context/src/index.ts', "export const packageName = '@klo/context';"), + scanFileContent('packages/context/src/index.ts', "export const packageName = '@ktx/context';"), [], ); assert.deepEqual( - scanFileContent('packages/context/prompts/memory_agent_bundle_ingest_work_unit.md', 'Write output for KLO.'), + scanFileContent('packages/context/prompts/memory_agent_bundle_ingest_work_unit.md', 'Write output for KTX.'), [], ); }); - it('rejects context-owned LLM provider construction after @klo/llm migration', () => { + it('rejects context-owned LLM provider construction after @ktx/llm migration', () => { const violations = [ ...scanFileContent( 'packages/context/src/agent/local-llm-provider.ts', @@ -92,10 +92,10 @@ describe('scanFileContent', () => { ); }); - it('rejects old KLO LLM port declarations in context', () => { + it('rejects old KTX LLM port declarations in context', () => { const violations = [ ...scanFileContent('packages/context/src/agent/agent-runner.service.ts', 'export interface LlmProviderPort {}'), - ...scanFileContent('packages/context/src/scan/types.ts', 'export interface KloScanLlmPort {}'), + ...scanFileContent('packages/context/src/scan/types.ts', 'export interface KtxScanLlmPort {}'), ...scanFileContent('packages/context/src/agent/gateway-llm-provider.ts', 'export function createGatewayLlmProvider() {}'), ]; @@ -115,7 +115,7 @@ describe('scanFileContent', () => { assert.equal(violations[0]?.kind, 'llm-boundary'); assert.equal( violations[0]?.message, - 'Forbidden context getModelByName call; use getModel(role) inside @klo/context', + 'Forbidden context getModelByName call; use getModel(role) inside @ktx/context', ); }); diff --git a/scripts/ci-artifact-upload.test.mjs b/scripts/ci-artifact-upload.test.mjs index d8ba166c..d18db979 100644 --- a/scripts/ci-artifact-upload.test.mjs +++ b/scripts/ci-artifact-upload.test.mjs @@ -12,7 +12,7 @@ async function readCiWorkflowOrSkip(testContext) { await access(ciWorkflowPath); } catch (error) { if (error && error.code === 'ENOENT') { - testContext.skip('root CI workflow is absent from sparse klo checkout'); + testContext.skip('root CI workflow is absent from sparse ktx checkout'); return null; } throw error; @@ -20,8 +20,8 @@ async function readCiWorkflowOrSkip(testContext) { return readFile(ciWorkflowPath, 'utf-8'); } -describe('KLO CI artifact upload contract', () => { - it('uploads verified KLO package artifacts from check-klo-subtree', async (testContext) => { +describe('KTX CI artifact upload contract', () => { + it('uploads verified KTX package artifacts from check-ktx-subtree', async (testContext) => { const workflow = await readCiWorkflowOrSkip(testContext); if (workflow === null) { return; @@ -29,14 +29,14 @@ describe('KLO CI artifact upload contract', () => { assert.match( workflow, - /name: Build klo package artifacts and verify public smoke\s+run: cd klo && pnpm run artifacts:build && pnpm run artifacts:verify-manifest && pnpm run artifacts:verify-demo\s+- name: Upload klo package artifacts/s, + /name: Build ktx package artifacts and verify public smoke\s+run: cd ktx && pnpm run artifacts:build && pnpm run artifacts:verify-manifest && pnpm run artifacts:verify-demo\s+- name: Upload ktx package artifacts/s, ); assert.match(workflow, /uses: actions\/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f/); - assert.match(workflow, /name: klo-package-artifacts-\$\{\{ github\.sha \}\}/); - assert.match(workflow, /klo\/dist\/artifacts\/manifest\.json/); - assert.match(workflow, /klo\/dist\/artifacts\/npm\/\*\.tgz/); - assert.match(workflow, /klo\/dist\/artifacts\/python\/\*\.whl/); - assert.match(workflow, /klo\/dist\/artifacts\/python\/\*\.tar\.gz/); + assert.match(workflow, /name: ktx-package-artifacts-\$\{\{ github\.sha \}\}/); + assert.match(workflow, /ktx\/dist\/artifacts\/manifest\.json/); + assert.match(workflow, /ktx\/dist\/artifacts\/npm\/\*\.tgz/); + assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.whl/); + assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.tar\.gz/); assert.match(workflow, /if-no-files-found: error/); assert.match(workflow, /retention-days: 7/); }); @@ -47,11 +47,11 @@ describe('KLO CI artifact upload contract', () => { return; } - assert.match(workflow, /check-klo-packed-demo:/); + assert.match(workflow, /check-ktx-packed-demo:/); assert.match(workflow, /matrix:\s+os: \[ubuntu-latest, macos-latest\]/s); - assert.match(workflow, /name: Download klo package artifacts/); - assert.match(workflow, /path: klo\/dist\/artifacts/); - assert.match(workflow, /run: cd klo && pnpm run artifacts:verify-demo/); + assert.match(workflow, /name: Download ktx package artifacts/); + assert.match(workflow, /path: ktx\/dist\/artifacts/); + assert.match(workflow, /run: cd ktx && pnpm run artifacts:verify-demo/); }); it('includes packed demo artifact smoke in ci-success', async (testContext) => { @@ -62,9 +62,9 @@ describe('KLO CI artifact upload contract', () => { assert.match( workflow, - /needs: \[check-klo-subtree, check-klo-packed-demo, build-python-service, test-server, build-frontend, run-pre-commit, build-docker-images\]/, + /needs: \[check-ktx-subtree, check-ktx-packed-demo, build-python-service, test-server, build-frontend, run-pre-commit, build-docker-images\]/, ); - assert.match(workflow, /needs\.check-klo-packed-demo\.result.*== "failure"/); - assert.match(workflow, /needs\.check-klo-packed-demo\.result.*== "cancelled"/); + assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "failure"/); + assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "cancelled"/); }); }); diff --git a/scripts/examples-docs.test.mjs b/scripts/examples-docs.test.mjs index 1fc338f3..3d3aa168 100644 --- a/scripts/examples-docs.test.mjs +++ b/scripts/examples-docs.test.mjs @@ -18,7 +18,7 @@ describe('standalone example docs', () => { it('documents the Orbit relationship verification example project', async () => { const examples = await readText('examples/README.md'); const readme = await readText('examples/orbit-relationship-verification/README.md'); - const config = await readText('examples/orbit-relationship-verification/klo.yaml'); + const config = await readText('examples/orbit-relationship-verification/ktx.yaml'); assert.match(examples, /orbit-relationship-verification/); assert.match(examples, /relationships:verify-orbit/); @@ -51,14 +51,14 @@ describe('standalone example docs', () => { assert.match(examples, /pg_stat_statements/); assert.match(readme, /--enable-historic-sql/); assert.match(readme, /--historic-sql-min-calls 2/); - assert.match(readme, /klo dev doctor --project-dir/); + assert.match(readme, /ktx dev doctor --project-dir/); assert.match(readme, /Postgres Historic SQL/); assert.match(readme, /dev ingest run/); assert.match(compose, /postgres:14/); assert.match(compose, /shared_preload_libraries=pg_stat_statements/); assert.match(compose, /pg_stat_statements.track=top/); assert.match(initSql, /CREATE EXTENSION IF NOT EXISTS pg_stat_statements/); - assert.match(initSql, /GRANT pg_read_all_stats TO klo_reader/); + assert.match(initSql, /GRANT pg_read_all_stats TO ktx_reader/); assert.match(workload, /JOIN customers/); assert.match(workload, /app_user/); assert.match(workload, /etl_user/); @@ -81,8 +81,8 @@ describe('standalone example docs', () => { assert.match(rootReadme, /`packages\/connector-snowflake`/); assert.match(rootReadme, /`packages\/connector-sqlite`/); assert.match(rootReadme, /`packages\/connector-sqlserver`/); - assert.match(rootReadme, /`python\/klo-sl`/); - assert.match(rootReadme, /`python\/klo-daemon`/); + assert.match(rootReadme, /`python\/ktx-sl`/); + assert.match(rootReadme, /`python\/ktx-daemon`/); }); it('documents every standalone MCP tool that the CLI server exposes', async () => { @@ -103,7 +103,7 @@ describe('standalone example docs', () => { assert.match(rootReadme, /`ingest_replay`/); }); - it('walks through klo connection list and klo connection test in the README quickstart', async () => { + it('walks through ktx connection list and ktx connection test in the README quickstart', async () => { const rootReadme = await readText('README.md'); assert.match(rootReadme, /connection list --project-dir/); @@ -112,7 +112,7 @@ describe('standalone example docs', () => { assert.match(rootReadme, /Tables: 1/); }); - it('replaces the fake-ingest smoke with a klo scan walkthrough in the README', async () => { + it('replaces the fake-ingest smoke with a ktx scan walkthrough in the README', async () => { const rootReadme = await readText('README.md'); assert.match(rootReadme, /### Scan the demo warehouse/); @@ -121,15 +121,15 @@ describe('standalone example docs', () => { assert.match(rootReadme, /scan report --project-dir/); assert.match(rootReadme, /raw-sources\/warehouse\/live-database/); assert.doesNotMatch(rootReadme, /Run a local ingest smoke test/); - assert.doesNotMatch(rootReadme, /klo dev ingest run --project-dir/); - assert.doesNotMatch(rootReadme, /klo ingest status --project-dir/); + assert.doesNotMatch(rootReadme, /ktx dev ingest run --project-dir/); + assert.doesNotMatch(rootReadme, /ktx ingest status --project-dir/); }); it('documents pnpm setup as a prerequisite when optional dev linking fails', async () => { const rootReadme = await readText('README.md'); assert.match(rootReadme, /pnpm run link:dev/); - assert.match(rootReadme, /klo-dev --help/); + assert.match(rootReadme, /ktx-dev --help/); assert.doesNotMatch( rootReadme, /If the setup command reports that pnpm's global bin directory is not on your\n`PATH`, add the printed directory to your shell profile/, @@ -144,7 +144,7 @@ describe('standalone example docs', () => { }); it('documents daemon HTTP database, source generation, LookML, embedding, and code execution support', async () => { - const readme = await readText('python/klo-daemon/README.md'); + const readme = await readText('python/ktx-daemon/README.md'); assert.match(readme, /semantic-generate-sources/); assert.match(readme, /database-introspect/); diff --git a/scripts/installed-live-database-smoke.mjs b/scripts/installed-live-database-smoke.mjs index c14d410a..bad155dd 100644 --- a/scripts/installed-live-database-smoke.mjs +++ b/scripts/installed-live-database-smoke.mjs @@ -16,13 +16,13 @@ import { pythonArtifactInstallArgs, } from './package-artifacts.mjs'; -const POSTGRES_IMAGE = process.env.KLO_ARTIFACT_POSTGRES_IMAGE ?? 'postgres:16-alpine'; -const POSTGRES_USER = 'klo'; +const POSTGRES_IMAGE = process.env.KTX_ARTIFACT_POSTGRES_IMAGE ?? 'postgres:16-alpine'; +const POSTGRES_USER = 'ktx'; const POSTGRES_PASSWORD = 'postgres'; // pragma: allowlist secret const POSTGRES_DB = 'warehouse'; export function smokeContainerName(pid = process.pid, now = Date.now()) { - return `klo-live-db-smoke-${pid}-${now}`; + return `ktx-live-db-smoke-${pid}-${now}`; } export function buildPostgresUrl(hostPort) { @@ -88,7 +88,7 @@ export function buildSeedSql() { ].join('\n'); } -export function buildKloYaml(postgresUrl) { +export function buildKtxYaml(postgresUrl) { return [ 'project: artifact-live-database', 'connections:', @@ -109,7 +109,7 @@ export function buildKloYaml(postgresUrl) { export function buildLiveDatabaseIngestArgs(projectDir, databaseIntrospectionUrl) { return [ 'exec', - 'klo', + 'ktx', 'dev', 'ingest', 'run', @@ -125,7 +125,7 @@ export function buildLiveDatabaseIngestArgs(projectDir, databaseIntrospectionUrl } export function buildLiveDatabaseStatusArgs(projectDir, runId) { - return ['exec', 'klo', 'ingest', 'status', '--project-dir', projectDir, runId]; + return ['exec', 'ktx', 'ingest', 'status', '--project-dir', projectDir, runId]; } async function run(command, args, options = {}) { @@ -283,12 +283,12 @@ async function waitForHttpHealth(url, daemon) { if (daemon.error()) { const output = daemon.output(); throw new Error( - `Failed to start klo-daemon: ${daemon.error().message}\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`, + `Failed to start ktx-daemon: ${daemon.error().message}\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`, ); } if (daemon.child.exitCode !== null || daemon.child.signalCode !== null) { const output = daemon.output(); - throw new Error(`klo-daemon exited before health check passed\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`); + throw new Error(`ktx-daemon exited before health check passed\nstdout:\n${output.stdout}\nstderr:\n${output.stderr}`); } try { if (await httpGetOk(url)) { @@ -306,7 +306,7 @@ async function waitForHttpHealth(url, daemon) { async function startDaemon(port, cleanInstallDir) { const daemon = spawnLogged( - 'klo-daemon', + 'ktx-daemon', ['serve-http', '--host', '127.0.0.1', '--port', String(port), '--log-level', 'warning'], { cwd: cleanInstallDir, env: npmSmokePythonEnv(cleanInstallDir) }, ); @@ -337,8 +337,8 @@ async function assertPathExists(path, label) { async function prepareCleanInstall(layout, cleanInstallDir) { const pythonArtifacts = await findPythonArtifacts(layout.pythonDir); - await assertPathExists(layout.contextTarball, '@klo/context tarball'); - await assertPathExists(layout.cliTarball, '@klo/cli tarball'); + await assertPathExists(layout.contextTarball, '@ktx/context tarball'); + await assertPathExists(layout.cliTarball, '@ktx/cli tarball'); await mkdir(cleanInstallDir, { recursive: true }); await writeFile(join(cleanInstallDir, 'package.json'), `${JSON.stringify(npmSmokePackageJson(layout), null, 2)}\n`); await run('pnpm', ['install'], { cwd: cleanInstallDir, timeout: 120_000 }).then((result) => @@ -362,7 +362,7 @@ async function prepareCleanInstall(layout, cleanInstallDir) { async function main() { const layout = packageArtifactLayout(); - const root = await mkdtemp(join(tmpdir(), 'klo-live-db-artifact-smoke-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-live-db-artifact-smoke-')); const containerName = smokeContainerName(); let daemon; try { @@ -379,12 +379,12 @@ async function main() { await prepareCleanInstall(layout, cleanInstallDir); await mkdir(projectDir, { recursive: true }); - const init = await run('pnpm', ['exec', 'klo', 'init', projectDir, '--name', 'artifact-live-database'], { + const init = await run('pnpm', ['exec', 'ktx', 'init', projectDir, '--name', 'artifact-live-database'], { cwd: cleanInstallDir, timeout: 30_000, }); - requireSuccess('klo init', init); - await writeFile(join(projectDir, 'klo.yaml'), buildKloYaml(postgresUrl), 'utf8'); + requireSuccess('ktx init', init); + await writeFile(join(projectDir, 'ktx.yaml'), buildKtxYaml(postgresUrl), 'utf8'); daemon = await startDaemon(daemonPort, cleanInstallDir); @@ -393,12 +393,12 @@ async function main() { env: npmSmokePythonEnv(cleanInstallDir), timeout: 120_000, }); - requireSuccess('klo dev ingest run live-database', ingestRun); - requireOutput('klo dev ingest run live-database', ingestRun, /Status: done/); - requireOutput('klo dev ingest run live-database', ingestRun, /Adapter: live-database/); - requireOutput('klo dev ingest run live-database', ingestRun, /Diff: \+4\/~0\/-0\/=0/); - requireOutput('klo dev ingest run live-database', ingestRun, /Raw files: 4/); - requireOutput('klo dev ingest run live-database', ingestRun, /Work units: 2/); + requireSuccess('ktx dev ingest run live-database', ingestRun); + requireOutput('ktx dev ingest run live-database', ingestRun, /Status: done/); + requireOutput('ktx dev ingest run live-database', ingestRun, /Adapter: live-database/); + requireOutput('ktx dev ingest run live-database', ingestRun, /Diff: \+4\/~0\/-0\/=0/); + requireOutput('ktx dev ingest run live-database', ingestRun, /Raw files: 4/); + requireOutput('ktx dev ingest run live-database', ingestRun, /Work units: 2/); const runId = getRunId(ingestRun.stdout); const ingestStatus = await run('pnpm', buildLiveDatabaseStatusArgs(projectDir, runId), { @@ -406,12 +406,12 @@ async function main() { env: npmSmokePythonEnv(cleanInstallDir), timeout: 30_000, }); - requireSuccess('klo ingest status live-database', ingestStatus); - requireOutput('klo ingest status live-database', ingestStatus, new RegExp(`Run: ${runId}`)); - requireOutput('klo ingest status live-database', ingestStatus, /Status: done/); - requireOutput('klo ingest status live-database', ingestStatus, /Raw files: 4/); - requireOutput('klo ingest status live-database', ingestStatus, /Work units: 2/); - await assertPathExists(join(projectDir, '.klo', 'db.sqlite'), 'SQLite local ingest state'); + requireSuccess('ktx ingest status live-database', ingestStatus); + requireOutput('ktx ingest status live-database', ingestStatus, new RegExp(`Run: ${runId}`)); + requireOutput('ktx ingest status live-database', ingestStatus, /Status: done/); + requireOutput('ktx ingest status live-database', ingestStatus, /Raw files: 4/); + requireOutput('ktx ingest status live-database', ingestStatus, /Work units: 2/); + await assertPathExists(join(projectDir, '.ktx', 'db.sqlite'), 'SQLite local ingest state'); process.stdout.write(`Installed live-database artifact smoke passed: ${runId}\n`); } finally { if (daemon) { diff --git a/scripts/installed-live-database-smoke.test.mjs b/scripts/installed-live-database-smoke.test.mjs index 58da8222..a3d6be9e 100644 --- a/scripts/installed-live-database-smoke.test.mjs +++ b/scripts/installed-live-database-smoke.test.mjs @@ -3,7 +3,7 @@ import { describe, it } from 'node:test'; import { buildDockerRunArgs, - buildKloYaml, + buildKtxYaml, buildLiveDatabaseIngestArgs, buildLiveDatabaseStatusArgs, buildPostgresUrl, @@ -16,7 +16,7 @@ describe('installed live-database artifact smoke helpers', () => { it('builds a deterministic disposable Postgres container command', () => { assert.deepEqual( buildDockerRunArgs({ - containerName: 'klo-live-db-smoke-test', + containerName: 'ktx-live-db-smoke-test', hostPort: 15432, image: 'postgres:16-alpine', }), @@ -25,11 +25,11 @@ describe('installed live-database artifact smoke helpers', () => { '--rm', '-d', '--name', - 'klo-live-db-smoke-test', + 'ktx-live-db-smoke-test', '-e', 'POSTGRES_PASSWORD=postgres', // pragma: allowlist secret '-e', - 'POSTGRES_USER=klo', + 'POSTGRES_USER=ktx', '-e', 'POSTGRES_DB=warehouse', '-p', @@ -40,25 +40,25 @@ describe('installed live-database artifact smoke helpers', () => { }); it('uses a collision-resistant Docker container name prefix', () => { - assert.match(smokeContainerName(1234, 5678), /^klo-live-db-smoke-1234-5678$/); + assert.match(smokeContainerName(1234, 5678), /^ktx-live-db-smoke-1234-5678$/); }); - it('builds the Postgres URL used by klo.yaml and daemon introspection', () => { + it('builds the Postgres URL used by ktx.yaml and daemon introspection', () => { assert.equal( buildPostgresUrl(15432), - 'postgresql://klo:postgres@127.0.0.1:15432/warehouse', // pragma: allowlist secret + 'postgresql://ktx:postgres@127.0.0.1:15432/warehouse', // pragma: allowlist secret ); }); - it('writes a live-database-only KLO project config with SQLite local state', () => { + it('writes a live-database-only KTX project config with SQLite local state', () => { assert.equal( - buildKloYaml('postgresql://klo:postgres@127.0.0.1:15432/warehouse'), // pragma: allowlist secret + buildKtxYaml('postgresql://ktx:postgres@127.0.0.1:15432/warehouse'), // pragma: allowlist secret [ 'project: artifact-live-database', 'connections:', ' warehouse:', ' driver: postgres', - ' url: "postgresql://klo:postgres@127.0.0.1:15432/warehouse"', // pragma: allowlist secret + ' url: "postgresql://ktx:postgres@127.0.0.1:15432/warehouse"', // pragma: allowlist secret ' readonly: true', 'storage:', ' state: sqlite', @@ -83,12 +83,12 @@ describe('installed live-database artifact smoke helpers', () => { }); it('waits for a real SQL connection to the target Postgres database', () => { - assert.deepEqual(buildPostgresReadyArgs('klo-live-db-smoke-test'), [ + assert.deepEqual(buildPostgresReadyArgs('ktx-live-db-smoke-test'), [ 'exec', - 'klo-live-db-smoke-test', + 'ktx-live-db-smoke-test', 'psql', '-U', - 'klo', + 'ktx', '-d', 'warehouse', '-v', @@ -101,7 +101,7 @@ describe('installed live-database artifact smoke helpers', () => { it('builds installed CLI live-database ingest and status commands', () => { assert.deepEqual(buildLiveDatabaseIngestArgs('/tmp/project', 'http://127.0.0.1:8765'), [ 'exec', - 'klo', + 'ktx', 'dev', 'ingest', 'run', @@ -117,7 +117,7 @@ describe('installed live-database artifact smoke helpers', () => { assert.deepEqual(buildLiveDatabaseStatusArgs('/tmp/project', 'local-run-1'), [ 'exec', - 'klo', + 'ktx', 'ingest', 'status', '--project-dir', diff --git a/scripts/link-dev-cli.mjs b/scripts/link-dev-cli.mjs index 96e95486..20893bd8 100644 --- a/scripts/link-dev-cli.mjs +++ b/scripts/link-dev-cli.mjs @@ -6,7 +6,7 @@ import { access as fsAccess, chmod as fsChmod, writeFile as fsWriteFile } from ' import { delimiter, join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { promisify } from 'node:util'; -import { ensureCliBinExecutable, kloRootDir } from './prepare-cli-bin.mjs'; +import { ensureCliBinExecutable, ktxRootDir } from './prepare-cli-bin.mjs'; const execFileAsync = promisify(execFile); @@ -73,7 +73,7 @@ async function writePinnedPosixLauncher(globalBin, binPath, binaryName, writeFil const launcherPath = join(globalBin, binaryName); const script = [ '#!/bin/sh', - '# Generated by `pnpm run link:dev` in the KLO workspace.', + '# Generated by `pnpm run link:dev` in the KTX workspace.', '# Keep this launcher pinned to the Node binary that built native dependencies.', `exec ${shellDoubleQuote(process.execPath)} ${shellDoubleQuote(binPath)} "$@"`, '', @@ -88,7 +88,7 @@ async function writePinnedWindowsLauncher(globalBin, binPath, binaryName, writeF const launcherPath = join(globalBin, `${binaryName}.cmd`); const script = [ '@echo off', - 'REM Generated by `pnpm run link:dev` in the KLO workspace.', + 'REM Generated by `pnpm run link:dev` in the KTX workspace.', `"${process.execPath}" "${binPath}" %*`, '', ].join('\r\n'); @@ -134,8 +134,8 @@ async function assertBuiltCli(rootDir, access, binPathOverride) { } export async function linkDevCli(options = {}) { - const rootDir = options.rootDir ?? kloRootDir(); - const binaryName = options.binaryName ?? 'klo-dev'; + const rootDir = options.rootDir ?? ktxRootDir(); + const binaryName = options.binaryName ?? 'ktx-dev'; const access = options.access ?? fsAccess; const chmod = options.chmod ?? fsChmod; const writeFile = options.writeFile ?? fsWriteFile; @@ -177,9 +177,9 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) { try { const result = await linkDevCli({ checkOnly: hasFlag('--check-only'), - binaryName: optionValue('--name', 'klo-dev'), + binaryName: optionValue('--name', 'ktx-dev'), }); - process.stdout.write(`KLO CLI bin: ${result.binPath}\n`); + process.stdout.write(`KTX CLI bin: ${result.binPath}\n`); if (result.linked) { process.stdout.write(`Linked binary: ${result.binaryName}\n`); process.stdout.write(`Verified: ${result.verification.output}\n`); diff --git a/scripts/link-dev-cli.test.mjs b/scripts/link-dev-cli.test.mjs index 3db235e8..db4f9f4e 100644 --- a/scripts/link-dev-cli.test.mjs +++ b/scripts/link-dev-cli.test.mjs @@ -2,44 +2,44 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { linkDevCli } from './link-dev-cli.mjs'; -test('linkDevCli writes a klo-dev launcher by default', async () => { +test('linkDevCli writes a ktx-dev launcher by default', async () => { const writes = []; const chmods = []; const result = await linkDevCli({ - rootDir: '/workspace/klo', + rootDir: '/workspace/ktx', globalBin: '/pnpm/bin', - binPath: '/workspace/klo/packages/cli/dist/bin.js', + binPath: '/workspace/ktx/packages/cli/dist/bin.js', execText: async (command, args) => { - assert.equal(command, 'klo-dev'); + assert.equal(command, 'ktx-dev'); assert.deepEqual(args, ['--version']); - return '@klo/cli 0.0.0-private'; + return '@ktx/cli 0.0.0-private'; }, writeFile: async (path, content) => writes.push({ path, content }), chmod: async (path, mode) => chmods.push({ path, mode }), access: async () => undefined, }); - assert.equal(result.binaryName, 'klo-dev'); - assert.equal(writes[0].path, '/pnpm/bin/klo-dev'); + assert.equal(result.binaryName, 'ktx-dev'); + assert.equal(writes[0].path, '/pnpm/bin/ktx-dev'); assert.match(writes[0].content, /packages\/cli\/dist\/bin.js/); - assert.deepEqual(chmods, [{ path: '/pnpm/bin/klo-dev', mode: 0o755 }]); + assert.deepEqual(chmods, [{ path: '/pnpm/bin/ktx-dev', mode: 0o755 }]); }); -test('linkDevCli can explicitly write klo when requested', async () => { +test('linkDevCli can explicitly write ktx when requested', async () => { const writes = []; const result = await linkDevCli({ - rootDir: '/workspace/klo', - binaryName: 'klo', + rootDir: '/workspace/ktx', + binaryName: 'ktx', globalBin: '/pnpm/bin', - binPath: '/workspace/klo/packages/cli/dist/bin.js', - execText: async () => '@klo/cli 0.0.0-private', + binPath: '/workspace/ktx/packages/cli/dist/bin.js', + execText: async () => '@ktx/cli 0.0.0-private', writeFile: async (path, content) => writes.push({ path, content }), chmod: async () => undefined, access: async () => undefined, }); - assert.equal(result.binaryName, 'klo'); - assert.equal(writes[0].path, '/pnpm/bin/klo'); + assert.equal(result.binaryName, 'ktx'); + assert.equal(writes[0].path, '/pnpm/bin/ktx'); }); diff --git a/scripts/package-artifacts.mjs b/scripts/package-artifacts.mjs index 56c79a1d..52d49470 100644 --- a/scripts/package-artifacts.mjs +++ b/scripts/package-artifacts.mjs @@ -11,22 +11,22 @@ const PACKAGE_VERSION = '0.0.0-private'; const PYTHON_PACKAGE_VERSION = '0.1.0'; export const NPM_ARTIFACT_PACKAGES = [ - { name: '@klo/context', packageRoot: 'packages/context' }, - { name: '@klo/llm', packageRoot: 'packages/llm' }, - { name: '@klo/connector-bigquery', packageRoot: 'packages/connector-bigquery' }, - { name: '@klo/connector-clickhouse', packageRoot: 'packages/connector-clickhouse' }, - { name: '@klo/connector-mysql', packageRoot: 'packages/connector-mysql' }, - { name: '@klo/connector-postgres', packageRoot: 'packages/connector-postgres' }, - { name: '@klo/connector-posthog', packageRoot: 'packages/connector-posthog' }, - { name: '@klo/connector-snowflake', packageRoot: 'packages/connector-snowflake' }, - { name: '@klo/connector-sqlite', packageRoot: 'packages/connector-sqlite' }, - { name: '@klo/connector-sqlserver', packageRoot: 'packages/connector-sqlserver' }, - { name: '@klo/cli', packageRoot: 'packages/cli' }, + { name: '@ktx/context', packageRoot: 'packages/context' }, + { name: '@ktx/llm', packageRoot: 'packages/llm' }, + { name: '@ktx/connector-bigquery', packageRoot: 'packages/connector-bigquery' }, + { name: '@ktx/connector-clickhouse', packageRoot: 'packages/connector-clickhouse' }, + { name: '@ktx/connector-mysql', packageRoot: 'packages/connector-mysql' }, + { name: '@ktx/connector-postgres', packageRoot: 'packages/connector-postgres' }, + { name: '@ktx/connector-posthog', packageRoot: 'packages/connector-posthog' }, + { name: '@ktx/connector-snowflake', packageRoot: 'packages/connector-snowflake' }, + { name: '@ktx/connector-sqlite', packageRoot: 'packages/connector-sqlite' }, + { name: '@ktx/connector-sqlserver', packageRoot: 'packages/connector-sqlserver' }, + { name: '@ktx/cli', packageRoot: 'packages/cli' }, ]; const CONNECTOR_PACKAGE_NAMES = NPM_ARTIFACT_PACKAGES .map((packageInfo) => packageInfo.name) - .filter((packageName) => packageName.startsWith('@klo/connector-')); + .filter((packageName) => packageName.startsWith('@ktx/connector-')); const ordersSource = { name: 'orders', @@ -46,7 +46,7 @@ function scriptRootDir() { } function npmPackageTarballName(packageName) { - return `${packageName.replace('@klo/', 'klo-')}-${PACKAGE_VERSION}.tgz`; + return `${packageName.replace('@ktx/', 'ktx-')}-${PACKAGE_VERSION}.tgz`; } function npmPackageTarballs(npmDir) { @@ -67,8 +67,8 @@ export function packageArtifactLayout(rootDir = scriptRootDir()) { npmDir, pythonDir, npmTarballs, - contextTarball: npmTarballs['@klo/context'], - cliTarball: npmTarballs['@klo/cli'], + contextTarball: npmTarballs['@ktx/context'], + cliTarball: npmTarballs['@ktx/cli'], connectorTarballs: Object.fromEntries( CONNECTOR_PACKAGE_NAMES.map((packageName) => [packageName, npmTarballs[packageName]]), ), @@ -93,12 +93,12 @@ export function buildArtifactCommands(layout) { ...npmPackCommands, { command: 'uv', - args: ['build', '--package', 'klo-sl', '--out-dir', layout.pythonDir], + args: ['build', '--package', 'ktx-sl', '--out-dir', layout.pythonDir], cwd: layout.rootDir, }, { command: 'uv', - args: ['build', '--package', 'klo-daemon', '--out-dir', layout.pythonDir], + args: ['build', '--package', 'ktx-daemon', '--out-dir', layout.pythonDir], cwd: layout.rootDir, }, ]; @@ -136,10 +136,10 @@ export async function findPythonArtifacts(pythonDir) { const files = await readdir(pythonDir); return { - kloSlWheel: findOne(files, 'klo-sl', '.whl', 'klo-sl wheel', pythonDir), - kloSlSdist: findOne(files, 'klo-sl', '.tar.gz', 'klo-sl source distribution', pythonDir), - kloDaemonWheel: findOne(files, 'klo-daemon', '.whl', 'klo-daemon wheel', pythonDir), - kloDaemonSdist: findOne(files, 'klo-daemon', '.tar.gz', 'klo-daemon source distribution', pythonDir), + ktxSlWheel: findOne(files, 'ktx-sl', '.whl', 'ktx-sl wheel', pythonDir), + ktxSlSdist: findOne(files, 'ktx-sl', '.tar.gz', 'ktx-sl source distribution', pythonDir), + ktxDaemonWheel: findOne(files, 'ktx-daemon', '.whl', 'ktx-daemon wheel', pythonDir), + ktxDaemonSdist: findOne(files, 'ktx-daemon', '.tar.gz', 'ktx-daemon source distribution', pythonDir), }; } @@ -223,23 +223,23 @@ export async function packageReleaseMetadata(rootDir = scriptRootDir()) { const npmPackages = await Promise.all( NPM_ARTIFACT_PACKAGES.map((packageInfo) => readNpmPackageMetadata(rootDir, packageInfo)), ); - const kloSlPackage = await readPyprojectMetadata(join(rootDir, 'python', 'klo-sl', 'pyproject.toml')); - const kloDaemonPackage = await readPyprojectMetadata(join(rootDir, 'python', 'klo-daemon', 'pyproject.toml')); + const ktxSlPackage = await readPyprojectMetadata(join(rootDir, 'python', 'ktx-sl', 'pyproject.toml')); + const ktxDaemonPackage = await readPyprojectMetadata(join(rootDir, 'python', 'ktx-daemon', 'pyproject.toml')); return [ ...npmPackages, releaseMetadataEntry({ ecosystem: 'python', - packageName: kloSlPackage.name, - packageRoot: 'python/klo-sl', - packageVersion: kloSlPackage.version, + packageName: ktxSlPackage.name, + packageRoot: 'python/ktx-sl', + packageVersion: ktxSlPackage.version, privatePackage: false, }), releaseMetadataEntry({ ecosystem: 'python', - packageName: kloDaemonPackage.name, - packageRoot: 'python/klo-daemon', - packageVersion: kloDaemonPackage.version, + packageName: ktxDaemonPackage.name, + packageRoot: 'python/ktx-daemon', + packageVersion: ktxDaemonPackage.version, privatePackage: false, }), ]; @@ -269,23 +269,23 @@ function artifactPackageRecords(layout, pythonArtifacts, packages) { ...npmRecords, { artifactKind: 'wheel', - artifactPath: pythonArtifacts.kloSlWheel, - metadata: requirePackageMetadata(packagesByName, 'klo-sl'), + artifactPath: pythonArtifacts.ktxSlWheel, + metadata: requirePackageMetadata(packagesByName, 'ktx-sl'), }, { artifactKind: 'sdist', - artifactPath: pythonArtifacts.kloSlSdist, - metadata: requirePackageMetadata(packagesByName, 'klo-sl'), + artifactPath: pythonArtifacts.ktxSlSdist, + metadata: requirePackageMetadata(packagesByName, 'ktx-sl'), }, { artifactKind: 'wheel', - artifactPath: pythonArtifacts.kloDaemonWheel, - metadata: requirePackageMetadata(packagesByName, 'klo-daemon'), + artifactPath: pythonArtifacts.ktxDaemonWheel, + metadata: requirePackageMetadata(packagesByName, 'ktx-daemon'), }, { artifactKind: 'sdist', - artifactPath: pythonArtifacts.kloDaemonSdist, - metadata: requirePackageMetadata(packagesByName, 'klo-daemon'), + artifactPath: pythonArtifacts.ktxDaemonSdist, + metadata: requirePackageMetadata(packagesByName, 'ktx-daemon'), }, ]; } @@ -399,7 +399,7 @@ export async function verifyArtifactManifest(layout, options = {}) { const manifest = await readJson(artifactManifestPath(layout)); assertManifestShape(manifest); - const expectedSourceRevision = options.expectedSourceRevision ?? process.env.KLO_EXPECTED_SOURCE_REVISION; + const expectedSourceRevision = options.expectedSourceRevision ?? process.env.KTX_EXPECTED_SOURCE_REVISION; if (expectedSourceRevision !== undefined && manifest.sourceRevision !== expectedSourceRevision) { throw new Error( `Artifact manifest sourceRevision mismatch: expected ${expectedSourceRevision}, got ${manifest.sourceRevision}`, @@ -435,8 +435,8 @@ export function pythonArtifactInstallArgs(python, pythonArtifacts) { 'install', '--python', python, - pythonArtifacts.kloSlWheel, - pythonArtifacts.kloDaemonWheel, + pythonArtifacts.ktxSlWheel, + pythonArtifacts.ktxDaemonWheel, ]; } @@ -486,7 +486,7 @@ function npmTarballDependencyEntries(layout) { export function npmSmokePackageJson(layout) { const npmTarballDependencies = npmTarballDependencyEntries(layout); return { - name: 'klo-artifact-npm-smoke', + name: 'ktx-artifact-npm-smoke', version: '0.0.0', private: true, type: 'module', @@ -503,53 +503,53 @@ export function npmSmokePackageJson(layout) { export function npmVerifySource() { return ` -const context = await import('@klo/context'); -const project = await import('@klo/context/project'); -const mcp = await import('@klo/context/mcp'); -const memory = await import('@klo/context/memory'); -const daemon = await import('@klo/context/daemon'); -const ingest = await import('@klo/context/ingest'); -const search = await import('@klo/context/search'); -const llm = await import('@klo/llm'); -const cli = await import('@klo/cli'); -const bigqueryConnector = await import('@klo/connector-bigquery'); -const clickhouseConnector = await import('@klo/connector-clickhouse'); -const mysqlConnector = await import('@klo/connector-mysql'); -const postgresConnector = await import('@klo/connector-postgres'); -const posthogConnector = await import('@klo/connector-posthog'); -const snowflakeConnector = await import('@klo/connector-snowflake'); -const sqliteConnector = await import('@klo/connector-sqlite'); -const sqlserverConnector = await import('@klo/connector-sqlserver'); +const context = await import('@ktx/context'); +const project = await import('@ktx/context/project'); +const mcp = await import('@ktx/context/mcp'); +const memory = await import('@ktx/context/memory'); +const daemon = await import('@ktx/context/daemon'); +const ingest = await import('@ktx/context/ingest'); +const search = await import('@ktx/context/search'); +const llm = await import('@ktx/llm'); +const cli = await import('@ktx/cli'); +const bigqueryConnector = await import('@ktx/connector-bigquery'); +const clickhouseConnector = await import('@ktx/connector-clickhouse'); +const mysqlConnector = await import('@ktx/connector-mysql'); +const postgresConnector = await import('@ktx/connector-postgres'); +const posthogConnector = await import('@ktx/connector-posthog'); +const snowflakeConnector = await import('@ktx/connector-snowflake'); +const sqliteConnector = await import('@ktx/connector-sqlite'); +const sqlserverConnector = await import('@ktx/connector-sqlserver'); -if (context.kloContextPackageInfo.name !== '@klo/context') { - throw new Error('Unexpected @klo/context package info'); +if (context.ktxContextPackageInfo.name !== '@ktx/context') { + throw new Error('Unexpected @ktx/context package info'); } -if (typeof llm.createKloLlmProvider !== 'function') { - throw new Error('Missing createKloLlmProvider export'); +if (typeof llm.createKtxLlmProvider !== 'function') { + throw new Error('Missing createKtxLlmProvider export'); } -if (typeof llm.KloMessageBuilder !== 'function') { - throw new Error('Missing KloMessageBuilder export'); +if (typeof llm.KtxMessageBuilder !== 'function') { + throw new Error('Missing KtxMessageBuilder export'); } -if (typeof llm.createKloEmbeddingProvider !== 'function') { - throw new Error('Missing createKloEmbeddingProvider export'); +if (typeof llm.createKtxEmbeddingProvider !== 'function') { + throw new Error('Missing createKtxEmbeddingProvider export'); } -if (typeof project.initKloProject !== 'function') { - throw new Error('Missing initKloProject export'); +if (typeof project.initKtxProject !== 'function') { + throw new Error('Missing initKtxProject export'); } -if (typeof mcp.createDefaultKloMcpServer !== 'function') { - throw new Error('Missing createDefaultKloMcpServer export'); +if (typeof mcp.createDefaultKtxMcpServer !== 'function') { + throw new Error('Missing createDefaultKtxMcpServer export'); } if (typeof memory.createLocalProjectMemoryCapture !== 'function') { throw new Error('Missing createLocalProjectMemoryCapture export'); } if (typeof search.HybridSearchCore !== 'function') { - throw new Error('Missing HybridSearchCore export from @klo/context/search'); + throw new Error('Missing HybridSearchCore export from @ktx/context/search'); } if (typeof search.assertSearchBackendConformanceCase !== 'function') { - throw new Error('Missing assertSearchBackendConformanceCase export from @klo/context/search'); + throw new Error('Missing assertSearchBackendConformanceCase export from @ktx/context/search'); } if (typeof search.assertSearchBackendCapabilities !== 'function') { - throw new Error('Missing assertSearchBackendCapabilities export from @klo/context/search'); + throw new Error('Missing assertSearchBackendCapabilities export from @ktx/context/search'); } if (typeof daemon.createPythonSemanticLayerComputePort !== 'function') { throw new Error('Missing createPythonSemanticLayerComputePort export'); @@ -576,21 +576,21 @@ const metricflowConfig = ingest.parseMetricflowPullConfig({ repoUrl: 'https://example.com/acme/analytics.git', }); if (metricflowConfig.branch !== 'main' || metricflowConfig.path !== null) { - throw new Error('Unexpected MetricFlow pull-config defaults from installed @klo/context/ingest'); + throw new Error('Unexpected MetricFlow pull-config defaults from installed @ktx/context/ingest'); } -if (cli.getKloCliPackageInfo().name !== '@klo/cli') { - throw new Error('Unexpected @klo/cli package info'); +if (cli.getKtxCliPackageInfo().name !== '@ktx/cli') { + throw new Error('Unexpected @ktx/cli package info'); } const connectorExports = [ - ['@klo/connector-bigquery', bigqueryConnector.KloBigQueryScanConnector, bigqueryConnector.KloBigQueryDialect], - ['@klo/connector-clickhouse', clickhouseConnector.KloClickHouseScanConnector, clickhouseConnector.KloClickHouseDialect], - ['@klo/connector-mysql', mysqlConnector.KloMysqlScanConnector, mysqlConnector.KloMysqlDialect], - ['@klo/connector-postgres', postgresConnector.KloPostgresScanConnector, postgresConnector.KloPostgresDialect], - ['@klo/connector-posthog', posthogConnector.KloPostHogScanConnector, posthogConnector.KloPostHogDialect], - ['@klo/connector-snowflake', snowflakeConnector.KloSnowflakeScanConnector, snowflakeConnector.KloSnowflakeDialect], - ['@klo/connector-sqlite', sqliteConnector.KloSqliteScanConnector, sqliteConnector.KloSqliteDialect], - ['@klo/connector-sqlserver', sqlserverConnector.KloSqlServerScanConnector, sqlserverConnector.KloSqlServerDialect], + ['@ktx/connector-bigquery', bigqueryConnector.KtxBigQueryScanConnector, bigqueryConnector.KtxBigQueryDialect], + ['@ktx/connector-clickhouse', clickhouseConnector.KtxClickHouseScanConnector, clickhouseConnector.KtxClickHouseDialect], + ['@ktx/connector-mysql', mysqlConnector.KtxMysqlScanConnector, mysqlConnector.KtxMysqlDialect], + ['@ktx/connector-postgres', postgresConnector.KtxPostgresScanConnector, postgresConnector.KtxPostgresDialect], + ['@ktx/connector-posthog', posthogConnector.KtxPostHogScanConnector, posthogConnector.KtxPostHogDialect], + ['@ktx/connector-snowflake', snowflakeConnector.KtxSnowflakeScanConnector, snowflakeConnector.KtxSnowflakeDialect], + ['@ktx/connector-sqlite', sqliteConnector.KtxSqliteScanConnector, sqliteConnector.KtxSqliteDialect], + ['@ktx/connector-sqlserver', sqlserverConnector.KtxSqlServerScanConnector, sqlserverConnector.KtxSqlServerDialect], ]; for (const [packageName, ScanConnector, Dialect] of connectorExports) { @@ -621,11 +621,11 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { createDaemonLookerTableIdentifierParser, LocalLookerRuntimeStore, -} from '@klo/context/ingest'; +} from '@ktx/context/ingest'; const execFileAsync = promisify(execFile); const require = createRequire(import.meta.url); -const contextPackageRoot = dirname(require.resolve('@klo/context/package.json')); +const contextPackageRoot = dirname(require.resolve('@ktx/context/package.json')); async function requireContextRuntimeAsset(relativePath) { await access(join(contextPackageRoot, relativePath)); @@ -760,7 +760,7 @@ async function waitForHttpHealth(url, daemon) { if (daemon.error()) { const output = daemon.output(); throw new Error( - 'Failed to start klo-daemon serve-http: ' + + 'Failed to start ktx-daemon serve-http: ' + daemon.error().message + '\\nstdout:\\n' + output.stdout + @@ -771,7 +771,7 @@ async function waitForHttpHealth(url, daemon) { if (daemon.child.exitCode !== null || daemon.child.signalCode !== null) { const output = daemon.output(); throw new Error( - 'klo-daemon serve-http exited before health check passed\\nstdout:\\n' + + 'ktx-daemon serve-http exited before health check passed\\nstdout:\\n' + output.stdout + '\\nstderr:\\n' + output.stderr, @@ -792,7 +792,7 @@ async function waitForHttpHealth(url, daemon) { } async function startSemanticDaemon(port) { - const daemon = spawnLogged('klo-daemon', [ + const daemon = spawnLogged('ktx-daemon', [ 'serve-http', '--host', '127.0.0.1', @@ -847,7 +847,7 @@ await requireContextRuntimeAsset('prompts/skills/page_triage_classifier.md'); await requireContextRuntimeAsset('prompts/skills/light_extraction.md'); process.stdout.write('packaged ingest runtime assets verified\\n'); -const root = await mkdtemp(join(tmpdir(), 'klo-installed-cli-smoke-')); +const root = await mkdtemp(join(tmpdir(), 'ktx-installed-cli-smoke-')); try { const projectDir = join(root, 'project'); const sourceDir = join(root, 'source'); @@ -856,7 +856,7 @@ try { await mkdir(missingProjectDir, { recursive: true }); const missingProjectSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'sl', 'list', @@ -866,19 +866,19 @@ try { '--project-dir', missingProjectDir, ]); - const missingProjectError = parseJsonFailure('klo agent sl list missing project', missingProjectSearch); + const missingProjectError = parseJsonFailure('ktx agent sl list missing project', missingProjectSearch); assert.equal(missingProjectError.error.code, 'agent_sl_search_missing_project'); assert.deepEqual(missingProjectError.error.nextSteps, [ - 'klo demo', - 'klo setup --project-dir ' + missingProjectDir, - 'klo ingest ', - 'klo agent sl list --json --query "revenue" --project-dir ' + missingProjectDir, + 'ktx demo', + 'ktx setup --project-dir ' + missingProjectDir, + 'ktx ingest ', + 'ktx agent sl list --json --query "revenue" --project-dir ' + missingProjectDir, ]); - process.stdout.write('klo agent sl list missing project guidance verified\\n'); + process.stdout.write('ktx agent sl list missing project guidance verified\\n'); const init = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'setup', '--project-dir', projectDir, @@ -891,13 +891,13 @@ try { '--skip-sources', '--skip-agents', ]); - requireSuccess('klo setup', init); - requireOutput('klo setup', init, /Project: /); + requireSuccess('ktx setup', init); + requireOutput('ktx setup', init, /Project: /); const emptyProjectDir = join(root, 'empty-project'); const emptyInit = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'setup', '--project-dir', emptyProjectDir, @@ -910,10 +910,10 @@ try { '--skip-sources', '--skip-agents', ]); - requireSuccess('klo setup empty project', emptyInit); + requireSuccess('ktx setup empty project', emptyInit); const emptySearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'sl', 'list', @@ -923,18 +923,18 @@ try { '--project-dir', emptyProjectDir, ]); - const emptySearchError = parseJsonFailure('klo agent sl list no connections', emptySearch); + const emptySearchError = parseJsonFailure('ktx agent sl list no connections', emptySearch); assert.equal(emptySearchError.error.code, 'agent_sl_search_no_connections'); assert.deepEqual(emptySearchError.error.nextSteps, [ - 'klo demo', - 'klo setup --project-dir ' + emptyProjectDir, - 'klo ingest ', - 'klo agent sl list --json --query "revenue" --project-dir ' + emptyProjectDir, + 'ktx demo', + 'ktx setup --project-dir ' + emptyProjectDir, + 'ktx ingest ', + 'ktx agent sl list --json --query "revenue" --project-dir ' + emptyProjectDir, ]); - process.stdout.write('klo agent sl list no connections guidance verified\\n'); + process.stdout.write('ktx agent sl list no connections guidance verified\\n'); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -958,7 +958,7 @@ try { ); await writeSqliteWarehouse(projectDir); - const lookerStore = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.klo', 'db.sqlite') }); + const lookerStore = new LocalLookerRuntimeStore({ dbPath: join(projectDir, '.ktx', 'db.sqlite') }); await lookerStore.setCursors('prod-looker', { dashboardsLastSyncedAt: null, looksLastSyncedAt: null, @@ -966,12 +966,12 @@ try { await lookerStore.upsertConnectionMapping({ lookerConnectionId: 'prod-looker', lookerConnectionName: 'analytics', - kloConnectionId: 'warehouse', + ktxConnectionId: 'warehouse', source: 'cli', }); const lookerMappings = await lookerStore.readMappings('prod-looker'); assert.equal(lookerMappings.length, 1); - assert.equal(lookerMappings[0].kloConnectionId, 'warehouse'); + assert.equal(lookerMappings[0].ktxConnectionId, 'warehouse'); process.stdout.write('Looker local runtime store verified\\n'); await mkdir(join(projectDir, 'knowledge', 'global'), { recursive: true }); @@ -995,7 +995,7 @@ try { const agentWikiSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'wiki', 'search', @@ -1006,19 +1006,19 @@ try { '--project-dir', projectDir, ]); - const agentWikiSearchJson = parseJsonResult('klo agent wiki search', agentWikiSearch); + const agentWikiSearchJson = parseJsonResult('ktx agent wiki search', agentWikiSearch); assert.equal(agentWikiSearchJson.totalFound, 1); assert.equal(agentWikiSearchJson.results[0].key, 'revenue'); assert.equal(agentWikiSearchJson.results[0].path, 'knowledge/global/revenue.md'); assert.equal(typeof agentWikiSearchJson.results[0].score, 'number'); requireIncludes(agentWikiSearchJson.results[0].matchReasons, 'lexical', 'agent wiki search match reasons'); - process.stdout.write('klo agent wiki search hybrid metadata verified\\n'); - await access(join(projectDir, '.klo', 'db.sqlite')); - process.stdout.write('SQLite knowledge index: ' + join(projectDir, '.klo', 'db.sqlite') + '\\n'); + process.stdout.write('ktx agent wiki search hybrid metadata verified\\n'); + await access(join(projectDir, '.ktx', 'db.sqlite')); + process.stdout.write('SQLite knowledge index: ' + join(projectDir, '.ktx', 'db.sqlite') + '\\n'); const noSourceSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'sl', 'list', @@ -1030,15 +1030,15 @@ try { '--project-dir', projectDir, ]); - const noSourceSearchError = parseJsonFailure('klo agent sl list no indexed sources', noSourceSearch); + const noSourceSearchError = parseJsonFailure('ktx agent sl list no indexed sources', noSourceSearch); assert.equal(noSourceSearchError.error.code, 'agent_sl_search_no_indexed_sources'); assert.deepEqual(noSourceSearchError.error.nextSteps, [ - 'klo demo', - 'klo setup --project-dir ' + projectDir, - 'klo ingest ', - 'klo agent sl list --json --query "revenue" --project-dir ' + projectDir, + 'ktx demo', + 'ktx setup --project-dir ' + projectDir, + 'ktx ingest ', + 'ktx agent sl list --json --query "revenue" --project-dir ' + projectDir, ]); - process.stdout.write('klo agent sl list no indexed sources guidance verified\\n'); + process.stdout.write('ktx agent sl list no indexed sources guidance verified\\n'); const slYaml = [ 'name: orders', @@ -1062,7 +1062,7 @@ try { const agentSlSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'sl', 'list', @@ -1074,18 +1074,18 @@ try { '--project-dir', projectDir, ]); - const agentSlSearchJson = parseJsonResult('klo agent sl list', agentSlSearch); + const agentSlSearchJson = parseJsonResult('ktx agent sl list', agentSlSearch); assert.equal(agentSlSearchJson.totalSources, 1); assert.equal(agentSlSearchJson.sources[0].connectionId, 'warehouse'); assert.equal(agentSlSearchJson.sources[0].name, 'orders'); assert.equal(typeof agentSlSearchJson.sources[0].score, 'number'); requireIncludes(agentSlSearchJson.sources[0].matchReasons, 'lexical', 'agent sl search match reasons'); - process.stdout.write('klo agent sl list hybrid metadata verified\\n'); + process.stdout.write('ktx agent sl list hybrid metadata verified\\n'); const slQueryFile = join(projectDir, 'sl-query.json'); await writeFile(slQueryFile, '{"measures":["orders.order_count"],"dimensions":[]}\\n', 'utf-8'); - const slQuery = await run('pnpm', ['exec', 'klo', 'agent', 'sl', 'query', + const slQuery = await run('pnpm', ['exec', 'ktx', 'agent', 'sl', 'query', '--json', '--connection-id', 'warehouse', @@ -1094,11 +1094,11 @@ try { '--project-dir', projectDir, ]); - requireSuccess('klo agent sl query', slQuery); - requireOutput('klo agent sl query', slQuery, /"mode": "compile_only"/); - requireOutput('klo agent sl query', slQuery, /orders/); + requireSuccess('ktx agent sl query', slQuery); + requireOutput('ktx agent sl query', slQuery, /"mode": "compile_only"/); + requireOutput('ktx agent sl query', slQuery, /orders/); - const sqliteSlQuery = await run('pnpm', ['exec', 'klo', 'agent', 'sl', 'query', + const sqliteSlQuery = await run('pnpm', ['exec', 'ktx', 'agent', 'sl', 'query', '--json', '--connection-id', 'warehouse', @@ -1110,40 +1110,40 @@ try { '--project-dir', projectDir, ]); - requireSuccess('klo agent sl query sqlite execute', sqliteSlQuery); - requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/); - requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/); - requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"driver": "sqlite"/); - requireOutput('klo agent sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/); - process.stdout.write('klo agent sl query sqlite execute verified\\n'); + requireSuccess('ktx agent sl query sqlite execute', sqliteSlQuery); + requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"dialect": "sqlite"/); + requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"mode": "executed"/); + requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"driver": "sqlite"/); + requireOutput('ktx agent sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/); + process.stdout.write('ktx agent sl query sqlite execute verified\\n'); - const structuralScan = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'warehouse', + const structuralScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse', '--project-dir', projectDir, ]); - requireSuccess('klo scan structural', structuralScan); - requireOutput('klo scan structural', structuralScan, /Status: done/); - requireOutput('klo scan structural', structuralScan, /Mode: structural/); - requireOutput('klo scan structural', structuralScan, /Needs attention\\s+None/); + requireSuccess('ktx scan structural', structuralScan); + requireOutput('ktx scan structural', structuralScan, /Status: done/); + requireOutput('ktx scan structural', structuralScan, /Mode: structural/); + requireOutput('ktx scan structural', structuralScan, /Needs attention\\s+None/); const structuralScanRunId = getRunId(structuralScan.stdout); - const scanStatus = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'status', + const scanStatus = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'status', '--project-dir', projectDir, structuralScanRunId, ]); - requireSuccess('klo scan status', scanStatus); - requireOutput('klo scan status', scanStatus, new RegExp('Run: ' + structuralScanRunId)); - requireOutput('klo scan status', scanStatus, /Status: done/); - requireOutput('klo scan status', scanStatus, /Mode: structural/); + requireSuccess('ktx scan status', scanStatus); + requireOutput('ktx scan status', scanStatus, new RegExp('Run: ' + structuralScanRunId)); + requireOutput('ktx scan status', scanStatus, /Status: done/); + requireOutput('ktx scan status', scanStatus, /Mode: structural/); - const scanReport = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'report', + const scanReport = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'report', '--project-dir', projectDir, '--json', structuralScanRunId, ]); - requireSuccess('klo scan report', scanReport); + requireSuccess('ktx scan report', scanReport); const scanReportJson = JSON.parse(scanReport.stdout); assert.equal(scanReportJson.mode, 'structural'); assert.equal(scanReportJson.connectionId, 'warehouse'); @@ -1151,35 +1151,35 @@ try { assert.deepEqual(scanReportJson.artifactPaths.enrichmentArtifacts, []); assert.deepEqual(scanReportJson.artifactPaths.manifestShards, ['semantic-layer/warehouse/_schema/public.yaml']); await access(join(projectDir, 'semantic-layer', 'warehouse', '_schema', 'public.yaml')); - process.stdout.write('klo scan structural verified: ' + structuralScanRunId + '\\n'); + process.stdout.write('ktx scan structural verified: ' + structuralScanRunId + '\\n'); - const enrichedScan = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'warehouse', + const enrichedScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse', '--project-dir', projectDir, '--mode', 'enriched', ]); - requireSuccess('klo scan enriched', enrichedScan); - requireOutput('klo scan enriched', enrichedScan, /Status: done/); - requireOutput('klo scan enriched', enrichedScan, /Mode: enriched/); + requireSuccess('ktx scan enriched', enrichedScan); + requireOutput('ktx scan enriched', enrichedScan, /Status: done/); + requireOutput('ktx scan enriched', enrichedScan, /Mode: enriched/); const enrichedScanRunId = getRunId(enrichedScan.stdout); - const enrichedScanReport = await run('pnpm', ['exec', 'klo', 'dev', 'scan', 'report', + const enrichedScanReport = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'report', '--project-dir', projectDir, '--json', enrichedScanRunId, ]); - requireSuccess('klo scan enriched report', enrichedScanReport); + requireSuccess('ktx scan enriched report', enrichedScanReport); const enrichedScanReportJson = JSON.parse(enrichedScanReport.stdout); assert.equal(enrichedScanReportJson.mode, 'enriched'); assert.ok(enrichedScanReportJson.artifactPaths.enrichmentArtifacts.length > 0); assert.deepEqual(enrichedScanReportJson.artifactPaths.manifestShards, ['semantic-layer/warehouse/_schema/public.yaml']); - process.stdout.write('klo scan enriched verified: ' + enrichedScanRunId + '\\n'); + process.stdout.write('ktx scan enriched verified: ' + enrichedScanRunId + '\\n'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); await writeFile(join(sourceDir, 'orders', 'orders.json'), '{"name":"orders"}\\n', 'utf-8'); - const ingestRun = await run('pnpm', ['exec', 'klo', 'dev', 'ingest', 'run', + const ingestRun = await run('pnpm', ['exec', 'ktx', 'dev', 'ingest', 'run', '--project-dir', projectDir, '--connection-id', @@ -1189,17 +1189,17 @@ try { '--source-dir', sourceDir, ]); - assert.equal(ingestRun.code, 1, 'klo dev ingest run without an LLM provider must fail'); + assert.equal(ingestRun.code, 1, 'ktx dev ingest run without an LLM provider must fail'); assert.match( ingestRun.stderr, - /klo dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/, + /ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway, or an injected agentRunner/, ); - await access(join(projectDir, '.klo', 'db.sqlite')); - process.stdout.write('klo dev ingest provider guard verified\\n'); + await access(join(projectDir, '.ktx', 'db.sqlite')); + process.stdout.write('ktx dev ingest provider guard verified\\n'); await writeFile( - join(projectDir, 'klo.yaml'), + join(projectDir, 'ktx.yaml'), [ 'project: warehouse', 'connections:', @@ -1231,7 +1231,7 @@ try { const daemonPort = await getAvailablePort(); const semanticComputeUrl = 'http://127.0.0.1:' + daemonPort; - process.stdout.write('klo-daemon serve-http --host 127.0.0.1 --port ' + daemonPort + '\\n'); + process.stdout.write('ktx-daemon serve-http --host 127.0.0.1 --port ' + daemonPort + '\\n'); const daemon = await startSemanticDaemon(daemonPort); const lookerParser = createDaemonLookerTableIdentifierParser({ baseUrl: semanticComputeUrl }); const parsedLookerTables = await lookerParser.parse([ @@ -1241,13 +1241,13 @@ try { assert.equal(parsedLookerTables.orders.name, 'orders'); assert.equal(parsedLookerTables.orders.canonical_table, 'orders'); process.stdout.write('Looker daemon table identifier parser verified\\n'); - const client = new Client({ name: 'klo-artifact-smoke-client', version: '0.0.0' }); - process.stdout.write('klo serve --mcp stdio --semantic-compute-url ' + semanticComputeUrl + ' --execute-queries\\n'); + const client = new Client({ name: 'ktx-artifact-smoke-client', version: '0.0.0' }); + process.stdout.write('ktx serve --mcp stdio --semantic-compute-url ' + semanticComputeUrl + ' --execute-queries\\n'); const transport = new StdioClientTransport({ command: 'pnpm', args: [ 'exec', - 'klo', + 'ktx', 'serve', '--mcp', 'stdio', '--project-dir', projectDir, @@ -1379,7 +1379,7 @@ try { } catch (error) { const stderr = Buffer.concat(mcpServerStderr).toString('utf8'); if (stderr) { - error.message += '\\nklo serve stderr:\\n' + stderr; + error.message += '\\nktx serve stderr:\\n' + stderr; } throw error; } finally { @@ -1434,31 +1434,31 @@ function requireStdout(label, result, pattern) { assert.match(result.stdout, pattern, label + ' stdout did not match ' + pattern); } -const root = await mkdtemp(join(tmpdir(), 'klo-packed-demo-smoke-')); +const root = await mkdtemp(join(tmpdir(), 'ktx-packed-demo-smoke-')); try { const projectDir = join(root, 'demo-project'); - const help = await run('pnpm', ['exec', 'klo', '--help']); - requireSuccess('klo --help', help); - requireStdout('klo --help', help, /Usage: klo/); - requireStdout('klo --help', help, /setup/); + const help = await run('pnpm', ['exec', 'ktx', '--help']); + requireSuccess('ktx --help', help); + requireStdout('ktx --help', help, /Usage: ktx/); + requireStdout('ktx --help', help, /setup/); const seeded = await run( 'pnpm', - ['exec', 'klo', 'setup', 'demo', '--project-dir', projectDir, '--no-input', '--plain'], + ['exec', 'ktx', 'setup', 'demo', '--project-dir', projectDir, '--no-input', '--plain'], ); - requireSuccess('klo setup demo seeded', seeded); - requireStdout('klo setup demo seeded', seeded, /Mode: seeded/); - requireStdout('klo setup demo seeded', seeded, /Source: packaged demo project/); - requireStdout('klo setup demo seeded', seeded, /LLM calls: none/); - requireStdout('klo setup demo seeded', seeded, /klo serve --mcp stdio/); + requireSuccess('ktx setup demo seeded', seeded); + requireStdout('ktx setup demo seeded', seeded, /Mode: seeded/); + requireStdout('ktx setup demo seeded', seeded, /Source: packaged demo project/); + requireStdout('ktx setup demo seeded', seeded, /LLM calls: none/); + requireStdout('ktx setup demo seeded', seeded, /ktx serve --mcp stdio/); assert.doesNotMatch(seeded.stdout, new RegExp(['--mode', 'deterministic'].join(' '))); - assert.doesNotMatch(seeded.stdout, /KLO memory flow/); - assert.equal(seeded.stderr, '', 'klo setup demo seeded wrote unexpected stderr'); + assert.doesNotMatch(seeded.stdout, /KTX memory flow/); + assert.equal(seeded.stderr, '', 'ktx setup demo seeded wrote unexpected stderr'); const demoWikiSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'wiki', 'search', @@ -1469,18 +1469,18 @@ try { '--project-dir', projectDir, ]); - requireSuccess('klo seeded demo agent wiki search', demoWikiSearch); + requireSuccess('ktx seeded demo agent wiki search', demoWikiSearch); const demoWikiSearchJson = JSON.parse(demoWikiSearch.stdout); assert.ok(demoWikiSearchJson.totalFound > 0, 'seeded demo wiki search should find results'); assert.ok( demoWikiSearchJson.results.some((result) => Array.isArray(result.matchReasons) && result.matchReasons.length > 0), 'seeded demo wiki search should expose match reasons', ); - process.stdout.write('klo seeded demo agent wiki search verified\\n'); + process.stdout.write('ktx seeded demo agent wiki search verified\\n'); const demoSlSearch = await run('pnpm', [ 'exec', - 'klo', + 'ktx', 'agent', 'sl', 'list', @@ -1490,20 +1490,20 @@ try { '--project-dir', projectDir, ]); - requireSuccess('klo seeded demo agent sl search', demoSlSearch); + requireSuccess('ktx seeded demo agent sl search', demoSlSearch); const demoSlSearchJson = JSON.parse(demoSlSearch.stdout); assert.ok(demoSlSearchJson.totalSources > 0, 'seeded demo semantic-layer search should find sources'); assert.ok( demoSlSearchJson.sources.some((source) => Array.isArray(source.matchReasons) && source.matchReasons.length > 0), 'seeded demo semantic-layer search should expose match reasons', ); - process.stdout.write('klo seeded demo agent sl search verified\\n'); + process.stdout.write('ktx seeded demo agent sl search verified\\n'); - const doctor = await run('pnpm', ['exec', 'klo', 'dev', 'doctor', 'setup', '--no-input']); - assert.ok([0, 1].includes(doctor.code), 'klo dev doctor setup exit code must be 0 or 1'); - requireStdout('klo dev doctor setup', doctor, /KLO setup doctor/); - requireStdout('klo dev doctor setup', doctor, /Node 22\\+/); - assert.equal(doctor.stderr, '', 'klo dev doctor setup wrote unexpected stderr'); + const doctor = await run('pnpm', ['exec', 'ktx', 'dev', 'doctor', 'setup', '--no-input']); + assert.ok([0, 1].includes(doctor.code), 'ktx dev doctor setup exit code must be 0 or 1'); + requireStdout('ktx dev doctor setup', doctor, /KTX setup doctor/); + requireStdout('ktx dev doctor setup', doctor, /Node 22\\+/); + assert.equal(doctor.stderr, '', 'ktx dev doctor setup wrote unexpected stderr'); } finally { await rm(root, { recursive: true, force: true }); } @@ -1513,13 +1513,13 @@ try { export function pythonVerifySource() { return ` import importlib.metadata -import klo_daemon +import ktx_daemon import semantic_layer -assert importlib.metadata.version("klo-sl") == "0.1.0" -assert importlib.metadata.version("klo-daemon") == "0.1.0" +assert importlib.metadata.version("ktx-sl") == "0.1.0" +assert importlib.metadata.version("ktx-daemon") == "0.1.0" assert semantic_layer is not None -assert klo_daemon.PACKAGE_NAME == "klo-daemon" +assert ktx_daemon.PACKAGE_NAME == "ktx-daemon" `; } @@ -1580,7 +1580,7 @@ async function verifyNpmArtifacts(layout, tmpRoot) { cwd: projectDir, }); await runCommand('node', ['verify-npm.mjs'], { cwd: projectDir }); - await runCommand('pnpm', ['exec', 'klo', '--version'], { cwd: projectDir }); + await runCommand('pnpm', ['exec', 'ktx', '--version'], { cwd: projectDir }); await runCommand('node', ['verify-installed-cli.mjs'], { cwd: projectDir, env: npmSmokePythonEnv(projectDir), @@ -1618,7 +1618,7 @@ async function verifyPythonArtifacts(layout, tmpRoot) { cwd: projectDir, }); await runCommand(python, ['verify_python.py'], { cwd: projectDir }); - await runCommand(python, ['-m', 'klo_daemon', 'semantic-validate'], { + await runCommand(python, ['-m', 'ktx_daemon', 'semantic-validate'], { cwd: projectDir, input: `${JSON.stringify({ sources: [ordersSource], dialect: 'postgres' })}\n`, }); @@ -1627,7 +1627,7 @@ async function verifyPythonArtifacts(layout, tmpRoot) { async function verifyArtifacts(layout) { await verifyArtifactManifest(layout); - const tmpRoot = await mkdtemp(join(tmpdir(), 'klo-artifacts-')); + const tmpRoot = await mkdtemp(join(tmpdir(), 'ktx-artifacts-')); try { await verifyNpmArtifacts(layout, tmpRoot); await verifyPythonArtifacts(layout, tmpRoot); @@ -1639,7 +1639,7 @@ async function verifyArtifacts(layout) { async function verifyDemoArtifacts(layout) { await verifyArtifactManifest(layout); - const tmpRoot = await mkdtemp(join(tmpdir(), 'klo-demo-artifacts-')); + const tmpRoot = await mkdtemp(join(tmpdir(), 'ktx-demo-artifacts-')); try { await verifyNpmDemoArtifacts(layout, tmpRoot); } finally { diff --git a/scripts/package-artifacts.test.mjs b/scripts/package-artifacts.test.mjs index 58d308b6..4aec3c6e 100644 --- a/scripts/package-artifacts.test.mjs +++ b/scripts/package-artifacts.test.mjs @@ -30,29 +30,29 @@ async function writeJson(path, value) { } const CONNECTOR_PACKAGE_NAMES = [ - '@klo/connector-bigquery', - '@klo/connector-clickhouse', - '@klo/connector-mysql', - '@klo/connector-postgres', - '@klo/connector-posthog', - '@klo/connector-snowflake', - '@klo/connector-sqlite', - '@klo/connector-sqlserver', + '@ktx/connector-bigquery', + '@ktx/connector-clickhouse', + '@ktx/connector-mysql', + '@ktx/connector-postgres', + '@ktx/connector-posthog', + '@ktx/connector-snowflake', + '@ktx/connector-sqlite', + '@ktx/connector-sqlserver', ]; function packageRootForName(packageName) { - return `packages/${packageName.replace('@klo/', '')}`; + return `packages/${packageName.replace('@ktx/', '')}`; } function expectedNpmArtifactPath(packageName) { - return `npm/${packageName.replace('@klo/', 'klo-')}-0.0.0-private.tgz`; + return `npm/${packageName.replace('@ktx/', 'ktx-')}-0.0.0-private.tgz`; } async function writeReleaseMetadataInputs(root) { - const npmPackages = ['@klo/context', '@klo/llm', ...CONNECTOR_PACKAGE_NAMES, '@klo/cli']; + const npmPackages = ['@ktx/context', '@ktx/llm', ...CONNECTOR_PACKAGE_NAMES, '@ktx/cli']; for (const packageName of npmPackages) { - const packageRoot = packageName === '@klo/context' ? 'packages/context' : packageRootForName(packageName); + const packageRoot = packageName === '@ktx/context' ? 'packages/context' : packageRootForName(packageName); await mkdir(join(root, packageRoot), { recursive: true }); await writeJson(join(root, packageRoot, 'package.json'), { name: packageName, @@ -61,15 +61,15 @@ async function writeReleaseMetadataInputs(root) { }); } - await mkdir(join(root, 'python', 'klo-sl'), { recursive: true }); - await mkdir(join(root, 'python', 'klo-daemon'), { recursive: true }); + await mkdir(join(root, 'python', 'ktx-sl'), { recursive: true }); + await mkdir(join(root, 'python', 'ktx-daemon'), { recursive: true }); await writeFile( - join(root, 'python', 'klo-sl', 'pyproject.toml'), - ['[project]', 'name = "klo-sl"', 'version = "0.1.0"', ''].join('\n'), + join(root, 'python', 'ktx-sl', 'pyproject.toml'), + ['[project]', 'name = "ktx-sl"', 'version = "0.1.0"', ''].join('\n'), ); await writeFile( - join(root, 'python', 'klo-daemon', 'pyproject.toml'), - ['[project]', 'name = "klo-daemon"', 'version = "0.1.0"', ''].join('\n'), + join(root, 'python', 'ktx-daemon', 'pyproject.toml'), + ['[project]', 'name = "ktx-daemon"', 'version = "0.1.0"', ''].join('\n'), ); } @@ -82,10 +82,10 @@ async function writeUploadableArtifactFixtures(layout) { layout.npmTarballs[packageInfo.name], `${packageInfo.name}-tarball`, ]), - [join(layout.pythonDir, 'klo_sl-0.1.0-py3-none-any.whl'), 'klo-sl-wheel'], - [join(layout.pythonDir, 'klo_sl-0.1.0.tar.gz'), 'klo-sl-sdist'], - [join(layout.pythonDir, 'klo_daemon-0.1.0-py3-none-any.whl'), 'klo-daemon-wheel'], - [join(layout.pythonDir, 'klo_daemon-0.1.0.tar.gz'), 'klo-daemon-sdist'], + [join(layout.pythonDir, 'ktx_sl-0.1.0-py3-none-any.whl'), 'ktx-sl-wheel'], + [join(layout.pythonDir, 'ktx_sl-0.1.0.tar.gz'), 'ktx-sl-sdist'], + [join(layout.pythonDir, 'ktx_daemon-0.1.0-py3-none-any.whl'), 'ktx-daemon-wheel'], + [join(layout.pythonDir, 'ktx_daemon-0.1.0.tar.gz'), 'ktx-daemon-sdist'], ]); for (const [path, contents] of fileContents) { @@ -94,21 +94,21 @@ async function writeUploadableArtifactFixtures(layout) { } describe('packageArtifactLayout', () => { - it('uses stable artifact paths under klo/dist/artifacts', () => { - const layout = packageArtifactLayout('/repo/klo'); + it('uses stable artifact paths under ktx/dist/artifacts', () => { + const layout = packageArtifactLayout('/repo/ktx'); - assert.equal(layout.artifactDir, '/repo/klo/dist/artifacts'); - assert.equal(layout.npmDir, '/repo/klo/dist/artifacts/npm'); - assert.equal(layout.pythonDir, '/repo/klo/dist/artifacts/python'); - assert.equal(layout.contextTarball, '/repo/klo/dist/artifacts/npm/klo-context-0.0.0-private.tgz'); - assert.equal(layout.cliTarball, '/repo/klo/dist/artifacts/npm/klo-cli-0.0.0-private.tgz'); + assert.equal(layout.artifactDir, '/repo/ktx/dist/artifacts'); + assert.equal(layout.npmDir, '/repo/ktx/dist/artifacts/npm'); + assert.equal(layout.pythonDir, '/repo/ktx/dist/artifacts/python'); + assert.equal(layout.contextTarball, '/repo/ktx/dist/artifacts/npm/ktx-context-0.0.0-private.tgz'); + assert.equal(layout.cliTarball, '/repo/ktx/dist/artifacts/npm/ktx-cli-0.0.0-private.tgz'); assert.equal( - layout.connectorTarballs['@klo/connector-sqlite'], - '/repo/klo/dist/artifacts/npm/klo-connector-sqlite-0.0.0-private.tgz', + layout.connectorTarballs['@ktx/connector-sqlite'], + '/repo/ktx/dist/artifacts/npm/ktx-connector-sqlite-0.0.0-private.tgz', ); assert.equal( - layout.connectorTarballs['@klo/connector-postgres'], - '/repo/klo/dist/artifacts/npm/klo-connector-postgres-0.0.0-private.tgz', + layout.connectorTarballs['@ktx/connector-postgres'], + '/repo/ktx/dist/artifacts/npm/ktx-connector-postgres-0.0.0-private.tgz', ); assert.deepEqual( Object.keys(layout.npmTarballs), @@ -119,7 +119,7 @@ describe('packageArtifactLayout', () => { describe('buildArtifactCommands', () => { it('builds all TypeScript packages before packing npm artifacts and builds both Python packages', () => { - const layout = packageArtifactLayout('/repo/klo'); + const layout = packageArtifactLayout('/repo/ktx'); const commands = buildArtifactCommands(layout); assert.deepEqual( @@ -138,8 +138,8 @@ describe('buildArtifactCommands', () => { assert.deepEqual( commands.slice(NPM_ARTIFACT_PACKAGES.length * 2).map((command) => [command.command, command.args]), [ - ['uv', ['build', '--package', 'klo-sl', '--out-dir', '/repo/klo/dist/artifacts/python']], - ['uv', ['build', '--package', 'klo-daemon', '--out-dir', '/repo/klo/dist/artifacts/python']], + ['uv', ['build', '--package', 'ktx-sl', '--out-dir', '/repo/ktx/dist/artifacts/python']], + ['uv', ['build', '--package', 'ktx-daemon', '--out-dir', '/repo/ktx/dist/artifacts/python']], ], ); }); @@ -147,7 +147,7 @@ describe('buildArtifactCommands', () => { describe('packageReleaseMetadata', () => { it('reads package identities and versions from package manifests', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-metadata-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-metadata-test-')); try { await writeReleaseMetadataInputs(root); @@ -162,16 +162,16 @@ describe('packageReleaseMetadata', () => { })), { ecosystem: 'python', - packageName: 'klo-sl', - packageRoot: 'python/klo-sl', + packageName: 'ktx-sl', + packageRoot: 'python/ktx-sl', packageVersion: '0.1.0', private: false, releaseMode: 'ci-artifact-only', }, { ecosystem: 'python', - packageName: 'klo-daemon', - packageRoot: 'python/klo-daemon', + packageName: 'ktx-daemon', + packageRoot: 'python/ktx-daemon', packageVersion: '0.1.0', private: false, releaseMode: 'ci-artifact-only', @@ -185,18 +185,18 @@ describe('packageReleaseMetadata', () => { describe('findPythonArtifacts', () => { it('finds one wheel and one source distribution for each Python package', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-')); try { - await writeFile(join(root, 'klo_sl-0.1.0-py3-none-any.whl'), ''); - await writeFile(join(root, 'klo_sl-0.1.0.tar.gz'), ''); - await writeFile(join(root, 'klo_daemon-0.1.0-py3-none-any.whl'), ''); - await writeFile(join(root, 'klo_daemon-0.1.0.tar.gz'), ''); + await writeFile(join(root, 'ktx_sl-0.1.0-py3-none-any.whl'), ''); + await writeFile(join(root, 'ktx_sl-0.1.0.tar.gz'), ''); + await writeFile(join(root, 'ktx_daemon-0.1.0-py3-none-any.whl'), ''); + await writeFile(join(root, 'ktx_daemon-0.1.0.tar.gz'), ''); assert.deepEqual(await findPythonArtifacts(root), { - kloSlWheel: join(root, 'klo_sl-0.1.0-py3-none-any.whl'), - kloSlSdist: join(root, 'klo_sl-0.1.0.tar.gz'), - kloDaemonWheel: join(root, 'klo_daemon-0.1.0-py3-none-any.whl'), - kloDaemonSdist: join(root, 'klo_daemon-0.1.0.tar.gz'), + ktxSlWheel: join(root, 'ktx_sl-0.1.0-py3-none-any.whl'), + ktxSlSdist: join(root, 'ktx_sl-0.1.0.tar.gz'), + ktxDaemonWheel: join(root, 'ktx_daemon-0.1.0-py3-none-any.whl'), + ktxDaemonSdist: join(root, 'ktx_daemon-0.1.0.tar.gz'), }); } finally { await rm(root, { recursive: true, force: true }); @@ -204,9 +204,9 @@ describe('findPythonArtifacts', () => { }); it('throws when a required Python artifact is missing', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-')); try { - await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: klo-sl wheel/); + await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: ktx-sl wheel/); } finally { await rm(root, { recursive: true, force: true }); } @@ -215,7 +215,7 @@ describe('findPythonArtifacts', () => { describe('artifact manifest', () => { it('writes release metadata, source revision, checksums, and byte counts for every uploadable artifact', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-manifest-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-manifest-test-')); const layout = packageArtifactLayout(root); try { await writeReleaseMetadataInputs(root); @@ -245,16 +245,16 @@ describe('artifact manifest', () => { [ { ecosystem: 'python', - packageName: 'klo-sl', - packageRoot: 'python/klo-sl', + packageName: 'ktx-sl', + packageRoot: 'python/ktx-sl', packageVersion: '0.1.0', private: false, releaseMode: 'ci-artifact-only', }, { ecosystem: 'python', - packageName: 'klo-daemon', - packageRoot: 'python/klo-daemon', + packageName: 'ktx-daemon', + packageRoot: 'python/ktx-daemon', packageVersion: '0.1.0', private: false, releaseMode: 'ci-artifact-only', @@ -294,38 +294,38 @@ describe('artifact manifest', () => { { artifactKind: 'wheel', ecosystem: 'python', - packageName: 'klo-daemon', + packageName: 'ktx-daemon', packageVersion: '0.1.0', - path: 'python/klo_daemon-0.1.0-py3-none-any.whl', + path: 'python/ktx_daemon-0.1.0-py3-none-any.whl', }, { artifactKind: 'sdist', ecosystem: 'python', - packageName: 'klo-daemon', + packageName: 'ktx-daemon', packageVersion: '0.1.0', - path: 'python/klo_daemon-0.1.0.tar.gz', + path: 'python/ktx_daemon-0.1.0.tar.gz', }, { artifactKind: 'wheel', ecosystem: 'python', - packageName: 'klo-sl', + packageName: 'ktx-sl', packageVersion: '0.1.0', - path: 'python/klo_sl-0.1.0-py3-none-any.whl', + path: 'python/ktx_sl-0.1.0-py3-none-any.whl', }, { artifactKind: 'sdist', ecosystem: 'python', - packageName: 'klo-sl', + packageName: 'ktx-sl', packageVersion: '0.1.0', - path: 'python/klo_sl-0.1.0.tar.gz', + path: 'python/ktx_sl-0.1.0.tar.gz', }, ], ); - const sqliteEntry = manifest.files.find((file) => file.path === 'npm/klo-connector-sqlite-0.0.0-private.tgz'); + const sqliteEntry = manifest.files.find((file) => file.path === 'npm/ktx-connector-sqlite-0.0.0-private.tgz'); assert.ok(sqliteEntry); - assert.equal(sqliteEntry.bytes, Buffer.byteLength('@klo/connector-sqlite-tarball')); - assert.equal(sqliteEntry.sha256, createHash('sha256').update('@klo/connector-sqlite-tarball').digest('hex')); + assert.equal(sqliteEntry.bytes, Buffer.byteLength('@ktx/connector-sqlite-tarball')); + assert.equal(sqliteEntry.sha256, createHash('sha256').update('@ktx/connector-sqlite-tarball').digest('hex')); const writtenManifest = JSON.parse(await readFile(artifactManifestPath(layout), 'utf-8')); assert.deepEqual(writtenManifest, manifest); @@ -337,7 +337,7 @@ describe('artifact manifest', () => { describe('verifyArtifactManifest', () => { it('accepts a schema version 2 manifest that matches the artifact directory', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-verify-manifest-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-verify-manifest-test-')); const layout = packageArtifactLayout(root); try { await writeReleaseMetadataInputs(root); @@ -359,7 +359,7 @@ describe('verifyArtifactManifest', () => { }); it('rejects a manifest when a file checksum has drifted', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-checksum-drift-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-checksum-drift-test-')); const layout = packageArtifactLayout(root); try { await writeReleaseMetadataInputs(root); @@ -379,7 +379,7 @@ describe('verifyArtifactManifest', () => { }); it('rejects a manifest with an unsafe artifact path', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-path-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-path-test-')); const layout = packageArtifactLayout(root); try { await writeReleaseMetadataInputs(root); @@ -397,7 +397,7 @@ describe('verifyArtifactManifest', () => { }); it('rejects a manifest from the wrong source revision when one is required', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-artifacts-revision-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-revision-test-')); const layout = packageArtifactLayout(root); try { await writeReleaseMetadataInputs(root); @@ -422,10 +422,10 @@ describe('verifyArtifactManifest', () => { describe('pythonArtifactInstallArgs', () => { it('installs the built Python wheels by artifact path', () => { const args = pythonArtifactInstallArgs('/tmp/smoke/.venv/bin/python', { - kloSlWheel: '/repo/klo/dist/artifacts/python/klo_sl-0.1.0-py3-none-any.whl', - kloSlSdist: '/repo/klo/dist/artifacts/python/klo_sl-0.1.0.tar.gz', - kloDaemonWheel: '/repo/klo/dist/artifacts/python/klo_daemon-0.1.0-py3-none-any.whl', - kloDaemonSdist: '/repo/klo/dist/artifacts/python/klo_daemon-0.1.0.tar.gz', + ktxSlWheel: '/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0-py3-none-any.whl', + ktxSlSdist: '/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0.tar.gz', + ktxDaemonWheel: '/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0-py3-none-any.whl', + ktxDaemonSdist: '/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0.tar.gz', }); assert.deepEqual(args, [ @@ -433,26 +433,26 @@ describe('pythonArtifactInstallArgs', () => { 'install', '--python', '/tmp/smoke/.venv/bin/python', - '/repo/klo/dist/artifacts/python/klo_sl-0.1.0-py3-none-any.whl', - '/repo/klo/dist/artifacts/python/klo_daemon-0.1.0-py3-none-any.whl', + '/repo/ktx/dist/artifacts/python/ktx_sl-0.1.0-py3-none-any.whl', + '/repo/ktx/dist/artifacts/python/ktx_daemon-0.1.0-py3-none-any.whl', ]); - assert.equal(args.includes('klo-daemon'), false); + assert.equal(args.includes('ktx-daemon'), false); assert.equal(args.includes('--find-links'), false); }); }); describe('npmSmokePythonEnv', () => { it('prepends the npm smoke virtualenv bin directory to PATH', () => { - const env = npmSmokePythonEnv('/tmp/klo-npm-smoke', { PATH: '/usr/bin' }); + const env = npmSmokePythonEnv('/tmp/ktx-npm-smoke', { PATH: '/usr/bin' }); - assert.match(env.PATH, /^\/tmp\/klo-npm-smoke\/\.venv\/(bin|Scripts)/); + assert.match(env.PATH, /^\/tmp\/ktx-npm-smoke\/\.venv\/(bin|Scripts)/); assert.match(env.PATH, /\/usr\/bin$/); }); }); describe('verification snippets', () => { it('pins smoke dependencies and connector packages to clean-install-safe artifacts', () => { - const layout = packageArtifactLayout('/repo/klo'); + const layout = packageArtifactLayout('/repo/ktx'); const packageJson = npmSmokePackageJson(layout); for (const packageInfo of NPM_ARTIFACT_PACKAGES) { @@ -473,10 +473,10 @@ describe('verification snippets', () => { assert.equal(packageJson.scripts['artifacts:verify-manifest'], 'node scripts/package-artifacts.mjs verify-manifest'); }); - it('verifies installed dbt extraction exports from @klo/context/ingest', () => { + it('verifies installed dbt extraction exports from @ktx/context/ingest', () => { const source = npmVerifySource(); - assert.match(source, /const ingest = await import\('@klo\/context\/ingest'\);/); + assert.match(source, /const ingest = await import\('@ktx\/context\/ingest'\);/); assert.match(source, /const dbtExtractionExports = \[/); assert.match(source, /throw new Error\('Missing dbt extraction export: ' \+ exportName\);/); @@ -498,26 +498,26 @@ describe('verification snippets', () => { it('asserts the public npm and connector entry points that clean installs must expose', () => { const source = npmVerifySource(); - assert.match(source, /@klo\/context/); - assert.match(source, /@klo\/context\/project/); - assert.match(source, /@klo\/context\/mcp/); - assert.match(source, /@klo\/context\/memory/); - assert.match(source, /@klo\/context\/daemon/); - assert.match(source, /@klo\/cli/); - assert.match(source, /@klo\/llm/); - assert.match(source, /createKloLlmProvider/); - assert.match(source, /KloMessageBuilder/); - assert.match(source, /createKloEmbeddingProvider/); + assert.match(source, /@ktx\/context/); + assert.match(source, /@ktx\/context\/project/); + assert.match(source, /@ktx\/context\/mcp/); + assert.match(source, /@ktx\/context\/memory/); + assert.match(source, /@ktx\/context\/daemon/); + assert.match(source, /@ktx\/cli/); + assert.match(source, /@ktx\/llm/); + assert.match(source, /createKtxLlmProvider/); + assert.match(source, /KtxMessageBuilder/); + assert.match(source, /createKtxEmbeddingProvider/); assert.doesNotMatch(source, /createGatewayLlmProvider/); assert.match(source, /createLocalProjectMemoryCapture/); for (const packageName of CONNECTOR_PACKAGE_NAMES) { assert.match(source, new RegExp(packageName.replace('/', '\\/'))); } - assert.match(source, /KloSqliteScanConnector/); - assert.match(source, /KloPostgresScanConnector/); - assert.match(source, /KloBigQueryScanConnector/); - assert.match(source, /KloSnowflakeScanConnector/); - assert.match(source, /KloPostHogScanConnector/); + assert.match(source, /KtxSqliteScanConnector/); + assert.match(source, /KtxPostgresScanConnector/); + assert.match(source, /KtxBigQueryScanConnector/); + assert.match(source, /KtxSnowflakeScanConnector/); + assert.match(source, /KtxPostHogScanConnector/); }); it('asserts installed hybrid search exports and CLI smoke coverage', () => { @@ -525,19 +525,19 @@ describe('verification snippets', () => { const runtimeSource = npmRuntimeSmokeSource(); const demoSource = npmDemoSmokeSource(); - assert.match(verifySource, /const search = await import\('@klo\/context\/search'\);/); + assert.match(verifySource, /const search = await import\('@ktx\/context\/search'\);/); assert.match(verifySource, /HybridSearchCore/); assert.match(verifySource, /assertSearchBackendConformanceCase/); assert.match(verifySource, /assertSearchBackendCapabilities/); - assert.match(runtimeSource, /klo agent wiki search hybrid metadata verified/); - assert.match(runtimeSource, /klo agent sl list hybrid metadata verified/); + assert.match(runtimeSource, /ktx agent wiki search hybrid metadata verified/); + assert.match(runtimeSource, /ktx agent sl list hybrid metadata verified/); assert.match(runtimeSource, /agent_sl_search_missing_project/); assert.match(runtimeSource, /agent_sl_search_no_connections/); assert.match(runtimeSource, /agent_sl_search_no_indexed_sources/); - assert.match(demoSource, /klo seeded demo agent wiki search verified/); - assert.match(demoSource, /klo seeded demo agent sl search verified/); + assert.match(demoSource, /ktx seeded demo agent wiki search verified/); + assert.match(demoSource, /ktx seeded demo agent sl search verified/); }); it('runs installed CLI commands and MCP through an installed daemon HTTP server', () => { @@ -552,7 +552,7 @@ describe('verification snippets', () => { assert.match(source, /startSemanticDaemon/); assert.match(source, /waitForHttpHealth/); assert.match(source, /stopSemanticDaemon/); - assert.match(source, /'klo-daemon'/); + assert.match(source, /'ktx-daemon'/); assert.match(source, /'serve-http'/); assert.match(source, /'--host'/); assert.match(source, /'127\.0\.0\.1'/); @@ -564,12 +564,12 @@ describe('verification snippets', () => { assert.match(source, /Looker daemon table identifier parser verified/); assert.match(source, /Looker local runtime store verified/); assert.match(source, /semanticComputeUrl/); - assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'setup'/); + assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'setup'/); assert.match(source, /knowledge', 'global', 'revenue\.md'/); - assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'wiki',\s*'search'/); + assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'wiki',\s*'search'/); assert.match(source, /semantic-layer', 'warehouse', 'orders\.yaml'/); - assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'sl',\s*'list'/); - assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'agent',\s*'sl',\s*'query'/); + assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'sl',\s*'list'/); + assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'agent',\s*'sl',\s*'query'/); assert.match(source, /orders\.order_count/); assert.match(source, /sqlite3/); assert.match(source, /driver: sqlite/); @@ -582,12 +582,12 @@ describe('verification snippets', () => { assert.match(source, /slQueryResult\.plan\.execution\.driver, 'sqlite'/); assert.match(source, /"mode": "compile_only"/); assert.match(source, /"mode": "executed"/); - assert.match(source, /klo agent sl query sqlite execute/); - assert.match(source, /run\('pnpm', \[\s*'exec',\s*'klo',\s*'dev',\s*'scan',\s*'warehouse'/); + assert.match(source, /ktx agent sl query sqlite execute/); + assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'dev',\s*'scan',\s*'warehouse'/); assert.match(source, /'--mode',\s*'enriched'/); assert.doesNotMatch(source, /'--enrich'/); - assert.match(source, /klo scan structural verified/); - assert.match(source, /klo scan enriched verified/); + assert.match(source, /ktx scan structural verified/); + assert.match(source, /ktx scan enriched verified/); assert.match(source, /scanReportJson\.artifactPaths\.manifestShards/); assert.match(source, /scanReportJson\.artifactPaths\.enrichmentArtifacts/); assert.match(source, /enrichment:/); @@ -596,12 +596,12 @@ describe('verification snippets', () => { assert.match(source, /models:/); assert.match(source, /default: smoke\/provider/); assert.match(source, /api_key: env:AI_GATEWAY_API_KEY/); - assert.match(source, /run\('pnpm', \['exec', 'klo', 'dev', 'ingest', 'run'/); + assert.match(source, /run\('pnpm', \['exec', 'ktx', 'dev', 'ingest', 'run'/); assert.match(source, /'serve', '--mcp', 'stdio'/); assert.doesNotMatch(source, /'--semantic-compute',\n\s*'--execute-queries'/); assert.match(source, /'--memory-capture', '--memory-model', 'smoke\/provider'/); assert.match(source, /mcpServerStderr/); - assert.match(source, /klo serve stderr/); + assert.match(source, /ktx serve stderr/); assert.match(source, /sl_validate/); assert.match(source, /sl_query/); assert.match(source, /memory_capture/); @@ -614,26 +614,26 @@ describe('verification snippets', () => { assert.match(source, /scan_read_artifact/); assert.match(source, /mcpScanArtifacts\.artifacts\.find/); assert.match(source, /AI_GATEWAY_API_KEY/); - assert.match(source, /access\(join\(projectDir, '\.klo', 'db\.sqlite'\)\)/); + assert.match(source, /access\(join\(projectDir, '\.ktx', 'db\.sqlite'\)\)/); assert.match(source, /SQLite knowledge index/); - assert.match(source, /klo dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/); - assert.match(source, /klo dev ingest provider guard verified/); + assert.match(source, /ktx dev ingest run requires llm\\.provider\\.backend: anthropic, vertex, or gateway/); + assert.match(source, /ktx dev ingest provider guard verified/); }); describe('npmDemoSmokeSource', () => { it('exercises the public packed-demo first-run contract', () => { const source = npmDemoSmokeSource(); - assert.match(source, /pnpm', \['exec', 'klo', '--help'\]/); + assert.match(source, /pnpm', \['exec', 'ktx', '--help'\]/); assert.match(source, /'demo', '--project-dir', projectDir, '--no-input', '--plain'/); assert.match(source, /Mode: seeded/); assert.match(source, /Source: packaged demo project/); assert.match(source, /LLM calls: none/); - assert.match(source, /klo serve --mcp stdio/); + assert.match(source, /ktx serve --mcp stdio/); assert.doesNotMatch(source, new RegExp(["'demo'", "'--mode'", "'deterministic'"].join(', '))); assert.match(source, /'dev', 'doctor', 'setup', '--no-input'/); assert.match(source, /'--plain'/); - assert.match(source, /klo setup demo seeded wrote unexpected stderr/); + assert.match(source, /ktx setup demo seeded wrote unexpected stderr/); }); }); @@ -649,7 +649,7 @@ describe('verification snippets', () => { const source = pythonVerifySource(); assert.match(source, /semantic_layer/); - assert.match(source, /klo_daemon/); + assert.match(source, /ktx_daemon/); assert.match(source, /importlib.metadata/); }); }); diff --git a/scripts/precommit-check.mjs b/scripts/precommit-check.mjs index feedf28f..d112752d 100644 --- a/scripts/precommit-check.mjs +++ b/scripts/precommit-check.mjs @@ -5,8 +5,8 @@ import { dirname, join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptPath = fileURLToPath(import.meta.url); -const kloRoot = dirname(dirname(scriptPath)); -const repoRoot = dirname(kloRoot); +const ktxRoot = dirname(dirname(scriptPath)); +const repoRoot = dirname(ktxRoot); const packageNameByDir = new Map( [ @@ -22,7 +22,7 @@ const packageNameByDir = new Map( 'context', 'llm', ].map((packageDir) => { - const manifestPath = join(kloRoot, 'packages', packageDir, 'package.json'); + const manifestPath = join(ktxRoot, 'packages', packageDir, 'package.json'); const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); return [packageDir, manifest.name]; }), @@ -31,8 +31,8 @@ const packageNameByDir = new Map( const packageCodePattern = /\.(?:ts|tsx|js|jsx|json)$/; const scriptPattern = /\.(?:mjs|js|json)$/; const pythonPackageTests = new Map([ - ['klo-sl', 'python/klo-sl/tests'], - ['klo-daemon', 'python/klo-daemon/tests'], + ['ktx-sl', 'python/ktx-sl/tests'], + ['ktx-daemon', 'python/ktx-daemon/tests'], ]); function normalizeFilePath(filePath) { @@ -57,7 +57,7 @@ function maybeScriptTest(scriptFile) { } const testFile = scriptFile.replace(/\.mjs$/, '.test.mjs'); - return existsSync(join(kloRoot, testFile)) ? testFile : null; + return existsSync(join(ktxRoot, testFile)) ? testFile : null; } export function planChecks(files) { @@ -71,14 +71,14 @@ export function planChecks(files) { for (const rawFile of files) { const file = normalizeFilePath(rawFile); - if (!file.startsWith('klo/')) { + if (!file.startsWith('ktx/')) { continue; } - const kloFile = file.slice('klo/'.length); + const ktxFile = file.slice('ktx/'.length); - if (kloFile.startsWith('packages/')) { - const [, packageDir, ...rest] = kloFile.split('/'); + if (ktxFile.startsWith('packages/')) { + const [, packageDir, ...rest] = ktxFile.split('/'); const packageName = packageNameByDir.get(packageDir); const packageFile = rest.join('/'); @@ -90,8 +90,8 @@ export function planChecks(files) { continue; } - if (kloFile.startsWith('scripts/') && scriptPattern.test(kloFile)) { - const testFile = maybeScriptTest(kloFile); + if (ktxFile.startsWith('scripts/') && scriptPattern.test(ktxFile)) { + const testFile = maybeScriptTest(ktxFile); if (testFile) { stablePush(commands, `script-test:${testFile}`, 'node', ['--test', testFile]); @@ -100,8 +100,8 @@ export function planChecks(files) { continue; } - if (kloFile.startsWith('python/')) { - const [, packageDir] = kloFile.split('/'); + if (ktxFile.startsWith('python/')) { + const [, packageDir] = ktxFile.split('/'); if (pythonPackageTests.has(packageDir)) { pythonPackages.add(packageDir); @@ -112,7 +112,7 @@ export function planChecks(files) { if ( ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'release-policy.json', 'tsconfig.base.json'].includes( - kloFile, + ktxFile, ) ) { runBoundaryCheck = true; @@ -120,7 +120,7 @@ export function planChecks(files) { continue; } - if (['pyproject.toml', 'uv.lock', 'uv.toml'].includes(kloFile)) { + if (['pyproject.toml', 'uv.lock', 'uv.toml'].includes(ktxFile)) { runAllPythonTests = true; } } @@ -164,7 +164,7 @@ export function runChecks(files) { const commands = planChecks(files); if (commands.length === 0) { - console.log('No KLO package checks needed for these files.'); + console.log('No KTX package checks needed for these files.'); return 0; } @@ -172,7 +172,7 @@ export function runChecks(files) { printCommand(command); const result = spawnSync(command.cmd, command.args, { - cwd: kloRoot, + cwd: ktxRoot, stdio: 'inherit', env: process.env, }); @@ -190,6 +190,6 @@ export function runChecks(files) { return 0; } -if (process.argv[1] && relative(repoRoot, process.argv[1]).split(sep).join('/') === 'klo/scripts/precommit-check.mjs') { +if (process.argv[1] && relative(repoRoot, process.argv[1]).split(sep).join('/') === 'ktx/scripts/precommit-check.mjs') { process.exitCode = runChecks(process.argv.slice(2)); } diff --git a/scripts/precommit-check.test.mjs b/scripts/precommit-check.test.mjs index ee5366b2..94f71e09 100644 --- a/scripts/precommit-check.test.mjs +++ b/scripts/precommit-check.test.mjs @@ -8,26 +8,26 @@ function commandKeys(files) { } describe('precommit-check', () => { - it('skips files outside klo', () => { + it('skips files outside ktx', () => { assert.deepEqual(commandKeys(['server/src/app.ts']), []); }); it('runs only the touched package checks for package code', () => { - assert.deepEqual(commandKeys(['klo/packages/cli/src/index.ts']), [ + assert.deepEqual(commandKeys(['ktx/packages/cli/src/index.ts']), [ 'boundary-check', - 'type-check:@klo/cli', - 'build:@klo/cli', - 'test:@klo/cli', + 'type-check:@ktx/cli', + 'build:@ktx/cli', + 'test:@ktx/cli', ]); }); it('runs the matching script test when a script changes', () => { - assert.deepEqual(commandKeys(['klo/scripts/check-boundaries.mjs']), [ + assert.deepEqual(commandKeys(['ktx/scripts/check-boundaries.mjs']), [ 'script-test:scripts/check-boundaries.test.mjs', ]); }); it('runs the touched python package tests', () => { - assert.deepEqual(commandKeys(['klo/python/klo-sl/semantic_layer/parser.py']), ['pytest:klo-sl']); + assert.deepEqual(commandKeys(['ktx/python/ktx-sl/semantic_layer/parser.py']), ['pytest:ktx-sl']); }); }); diff --git a/scripts/prepare-cli-bin.mjs b/scripts/prepare-cli-bin.mjs index 99ffceac..e9b106b0 100644 --- a/scripts/prepare-cli-bin.mjs +++ b/scripts/prepare-cli-bin.mjs @@ -5,11 +5,11 @@ import { access, chmod } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -export function kloRootDir() { +export function ktxRootDir() { return resolve(dirname(fileURLToPath(import.meta.url)), '..'); } -export function cliBinPath(rootDir = kloRootDir()) { +export function cliBinPath(rootDir = ktxRootDir()) { return resolve(rootDir, 'packages', 'cli', 'dist', 'bin.js'); } @@ -22,7 +22,7 @@ async function canExecute(path) { } } -export async function ensureCliBinExecutable(rootDir = kloRootDir()) { +export async function ensureCliBinExecutable(rootDir = ktxRootDir()) { const binPath = cliBinPath(rootDir); await access(binPath, constants.R_OK); @@ -36,7 +36,7 @@ export async function ensureCliBinExecutable(rootDir = kloRootDir()) { if (import.meta.url === pathToFileURL(process.argv[1]).href) { try { const binPath = await ensureCliBinExecutable(); - process.stdout.write(`Prepared KLO CLI bin: ${binPath}\n`); + process.stdout.write(`Prepared KTX CLI bin: ${binPath}\n`); } catch (error) { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; diff --git a/scripts/published-package-smoke-config.mjs b/scripts/published-package-smoke-config.mjs index 148dd0e4..71bcd862 100644 --- a/scripts/published-package-smoke-config.mjs +++ b/scripts/published-package-smoke-config.mjs @@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises'; export const DEFAULT_VERSION_TAG = 'latest'; export const NO_PACKAGE_REASON = - 'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.'; + 'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.'; function optionalTrimmedString(value) { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null; @@ -53,7 +53,7 @@ export function readPublishedPackageSmokeConfig(env = process.env, args = proces const requireConfig = args.includes('--require-config'); const policy = normalizePolicyConfig(policyConfig); - const envPackageName = optionalTrimmedString(env.KLO_PUBLISHED_KLO_PACKAGE); + const envPackageName = optionalTrimmedString(env.KTX_PUBLISHED_KTX_PACKAGE); const packageName = envPackageName ?? policy.packageName; if (!packageName) { @@ -68,24 +68,24 @@ export function readPublishedPackageSmokeConfig(env = process.env, args = proces assertSafePackageName( packageName, configSource === 'environment' - ? 'KLO_PUBLISHED_KLO_PACKAGE' + ? 'KTX_PUBLISHED_KTX_PACKAGE' : 'release-policy.json publishedPackageSmoke.packageName', ); - const packageVersion = optionalTrimmedString(env.KLO_PUBLISHED_KLO_VERSION) ?? policy.version; + const packageVersion = optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION) ?? policy.version; assertSafeVersionTag( packageVersion, - optionalTrimmedString(env.KLO_PUBLISHED_KLO_VERSION) - ? 'KLO_PUBLISHED_KLO_VERSION' + optionalTrimmedString(env.KTX_PUBLISHED_KTX_VERSION) + ? 'KTX_PUBLISHED_KTX_VERSION' : 'release-policy.json publishedPackageSmoke.version', ); - const registry = optionalTrimmedString(env.KLO_PUBLISHED_KLO_REGISTRY) ?? policy.registry; + const registry = optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY) ?? policy.registry; if (registry) { assertHttpRegistry( registry, - optionalTrimmedString(env.KLO_PUBLISHED_KLO_REGISTRY) - ? 'KLO_PUBLISHED_KLO_REGISTRY' + optionalTrimmedString(env.KTX_PUBLISHED_KTX_REGISTRY) + ? 'KTX_PUBLISHED_KTX_REGISTRY' : 'release-policy.json publishedPackageSmoke.registry', ); } diff --git a/scripts/published-package-smoke.mjs b/scripts/published-package-smoke.mjs index e9d18f0d..d97cdda0 100644 --- a/scripts/published-package-smoke.mjs +++ b/scripts/published-package-smoke.mjs @@ -94,19 +94,19 @@ function assertMissingProjectReadiness(result, emptyProjectDir) { ok: false, error: { code: 'agent_sl_search_missing_project', - message: `Semantic-layer search needs an initialized KLO project at ${emptyProjectDir}.`, + message: `Semantic-layer search needs an initialized KTX project at ${emptyProjectDir}.`, nextSteps: [ - 'klo demo', - `klo setup --project-dir ${emptyProjectDir}`, - 'klo ingest ', - `klo agent sl list --json --query "revenue" --project-dir ${emptyProjectDir}`, + 'ktx demo', + `ktx setup --project-dir ${emptyProjectDir}`, + 'ktx ingest ', + `ktx agent sl list --json --query "revenue" --project-dir ${emptyProjectDir}`, ], }, }); } export async function runPublishedPackageSmoke(config) { - const root = await mkdtemp(join(tmpdir(), 'klo-published-package-smoke-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-published-package-smoke-')); try { const projectDir = join(root, 'demo-project'); const emptyProjectDir = join(root, 'empty-project'); @@ -147,7 +147,7 @@ async function main() { if (config.requireConfig) { throw new Error(config.reason); } - process.stdout.write(`Published KLO package smoke skipped: ${config.reason}\n`); + process.stdout.write(`Published KTX package smoke skipped: ${config.reason}\n`); return; } diff --git a/scripts/published-package-smoke.test.mjs b/scripts/published-package-smoke.test.mjs index cf33524e..66fa6670 100644 --- a/scripts/published-package-smoke.test.mjs +++ b/scripts/published-package-smoke.test.mjs @@ -15,7 +15,7 @@ describe('published package smoke config', () => { enabled: false, requireConfig: false, reason: - 'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.', + 'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.', }); }); @@ -24,7 +24,7 @@ describe('published package smoke config', () => { enabled: false, requireConfig: true, reason: - 'Set KLO_PUBLISHED_KLO_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.', + 'Set KTX_PUBLISHED_KTX_PACKAGE or release-policy.json publishedPackageSmoke.packageName to the published npm package name after the release decision.', }); }); @@ -32,9 +32,9 @@ describe('published package smoke config', () => { assert.deepEqual( readPublishedPackageSmokeConfig( { - KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public', - KLO_PUBLISHED_KLO_VERSION: 'latest', - KLO_PUBLISHED_KLO_REGISTRY: 'https://registry.npmjs.org/', + KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public', + KTX_PUBLISHED_KTX_VERSION: 'latest', + KTX_PUBLISHED_KTX_REGISTRY: 'https://registry.npmjs.org/', }, [], ), @@ -42,7 +42,7 @@ describe('published package smoke config', () => { enabled: true, requireConfig: false, configSource: 'environment', - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', packageVersion: 'latest', registry: 'https://registry.npmjs.org/', }, @@ -55,7 +55,7 @@ describe('published package smoke config', () => { {}, [], { - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -64,7 +64,7 @@ describe('published package smoke config', () => { enabled: true, requireConfig: false, configSource: 'release-policy', - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', packageVersion: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -75,12 +75,12 @@ describe('published package smoke config', () => { assert.deepEqual( readPublishedPackageSmokeConfig( { - KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-from-env', - KLO_PUBLISHED_KLO_VERSION: 'latest', + KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-from-env', + KTX_PUBLISHED_KTX_VERSION: 'latest', }, [], { - packageName: '@klo/cli-from-policy', + packageName: '@ktx/cli-from-policy', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -89,7 +89,7 @@ describe('published package smoke config', () => { enabled: true, requireConfig: false, configSource: 'environment', - packageName: '@klo/cli-from-env', + packageName: '@ktx/cli-from-env', packageVersion: 'latest', registry: 'https://registry.npmjs.org/', }, @@ -98,12 +98,12 @@ describe('published package smoke config', () => { it('rejects package names that would be unsafe as npx package specs', () => { assert.throws( - () => readPublishedPackageSmokeConfig({ KLO_PUBLISHED_KLO_PACKAGE: '--package=@evil/pkg' }, []), - /Invalid KLO_PUBLISHED_KLO_PACKAGE/, + () => readPublishedPackageSmokeConfig({ KTX_PUBLISHED_KTX_PACKAGE: '--package=@evil/pkg' }, []), + /Invalid KTX_PUBLISHED_KTX_PACKAGE/, ); assert.throws( - () => readPublishedPackageSmokeConfig({ KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli public' }, []), - /Invalid KLO_PUBLISHED_KLO_PACKAGE/, + () => readPublishedPackageSmokeConfig({ KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli public' }, []), + /Invalid KTX_PUBLISHED_KTX_PACKAGE/, ); assert.throws( () => @@ -111,7 +111,7 @@ describe('published package smoke config', () => { {}, [], { - packageName: '@klo/cli public', + packageName: '@ktx/cli public', version: 'latest', registry: null, }, @@ -125,23 +125,23 @@ describe('published package smoke config', () => { () => readPublishedPackageSmokeConfig( { - KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public', - KLO_PUBLISHED_KLO_VERSION: '--tag latest', + KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public', + KTX_PUBLISHED_KTX_VERSION: '--tag latest', }, [], ), - /Invalid KLO_PUBLISHED_KLO_VERSION/, + /Invalid KTX_PUBLISHED_KTX_VERSION/, ); assert.throws( () => readPublishedPackageSmokeConfig( { - KLO_PUBLISHED_KLO_PACKAGE: '@klo/cli-public', - KLO_PUBLISHED_KLO_REGISTRY: 'file:///tmp/npm', + KTX_PUBLISHED_KTX_PACKAGE: '@ktx/cli-public', + KTX_PUBLISHED_KTX_REGISTRY: 'file:///tmp/npm', }, [], ), - /KLO_PUBLISHED_KLO_REGISTRY must be an http\(s\) URL/, + /KTX_PUBLISHED_KTX_REGISTRY must be an http\(s\) URL/, ); }); }); @@ -150,30 +150,30 @@ describe('published package smoke command construction', () => { const config = { enabled: true, requireConfig: false, - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', packageVersion: 'latest', registry: 'https://registry.npmjs.org/', }; it('builds the npx package spec from package name and version tag', () => { - assert.equal(publishedPackageSpec(config), '@klo/cli-public@latest'); + assert.equal(publishedPackageSpec(config), '@ktx/cli-public@latest'); }); it('builds npx commands with a registry env patch instead of shell interpolation', () => { assert.deepEqual(buildPublishedPackageNpxCommand(config, ['--version']), { label: 'published package command', command: 'npx', - args: ['--yes', '@klo/cli-public@latest', '--version'], + args: ['--yes', '@ktx/cli-public@latest', '--version'], env: { npm_config_registry: 'https://registry.npmjs.org/' }, }); }); it('builds the full hybrid-search smoke command list', () => { - assert.deepEqual(buildPublishedPackageSmokeCommands(config, '/tmp/klo-smoke/demo', '/tmp/klo-smoke/empty'), [ + assert.deepEqual(buildPublishedPackageSmokeCommands(config, '/tmp/ktx-smoke/demo', '/tmp/ktx-smoke/empty'), [ { label: 'published package version', command: 'npx', - args: ['--yes', '@klo/cli-public@latest', '--version'], + args: ['--yes', '@ktx/cli-public@latest', '--version'], env: { npm_config_registry: 'https://registry.npmjs.org/' }, }, { @@ -181,10 +181,10 @@ describe('published package smoke command construction', () => { command: 'npx', args: [ '--yes', - '@klo/cli-public@latest', + '@ktx/cli-public@latest', 'demo', '--project-dir', - '/tmp/klo-smoke/demo', + '/tmp/ktx-smoke/demo', '--no-input', '--plain', ], @@ -195,7 +195,7 @@ describe('published package smoke command construction', () => { command: 'npx', args: [ '--yes', - '@klo/cli-public@latest', + '@ktx/cli-public@latest', 'agent', 'wiki', 'search', @@ -204,7 +204,7 @@ describe('published package smoke command construction', () => { '--limit', '5', '--project-dir', - '/tmp/klo-smoke/demo', + '/tmp/ktx-smoke/demo', ], env: { npm_config_registry: 'https://registry.npmjs.org/' }, }, @@ -213,7 +213,7 @@ describe('published package smoke command construction', () => { command: 'npx', args: [ '--yes', - '@klo/cli-public@latest', + '@ktx/cli-public@latest', 'agent', 'sl', 'list', @@ -221,7 +221,7 @@ describe('published package smoke command construction', () => { '--query', 'ARR', '--project-dir', - '/tmp/klo-smoke/demo', + '/tmp/ktx-smoke/demo', ], env: { npm_config_registry: 'https://registry.npmjs.org/' }, }, @@ -230,7 +230,7 @@ describe('published package smoke command construction', () => { command: 'npx', args: [ '--yes', - '@klo/cli-public@latest', + '@ktx/cli-public@latest', 'agent', 'sl', 'list', @@ -238,7 +238,7 @@ describe('published package smoke command construction', () => { '--query', 'revenue', '--project-dir', - '/tmp/klo-smoke/empty', + '/tmp/ktx-smoke/empty', ], env: { npm_config_registry: 'https://registry.npmjs.org/' }, }, diff --git a/scripts/relationship-orbit-verification.mjs b/scripts/relationship-orbit-verification.mjs index 8bc6e51c..1c24a4e9 100644 --- a/scripts/relationship-orbit-verification.mjs +++ b/scripts/relationship-orbit-verification.mjs @@ -5,14 +5,14 @@ import { execFile as childExecFile } from 'node:child_process'; import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { promisify } from 'node:util'; -import { runWorkspaceKlo } from './run-klo.mjs'; +import { runWorkspaceKtx } from './run-ktx.mjs'; const scriptDir = dirname(fileURLToPath(import.meta.url)); -const kloRootDir = resolve(scriptDir, '..'); -const repoRootDir = resolve(kloRootDir, '..'); -const defaultProjectDir = resolve(kloRootDir, 'examples/orbit-relationship-verification'); +const ktxRootDir = resolve(scriptDir, '..'); +const repoRootDir = resolve(ktxRootDir, '..'); +const defaultProjectDir = resolve(ktxRootDir, 'examples/orbit-relationship-verification'); const defaultReportPath = resolve( - kloRootDir, + ktxRootDir, 'examples/orbit-relationship-verification/reports/orbit-verification.md', ); const defaultExecFile = promisify(childExecFile); @@ -43,7 +43,7 @@ export function defaultOrbitVerificationProjectDir() { } function shellCommand(argv) { - return ['pnpm', 'run', 'klo', '--', ...argv].join(' '); + return ['pnpm', 'run', 'ktx', '--', ...argv].join(' '); } function firstNonEmptyLine(...values) { @@ -61,8 +61,8 @@ function firstNonEmptyLine(...values) { function parseArgs(argv) { const options = { - connectionId: process.env.KLO_ORBIT_CONNECTION_ID ?? 'orbit', - projectDir: process.env.KLO_ORBIT_PROJECT_DIR ?? defaultProjectDir, + connectionId: process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit', + projectDir: process.env.KTX_ORBIT_PROJECT_DIR ?? defaultProjectDir, reportPath: defaultReportPath, }; @@ -190,7 +190,7 @@ function formatBlocked(result) { export function formatOrbitVerificationMarkdown(result) { const lines = [ - '# KLO Relationship Discovery Orbit Verification', + '# KTX Relationship Discovery Orbit Verification', '', `Date: ${result.date}`, '', @@ -219,7 +219,7 @@ export function formatOrbitVerificationMarkdown(result) { return `${lines.join('\n')}\n`; } -async function runBufferedWorkspaceKlo(runner, argv, rootDir, execFile) { +async function runBufferedWorkspaceKtx(runner, argv, rootDir, execFile) { const stdout = new BufferWriter(); const stderr = new BufferWriter(); const exitCode = await runner(argv, { rootDir, execFile, stdout, stderr }); @@ -241,11 +241,11 @@ function orbitVerificationEnv(projectDir) { } export async function runOrbitVerification(options = {}) { - const connectionId = options.connectionId ?? process.env.KLO_ORBIT_CONNECTION_ID ?? 'orbit'; - const projectDir = options.projectDir ?? process.env.KLO_ORBIT_PROJECT_DIR ?? defaultProjectDir; + const connectionId = options.connectionId ?? process.env.KTX_ORBIT_CONNECTION_ID ?? 'orbit'; + const projectDir = options.projectDir ?? process.env.KTX_ORBIT_PROJECT_DIR ?? defaultProjectDir; const reportPath = options.reportPath ?? defaultReportPath; - const rootDir = options.rootDir ?? kloRootDir; - const runner = options.runWorkspaceKlo ?? runWorkspaceKlo; + const rootDir = options.rootDir ?? ktxRootDir; + const runner = options.runWorkspaceKtx ?? runWorkspaceKtx; const execFile = options.execFile ?? defaultExecFile; const now = options.now ?? (() => new Date()); const mkdir = options.mkdir ?? fsMkdir; @@ -255,7 +255,7 @@ export async function runOrbitVerification(options = {}) { const runWithEnv = (argv, runnerOptions) => runner(argv, { ...runnerOptions, env }); const scanArgv = buildOrbitScanArgv({ connectionId, projectDir }); - const scan = await runBufferedWorkspaceKlo(runWithEnv, scanArgv, rootDir, execFile); + const scan = await runBufferedWorkspaceKtx(runWithEnv, scanArgv, rootDir, execFile); let result; if (scan.exitCode !== 0) { @@ -280,13 +280,13 @@ export async function runOrbitVerification(options = {}) { projectDir, scanCommand: shellCommand(scanArgv), scanExitCode: scan.exitCode, - blocker: 'KLO scan completed without printing a Run id', + blocker: 'KTX scan completed without printing a Run id', scanStdout: scan.stdout, scanStderr: scan.stderr, }; } else { const reportArgv = buildOrbitReportArgv({ projectDir, runId }); - const reportOutput = await runBufferedWorkspaceKlo(runWithEnv, reportArgv, rootDir, execFile); + const reportOutput = await runBufferedWorkspaceKtx(runWithEnv, reportArgv, rootDir, execFile); if (reportOutput.exitCode !== 0) { result = { status: 'blocked', diff --git a/scripts/relationship-orbit-verification.test.mjs b/scripts/relationship-orbit-verification.test.mjs index c2af6ddb..c7cdaffc 100644 --- a/scripts/relationship-orbit-verification.test.mjs +++ b/scripts/relationship-orbit-verification.test.mjs @@ -50,7 +50,7 @@ function successReportJson() { } describe('relationship Orbit verification helper', () => { - it('exposes the Orbit verification command from the KLO workspace package', async () => { + it('exposes the Orbit verification command from the KTX workspace package', async () => { const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); assert.equal( @@ -59,7 +59,7 @@ describe('relationship Orbit verification helper', () => { ); }); - it('builds the current KLO launcher arguments for scan and JSON report commands', () => { + it('builds the current KTX launcher arguments for scan and JSON report commands', () => { assert.deepEqual(buildOrbitScanArgv({ connectionId: 'orbit', projectDir: '/tmp/orbit-project' }), [ 'dev', 'scan', @@ -92,14 +92,14 @@ describe('relationship Orbit verification helper', () => { writeFile: async (path, content) => { writes.push({ path, content }); }, - runWorkspaceKlo: async (argv, options) => { + runWorkspaceKtx: async (argv, options) => { calls.push(argv); envs.push(options.env); if (argv[2] === 'report') { options.stdout.write(successReportJson()); return 0; } - options.stdout.write('KLO scan completed\nRun: scan-orbit-1\nConnection: orbit\n'); + options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n'); return 0; }, }); @@ -116,8 +116,8 @@ describe('relationship Orbit verification helper', () => { }); it('extracts the run id from human scan output', () => { - assert.equal(extractRunId(`KLO scan completed\nStatus: done\nRun: scan-orbit-1\nConnection: orbit\n`), 'scan-orbit-1'); - assert.equal(extractRunId('KLO scan completed without a run line\n'), null); + assert.equal(extractRunId(`KTX scan completed\nStatus: done\nRun: scan-orbit-1\nConnection: orbit\n`), 'scan-orbit-1'); + assert.equal(extractRunId('KTX scan completed without a run line\n'), null); }); it('formats successful Orbit verification evidence from the JSON report', () => { @@ -126,16 +126,16 @@ describe('relationship Orbit verification helper', () => { date: '2026-05-07', connectionId: 'orbit', projectDir: '/tmp/orbit-project', - scanCommand: 'pnpm run klo -- dev scan orbit --enrich --project-dir /tmp/orbit-project', - reportCommand: 'pnpm run klo -- dev scan report --json --project-dir /tmp/orbit-project scan-orbit-1', + scanCommand: 'pnpm run ktx -- dev scan orbit --enrich --project-dir /tmp/orbit-project', + reportCommand: 'pnpm run ktx -- dev scan report --json --project-dir /tmp/orbit-project scan-orbit-1', scanExitCode: 0, reportExitCode: 0, - scanStdout: 'KLO scan completed\nRun: scan-orbit-1\n', + scanStdout: 'KTX scan completed\nRun: scan-orbit-1\n', scanStderr: '', report: JSON.parse(successReportJson()), }); - assert.match(markdown, /# KLO Relationship Discovery Orbit Verification/); + assert.match(markdown, /# KTX Relationship Discovery Orbit Verification/); assert.match(markdown, /Outcome/); assert.match(markdown, /Exit code: 0/); assert.match(markdown, /Accepted: 14/); @@ -152,7 +152,7 @@ describe('relationship Orbit verification helper', () => { date: '2026-05-07', connectionId: 'orbit', projectDir: '/tmp/orbit-project', - scanCommand: 'pnpm run klo -- dev scan orbit --enrich --project-dir /tmp/orbit-project', + scanCommand: 'pnpm run ktx -- dev scan orbit --enrich --project-dir /tmp/orbit-project', scanExitCode: 1, blocker: 'Connection "orbit" was not found', scanStdout: '', @@ -177,13 +177,13 @@ describe('relationship Orbit verification helper', () => { writeFile: async (path, content) => { writes.push({ path, content }); }, - runWorkspaceKlo: async (argv, options) => { + runWorkspaceKtx: async (argv, options) => { calls.push(argv); if (argv[2] === 'report') { options.stdout.write(successReportJson()); return 0; } - options.stdout.write('KLO scan completed\nRun: scan-orbit-1\nConnection: orbit\n'); + options.stdout.write('KTX scan completed\nRun: scan-orbit-1\nConnection: orbit\n'); return 0; }, }); @@ -209,7 +209,7 @@ describe('relationship Orbit verification helper', () => { writeFile: async (path, content) => { writes.push({ path, content }); }, - runWorkspaceKlo: async (_argv, options) => { + runWorkspaceKtx: async (_argv, options) => { options.stderr.write('Connection "orbit" was not found\n'); return 1; }, @@ -231,14 +231,14 @@ describe('relationship Orbit verification helper', () => { mkdir: async () => {}, writeFile: async () => {}, execFile: async () => ({ stdout: '', stderr: '' }), - runWorkspaceKlo: async (_argv, options) => { + runWorkspaceKtx: async (_argv, options) => { sawExecFile = typeof options.execFile === 'function'; - options.stderr.write('ENOENT: no such file or directory, open \'/tmp/orbit-project/klo.yaml\'\n'); + options.stderr.write('ENOENT: no such file or directory, open \'/tmp/orbit-project/ktx.yaml\'\n'); return 1; }, }); assert.equal(sawExecFile, true); - assert.equal(result.blocker, "ENOENT: no such file or directory, open '/tmp/orbit-project/klo.yaml'"); + assert.equal(result.blocker, "ENOENT: no such file or directory, open '/tmp/orbit-project/ktx.yaml'"); }); }); diff --git a/scripts/release-readiness.mjs b/scripts/release-readiness.mjs index 24839687..48a57e6c 100644 --- a/scripts/release-readiness.mjs +++ b/scripts/release-readiness.mjs @@ -218,9 +218,9 @@ async function main() { return; } - process.stdout.write(`KLO release mode: ${report.releaseMode}\n`); - process.stdout.write(`KLO source revision: ${report.sourceRevision ?? 'local'}\n`); - process.stdout.write(`KLO packages: ${report.packageNames.join(', ')}\n`); + process.stdout.write(`KTX release mode: ${report.releaseMode}\n`); + process.stdout.write(`KTX source revision: ${report.sourceRevision ?? 'local'}\n`); + process.stdout.write(`KTX packages: ${report.packageNames.join(', ')}\n`); process.stdout.write(`Published package smoke: ${report.publishedPackageSmokeGate.status}\n`); process.stdout.write(`Published package smoke script: ${report.publishedPackageSmokeGate.script}\n`); process.stdout.write(`Published package smoke reason: ${report.publishedPackageSmokeGate.reason}\n`); diff --git a/scripts/release-readiness.test.mjs b/scripts/release-readiness.test.mjs index f22fe42e..6bfa2484 100644 --- a/scripts/release-readiness.test.mjs +++ b/scripts/release-readiness.test.mjs @@ -18,24 +18,24 @@ async function writeReleaseMetadataInputs(root, options = {}) { name: packageInfo.name, version: '0.0.0-private', private: - packageInfo.name === '@klo/context' + packageInfo.name === '@ktx/context' ? (options.contextPrivate ?? true) - : packageInfo.name === '@klo/cli' + : packageInfo.name === '@ktx/cli' ? (options.cliPrivate ?? true) : true, }); } - await mkdir(join(root, 'python', 'klo-sl'), { recursive: true }); - await mkdir(join(root, 'python', 'klo-daemon'), { recursive: true }); + await mkdir(join(root, 'python', 'ktx-sl'), { recursive: true }); + await mkdir(join(root, 'python', 'ktx-daemon'), { recursive: true }); await writeFile( - join(root, 'python', 'klo-sl', 'pyproject.toml'), - ['[project]', 'name = "klo-sl"', 'version = "0.1.0"', ''].join('\n'), + join(root, 'python', 'ktx-sl', 'pyproject.toml'), + ['[project]', 'name = "ktx-sl"', 'version = "0.1.0"', ''].join('\n'), ); await writeFile( - join(root, 'python', 'klo-daemon', 'pyproject.toml'), - ['[project]', 'name = "klo-daemon"', 'version = "0.1.0"', ''].join('\n'), + join(root, 'python', 'ktx-daemon', 'pyproject.toml'), + ['[project]', 'name = "ktx-daemon"', 'version = "0.1.0"', ''].join('\n'), ); } @@ -48,10 +48,10 @@ async function writeUploadableArtifactFixtures(layout) { layout.npmTarballs[packageInfo.name], `${packageInfo.name}-tarball`, ]), - [join(layout.pythonDir, 'klo_sl-0.1.0-py3-none-any.whl'), 'klo-sl-wheel'], - [join(layout.pythonDir, 'klo_sl-0.1.0.tar.gz'), 'klo-sl-sdist'], - [join(layout.pythonDir, 'klo_daemon-0.1.0-py3-none-any.whl'), 'klo-daemon-wheel'], - [join(layout.pythonDir, 'klo_daemon-0.1.0.tar.gz'), 'klo-daemon-sdist'], + [join(layout.pythonDir, 'ktx_sl-0.1.0-py3-none-any.whl'), 'ktx-sl-wheel'], + [join(layout.pythonDir, 'ktx_sl-0.1.0.tar.gz'), 'ktx-sl-sdist'], + [join(layout.pythonDir, 'ktx_daemon-0.1.0-py3-none-any.whl'), 'ktx-daemon-wheel'], + [join(layout.pythonDir, 'ktx_daemon-0.1.0.tar.gz'), 'ktx-daemon-sdist'], ]); for (const [path, contents] of fileContents) { @@ -74,7 +74,7 @@ function releasePolicy(overrides = {}) { python: { publish: false, repository: null, - packages: ['klo-sl', 'klo-daemon'], + packages: ['ktx-sl', 'ktx-daemon'], ...pythonOverrides, }, publishedPackageSmoke: { @@ -109,8 +109,8 @@ async function writeReadyFixture(root, options = {}) { } describe('release readiness policy', () => { - it('reads the checked release policy path from the KLO root', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-policy-test-')); + it('reads the checked release policy path from the KTX root', async () => { + const root = await mkdtemp(join(tmpdir(), 'ktx-release-policy-test-')); try { const policy = releasePolicy(); await writePolicy(root, policy); @@ -123,7 +123,7 @@ describe('release readiness policy', () => { }); it('accepts the current ci-artifact-only policy, package metadata, and artifact manifest', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-ready-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-ready-test-')); try { await writeReadyFixture(root); @@ -135,7 +135,7 @@ describe('release readiness policy', () => { sourceRevision: 'abc123', npmPublishEnabled: false, pythonPublishEnabled: false, - packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'klo-sl', 'klo-daemon'], + packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'ktx-sl', 'ktx-daemon'], publishedPackageSmokeGate: { status: 'not_required', script: 'pnpm run release:published-smoke', @@ -159,12 +159,12 @@ describe('release readiness policy', () => { }); it('reports policy-controlled published package smoke config when present', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-config-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-config-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ publishedPackageSmoke: { - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -178,7 +178,7 @@ describe('release readiness policy', () => { script: 'pnpm run release:published-smoke', reason: 'Published package smoke remains pending until release-policy.json enables npm registry publishing.', configSource: 'release-policy', - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }); @@ -188,13 +188,13 @@ describe('release readiness policy', () => { }); it('reports required published package smoke when release mode requires it', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ releaseMode: 'published-package-smoke-required', publishedPackageSmoke: { - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -210,13 +210,13 @@ describe('release readiness policy', () => { sourceRevision: 'abc123', npmPublishEnabled: false, pythonPublishEnabled: false, - packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'klo-sl', 'klo-daemon'], + packageNames: [...NPM_ARTIFACT_PACKAGES.map((packageInfo) => packageInfo.name), 'ktx-sl', 'ktx-daemon'], publishedPackageSmokeGate: { status: 'required', script: 'pnpm run release:published-smoke', reason: 'Run the published package smoke before accepting the hybrid-search release.', configSource: 'release-policy', - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: '2026.5.8', registry: 'https://registry.npmjs.org/', }, @@ -228,7 +228,7 @@ describe('release readiness policy', () => { }); it('rejects required published smoke mode without a package name', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-missing-config-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-missing-config-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ @@ -247,13 +247,13 @@ describe('release readiness policy', () => { }); it('rejects required published smoke mode while publishing decisions remain', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-required-blocked-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-required-blocked-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ releaseMode: 'published-package-smoke-required', publishedPackageSmoke: { - packageName: '@klo/cli-public', + packageName: '@ktx/cli-public', version: 'latest', registry: null, }, @@ -270,7 +270,7 @@ describe('release readiness policy', () => { }); it('rejects unsupported release modes', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-unsupported-mode-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-unsupported-mode-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ @@ -288,7 +288,7 @@ describe('release readiness policy', () => { }); it('rejects publish-enabled npm policy while releaseMode is ci-artifact-only', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-npm-publish-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-npm-publish-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ @@ -306,7 +306,7 @@ describe('release readiness policy', () => { }); it('rejects publish-enabled Python policy while releaseMode is ci-artifact-only', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-python-publish-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-python-publish-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ @@ -324,12 +324,12 @@ describe('release readiness policy', () => { }); it('rejects unsafe release-policy published package smoke config', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-smoke-invalid-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-smoke-invalid-test-')); try { await writeReadyFixture(root, { policy: releasePolicy({ publishedPackageSmoke: { - packageName: '@klo/cli public', + packageName: '@ktx/cli public', version: 'latest', registry: null, }, @@ -346,13 +346,13 @@ describe('release readiness policy', () => { }); it('rejects a public npm package while releaseMode is ci-artifact-only', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-public-npm-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-public-npm-test-')); try { await writeReadyFixture(root, { contextPrivate: false }); await assert.rejects( () => releaseReadinessReport(root), - /ci-artifact-only policy npm package @klo\/context must remain private/, + /ci-artifact-only policy npm package @ktx\/context must remain private/, ); } finally { await rm(root, { recursive: true, force: true }); @@ -360,7 +360,7 @@ describe('release readiness policy', () => { }); it('rejects stale artifacts before reporting release readiness', async () => { - const root = await mkdtemp(join(tmpdir(), 'klo-release-stale-artifact-test-')); + const root = await mkdtemp(join(tmpdir(), 'ktx-release-stale-artifact-test-')); try { const layout = await writeReadyFixture(root); await writeFile(layout.cliTarball, 'changed-cli-tarball'); diff --git a/scripts/run-klo.mjs b/scripts/run-ktx.mjs similarity index 91% rename from scripts/run-klo.mjs rename to scripts/run-ktx.mjs index fd8da9c6..e595376f 100644 --- a/scripts/run-klo.mjs +++ b/scripts/run-ktx.mjs @@ -6,7 +6,7 @@ import { access as fsAccess, readdir as fsReaddir, stat as fsStat } from 'node:f import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -function kloRootDir() { +function ktxRootDir() { return resolve(dirname(fileURLToPath(import.meta.url)), '..'); } @@ -131,9 +131,9 @@ function runInherited(command, args, options) { }); } -export async function runWorkspaceKlo(argv, options = {}) { +export async function runWorkspaceKtx(argv, options = {}) { const cliArgv = argv[0] === '--' ? argv.slice(1) : argv; - const rootDir = options.rootDir ?? kloRootDir(); + const rootDir = options.rootDir ?? ktxRootDir(); const stdout = options.stdout ?? process.stdout; const stderr = options.stderr ?? process.stderr; const access = options.access ?? fsAccess; @@ -155,13 +155,13 @@ export async function runWorkspaceKlo(argv, options = {}) { if (needsBuild) { stderr.write( binExists - ? 'KLO CLI build output is stale. Rebuilding it now with `pnpm run build`...\n' - : 'KLO CLI build output is missing. Building it now with `pnpm run build`...\n', + ? 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n' + : 'KTX CLI build output is missing. Building it now with `pnpm run build`...\n', ); const buildExitCode = await runCommand('pnpm', ['run', 'build'], { cwd: rootDir, env: commandEnv }); if (buildExitCode !== 0) { stderr.write( - '\nKLO CLI build failed. Run `pnpm run setup:dev` from the KLO directory, then retry this command.\n', + '\nKTX CLI build failed. Run `pnpm run setup:dev` from the KTX directory, then retry this command.\n', ); return buildExitCode; } @@ -171,5 +171,5 @@ export async function runWorkspaceKlo(argv, options = {}) { } if (import.meta.url === pathToFileURL(process.argv[1]).href) { - process.exitCode = await runWorkspaceKlo(process.argv.slice(2)); + process.exitCode = await runWorkspaceKtx(process.argv.slice(2)); } diff --git a/scripts/run-klo.test.mjs b/scripts/run-ktx.test.mjs similarity index 73% rename from scripts/run-klo.test.mjs rename to scripts/run-ktx.test.mjs index f0175ce8..76315a36 100644 --- a/scripts/run-klo.test.mjs +++ b/scripts/run-ktx.test.mjs @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; -import { runWorkspaceKlo } from './run-klo.mjs'; +import { runWorkspaceKtx } from './run-ktx.mjs'; function freshBuildFs() { return { @@ -20,19 +20,19 @@ function freshBuildFs() { }; } -test('runWorkspaceKlo runs the built CLI when it already exists', async () => { +test('runWorkspaceKtx runs the built CLI when it already exists', async () => { const calls = []; const logs = []; const fs = freshBuildFs(); - const exitCode = await runWorkspaceKlo(['--version'], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['--version'], { + rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); - return { stdout: '@klo/cli 0.0.0-private\n', stderr: '' }; + return { stdout: '@ktx/cli 0.0.0-private\n', stderr: '' }; }, stdout: { write: (chunk) => logs.push(['stdout', chunk]) }, stderr: { write: (chunk) => logs.push(['stderr', chunk]) }, @@ -42,26 +42,26 @@ test('runWorkspaceKlo runs the built CLI when it already exists', async () => { assert.deepEqual(calls, [ { command: process.execPath, - args: ['/workspace/klo/packages/cli/dist/bin.js', '--version'], - cwd: '/workspace/klo', + args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'], + cwd: '/workspace/ktx', }, ]); - assert.deepEqual(logs, [['stdout', '@klo/cli 0.0.0-private\n']]); + assert.deepEqual(logs, [['stdout', '@ktx/cli 0.0.0-private\n']]); }); -test('runWorkspaceKlo forwards a caller-provided environment to buffered commands', async () => { +test('runWorkspaceKtx forwards a caller-provided environment to buffered commands', async () => { const calls = []; const fs = freshBuildFs(); - const exitCode = await runWorkspaceKlo(['--version'], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['--version'], { + rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, - env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/klo/examples' }, + env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' }, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd, env: options.env }); - return { stdout: '@klo/cli 0.0.0-private\n', stderr: '' }; + return { stdout: '@ktx/cli 0.0.0-private\n', stderr: '' }; }, stdout: { write: () => undefined }, stderr: { write: () => undefined }, @@ -71,25 +71,25 @@ test('runWorkspaceKlo forwards a caller-provided environment to buffered command assert.deepEqual(calls, [ { command: process.execPath, - args: ['/workspace/klo/packages/cli/dist/bin.js', '--version'], - cwd: '/workspace/klo', - env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/klo/examples' }, + args: ['/workspace/ktx/packages/cli/dist/bin.js', '--version'], + cwd: '/workspace/ktx', + env: { PATH: '/bin', GIT_CEILING_DIRECTORIES: '/workspace/ktx/examples' }, }, ]); }); -test('runWorkspaceKlo drops a leading npm argument separator', async () => { +test('runWorkspaceKtx drops a leading npm argument separator', async () => { const calls = []; const fs = freshBuildFs(); - const exitCode = await runWorkspaceKlo(['--', 'connection', 'test', 'warehouse', '--help'], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['--', 'connection', 'test', 'warehouse', '--help'], { + rootDir: '/workspace/ktx', access: async () => undefined, stat: fs.stat, readdir: fs.readdir, execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); - return { stdout: 'Usage: klo connection test\n', stderr: '' }; + return { stdout: 'Usage: ktx connection test\n', stderr: '' }; }, stdout: { write: () => undefined }, stderr: { write: () => undefined }, @@ -99,18 +99,18 @@ test('runWorkspaceKlo drops a leading npm argument separator', async () => { assert.deepEqual(calls, [ { command: process.execPath, - args: ['/workspace/klo/packages/cli/dist/bin.js', 'connection', 'test', 'warehouse', '--help'], - cwd: '/workspace/klo', + args: ['/workspace/ktx/packages/cli/dist/bin.js', 'connection', 'test', 'warehouse', '--help'], + cwd: '/workspace/ktx', }, ]); }); -test('runWorkspaceKlo skips stale-build checks for shell completion when dist exists', async () => { +test('runWorkspaceKtx skips stale-build checks for shell completion when dist exists', async () => { const calls = []; let statCalls = 0; - const exitCode = await runWorkspaceKlo(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'klo', ''], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['dev', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', ''], { + rootDir: '/workspace/ktx', access: async () => undefined, stat: async (path) => { statCalls += 1; @@ -136,7 +136,7 @@ test('runWorkspaceKlo skips stale-build checks for shell completion when dist ex { command: process.execPath, args: [ - '/workspace/klo/packages/cli/dist/bin.js', + '/workspace/ktx/packages/cli/dist/bin.js', 'dev', '__complete', '--shell', @@ -144,21 +144,21 @@ test('runWorkspaceKlo skips stale-build checks for shell completion when dist ex '--position', '2', '--', - 'klo', + 'ktx', '', ], - cwd: '/workspace/klo', + cwd: '/workspace/ktx', }, ]); }); -test('runWorkspaceKlo builds the workspace CLI before running it when dist is missing', async () => { +test('runWorkspaceKtx builds the workspace CLI before running it when dist is missing', async () => { const calls = []; const logs = []; let binExists = false; - const exitCode = await runWorkspaceKlo(['setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], { + rootDir: '/workspace/ktx', access: async () => { if (!binExists) { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); @@ -183,24 +183,24 @@ test('runWorkspaceKlo builds the workspace CLI before running it when dist is mi ['pnpm', ['run', 'build']], [ process.execPath, - ['/workspace/klo/packages/cli/dist/bin.js', 'setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], + ['/workspace/ktx/packages/cli/dist/bin.js', 'setup', 'demo', '--mode', 'replay', '--no-input', '--viz'], ], ], ); assert.deepEqual(logs, [ - ['stderr', 'KLO CLI build output is missing. Building it now with `pnpm run build`...\n'], + ['stderr', 'KTX CLI build output is missing. Building it now with `pnpm run build`...\n'], ['stdout', 'build ok\n'], ['stdout', 'Replay complete\n'], ]); }); -test('runWorkspaceKlo rebuilds before running when workspace sources are newer than dist', async () => { +test('runWorkspaceKtx rebuilds before running when workspace sources are newer than dist', async () => { const calls = []; const logs = []; let sourceMtimeMs = 3000; - const exitCode = await runWorkspaceKlo(['dev', 'scan', 'orbit', '--enrich'], { - rootDir: '/workspace/klo', + const exitCode = await runWorkspaceKtx(['dev', 'scan', 'orbit', '--enrich'], { + rootDir: '/workspace/ktx', access: async () => undefined, stat: async (path) => ({ mtimeMs: path.endsWith('/packages/cli/dist/bin.js') ? 2000 : sourceMtimeMs, @@ -232,11 +232,11 @@ test('runWorkspaceKlo rebuilds before running when workspace sources are newer t calls.map((call) => [call.command, call.args]), [ ['pnpm', ['run', 'build']], - [process.execPath, ['/workspace/klo/packages/cli/dist/bin.js', 'dev', 'scan', 'orbit', '--enrich']], + [process.execPath, ['/workspace/ktx/packages/cli/dist/bin.js', 'dev', 'scan', 'orbit', '--enrich']], ], ); assert.deepEqual(logs, [ - ['stderr', 'KLO CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'], + ['stderr', 'KTX CLI build output is stale. Rebuilding it now with `pnpm run build`...\n'], ['stdout', 'build ok\n'], ['stdout', 'scan ok\n'], ]); diff --git a/scripts/setup-dev.mjs b/scripts/setup-dev.mjs index aed414c6..2dbf37d1 100644 --- a/scripts/setup-dev.mjs +++ b/scripts/setup-dev.mjs @@ -7,7 +7,7 @@ import { promisify } from 'node:util'; const execFileAsync = promisify(execFileCallback); -function kloRootDir() { +function ktxRootDir() { return resolve(dirname(fileURLToPath(import.meta.url)), '..'); } @@ -19,7 +19,7 @@ function failureText(error) { } export async function runSetupDev(options = {}) { - const rootDir = options.rootDir ?? kloRootDir(); + const rootDir = options.rootDir ?? ktxRootDir(); const execFile = options.execFile ?? execFileAsync; const log = options.log ?? ((line) => process.stdout.write(`${line}\n`)); const phases = [ @@ -45,7 +45,7 @@ export async function runSetupDev(options = {}) { name: 'doctor setup', command: process.execPath, args: ['packages/cli/dist/bin.js', 'dev', 'doctor', 'setup', '--no-input'], - retry: 'pnpm run klo -- dev doctor setup --no-input', + retry: 'pnpm run ktx -- dev doctor setup --no-input', }, ]; @@ -61,7 +61,7 @@ export async function runSetupDev(options = {}) { } } - log('Workspace CLI: pnpm run klo -- --help'); + log('Workspace CLI: pnpm run ktx -- --help'); log('Optional global dev link: pnpm run link:dev'); return { ok: true }; } diff --git a/scripts/setup-dev.test.mjs b/scripts/setup-dev.test.mjs index d27a5f05..63be1952 100644 --- a/scripts/setup-dev.test.mjs +++ b/scripts/setup-dev.test.mjs @@ -7,7 +7,7 @@ test('runSetupDev runs phased setup without global linking', async () => { const logs = []; const result = await runSetupDev({ - rootDir: '/workspace/klo', + rootDir: '/workspace/ktx', execFile: async (command, args, options) => { calls.push({ command, args, cwd: options.cwd }); return { stdout: `${command} ${args.join(' ')}`, stderr: '' }; @@ -34,7 +34,7 @@ test('runSetupDev stops at the failed phase and prints a retry command', async ( const logs = []; const result = await runSetupDev({ - rootDir: '/workspace/klo', + rootDir: '/workspace/ktx', execFile: async (command, args) => { calls.push({ command, args }); if (args.includes('native:rebuild')) { diff --git a/scripts/standalone-ci-workflow.test.mjs b/scripts/standalone-ci-workflow.test.mjs index b1b90b57..bfcc1b64 100644 --- a/scripts/standalone-ci-workflow.test.mjs +++ b/scripts/standalone-ci-workflow.test.mjs @@ -12,11 +12,11 @@ function assertIncludesAll(text, values) { } } -describe('standalone KLO CI workflow', () => { +describe('standalone KTX CI workflow', () => { it('runs the package checks from a filtered repository root', async () => { const workflow = await readText('.github/workflows/ci.yml'); - assert.match(workflow, /^name: KLO CI/m); + assert.match(workflow, /^name: KTX CI/m); assertIncludesAll(workflow, [ 'permissions:', 'contents: read', @@ -37,9 +37,9 @@ describe('standalone KLO CI workflow', () => { ]); assert.doesNotMatch(workflow, /sparse-checkout/); - assert.doesNotMatch(workflow, /cd klo/); - assert.doesNotMatch(workflow, /klo\/pnpm-lock\.yaml/); - assert.doesNotMatch(workflow, /klo\/uv\.lock/); + assert.doesNotMatch(workflow, /cd ktx/); + assert.doesNotMatch(workflow, /ktx\/pnpm-lock\.yaml/); + assert.doesNotMatch(workflow, /ktx\/uv\.lock/); }); it('uploads verified artifacts from root-relative paths', async () => { @@ -47,7 +47,7 @@ describe('standalone KLO CI workflow', () => { assertIncludesAll(workflow, [ 'actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f', - 'name: klo-package-artifacts-${{ github.sha }}', + 'name: ktx-package-artifacts-${{ github.sha }}', 'dist/artifacts/manifest.json', 'dist/artifacts/npm/*.tgz', 'dist/artifacts/python/*.whl', @@ -56,7 +56,7 @@ describe('standalone KLO CI workflow', () => { 'retention-days: 7', ]); - assert.doesNotMatch(workflow, /klo\/dist\/artifacts/); + assert.doesNotMatch(workflow, /ktx\/dist\/artifacts/); }); it('syncs injected workspace packages after package builds', async () => { diff --git a/scripts/validate-llm-debug-jsonl.mjs b/scripts/validate-llm-debug-jsonl.mjs index 27c70a28..6db04c62 100644 --- a/scripts/validate-llm-debug-jsonl.mjs +++ b/scripts/validate-llm-debug-jsonl.mjs @@ -5,7 +5,7 @@ import { readFileSync } from 'node:fs'; const [backend, filePath] = process.argv.slice(2); function usage() { - process.stderr.write('Usage: node klo/scripts/validate-llm-debug-jsonl.mjs anthropic|vertex /path/to/debug.jsonl\n'); + process.stderr.write('Usage: node ktx/scripts/validate-llm-debug-jsonl.mjs anthropic|vertex /path/to/debug.jsonl\n'); } function fail(message) { diff --git a/scripts/validate-llm-debug-jsonl.test.mjs b/scripts/validate-llm-debug-jsonl.test.mjs index d5a049c5..e8253879 100644 --- a/scripts/validate-llm-debug-jsonl.test.mjs +++ b/scripts/validate-llm-debug-jsonl.test.mjs @@ -14,7 +14,7 @@ function runValidator(args) { } function writeDebugJsonl(records) { - const dir = mkdtempSync(join(tmpdir(), 'klo-llm-debug-validator-')); + const dir = mkdtempSync(join(tmpdir(), 'ktx-llm-debug-validator-')); const filePath = join(dir, 'debug.jsonl'); writeFileSync(filePath, `${records.map((record) => JSON.stringify(record)).join('\n')}\n`, 'utf8'); return filePath; @@ -52,7 +52,7 @@ test('prints usage and exits 2 when required arguments are missing', () => { const result = runValidator([]); assert.equal(result.status, 2); - assert.match(result.stderr, /Usage: node klo\/scripts\/validate-llm-debug-jsonl\.mjs anthropic\|vertex/); + assert.match(result.stderr, /Usage: node ktx\/scripts\/validate-llm-debug-jsonl\.mjs anthropic\|vertex/); }); test('accepts sanitized debug JSONL with message, message-part, and tool cache markers', () => { diff --git a/uv.lock b/uv.lock index f44ea7d3..df70d473 100644 --- a/uv.lock +++ b/uv.lock @@ -14,9 +14,9 @@ resolution-markers = [ [manifest] members = [ - "klo-daemon", - "klo-sl", - "klo-workspace", + "ktx-daemon", + "ktx-sl", + "ktx-workspace", ] [[package]] @@ -439,12 +439,12 @@ wheels = [ ] [[package]] -name = "klo-daemon" +name = "ktx-daemon" version = "0.1.0" -source = { editable = "python/klo-daemon" } +source = { editable = "python/ktx-daemon" } dependencies = [ { name = "fastapi" }, - { name = "klo-sl" }, + { name = "ktx-sl" }, { name = "lkml" }, { name = "numpy" }, { name = "orjson" }, @@ -468,7 +468,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "fastapi", specifier = ">=0.115.0" }, - { name = "klo-sl", editable = "python/klo-sl" }, + { name = "ktx-sl", editable = "python/ktx-sl" }, { name = "lkml", specifier = ">=1.3.7" }, { name = "numpy", specifier = ">=2.2.6" }, { name = "orjson", specifier = ">=3.11.4" }, @@ -489,9 +489,9 @@ dev = [ ] [[package]] -name = "klo-sl" +name = "ktx-sl" version = "0.1.0" -source = { editable = "python/klo-sl" } +source = { editable = "python/ktx-sl" } dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, @@ -535,7 +535,7 @@ dev = [ ] [[package]] -name = "klo-workspace" +name = "ktx-workspace" version = "0.0.0" source = { virtual = "." }