2026-05-14 14:35:55 +02:00
|
|
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
2026-05-10 23:12:26 +02:00
|
|
|
import { createRequire } from 'node:module';
|
|
|
|
|
import { tmpdir } from 'node:os';
|
|
|
|
|
import { join } from 'node:path';
|
test: split cli tests from source tree (#216)
* feat(cli): define full warehouse dialect contract
* test(cli): keep dialect edge tests focused
* fix(cli): stabilize dialect contract foundation
* refactor(connectors): own read-only query preparation
* refactor(connectors): resolve dialects through registry
* refactor(connectors): keep concrete dialect classes internal
* chore(workspace): enforce dialect import boundary
* refactor(cli): resolve relationship dialect at scan boundary
* refactor(cli): use dialect display parsing for entity details
* refactor(cli): use dialect display parsing for warehouse catalog
* refactor(cli): use dialect SQL in relationship workflows
* test(cli): verify solid dialect scan workflow closure
* test: split cli tests from source tree
* refactor(cli): standardize BigQuery scope listing
* feat(sqlite): implement connector scope listing
* test(connectors): cover required table listing
* feat(cli): add warehouse driver registry
* refactor(setup): route scope discovery through driver registry
* refactor(cli): route local query execution through driver registry
* refactor(historic-sql): route dialect support through driver registry
* refactor(cli): test warehouse connections through driver registry
* fix(cli): close driver registry type export gaps
* Improve setup daemon diagnostics
* refactor(setup): centralize rail-prefixed diagnostics + query-history fallback
Extract errorMessage, writePrefixedLines, and flushPrefixedBufferedCommandOutput
into clack.ts so the setup wizard, managed daemons, and embedding/agent steps
share one rail-formatted writer. setup-databases.ts also adds a
"disable query history and retry" option when the schema-context build fails
and query history is the likely culprit, surfaced via a new
failed-query-history-unavailable status.
* fix(cli): carry catalog through the picker so BigQuery/Snowflake/SQL Server scope filters match
The setup picker's KtxTableListEntry was a 2-level { schema, name }, so
qualifiedTableId always wrote db.name into enabled_tables. When BigQuery,
Snowflake, or SQL Server later ran fast ingest, their introspect step filtered
the scope set with scopedTableNames(scope, { catalog: projectId|database, db })
— catalog was non-null on the introspect side but null in the scope refs, so
every entry was rejected, the live-database adapter staged zero table files,
and detect() failed with 'Adapter "live-database" did not recognize fetched
source output'.
Align the picker boundary with the canonical 3-level KtxTableRef:
- Add catalog: string | null to KtxTableListEntry.
- BigQuery/Snowflake/SQL Server listTables populate catalog from the
resolved projectId / database; Postgres/MySQL/ClickHouse/SQLite set null.
- qualifiedTableId emits catalog.schema.name when catalog is non-null
(resolveEnabledTables already accepts the 3-part shape) and
schemasFromEnabledTables now goes through parseDottedTableEntry so it
recovers the schema correctly from both 2-part and 3-part entries.
- Export parseDottedTableEntry from enabled-tables.ts (@internal) for picker
reuse.
Update listTables expectations in all seven connector tests and the setup /
picker test fixtures. Add a picker regression test that covers the
catalog-bearing round-trip (save + refine).
* fix(cli): allow debug telemetry under opt-out env
2026-05-26 08:49:05 +02:00
|
|
|
import { initKtxProject } from '../src/context/project/project.js';
|
2026-05-10 23:12:26 +02:00
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
|
|
|
|
|
|
import {
|
2026-05-10 23:51:24 +02:00
|
|
|
getKtxCliPackageInfo,
|
2026-05-11 15:50:34 +02:00
|
|
|
packageInfoFromJson,
|
2026-05-10 23:12:26 +02:00
|
|
|
rendererUnavailableVizFallback,
|
|
|
|
|
renderMemoryFlowTui,
|
|
|
|
|
resolveVizFallback,
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli,
|
2026-05-10 23:12:26 +02:00
|
|
|
sanitizeMemoryFlowTuiError,
|
|
|
|
|
startLiveMemoryFlowTui,
|
|
|
|
|
warnVizFallbackOnce,
|
test: split cli tests from source tree (#216)
* feat(cli): define full warehouse dialect contract
* test(cli): keep dialect edge tests focused
* fix(cli): stabilize dialect contract foundation
* refactor(connectors): own read-only query preparation
* refactor(connectors): resolve dialects through registry
* refactor(connectors): keep concrete dialect classes internal
* chore(workspace): enforce dialect import boundary
* refactor(cli): resolve relationship dialect at scan boundary
* refactor(cli): use dialect display parsing for entity details
* refactor(cli): use dialect display parsing for warehouse catalog
* refactor(cli): use dialect SQL in relationship workflows
* test(cli): verify solid dialect scan workflow closure
* test: split cli tests from source tree
* refactor(cli): standardize BigQuery scope listing
* feat(sqlite): implement connector scope listing
* test(connectors): cover required table listing
* feat(cli): add warehouse driver registry
* refactor(setup): route scope discovery through driver registry
* refactor(cli): route local query execution through driver registry
* refactor(historic-sql): route dialect support through driver registry
* refactor(cli): test warehouse connections through driver registry
* fix(cli): close driver registry type export gaps
* Improve setup daemon diagnostics
* refactor(setup): centralize rail-prefixed diagnostics + query-history fallback
Extract errorMessage, writePrefixedLines, and flushPrefixedBufferedCommandOutput
into clack.ts so the setup wizard, managed daemons, and embedding/agent steps
share one rail-formatted writer. setup-databases.ts also adds a
"disable query history and retry" option when the schema-context build fails
and query history is the likely culprit, surfaced via a new
failed-query-history-unavailable status.
* fix(cli): carry catalog through the picker so BigQuery/Snowflake/SQL Server scope filters match
The setup picker's KtxTableListEntry was a 2-level { schema, name }, so
qualifiedTableId always wrote db.name into enabled_tables. When BigQuery,
Snowflake, or SQL Server later ran fast ingest, their introspect step filtered
the scope set with scopedTableNames(scope, { catalog: projectId|database, db })
— catalog was non-null on the introspect side but null in the scope refs, so
every entry was rejected, the live-database adapter staged zero table files,
and detect() failed with 'Adapter "live-database" did not recognize fetched
source output'.
Align the picker boundary with the canonical 3-level KtxTableRef:
- Add catalog: string | null to KtxTableListEntry.
- BigQuery/Snowflake/SQL Server listTables populate catalog from the
resolved projectId / database; Postgres/MySQL/ClickHouse/SQLite set null.
- qualifiedTableId emits catalog.schema.name when catalog is non-null
(resolveEnabledTables already accepts the 3-part shape) and
schemasFromEnabledTables now goes through parseDottedTableEntry so it
recovers the schema correctly from both 2-part and 3-part entries.
- Export parseDottedTableEntry from enabled-tables.ts (@internal) for picker
reuse.
Update listTables expectations in all seven connector tests and the setup /
picker test fixtures. Add a picker regression test that covers the
catalog-bearing round-trip (save + refine).
* fix(cli): allow debug telemetry under opt-out env
2026-05-26 08:49:05 +02:00
|
|
|
} from '../src/index.js';
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
|
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
const cliPackageJson = require('@kaelio/ktx/package.json') as { name: string; version: string };
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
const cliVersion = cliPackageJson.version;
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
function makeIo(options: { stdoutIsTty?: boolean } = {}) {
|
|
|
|
|
let stdout = '';
|
|
|
|
|
let stderr = '';
|
|
|
|
|
return {
|
|
|
|
|
io: {
|
|
|
|
|
stdout: {
|
|
|
|
|
isTTY: options.stdoutIsTty,
|
|
|
|
|
write: (chunk: string) => {
|
|
|
|
|
stdout += chunk;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
stderr: {
|
|
|
|
|
write: (chunk: string) => {
|
|
|
|
|
stderr += chunk;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
stdout: () => stdout,
|
|
|
|
|
stderr: () => stderr,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
describe('getKtxCliPackageInfo', () => {
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
it('identifies the CLI package', () => {
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(getKtxCliPackageInfo()).toEqual({
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
name: '@kaelio/ktx',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
version: cliVersion,
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exports package metadata for package managers and runtime diagnostics', () => {
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
const packageJson = require('@kaelio/ktx/package.json') as { name: string; version: string };
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(packageJson).toMatchObject({
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
name: '@kaelio/ktx',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
version: cliVersion,
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
expect(cliVersion).toMatch(/^\d+\.\d+\.\d+/);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
2026-05-11 15:50:34 +02:00
|
|
|
|
|
|
|
|
it('normalizes public package metadata from package.json contents', () => {
|
|
|
|
|
expect(
|
|
|
|
|
packageInfoFromJson({
|
|
|
|
|
name: '@kaelio/ktx',
|
|
|
|
|
version: '0.1.0',
|
|
|
|
|
}),
|
|
|
|
|
).toEqual({
|
|
|
|
|
name: '@kaelio/ktx',
|
|
|
|
|
version: '0.1.0',
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('memory-flow renderer exports', () => {
|
|
|
|
|
it('exports runtime-agnostic renderer entry points for hosted terminal clients', () => {
|
|
|
|
|
expect(renderMemoryFlowTui).toBeTypeOf('function');
|
|
|
|
|
expect(startLiveMemoryFlowTui).toBeTypeOf('function');
|
|
|
|
|
expect(sanitizeMemoryFlowTuiError('token=abc123')).toBe('[redacted]');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('exports shared visualization fallback helpers for hosted terminal clients', () => {
|
|
|
|
|
const fallback = resolveVizFallback({ stdout: { isTTY: true }, stderr: { write: vi.fn() } }, { TERM: 'dumb' });
|
|
|
|
|
|
|
|
|
|
expect(fallback).toEqual({
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'term-dumb',
|
|
|
|
|
message: 'TERM=dumb does not support the visual renderer',
|
|
|
|
|
});
|
|
|
|
|
expect(rendererUnavailableVizFallback()).toEqual({
|
|
|
|
|
shouldDegrade: true,
|
|
|
|
|
reason: 'renderer-unavailable',
|
|
|
|
|
message: 'the terminal renderer is unavailable',
|
|
|
|
|
});
|
|
|
|
|
expect(warnVizFallbackOnce).toBeTypeOf('function');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
describe('runKtxCli', () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
let tempDir: string;
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
2026-05-10 23:51:24 +02:00
|
|
|
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-'));
|
2026-05-14 17:39:31 +02:00
|
|
|
await writeFile(join(tempDir, 'ktx.yaml'), '{}\n', 'utf-8');
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('prints version information', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['--version'], testIo.io)).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
chore(workspace): gate dead-code with knip production mode (#196)
* refactor(workspace): relocate @ktx/llm source into packages/cli/src/llm
* refactor(workspace): rewrite @ktx/llm imports to relative paths
* refactor(workspace): fold internal packages into cli
* chore(workspace): gate dead-code with knip production mode
Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
* refactor(cli): delete internal barrel index.ts files
The 34 `index.ts` re-export barrels inside `packages/cli/src/` were
holdovers from the pre-fold multi-workspace structure. Post-fold-in they
served no production purpose: external consumers go through the single
package main entry, and in-repo callers mostly imported through them
only because the path was short. Internally, knip flagged most barrel
re-exports as production-dead (only reached via tests).
This change:
- Deletes every internal barrel except `packages/cli/src/index.ts`
(the published package entry).
- Rewrites ~270 source/test files to import each name directly from
the file that defines it.
- Moves `tools/warehouse-verification/index.ts` to
`create-warehouse-verification-tools.ts` (the function it defined
locally) and updates its single consumer.
- Renames `search/backend-conformance.ts` → `.test-utils.ts` to match
the existing test-helper file convention.
- Deletes 13 dead test-only chains (dbt-descriptions/*,
live-database/extracted-schema, live-database/structural-sync,
relationship-* feedback/review chain) plus their tests and a
cascading orphan integration test.
- Updates test mocks that pointed at deleted barrel paths
(notion-client, connector barrels in scan/local-scan-connectors
tests) to mock the source files instead.
- Points the maintainer benchmark script
(`scripts/relationship-benchmark-report.mjs`) at source files
instead of `dist/context/scan/index.js`.
- Drops the barrel `!` entries from `knip.json`; adds explicit
production entries only for the benchmark code reached via dist by
the maintainer script.
Net: 413 files changed, ~1.2k insertions, ~9.4k deletions.
`pnpm run dead-code` (Biome + knip default + knip production) and
`pnpm run type-check` are clean; 2277 tests pass.
* refactor(workspace): rename @ktx/cli to @kaelio/ktx and pack it directly
Promote the CLI workspace package to the public name `@kaelio/ktx` and
drop the separate `scripts/build-public-npm-package.mjs` wrapper. The
CLI package is now publishable in place (`publishConfig.access: public`,
`provenance: true`), so artifact packing uses `pnpm pack` against
`packages/cli/` instead of assembling a parallel package tree.
Updates all workspace filter invocations, docs, tests, and release
readiness checks to reference the new package name, and folds the
tarball-name helper into `scripts/public-npm-release-metadata.mjs`.
* docs: align "agent clients" and "data agents" terminology
Replace "client agents" with "agent clients" and "database agents" with
"data agents" across AGENTS.md, README.md, the docs-site copy, and the
matching setup-agents test description, matching the canonical
vocabulary in docs/terminology.md.
Also moves packages/cli/tsconfig.json's tsBuildInfoFile from
node_modules/.cache/ to dist/.tsbuildinfo so incremental builds survive
node_modules reinstalls.
* refactor(release): single source of truth for package version
Make packages/cli/package.json the single source of truth for the
@kaelio/ktx version. publicNpmPackageVersion() now reads it directly,
so artifact filenames, release-readiness checks, and the Python wheel
version all derive from one field. The duplicate
release-policy.json.publicNpmPackageVersion is removed.
Previously the two fields could drift: tarballs were named
kaelio-ktx-0.4.1.tgz while internally containing
@kaelio/ktx@0.0.0-private.
- update-public-release-version.mjs rewrites both Python pyproject.toml
files (ktx-daemon, ktx-sl) alongside the npm package.jsons,
normalizing the version for PEP 440 (e.g. 0.1.0-rc.2 -> 0.1.0rc2).
- semantic-release-config.cjs adds the two pyproject.toml files to
@semantic-release/git assets so the release commit back to main
carries every version source in lockstep.
- The six "?? '0.0.0-private'" fallback literals across the CLI are
replaced with "?? getKtxCliPackageInfo().version", and
createDefaultKtxMcpServer makes its version arg required.
- docs/release.md describes the actual commit-back model: the dev tree
always reflects the most recent release; no sentinel pin to
maintain.
Verified: pnpm run artifacts:build now produces
kaelio-ktx-0.4.1.tgz and kaelio_ktx-0.4.1-py3-none-any.whl with
@kaelio/ktx@0.4.1 inside. Full type-check, dead-code, and
2287 vitests + 173 script tests pass.
* refactor(cli): inject embedding provider resolution and detect sentence-transformers runtime
Make resolveProjectEmbeddingProvider and runtimeIo injectable in ingest and
scan command entrypoints so tests can stub them, and teach
resolvePublicIngestRuntimeRequirements to flag the local-embeddings runtime
feature when ktx.yaml selects sentence-transformers.
* chore(cli): mark buildLocalStatsStatus and LocalStatsStatus as @internal
Both symbols are consumed only by status-project.test.ts. Annotating with
/** @internal */ keeps knip's production-mode check clean without changing
runtime behavior.
* fix(cli): use real package metadata in print-command-tree
The stubbed package name embedded a forbidden product identifier that
tripped the boundary check in CI. Read the metadata from package.json
instead — keeps the rendered tree unchanged and removes a duplicate
source of truth.
* feat(cli): show embedding coverage in `ktx status`, drop duplicate disk counts
Inline `(N embedded)` next to the Wiki scope counts and Semantic-layer
source counts, computed with `SUM(embedding_json IS NOT NULL)` over
`knowledge_pages` and `local_sl_sources`. Rename the "Knowledge" label to
"Wiki" (canonical per `docs/terminology.md`) and rename the matching
`localStats.knowledgePages` field to `localStats.wikiPages`.
Drop `wiki=N md` and `semantic-layer=N yaml` from the Disk row — those
duplicated the per-surface rows above. Disk now reports only actual byte
usage (db, cache, raw-sources). The unused `wikiGlobalMarkdownCount` /
`semanticLayerYamlCount` fields, the `isMarkdownEntry` / `isYamlEntry`
helpers, and the `filter` arg on `summarizeDir` are removed.
2026-05-21 15:28:58 +02:00
|
|
|
expect(testIo.stdout()).toBe(`@kaelio/ktx ${cliVersion}\n`);
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
it('prints the public command surface in root help', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['--help'], testIo.io)).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
|
2026-05-13 15:55:00 +02:00
|
|
|
expect(testIo.stdout()).toContain('KTX data agent context layer CLI');
|
2026-05-20 01:36:54 +02:00
|
|
|
for (const command of ['setup', 'connection', 'ingest', 'wiki', 'sl', 'status', 'admin']) {
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stdout()).toContain(`${command}`);
|
|
|
|
|
}
|
2026-05-20 01:36:54 +02:00
|
|
|
expect(testIo.stdout()).not.toMatch(/^ dev\s/m);
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stdout()).not.toMatch(/^ scan\s/m);
|
2026-05-13 12:00:08 +02:00
|
|
|
for (const removed of ['demo', 'init', 'connect', 'ask', 'knowledge', 'agent', 'completion', 'serve']) {
|
2026-05-13 15:55:00 +02:00
|
|
|
expect(testIo.stdout()).not.toMatch(new RegExp(`^\\s+${removed}(?:\\s|\\[|$)`, 'm'));
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
expect(testIo.stdout()).toContain('--project-dir <path>');
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).toContain('KTX_PROJECT_DIR');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stdout()).toContain('--debug');
|
|
|
|
|
expect(testIo.stdout()).not.toContain('--' + 'verbose');
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stdout()).not.toContain('Advanced:');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('routes supported public wiki commands', async () => {
|
2026-05-13 15:41:10 +02:00
|
|
|
const knowledge = vi.fn(async () => 0);
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
const listIo = makeIo();
|
2026-05-20 01:52:37 +02:00
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', '--json'], listIo.io, { knowledge }))
|
2026-05-13 16:05:58 +02:00
|
|
|
.resolves.toBe(0);
|
|
|
|
|
expect(knowledge).toHaveBeenCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-14 01:43:06 +02:00
|
|
|
command: 'list',
|
2026-05-13 16:05:58 +02:00
|
|
|
projectDir: tempDir,
|
|
|
|
|
userId: 'local',
|
|
|
|
|
json: true,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-14 01:43:06 +02:00
|
|
|
listIo.io,
|
2026-05-13 16:05:58 +02:00
|
|
|
);
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
const searchIo = makeIo();
|
2026-05-13 16:05:58 +02:00
|
|
|
await expect(
|
2026-05-20 01:52:37 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', '--limit', '5'], searchIo.io, { knowledge }),
|
2026-05-13 16:05:58 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(knowledge).toHaveBeenLastCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-14 01:43:06 +02:00
|
|
|
command: 'search',
|
2026-05-13 16:05:58 +02:00
|
|
|
projectDir: tempDir,
|
2026-05-14 01:43:06 +02:00
|
|
|
query: 'revenue',
|
2026-05-13 16:05:58 +02:00
|
|
|
userId: 'local',
|
2026-05-14 01:43:06 +02:00
|
|
|
json: false,
|
|
|
|
|
limit: 5,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-14 01:43:06 +02:00
|
|
|
searchIo.io,
|
2026-05-13 16:05:58 +02:00
|
|
|
);
|
2026-05-17 02:32:41 +02:00
|
|
|
|
|
|
|
|
const debugSearchIo = makeIo();
|
|
|
|
|
await expect(
|
2026-05-20 01:52:37 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, '--debug', 'wiki', 'revenue'], debugSearchIo.io, { knowledge }),
|
2026-05-17 02:32:41 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(knowledge).toHaveBeenLastCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-17 02:32:41 +02:00
|
|
|
command: 'search',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
query: 'revenue',
|
|
|
|
|
userId: 'local',
|
|
|
|
|
json: false,
|
|
|
|
|
debug: true,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-17 02:32:41 +02:00
|
|
|
debugSearchIo.io,
|
|
|
|
|
);
|
2026-05-14 01:43:06 +02:00
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
const multiWordIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', 'policy'], multiWordIo.io, { knowledge }),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(knowledge).toHaveBeenLastCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-20 01:52:37 +02:00
|
|
|
command: 'search',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
query: 'revenue policy',
|
|
|
|
|
userId: 'local',
|
|
|
|
|
json: false,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-20 01:52:37 +02:00
|
|
|
multiWordIo.io,
|
|
|
|
|
);
|
2026-05-14 01:43:06 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
it('rejects unknown write-style flags on the flattened wiki and sl commands', async () => {
|
|
|
|
|
const knowledge = vi.fn(async () => 0);
|
2026-05-13 16:05:58 +02:00
|
|
|
const sl = vi.fn(async () => 0);
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
const wikiIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'wiki', 'revenue', '--summary', 'Revenue', '--content', 'Revenue.'],
|
|
|
|
|
wikiIo.io,
|
|
|
|
|
{ knowledge },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
expect(wikiIo.stderr()).toMatch(/unknown option|error:/);
|
|
|
|
|
expect(knowledge).not.toHaveBeenCalled();
|
2026-05-13 15:41:10 +02:00
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
const slIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'sl', 'orders', '--yaml', 'name: orders'],
|
|
|
|
|
slIo.io,
|
|
|
|
|
{ sl },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
expect(slIo.stderr()).toMatch(/unknown option|error:/);
|
2026-05-13 15:41:10 +02:00
|
|
|
expect(sl).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
it('routes sl search via the flattened query positional and rejects unknown flags', async () => {
|
2026-05-13 15:41:10 +02:00
|
|
|
const sl = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
const searchIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
2026-05-20 01:52:37 +02:00
|
|
|
['--project-dir', tempDir, 'sl', 'revenue', '--connection-id', 'warehouse', '--limit', '5', '--json'],
|
2026-05-13 15:41:10 +02:00
|
|
|
searchIo.io,
|
|
|
|
|
{ sl },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(sl).toHaveBeenCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-13 15:41:10 +02:00
|
|
|
command: 'search',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
query: 'revenue',
|
|
|
|
|
limit: 5,
|
|
|
|
|
json: true,
|
|
|
|
|
output: undefined,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-13 15:41:10 +02:00
|
|
|
searchIo.io,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
const bareIo = makeIo();
|
2026-05-13 15:41:10 +02:00
|
|
|
await expect(
|
2026-05-20 01:52:37 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'sl', '--connection-id', 'warehouse', '--json'], bareIo.io, { sl }),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(sl).toHaveBeenLastCalledWith(
|
2026-05-21 02:21:22 +02:00
|
|
|
expect.objectContaining({
|
2026-05-20 01:52:37 +02:00
|
|
|
command: 'list',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
json: true,
|
|
|
|
|
output: undefined,
|
2026-05-21 02:21:22 +02:00
|
|
|
}),
|
2026-05-20 01:52:37 +02:00
|
|
|
bareIo.io,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const unknownIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'sl', '--query', 'revenue'], unknownIo.io, { sl }),
|
2026-05-13 15:41:10 +02:00
|
|
|
).resolves.toBe(1);
|
2026-05-20 01:52:37 +02:00
|
|
|
expect(unknownIo.stderr()).toContain("unknown option '--query'");
|
2026-05-13 15:41:10 +02:00
|
|
|
});
|
|
|
|
|
|
refactor(release): drop release-policy.json runtime dep and next branch (#180)
* chore: standardize daemon naming on "KTX daemon"
Replace inconsistent names ("KTX Python daemon", "KTX local embeddings
daemon", "KTX managed daemon", "Python daemon") with the single name
"KTX daemon" in CLI output, errors, command descriptions, test
assertions, smoke scripts, docs, AGENTS.md, issue templates, and
codecov flags. The daemon is a portable compute server with endpoints
for SQL analysis, semantic layer, LookML, database introspection, and
embeddings; the previous labels misrepresented it as embeddings-only or
exposed implementation details ("Python", "managed").
The "KTX Python runtime" concept (installed interpreter + packages) is
deliberately left as-is — it is a separate concept from the daemon
process.
* refactor(release): drop release-policy.json runtime dep and next branch
Strips the release-policy.json fallback from release-version.ts so the CLI
reads its version straight from packages/cli/package.json. dev → 0.0.0-private,
installed @kaelio/ktx → the real semver baked into the published package.json.
KtxCliPackageInfo collapses to { name, version, contextPackageName }; /health
no longer depends on version files surviving past a CI run.
Replaces the dual-branch (main + next) semantic-release model with a single-
branch model on main. rcs and stables interleave on the same branch via
{ name: 'main', prerelease: 'rc', channel: 'next' } / ['main']. Drops
@semantic-release/git and @semantic-release/changelog (nothing is committed
back to the repo on any channel) and the workflow's "Prepare next prerelease
branch" step plus the KTX_PRERELEASE_BRANCH plumbing. The git tag plus the
published npm artifact carry the version forward.
Updates docs/release.md, removes the two now-unused devDeps, regenerates
pnpm-lock.yaml. 611/611 @ktx/cli tests, 173/173 script tests, type-check,
biome, knip all clean.
* fix(release): don't throw on non-main branches at config-load time
knip loads .releaserc.cjs on every PR run, where GITHUB_REF_NAME is the
merge ref (e.g. 180/merge). The previous version of releaseBranches threw
immediately when the branch wasn't main, which made knip fail to evaluate
the config and then mis-flag @semantic-release/exec as an unused dep.
semantic-release already refuses to publish when the current branch doesn't
match a configured release branch, so the explicit throw was redundant.
Drop it (and the unused currentBranch helper) and replace the
"rejects releases from non-main" assertion with one that exercises a CI-
shaped GITHUB_REF_NAME and confirms the config loads.
2026-05-20 13:53:14 +02:00
|
|
|
it('routes runtime management commands with the CLI package version', async () => {
|
2026-05-11 15:50:34 +02:00
|
|
|
const runtime = vi.fn(async () => 0);
|
|
|
|
|
const installIo = makeIo();
|
|
|
|
|
const startIo = makeIo();
|
|
|
|
|
const stopIo = makeIo();
|
2026-05-12 13:00:08 +02:00
|
|
|
const stopAllIo = makeIo();
|
2026-05-11 15:50:34 +02:00
|
|
|
const statusIo = makeIo();
|
|
|
|
|
const pruneIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-20 01:36:54 +02:00
|
|
|
runKtxCli(['admin', 'runtime', 'install', '--feature', 'local-embeddings', '--force', '--yes'], installIo.io, {
|
2026-05-11 15:50:34 +02:00
|
|
|
runtime,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
await expect(
|
2026-05-20 01:36:54 +02:00
|
|
|
runKtxCli(['admin', 'runtime', 'start', '--feature', 'local-embeddings', '--force'], startIo.io, { runtime }),
|
2026-05-11 15:50:34 +02:00
|
|
|
).resolves.toBe(0);
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', 'runtime', 'stop'], stopIo.io, { runtime })).resolves.toBe(0);
|
|
|
|
|
await expect(runKtxCli(['admin', 'runtime', 'stop', '--all'], stopAllIo.io, { runtime })).resolves.toBe(0);
|
|
|
|
|
await expect(runKtxCli(['admin', 'runtime', 'status', '--json'], statusIo.io, { runtime })).resolves.toBe(0);
|
|
|
|
|
await expect(runKtxCli(['admin', 'runtime', 'prune', '--dry-run'], pruneIo.io, { runtime })).resolves.toBe(1);
|
2026-05-11 15:50:34 +02:00
|
|
|
|
|
|
|
|
expect(runtime).toHaveBeenNthCalledWith(
|
|
|
|
|
1,
|
|
|
|
|
{
|
|
|
|
|
command: 'install',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
feature: 'local-embeddings',
|
|
|
|
|
force: true,
|
|
|
|
|
},
|
|
|
|
|
installIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(runtime).toHaveBeenNthCalledWith(
|
|
|
|
|
2,
|
|
|
|
|
{
|
|
|
|
|
command: 'start',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: expect.any(String),
|
2026-05-11 15:50:34 +02:00
|
|
|
feature: 'local-embeddings',
|
|
|
|
|
force: true,
|
|
|
|
|
},
|
|
|
|
|
startIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(runtime).toHaveBeenNthCalledWith(
|
|
|
|
|
3,
|
|
|
|
|
{
|
|
|
|
|
command: 'stop',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: expect.any(String),
|
2026-05-12 13:00:08 +02:00
|
|
|
all: false,
|
2026-05-11 15:50:34 +02:00
|
|
|
},
|
|
|
|
|
stopIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(runtime).toHaveBeenNthCalledWith(
|
|
|
|
|
4,
|
2026-05-12 13:00:08 +02:00
|
|
|
{
|
|
|
|
|
command: 'stop',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: expect.any(String),
|
2026-05-12 13:00:08 +02:00
|
|
|
all: true,
|
|
|
|
|
},
|
|
|
|
|
stopAllIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(runtime).toHaveBeenNthCalledWith(
|
|
|
|
|
5,
|
2026-05-11 15:50:34 +02:00
|
|
|
{
|
|
|
|
|
command: 'status',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
json: true,
|
|
|
|
|
},
|
|
|
|
|
statusIo.io,
|
|
|
|
|
);
|
2026-05-13 12:28:24 +02:00
|
|
|
expect(runtime).toHaveBeenCalledTimes(5);
|
|
|
|
|
for (const io of [installIo, startIo, stopIo, stopAllIo, statusIo]) {
|
2026-05-12 23:51:46 +02:00
|
|
|
expect(io.stderr()).toBe('');
|
|
|
|
|
}
|
2026-05-13 12:28:24 +02:00
|
|
|
expect(pruneIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-11 15:50:34 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-12 15:04:01 +02:00
|
|
|
it('prints the resolved project directory for ordinary project commands', async () => {
|
|
|
|
|
const connection = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, 'connection', 'list'], testIo.io, { connection })).resolves.toBe(
|
|
|
|
|
0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(connection).toHaveBeenCalledWith({ command: 'list', projectDir: tempDir }, testIo.io);
|
|
|
|
|
expect(testIo.stderr()).toBe(`Project: ${tempDir}\n`);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 09:16:35 -07:00
|
|
|
it('does not print the command-level project directory line for setup', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, 'setup', '--no-input'], testIo.io, { setup })).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('skips the project directory line for JSON output mode', async () => {
|
|
|
|
|
const publicIngest = vi.fn(async () => 0);
|
2026-05-12 15:04:01 +02:00
|
|
|
const jsonIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-14 01:43:06 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--json'], jsonIo.io, { publicIngest }),
|
2026-05-12 15:04:01 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(jsonIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-12 13:00:08 +02:00
|
|
|
it('documents runtime stop all in command help', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', 'runtime', 'stop', '--help'], testIo.io)).resolves.toBe(0);
|
2026-05-12 13:00:08 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stdout()).toContain('--all');
|
|
|
|
|
expect(testIo.stdout()).toContain('Stop all KTX daemon processes recorded or discoverable');
|
|
|
|
|
expect(testIo.stdout()).toContain('on this machine');
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-11 15:50:34 +02:00
|
|
|
it('routes sl query managed runtime install policies', async () => {
|
|
|
|
|
const sl = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
const promptIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'sl', 'query', '--measure', 'orders.order_count'], promptIo.io, { sl }),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(sl).toHaveBeenLastCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'query',
|
|
|
|
|
projectDir: tempDir,
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
runtimeInstallPolicy: 'prompt',
|
|
|
|
|
query: expect.objectContaining({ measures: ['orders.order_count'], dimensions: [] }),
|
|
|
|
|
}),
|
|
|
|
|
promptIo.io,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const autoIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'sl', 'query', '--measure', 'orders.order_count', '--yes'], autoIo.io, {
|
|
|
|
|
sl,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(sl).toHaveBeenLastCalledWith(
|
|
|
|
|
expect.objectContaining({
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
runtimeInstallPolicy: 'auto',
|
|
|
|
|
}),
|
|
|
|
|
autoIo.io,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const noInputIo = makeIo();
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'sl', 'query', '--measure', 'orders.order_count', '--no-input'],
|
|
|
|
|
noInputIo.io,
|
|
|
|
|
{ sl },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
expect(sl).toHaveBeenLastCalledWith(
|
|
|
|
|
expect.objectContaining({
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
runtimeInstallPolicy: 'never',
|
|
|
|
|
}),
|
|
|
|
|
noInputIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects conflicting sl query runtime install flags', async () => {
|
|
|
|
|
const io = makeIo();
|
|
|
|
|
const sl = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'sl', 'query', '--measure', 'orders.order_count', '--yes', '--no-input'],
|
|
|
|
|
io.io,
|
|
|
|
|
{ sl },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(sl).not.toHaveBeenCalled();
|
|
|
|
|
expect(io.stderr()).toContain('Choose only one runtime install mode: --yes or --no-input');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 17:55:25 -04:00
|
|
|
it('documents setup with only the common interactive options visible', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['setup', '--help'], testIo.io)).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 17:55:25 -04:00
|
|
|
const stdout = testIo.stdout();
|
|
|
|
|
expect(stdout).toContain('Usage: ktx setup [options]');
|
|
|
|
|
expect(stdout).toContain('--agents');
|
|
|
|
|
expect(stdout).toContain('--target <target>');
|
|
|
|
|
expect(stdout).toContain('--global');
|
2026-05-15 02:35:09 +02:00
|
|
|
expect(stdout).toContain('--local');
|
2026-05-13 17:55:25 -04:00
|
|
|
expect(stdout).toContain('--yes');
|
|
|
|
|
expect(stdout).toContain('--no-input');
|
|
|
|
|
expect(stdout).toContain('Global Options:');
|
|
|
|
|
expect(stdout.match(/--project-dir <path>/g)).toHaveLength(1);
|
|
|
|
|
expect(stdout).not.toContain('Commands:');
|
|
|
|
|
expect(stdout).not.toContain('setup demo');
|
|
|
|
|
expect(stdout).not.toContain('setup context');
|
|
|
|
|
|
|
|
|
|
for (const hiddenFlag of [
|
|
|
|
|
'--agent-scope',
|
|
|
|
|
'--skip-agents',
|
|
|
|
|
'--llm-backend',
|
|
|
|
|
'--anthropic-api-key-env',
|
|
|
|
|
'--vertex-project',
|
|
|
|
|
'--embedding-backend',
|
|
|
|
|
'--database ',
|
|
|
|
|
'--database-connection-id',
|
|
|
|
|
'--enable-historic-sql',
|
|
|
|
|
'--historic-sql-min-executions',
|
2026-05-14 01:43:06 +02:00
|
|
|
'--enable-query-history',
|
|
|
|
|
'--disable-query-history',
|
|
|
|
|
'--query-history-window-days',
|
|
|
|
|
'--query-history-min-executions',
|
|
|
|
|
'--query-history-service-account-pattern',
|
|
|
|
|
'--query-history-redaction-pattern',
|
2026-05-13 17:55:25 -04:00
|
|
|
'--skip-databases',
|
|
|
|
|
'--source ',
|
|
|
|
|
'--source-connection-id',
|
|
|
|
|
'--metabase-database-id',
|
|
|
|
|
'--notion-root-page-id',
|
|
|
|
|
'--skip-initial-source-ingest',
|
|
|
|
|
'--skip-sources',
|
|
|
|
|
'--skip-llm',
|
|
|
|
|
'--skip-embeddings',
|
|
|
|
|
'--embedding-model',
|
|
|
|
|
'--embedding-dimensions',
|
|
|
|
|
'--embedding-base-url',
|
|
|
|
|
]) {
|
|
|
|
|
expect(stdout).not.toContain(hiddenFlag);
|
|
|
|
|
}
|
|
|
|
|
expect(stdout).not.toMatch(/^ --project\s/m);
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(stdout).not.toContain('primary ' + 'source');
|
|
|
|
|
expect(stdout).not.toContain('primary ' + 'sources');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
it('prints help for bare ktx outside a TTY', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo({ stdoutIsTty: false });
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-12 11:36:15 +02:00
|
|
|
it('keeps representative JSON command stdout parseable', async () => {
|
|
|
|
|
const projectDir = join(tempDir, 'project');
|
2026-05-14 17:39:31 +02:00
|
|
|
await initKtxProject({ projectDir });
|
2026-05-12 11:36:15 +02:00
|
|
|
const commands = [
|
2026-05-13 00:38:26 +02:00
|
|
|
['--project-dir', projectDir, 'status', '--json'],
|
2026-05-20 01:52:37 +02:00
|
|
|
['--project-dir', projectDir, 'sl', '--json'],
|
2026-05-12 11:36:15 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const argv of commands) {
|
|
|
|
|
const testIo = makeIo();
|
2026-05-13 00:38:26 +02:00
|
|
|
const code = await runKtxCli(argv, testIo.io);
|
|
|
|
|
expect([0, 1]).toContain(code);
|
2026-05-12 11:36:15 +02:00
|
|
|
|
|
|
|
|
expect(() => JSON.parse(testIo.stdout())).not.toThrow();
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
it('starts setup for bare ktx in a TTY when no project is discoverable', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
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();
|
2026-05-10 23:51:24 +02:00
|
|
|
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-bare-setup-'));
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo({ stdoutIsTty: true });
|
2026-05-10 23:51:24 +02:00
|
|
|
const previousProjectDir = process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
const expectedProjectDir = await realpath(tempDir);
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-10 23:51:24 +02:00
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
process.chdir(tempDir);
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
{
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: expectedProjectDir,
|
|
|
|
|
mode: 'auto',
|
|
|
|
|
agents: false,
|
|
|
|
|
agentScope: 'project',
|
|
|
|
|
skipAgents: false,
|
|
|
|
|
inputMode: 'auto',
|
|
|
|
|
yes: false,
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-10 23:12:26 +02:00
|
|
|
skipLlm: false,
|
|
|
|
|
skipEmbeddings: false,
|
|
|
|
|
databaseSchemas: [],
|
|
|
|
|
skipDatabases: false,
|
|
|
|
|
skipSources: false,
|
|
|
|
|
},
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).not.toContain('Usage: ktx [options] [command]');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd);
|
|
|
|
|
if (previousProjectDir === undefined) {
|
2026-05-10 23:51:24 +02:00
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
} else {
|
2026-05-10 23:51:24 +02:00
|
|
|
process.env.KTX_PROJECT_DIR = previousProjectDir;
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
it('prints help without project status for bare ktx in a TTY when a project is discoverable', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
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();
|
2026-05-10 23:51:24 +02:00
|
|
|
const previousProjectDir = process.env.KTX_PROJECT_DIR;
|
|
|
|
|
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-bare-existing-'));
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo({ stdoutIsTty: true });
|
|
|
|
|
const expectedProjectDir = await realpath(tempDir);
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-10 23:51:24 +02:00
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
2026-05-14 17:39:31 +02:00
|
|
|
await writeFile(join(tempDir, 'ktx.yaml'), 'connections: {}\n', 'utf-8');
|
2026-05-10 23:12:26 +02:00
|
|
|
process.chdir(tempDir);
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stdout()).not.toContain(`Project: ${expectedProjectDir}`);
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd);
|
|
|
|
|
if (previousProjectDir === undefined) {
|
2026-05-10 23:51:24 +02:00
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
} else {
|
2026-05-10 23:51:24 +02:00
|
|
|
process.env.KTX_PROJECT_DIR = previousProjectDir;
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
it('does not invoke status for bare ktx in a TTY when status would fail', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
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 });
|
2026-05-10 23:51:24 +02:00
|
|
|
const previousProjectDir = process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
try {
|
2026-05-10 23:51:24 +02:00
|
|
|
process.env.KTX_PROJECT_DIR = tempDir;
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli([], testIo.io, { setup })).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
} finally {
|
|
|
|
|
if (previousProjectDir === undefined) {
|
2026-05-10 23:51:24 +02:00
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
2026-05-10 23:12:26 +02:00
|
|
|
} else {
|
2026-05-10 23:51:24 +02:00
|
|
|
process.env.KTX_PROJECT_DIR = previousProjectDir;
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects removed verbose global option through Commander', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const removedVerboseOption = '--' + 'verbose';
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli([removedVerboseOption, 'connection', 'list'], testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toContain(`unknown option '${removedVerboseOption}'`);
|
|
|
|
|
expect(testIo.stdout()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 12:00:08 +02:00
|
|
|
it('rejects removed shell completion commands', async () => {
|
|
|
|
|
const completionIo = makeIo();
|
|
|
|
|
const hiddenIo = makeIo();
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', 'completion', 'zsh'], completionIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
await expect(
|
2026-05-20 01:36:54 +02:00
|
|
|
runKtxCli(['admin', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', 'co'], hiddenIo.io),
|
2026-05-13 12:00:08 +02:00
|
|
|
).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 12:00:08 +02:00
|
|
|
expect(completionIo.stderr()).toMatch(/unknown command|error:/);
|
|
|
|
|
expect(hiddenIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
it('rejects removed serve commands', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, 'serve', '--mcp', 'stdio', '--user-id', 'agent'], testIo.io))
|
|
|
|
|
.resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('routes public connection-centric ingest shorthand', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
await expect(
|
2026-05-14 14:35:55 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--fast', '--no-input'], testIo.io, {
|
2026-05-14 01:43:06 +02:00
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).toHaveBeenCalledWith(
|
|
|
|
|
{
|
|
|
|
|
command: 'run',
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: tempDir,
|
2026-05-14 01:43:06 +02:00
|
|
|
targetConnectionId: 'warehouse',
|
|
|
|
|
all: false,
|
|
|
|
|
json: false,
|
|
|
|
|
inputMode: 'disabled',
|
|
|
|
|
depth: 'fast',
|
|
|
|
|
queryHistory: 'default',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 01:43:06 +02:00
|
|
|
runtimeInstallPolicy: 'never',
|
|
|
|
|
},
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
2026-05-14 14:35:55 +02:00
|
|
|
expect(testIo.stderr()).toBe(`Project: ${tempDir}\n`);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('routes public ingest --all --deep with JSON output', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
await expect(
|
2026-05-14 14:35:55 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', '--all', '--deep', '--json'], testIo.io, {
|
2026-05-14 01:43:06 +02:00
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).toHaveBeenCalledWith(
|
|
|
|
|
{
|
|
|
|
|
command: 'run',
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: tempDir,
|
2026-05-14 01:43:06 +02:00
|
|
|
all: true,
|
|
|
|
|
json: true,
|
|
|
|
|
inputMode: 'auto',
|
|
|
|
|
depth: 'deep',
|
|
|
|
|
queryHistory: 'default',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 01:43:06 +02:00
|
|
|
runtimeInstallPolicy: 'prompt',
|
|
|
|
|
},
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-17 10:27:29 +02:00
|
|
|
it('routes public ingest --yes as automatic runtime installation', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--yes'], testIo.io, {
|
|
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(publicIngest).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
targetConnectionId: 'warehouse',
|
|
|
|
|
runtimeInstallPolicy: 'auto',
|
|
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects conflicting public ingest runtime install modes', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--yes', '--no-input'], testIo.io, {
|
|
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toContain('Choose only one runtime install mode: --yes or --no-input');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('rejects mutually exclusive public ingest depth flags before dispatch', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
await expect(
|
2026-05-14 01:43:06 +02:00
|
|
|
runKtxCli(['--project-dir', '/tmp/project', 'ingest', 'warehouse', '--fast', '--deep'], testIo.io, {
|
|
|
|
|
publicIngest,
|
2026-05-10 23:12:26 +02:00
|
|
|
}),
|
2026-05-14 01:43:06 +02:00
|
|
|
).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toMatch(/option '--(deep|fast)' cannot be used with option '--(fast|deep)'/);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it.each(['run', 'status', 'watch', 'replay'])(
|
|
|
|
|
'routes former ingest subcommand name "%s" as a connection id',
|
|
|
|
|
async (connectionId) => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const publicIngest = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-14 14:35:55 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', connectionId, '--no-input'], testIo.io, {
|
2026-05-14 01:43:06 +02:00
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(publicIngest).toHaveBeenCalledWith(
|
|
|
|
|
{
|
|
|
|
|
command: 'run',
|
2026-05-14 14:35:55 +02:00
|
|
|
projectDir: tempDir,
|
2026-05-14 01:43:06 +02:00
|
|
|
targetConnectionId: connectionId,
|
|
|
|
|
all: false,
|
|
|
|
|
json: false,
|
|
|
|
|
inputMode: 'disabled',
|
|
|
|
|
queryHistory: 'default',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-14 01:43:06 +02:00
|
|
|
runtimeInstallPolicy: 'never',
|
|
|
|
|
},
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('rejects standalone demo commands', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
await expect(runKtxCli(['demo', '--mode', 'replay', '--no-input'], testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/i);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
it('rejects removed setup subcommands', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const cases = [
|
|
|
|
|
['setup', 'demo', '--mode', 'replay', '--no-input'],
|
|
|
|
|
['setup', '--no-input', 'demo', '--mode', 'seeded'],
|
|
|
|
|
['setup', 'demo', 'ingest', '--mode', 'full', '--no-input'],
|
|
|
|
|
['setup', 'context', 'build'],
|
|
|
|
|
['setup', 'context', 'watch', 'setup-context-local-1'],
|
|
|
|
|
['setup', 'context', 'status', 'setup-context-local-1', '--json'],
|
|
|
|
|
['setup', 'context', 'stop', 'setup-context-local-1'],
|
|
|
|
|
['setup', 'remove', '--agents'],
|
|
|
|
|
['setup', 'status', '--json'],
|
|
|
|
|
];
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
for (const args of cases) {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, ...args], testIo.io, { setup })).resolves.toBe(1);
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/i);
|
|
|
|
|
}
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
expect(setup).not.toHaveBeenCalled();
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-13 17:55:25 -04:00
|
|
|
it('rejects removed setup options', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const cases = [
|
2026-05-19 19:23:35 +02:00
|
|
|
['setup', '--new'],
|
|
|
|
|
['setup', '--existing'],
|
2026-05-13 17:55:25 -04:00
|
|
|
['setup', '--project'],
|
|
|
|
|
['setup', '--agent-scope', 'global'],
|
2026-05-19 19:23:35 +02:00
|
|
|
['setup', '--anthropic-model', 'claude-sonnet-4-6'],
|
|
|
|
|
['setup', '--new-database-connection-id', 'warehouse'],
|
2026-05-13 17:55:25 -04:00
|
|
|
['setup', '--skip-initial-source-ingest'],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const args of cases) {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, ...args], testIo.io, { setup })).resolves.toBe(1);
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown option|error:/i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 12:00:08 +02:00
|
|
|
it('prints ingest help without invoking ingest execution', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn();
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
await expect(runKtxCli(['ingest', '--help'], testIo.io, { publicIngest })).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx ingest');
|
|
|
|
|
expect(testIo.stdout()).toContain('Build or inspect KTX context');
|
|
|
|
|
expect(testIo.stdout()).toContain('--all');
|
|
|
|
|
expect(testIo.stdout()).toContain('--fast');
|
|
|
|
|
expect(testIo.stdout()).toContain('--deep');
|
|
|
|
|
expect(testIo.stdout()).toContain('--query-history');
|
|
|
|
|
expect(testIo.stdout()).toContain('--no-query-history');
|
|
|
|
|
expect(testIo.stdout()).toContain('--query-history-window-days <days>');
|
2026-05-20 01:52:37 +02:00
|
|
|
expect(testIo.stdout()).toContain('--text');
|
|
|
|
|
expect(testIo.stdout()).toContain('--file');
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stdout()).not.toMatch(/^ status\s/m);
|
|
|
|
|
expect(testIo.stdout()).not.toMatch(/^ replay\s/m);
|
|
|
|
|
expect(testIo.stdout()).not.toMatch(/^ run\s/m);
|
|
|
|
|
expect(testIo.stdout()).not.toMatch(/^ watch\s/m);
|
2026-05-13 19:32:49 +02:00
|
|
|
expect(testIo.stdout()).not.toContain('--manifest');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stderr()).toBe('');
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-13 19:32:49 +02:00
|
|
|
it('routes text memory ingest through Commander without exposing chat ids', async () => {
|
|
|
|
|
const textIngest = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'ingest',
|
|
|
|
|
'--text',
|
|
|
|
|
'Revenue means gross receipts.',
|
|
|
|
|
'--text',
|
|
|
|
|
'Orders are completed purchases.',
|
|
|
|
|
'--connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--user-id',
|
|
|
|
|
'agent',
|
|
|
|
|
'--json',
|
|
|
|
|
'--fail-fast',
|
|
|
|
|
],
|
|
|
|
|
testIo.io,
|
|
|
|
|
{ textIngest },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(textIngest).toHaveBeenCalledWith(
|
|
|
|
|
{
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
texts: ['Revenue means gross receipts.', 'Orders are completed purchases.'],
|
|
|
|
|
files: [],
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
userId: 'agent',
|
|
|
|
|
json: true,
|
|
|
|
|
failFast: true,
|
|
|
|
|
},
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
it('rejects a positional connection id when --text is supplied', async () => {
|
2026-05-13 19:32:49 +02:00
|
|
|
const textIngest = vi.fn(async () => 0);
|
2026-05-20 01:52:37 +02:00
|
|
|
const publicIngest = vi.fn(async () => 0);
|
2026-05-13 19:32:49 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-20 01:52:37 +02:00
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'ingest', 'warehouse', '--text', 'hello'],
|
|
|
|
|
testIo.io,
|
|
|
|
|
{ textIngest, publicIngest },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
2026-05-13 19:32:49 +02:00
|
|
|
|
|
|
|
|
expect(textIngest).not.toHaveBeenCalled();
|
2026-05-20 01:52:37 +02:00
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toMatch(/--text\/--file does not accept a positional connection id/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('treats bare ingest as ingest --all', async () => {
|
|
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'ingest', '--no-input'], testIo.io, { publicIngest }),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(publicIngest).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
all: true,
|
|
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
const args = publicIngest.mock.calls[0]?.[0] as { targetConnectionId?: string };
|
|
|
|
|
expect(args.targetConnectionId).toBeUndefined();
|
2026-05-13 19:32:49 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
it('rejects old adapter-backed ingest flags at the top level and under admin', async () => {
|
2026-05-14 01:43:06 +02:00
|
|
|
const rootRunIo = makeIo();
|
2026-05-10 23:12:26 +02:00
|
|
|
const devRunIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn(async () => 0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
await expect(
|
2026-05-14 01:43:06 +02:00
|
|
|
runKtxCli(['ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], rootRunIo.io, {
|
|
|
|
|
publicIngest,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
await expect(
|
2026-05-20 01:36:54 +02:00
|
|
|
runKtxCli(['admin', 'ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], devRunIo.io, {
|
2026-05-14 01:43:06 +02:00
|
|
|
publicIngest,
|
2026-05-10 23:12:26 +02:00
|
|
|
}),
|
2026-05-13 12:00:08 +02:00
|
|
|
).resolves.toBe(1);
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
|
|
|
expect(rootRunIo.stderr()).toMatch(/unknown option '--connection-id'|error:/);
|
2026-05-13 12:00:08 +02:00
|
|
|
expect(devRunIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
it('rejects removed admin doctor and removed ingest parser cases', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const doctor = vi.fn(async () => 0);
|
|
|
|
|
const doctorIo = makeIo();
|
|
|
|
|
const ingestRunIo = makeIo();
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', 'doctor', 'setup', '--json', '--no-input'], doctorIo.io, { doctor })).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'ingest',
|
|
|
|
|
'run',
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'--connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--adapter',
|
|
|
|
|
'fake',
|
|
|
|
|
'--source-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'--debug-llm-request-file',
|
|
|
|
|
`${tempDir}/debug.jsonl`,
|
|
|
|
|
'--json',
|
|
|
|
|
'--no-input',
|
|
|
|
|
],
|
|
|
|
|
ingestRunIo.io,
|
2026-05-14 01:43:06 +02:00
|
|
|
{},
|
2026-05-10 23:12:26 +02:00
|
|
|
),
|
2026-05-14 01:43:06 +02:00
|
|
|
).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
expect(doctor).not.toHaveBeenCalled();
|
|
|
|
|
expect(doctorIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(ingestRunIo.stderr()).toMatch(/unknown option '--connection-id'|error:/);
|
2026-05-11 15:50:34 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('dispatches public connection through the existing connection implementation', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const connection = vi.fn(async () => 0);
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['--project-dir', tempDir, 'connection', 'list'], testIo.io, { connection })).resolves.toBe(
|
2026-05-10 23:12:26 +02:00
|
|
|
0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(connection).toHaveBeenCalledWith({ command: 'list', projectDir: tempDir }, testIo.io);
|
2026-05-12 15:04:01 +02:00
|
|
|
expect(testIo.stderr()).toBe(`Project: ${tempDir}\n`);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
it('routes top-level status through doctor', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
2026-05-12 23:51:46 +02:00
|
|
|
const doctor = vi.fn(async () => 0);
|
2026-05-10 23:12:26 +02:00
|
|
|
const statusIo = makeIo();
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'status', '--json', '--no-input'], statusIo.io, { setup, doctor }),
|
|
|
|
|
).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
expect(setup).not.toHaveBeenCalled();
|
2026-05-12 23:51:46 +02:00
|
|
|
expect(doctor).toHaveBeenCalledWith(
|
2026-05-21 14:13:03 +02:00
|
|
|
{ command: 'project', projectDir: tempDir, outputMode: 'json', inputMode: 'disabled', verbose: false, fast: false },
|
2026-05-12 23:51:46 +02:00
|
|
|
statusIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(statusIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('routes top-level status without a project to setup doctor checks', async () => {
|
|
|
|
|
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 previousProjectDir = process.env.KTX_PROJECT_DIR;
|
|
|
|
|
const tempCwd = await mkdtemp(join(tmpdir(), 'ktx-status-no-project-'));
|
|
|
|
|
const doctor = vi.fn(async () => 0);
|
|
|
|
|
const statusIo = makeIo();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
|
|
|
|
process.chdir(tempCwd);
|
|
|
|
|
|
|
|
|
|
await expect(runKtxCli(['status', '--json', '--no-input'], statusIo.io, { doctor })).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(doctor).toHaveBeenCalledWith(
|
feat(cli): redesign ktx status output UX (#80)
* feat(cli): redesign ktx status output with grouped checks and color
Replace flat PASS/FAIL/WARN text output with a grouped, symbol-based
layout (Environment, Project, Semantic search, Query history). Passing
groups collapse to a single summary line; failing groups expand to show
individual checks with fix hints. Adds --verbose flag to show all checks
including passing ones, color support for TTY terminals, a dedicated
setup-mode report that guides users toward `ktx setup`, and timing info.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(cli): extract project checks and historic SQL doctor into status-project
Move project-level doctor checks, semantic search embedding checks, and
historic SQL doctor logic from doctor.ts into a dedicated status-project.ts
module. Removes historic-sql-doctor.ts and its test file, consolidating
everything into the new module with its own tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-13 18:47:58 -04:00
|
|
|
{ command: 'setup', outputMode: 'json', inputMode: 'disabled', verbose: false },
|
2026-05-12 23:51:46 +02:00
|
|
|
statusIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(statusIo.stderr()).toBe('');
|
|
|
|
|
} finally {
|
|
|
|
|
process.chdir(originalCwd);
|
|
|
|
|
if (previousProjectDir === undefined) {
|
|
|
|
|
delete process.env.KTX_PROJECT_DIR;
|
|
|
|
|
} else {
|
|
|
|
|
process.env.KTX_PROJECT_DIR = previousProjectDir;
|
|
|
|
|
}
|
|
|
|
|
await rm(tempCwd, { recursive: true, force: true });
|
|
|
|
|
}
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('dispatches Anthropic setup flags to the setup runner', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--anthropic-api-key-env',
|
|
|
|
|
'ANTHROPIC_API_KEY',
|
2026-05-19 19:23:35 +02:00
|
|
|
'--llm-model',
|
2026-05-10 23:12:26 +02:00
|
|
|
'claude-sonnet-4-6',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
inputMode: 'disabled',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-12 16:56:58 -04:00
|
|
|
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
2026-05-19 19:23:35 +02:00
|
|
|
llmModel: 'claude-sonnet-4-6',
|
2026-05-10 23:12:26 +02:00
|
|
|
skipLlm: false,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 08:42:38 -04:00
|
|
|
it('dispatches Vertex AI setup flags to the setup runner', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--llm-backend',
|
|
|
|
|
'vertex',
|
|
|
|
|
'--vertex-project',
|
|
|
|
|
'local-gcp-project',
|
|
|
|
|
'--vertex-location',
|
|
|
|
|
'us-east5',
|
2026-05-19 19:23:35 +02:00
|
|
|
'--llm-model',
|
2026-05-13 08:42:38 -04:00
|
|
|
'claude-sonnet-4-6',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
inputMode: 'disabled',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-13 08:42:38 -04:00
|
|
|
llmBackend: 'vertex',
|
|
|
|
|
vertexProject: 'local-gcp-project',
|
|
|
|
|
vertexLocation: 'us-east5',
|
2026-05-19 19:23:35 +02:00
|
|
|
llmModel: 'claude-sonnet-4-6',
|
2026-05-13 08:42:38 -04:00
|
|
|
skipLlm: false,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-16 12:06:34 +02:00
|
|
|
it('dispatches the provider-neutral LLM model setup flag to the setup runner', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--llm-backend',
|
|
|
|
|
'claude-code',
|
|
|
|
|
'--llm-model',
|
|
|
|
|
'opus',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
inputMode: 'disabled',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-16 12:06:34 +02:00
|
|
|
llmBackend: 'claude-code',
|
|
|
|
|
llmModel: 'opus',
|
|
|
|
|
skipLlm: false,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('rejects conflicting Anthropic credential setup flags', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--anthropic-api-key-env',
|
|
|
|
|
'ANTHROPIC_API_KEY',
|
|
|
|
|
'--anthropic-api-key-file',
|
|
|
|
|
'/tmp/anthropic-key',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(setupIo.stderr()).toContain('Choose only one Anthropic credential source');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('dispatches embedding setup flags to the setup runner', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--skip-llm',
|
|
|
|
|
'--embedding-backend',
|
|
|
|
|
'openai',
|
|
|
|
|
'--embedding-api-key-env',
|
|
|
|
|
'OPENAI_API_KEY',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
inputMode: 'disabled',
|
|
|
|
|
skipLlm: true,
|
|
|
|
|
embeddingBackend: 'openai',
|
2026-05-12 16:56:58 -04:00
|
|
|
embeddingApiKeyEnv: 'OPENAI_API_KEY', // pragma: allowlist secret
|
2026-05-10 23:12:26 +02:00
|
|
|
skipEmbeddings: false,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('dispatches database setup flags to the setup runner', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'setup',
|
|
|
|
|
'--project-dir',
|
|
|
|
|
'/tmp/project',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--yes',
|
|
|
|
|
'--skip-llm',
|
|
|
|
|
'--skip-embeddings',
|
|
|
|
|
'--database',
|
|
|
|
|
'postgres',
|
2026-05-19 19:23:35 +02:00
|
|
|
'--database-connection-id',
|
2026-05-10 23:12:26 +02:00
|
|
|
'warehouse',
|
|
|
|
|
'--database-url',
|
|
|
|
|
'env:DATABASE_URL',
|
|
|
|
|
'--database-schema',
|
|
|
|
|
'public',
|
2026-05-14 01:43:06 +02:00
|
|
|
'--enable-query-history',
|
|
|
|
|
'--query-history-window-days',
|
2026-05-10 23:12:26 +02:00
|
|
|
'30',
|
2026-05-14 01:43:06 +02:00
|
|
|
'--query-history-min-executions',
|
2026-05-10 23:12:26 +02:00
|
|
|
'12',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: '/tmp/project',
|
|
|
|
|
inputMode: 'disabled',
|
|
|
|
|
yes: true,
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-10 23:12:26 +02:00
|
|
|
skipLlm: true,
|
|
|
|
|
skipEmbeddings: true,
|
|
|
|
|
databaseDrivers: ['postgres'],
|
|
|
|
|
databaseConnectionId: 'warehouse',
|
|
|
|
|
databaseUrl: 'env:DATABASE_URL',
|
|
|
|
|
databaseSchemas: ['public'],
|
2026-05-14 01:43:06 +02:00
|
|
|
enableQueryHistory: true,
|
|
|
|
|
queryHistoryWindowDays: 30,
|
|
|
|
|
queryHistoryMinExecutions: 12,
|
2026-05-10 23:12:26 +02:00
|
|
|
skipDatabases: false,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('dispatches setup database connection ids that match former ingest subcommand names', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-19 19:23:35 +02:00
|
|
|
runKtxCli(['setup', '--database-connection-id', 'status', '--no-input'], testIo.io, { setup }),
|
2026-05-14 01:43:06 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
2026-05-19 19:23:35 +02:00
|
|
|
databaseConnectionIds: ['status'],
|
2026-05-14 01:43:06 +02:00
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-19 19:23:35 +02:00
|
|
|
it('dispatches non-TTY agents setup with target without requiring --no-input or --yes', async () => {
|
|
|
|
|
const testIo = makeIo({ stdoutIsTty: false });
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code'], testIo.io, { setup }),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
agents: true,
|
|
|
|
|
target: 'claude-code',
|
|
|
|
|
agentScope: 'project',
|
|
|
|
|
inputMode: 'auto',
|
|
|
|
|
yes: false,
|
|
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('dispatches setup source flags', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--source',
|
|
|
|
|
'metabase',
|
|
|
|
|
'--source-connection-id',
|
|
|
|
|
'prod_metabase',
|
|
|
|
|
'--source-url',
|
|
|
|
|
'https://metabase.example.com',
|
|
|
|
|
'--source-api-key-ref',
|
|
|
|
|
'env:METABASE_API_KEY',
|
|
|
|
|
'--source-warehouse-connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--metabase-database-id',
|
|
|
|
|
'1',
|
|
|
|
|
'--skip-llm',
|
|
|
|
|
'--skip-embeddings',
|
|
|
|
|
'--skip-databases',
|
|
|
|
|
],
|
|
|
|
|
testIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(setup).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
projectDir: tempDir,
|
|
|
|
|
source: 'metabase',
|
|
|
|
|
sourceConnectionId: 'prod_metabase',
|
|
|
|
|
sourceUrl: 'https://metabase.example.com',
|
2026-05-12 16:56:58 -04:00
|
|
|
sourceApiKeyRef: 'env:METABASE_API_KEY', // pragma: allowlist secret
|
2026-05-10 23:12:26 +02:00
|
|
|
sourceWarehouseConnectionId: 'warehouse',
|
|
|
|
|
metabaseDatabaseId: 1,
|
|
|
|
|
}),
|
|
|
|
|
testIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
it('dispatches setup agent flags', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--agents',
|
|
|
|
|
'--target',
|
|
|
|
|
'codex',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--yes',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
2026-05-13 00:38:26 +02:00
|
|
|
expect(setup).toHaveBeenCalledWith(
|
2026-05-10 23:12:26 +02:00
|
|
|
expect.objectContaining({
|
|
|
|
|
command: 'run',
|
|
|
|
|
agents: true,
|
|
|
|
|
target: 'codex',
|
|
|
|
|
agentScope: 'project',
|
|
|
|
|
inputMode: 'disabled',
|
|
|
|
|
yes: true,
|
|
|
|
|
}),
|
|
|
|
|
setupIo.io,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-15 02:35:09 +02:00
|
|
|
it('rejects --local with non-Claude targets', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'setup', '--agents', '--target', 'cursor', '--local', '--no-input'],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setupIo.stderr()).toContain('--local is only supported with --target claude-code');
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects --local and --global together', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code', '--local', '--global', '--no-input'],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setupIo.stderr()).toContain('Choose only one agent scope: --local or --global.');
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('rejects source-path with source-git-url', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--no-input',
|
|
|
|
|
'--source',
|
|
|
|
|
'dbt',
|
|
|
|
|
'--source-path',
|
|
|
|
|
'/repo/dbt',
|
|
|
|
|
'--source-git-url',
|
|
|
|
|
'https://github.com/acme/dbt.git',
|
|
|
|
|
],
|
|
|
|
|
testIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(testIo.stderr()).toContain('Choose only one source location');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects deterministic as a setup embedding backend', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'deterministic'], setupIo.io, { setup }),
|
2026-05-10 23:12:26 +02:00
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(setupIo.stderr()).toContain("invalid choice 'deterministic'");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects gateway as a setup embedding backend', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'setup', '--embedding-backend', 'gateway'], setupIo.io, { setup }),
|
2026-05-10 23:12:26 +02:00
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(setupIo.stderr()).toContain("invalid choice 'gateway'");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects conflicting embedding credential setup flags', async () => {
|
|
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'setup',
|
|
|
|
|
'--embedding-backend',
|
|
|
|
|
'openai',
|
|
|
|
|
'--embedding-api-key-env',
|
|
|
|
|
'OPENAI_API_KEY',
|
|
|
|
|
'--embedding-api-key-file',
|
|
|
|
|
'/tmp/openai-key',
|
|
|
|
|
],
|
|
|
|
|
setupIo.io,
|
|
|
|
|
{ setup },
|
|
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
|
|
|
|
expect(setupIo.stderr()).toContain('Choose only one embedding credential source');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('rejects conflicting query-history setup flags', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const setup = vi.fn(async () => 0);
|
|
|
|
|
const setupIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-14 01:43:06 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'setup', '--enable-query-history', '--disable-query-history'], setupIo.io, {
|
2026-05-10 23:12:26 +02:00
|
|
|
setup,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(setup).not.toHaveBeenCalled();
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(setupIo.stderr()).toContain(
|
|
|
|
|
'Choose only one query-history action: --enable-query-history or --disable-query-history.',
|
|
|
|
|
);
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-13 13:01:56 +02:00
|
|
|
it('rejects the removed hidden agent command', async () => {
|
|
|
|
|
const io = makeIo();
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 13:01:56 +02:00
|
|
|
await expect(runKtxCli(['agent'], io.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 13:01:56 +02:00
|
|
|
expect(io.stderr()).toContain("unknown command 'agent'");
|
|
|
|
|
expect(io.stdout()).toBe('');
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-13 13:01:56 +02:00
|
|
|
it('routes public SL query files with managed runtime policies', async () => {
|
2026-05-11 15:50:34 +02:00
|
|
|
const autoIo = makeIo();
|
|
|
|
|
const neverIo = makeIo();
|
|
|
|
|
const conflictIo = makeIo();
|
2026-05-13 13:01:56 +02:00
|
|
|
const sl = vi.fn(async () => 0);
|
2026-05-11 15:50:34 +02:00
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'sl',
|
|
|
|
|
'query',
|
|
|
|
|
'--connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--query-file',
|
|
|
|
|
'/tmp/query.json',
|
|
|
|
|
'--yes',
|
|
|
|
|
],
|
|
|
|
|
autoIo.io,
|
2026-05-13 13:01:56 +02:00
|
|
|
{ sl },
|
2026-05-11 15:50:34 +02:00
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'sl',
|
|
|
|
|
'query',
|
|
|
|
|
'--connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--query-file',
|
|
|
|
|
'/tmp/query.json',
|
|
|
|
|
'--no-input',
|
|
|
|
|
],
|
|
|
|
|
neverIo.io,
|
2026-05-13 13:01:56 +02:00
|
|
|
{ sl },
|
2026-05-11 15:50:34 +02:00
|
|
|
),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
|
runKtxCli(
|
|
|
|
|
[
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'sl',
|
|
|
|
|
'query',
|
|
|
|
|
'--connection-id',
|
|
|
|
|
'warehouse',
|
|
|
|
|
'--query-file',
|
|
|
|
|
'/tmp/query.json',
|
|
|
|
|
'--yes',
|
|
|
|
|
'--no-input',
|
|
|
|
|
],
|
|
|
|
|
conflictIo.io,
|
2026-05-13 13:01:56 +02:00
|
|
|
{ sl },
|
2026-05-11 15:50:34 +02:00
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
2026-05-13 13:01:56 +02:00
|
|
|
expect(sl).toHaveBeenNthCalledWith(
|
2026-05-11 15:50:34 +02:00
|
|
|
1,
|
|
|
|
|
{
|
2026-05-13 13:01:56 +02:00
|
|
|
command: 'query',
|
2026-05-11 15:50:34 +02:00
|
|
|
projectDir: tempDir,
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
queryFile: '/tmp/query.json',
|
|
|
|
|
execute: false,
|
2026-05-13 13:01:56 +02:00
|
|
|
format: 'json',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
runtimeInstallPolicy: 'auto',
|
|
|
|
|
},
|
|
|
|
|
autoIo.io,
|
|
|
|
|
);
|
2026-05-13 13:01:56 +02:00
|
|
|
expect(sl).toHaveBeenNthCalledWith(
|
2026-05-11 15:50:34 +02:00
|
|
|
2,
|
|
|
|
|
{
|
2026-05-13 13:01:56 +02:00
|
|
|
command: 'query',
|
2026-05-11 15:50:34 +02:00
|
|
|
projectDir: tempDir,
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
queryFile: '/tmp/query.json',
|
|
|
|
|
execute: false,
|
2026-05-13 13:01:56 +02:00
|
|
|
format: 'json',
|
docs(concepts): add Wiki retrieval pillar page (#191)
* docs(concepts): add Wiki retrieval pillar page
Adds a dedicated concept page covering the wiki side of the context
layer: the page contract, the hybrid retrieval pipeline (lexical,
semantic, token lanes fused by RRF), the refs/sl_refs/[[wikilink]]
graph, validation that keeps edges live, and where ingest sources
pages. Wired into concepts nav and cross-linked from the-context-layer
to mirror the existing Semantic querying link.
* test: derive release versions in tests instead of hardcoding 0.1.0-rc.1
After @semantic-release/git started committing version bumps back to the
branch, the 0.4.0 release rewrote package.json, packages/cli/package.json,
and release-policy.json — but the script and CLI tests still pinned the
pre-bump strings (0.0.0-private, 0.1.0-rc.1, 0.1.0rc1), so every new
branch off main failed TypeScript checks and Coverage.
Drive the version off the existing source of truth instead: read
@ktx/cli/package.json via createRequire in the CLI tests, and reuse the
already-imported PUBLIC_NPM_PACKAGE_VERSION / RUNTIME_WHEEL_PACKAGE_VERSION
constants in the script tests. The two assertions that pinned those
constants to specific values become semver shape checks.
2026-05-21 01:26:58 +02:00
|
|
|
cliVersion,
|
2026-05-11 15:50:34 +02:00
|
|
|
runtimeInstallPolicy: 'never',
|
|
|
|
|
},
|
|
|
|
|
neverIo.io,
|
|
|
|
|
);
|
|
|
|
|
expect(conflictIo.stderr()).toContain('Choose only one runtime install mode: --yes or --no-input');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
it('dispatches public connection subcommands through the existing connection implementation', async () => {
|
2026-05-10 23:51:24 +02:00
|
|
|
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-connection-dispatch-'));
|
2026-05-14 17:39:31 +02:00
|
|
|
await writeFile(join(tempDir, 'ktx.yaml'), '{}\n', 'utf-8');
|
2026-05-10 23:12:26 +02:00
|
|
|
const connection = vi.fn(async () => 0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'connection', 'list'], makeIo().io, { connection }),
|
2026-05-10 23:12:26 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
2026-05-13 15:04:50 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-10 23:12:26 +02:00
|
|
|
await expect(
|
2026-05-13 15:04:50 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, 'connection', 'test', 'warehouse'], testIo.io, {
|
2026-05-10 23:12:26 +02:00
|
|
|
connection,
|
|
|
|
|
}),
|
|
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(connection).toHaveBeenNthCalledWith(1, { command: 'list', projectDir: tempDir }, expect.anything());
|
|
|
|
|
expect(connection).toHaveBeenNthCalledWith(
|
|
|
|
|
2,
|
|
|
|
|
{
|
2026-05-13 15:04:50 +02:00
|
|
|
command: 'test',
|
2026-05-10 23:12:26 +02:00
|
|
|
projectDir: tempDir,
|
|
|
|
|
connectionId: 'warehouse',
|
|
|
|
|
},
|
|
|
|
|
expect.anything(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await rm(tempDir, { recursive: true, force: true });
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 15:04:50 +02:00
|
|
|
it('prints only list and test in connection help', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const helpIo = makeIo();
|
|
|
|
|
|
2026-05-13 15:04:50 +02:00
|
|
|
await expect(runKtxCli(['connection', '--help'], helpIo.io)).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(helpIo.stdout()).toContain('Usage: ktx connection');
|
|
|
|
|
expect(helpIo.stdout()).toContain('list');
|
2026-05-14 16:21:18 +02:00
|
|
|
expect(helpIo.stdout()).toContain('test [options] [connectionId]');
|
2026-05-13 15:04:50 +02:00
|
|
|
for (const removed of ['add', 'remove', 'map', 'mapping', 'metabase', 'notion']) {
|
|
|
|
|
expect(helpIo.stdout()).not.toMatch(new RegExp(`\\b${removed}\\b`));
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
expect(helpIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-13 15:04:50 +02:00
|
|
|
it('rejects removed connection subcommands', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
for (const argv of [
|
2026-05-13 15:04:50 +02:00
|
|
|
['connection', 'add', 'postgres', 'warehouse'],
|
|
|
|
|
['connection', 'remove', 'warehouse'],
|
|
|
|
|
['connection', 'map', 'prod-metabase'],
|
|
|
|
|
['connection', 'mapping'],
|
|
|
|
|
['connection', 'metabase'],
|
|
|
|
|
['connection', 'notion'],
|
2026-05-10 23:12:26 +02:00
|
|
|
]) {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-13 15:04:50 +02:00
|
|
|
await expect(runKtxCli(argv, testIo.io)).resolves.toBe(1);
|
|
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
|
|
|
}
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects commands removed from the May 6 root surface', async () => {
|
|
|
|
|
for (const argv of [
|
|
|
|
|
['init'],
|
|
|
|
|
['connect', 'list'],
|
|
|
|
|
['knowledge', 'list'],
|
|
|
|
|
['ask', 'What sources are connected?'],
|
|
|
|
|
]) {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(argv, testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('writes basic debug dispatch information when --debug is set', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
const connection = vi.fn().mockResolvedValue(0);
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(['--project-dir', tempDir, '--debug', 'connection', 'list'], testIo.io, { connection }),
|
2026-05-10 23:12:26 +02:00
|
|
|
).resolves.toBe(0);
|
|
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toContain(`[debug] projectDir=${tempDir}`);
|
|
|
|
|
expect(testIo.stderr()).toContain('[debug] dispatch=connection');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it.each([
|
|
|
|
|
{ argv: ['scan'] },
|
|
|
|
|
{ argv: ['scan', '--help'] },
|
|
|
|
|
{ argv: ['scan', 'warehouse'] },
|
|
|
|
|
{ argv: ['scan', 'warehouse', '--project-dir', '/tmp/project'] },
|
|
|
|
|
{ argv: ['scan', 'warehouse', '--mode', 'relationships'] },
|
|
|
|
|
])('rejects removed top-level scan command $argv', async ({ argv }) => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
2026-05-11 15:50:34 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
await expect(runKtxCli(argv, testIo.io, { publicIngest })).resolves.toBe(1);
|
2026-05-11 15:50:34 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
2026-05-11 15:50:34 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
it('rejects removed public serve command options before dispatch', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const serveIo = makeIo();
|
|
|
|
|
|
|
|
|
|
await expect(
|
2026-05-10 23:51:24 +02:00
|
|
|
runKtxCli(
|
2026-05-10 23:12:26 +02:00
|
|
|
[
|
|
|
|
|
'serve',
|
|
|
|
|
'--mcp',
|
|
|
|
|
'stdio',
|
|
|
|
|
'--project-dir',
|
|
|
|
|
tempDir,
|
|
|
|
|
'--semantic-compute-url',
|
|
|
|
|
'http://127.0.0.1:18080',
|
|
|
|
|
'--execute-queries',
|
|
|
|
|
'--memory-capture',
|
|
|
|
|
'--memory-model',
|
|
|
|
|
'openai/gpt-5.2',
|
|
|
|
|
],
|
|
|
|
|
serveIo.io,
|
2026-05-11 15:50:34 +02:00
|
|
|
),
|
|
|
|
|
).resolves.toBe(1);
|
|
|
|
|
|
2026-05-12 23:51:46 +02:00
|
|
|
expect(serveIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-11 15:50:34 +02:00
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
it('prints admin help for bare admin commands', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin'], testIo.io)).resolves.toBe(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
expect(testIo.stdout()).toContain('Usage: ktx admin [options] [command]');
|
2026-05-13 12:00:08 +02:00
|
|
|
expect(testIo.stdout()).toContain('Low-level project initialization');
|
|
|
|
|
expect(testIo.stdout()).toContain('init');
|
|
|
|
|
expect(testIo.stdout()).toContain('runtime');
|
|
|
|
|
expect(testIo.stdout()).not.toContain('scan');
|
|
|
|
|
expect(testIo.stdout()).not.toContain('ingest');
|
|
|
|
|
expect(testIo.stdout()).not.toContain('mapping');
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(testIo.stdout()).not.toContain('model');
|
|
|
|
|
expect(testIo.stdout()).not.toContain('knowledge');
|
|
|
|
|
expect(testIo.stderr()).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
it('rejects removed admin command groups without invoking execution', async () => {
|
2026-05-13 12:00:08 +02:00
|
|
|
for (const command of ['scan', 'ingest', 'mapping']) {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
2026-05-14 01:43:06 +02:00
|
|
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
2026-05-10 23:12:26 +02:00
|
|
|
const sl = vi.fn().mockResolvedValue(0);
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', command], testIo.io, { publicIngest, sl })).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-13 12:00:08 +02:00
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
2026-05-10 23:12:26 +02:00
|
|
|
expect(sl).not.toHaveBeenCalled();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
it('rejects removed reserved admin subcommands', async () => {
|
2026-05-10 23:12:26 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-20 01:36:54 +02:00
|
|
|
await expect(runKtxCli(['admin', 'artifacts'], testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
it('rejects mutually exclusive public ingest output modes before invoking runners', async () => {
|
|
|
|
|
const publicIngest = vi.fn(async () => 0);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
const testIo = makeIo();
|
|
|
|
|
await expect(runKtxCli(['ingest', 'warehouse', '--json', '--plain'], testIo.io, { publicIngest })).resolves.toBe(
|
|
|
|
|
1,
|
|
|
|
|
);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
expect(testIo.stderr()).toMatch(/conflict|cannot be used/i);
|
|
|
|
|
expect(publicIngest).not.toHaveBeenCalled();
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not expose root init after setup owns project creation', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['init'], testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toContain("error: unknown command 'init'");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns an error code for unknown commands', async () => {
|
|
|
|
|
const testIo = makeIo();
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
await expect(runKtxCli(['unknown'], testIo.io)).resolves.toBe(1);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
|
|
|
|
expect(testIo.stderr()).toContain("error: unknown command 'unknown'");
|
|
|
|
|
});
|
|
|
|
|
});
|