2026-05-10 23:12:26 +02:00
|
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
|
|
import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
|
|
import { join, resolve } from 'node:path';
|
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
|
|
|
import { type KtxLocalProject, loadKtxProject } from './context/project/project.js';
|
|
|
|
|
import { markKtxSetupStateStepComplete, readKtxSetupState } from './context/project/setup-config.js';
|
|
|
|
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
2026-05-10 23:51:24 +02:00
|
|
|
import type { KtxCliIo } from './cli-runtime.js';
|
2026-05-10 23:12:26 +02:00
|
|
|
import { buildPublicIngestPlan } from './public-ingest.js';
|
2026-05-14 01:43:06 +02:00
|
|
|
import {
|
|
|
|
|
type KtxDatabaseContextDepth,
|
|
|
|
|
databaseContextDepth,
|
|
|
|
|
} from './ingest-depth.js';
|
|
|
|
|
import type { KtxManagedPythonInstallPolicy } from './managed-python-command.js';
|
|
|
|
|
import { ensureSetupDatabaseContextDepths } from './setup-database-context-depth.js';
|
2026-05-10 17:08:55 -07:00
|
|
|
import {
|
|
|
|
|
type ContextBuildSourceProgressUpdate,
|
|
|
|
|
runContextBuild,
|
|
|
|
|
} from './context-build-view.js';
|
2026-05-13 17:01:48 +02:00
|
|
|
import {
|
|
|
|
|
createKtxSetupPromptAdapter,
|
|
|
|
|
type KtxSetupPromptOption,
|
|
|
|
|
} from './setup-prompts.js';
|
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
|
|
|
type KtxSetupContextBuildStatus =
|
2026-05-10 23:12:26 +02:00
|
|
|
| 'not_started'
|
|
|
|
|
| 'completed'
|
|
|
|
|
| 'failed'
|
|
|
|
|
| 'stale';
|
|
|
|
|
|
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
|
|
|
/** @internal */
|
2026-05-10 23:51:24 +02:00
|
|
|
export interface KtxSetupContextCommands {
|
2026-05-10 23:12:26 +02:00
|
|
|
build: string;
|
|
|
|
|
status: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export interface KtxSetupContextState {
|
2026-05-10 23:12:26 +02:00
|
|
|
runId?: string;
|
2026-05-10 23:51:24 +02:00
|
|
|
status: KtxSetupContextBuildStatus;
|
2026-05-10 23:12:26 +02:00
|
|
|
startedAt?: string;
|
|
|
|
|
updatedAt?: string;
|
|
|
|
|
completedAt?: string;
|
|
|
|
|
primarySourceConnectionIds: string[];
|
|
|
|
|
contextSourceConnectionIds: string[];
|
|
|
|
|
reportIds: string[];
|
|
|
|
|
artifactPaths: string[];
|
|
|
|
|
retryableFailedTargets: string[];
|
2026-05-10 23:51:24 +02:00
|
|
|
commands: KtxSetupContextCommands;
|
2026-05-10 23:12:26 +02:00
|
|
|
failureReason?: string;
|
2026-05-10 17:08:55 -07:00
|
|
|
sourceProgress?: ContextBuildSourceProgressUpdate[];
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export interface KtxSetupContextStatusSummary {
|
2026-05-10 23:12:26 +02:00
|
|
|
ready: boolean;
|
2026-05-10 23:51:24 +02:00
|
|
|
status: KtxSetupContextBuildStatus;
|
2026-05-10 23:12:26 +02:00
|
|
|
runId?: string;
|
|
|
|
|
statusCommand?: string;
|
|
|
|
|
retryCommand?: string;
|
|
|
|
|
detail?: string;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
interface KtxSetupContextReadiness {
|
2026-05-10 23:12:26 +02:00
|
|
|
ready: boolean;
|
|
|
|
|
agentContextReady: boolean;
|
|
|
|
|
semanticSearchReady: boolean;
|
|
|
|
|
details: string[];
|
|
|
|
|
failedTargets?: string[];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export type KtxSetupContextResult =
|
2026-05-10 23:12:26 +02:00
|
|
|
| { status: 'ready'; projectDir: string; runId: string }
|
|
|
|
|
| { status: 'skipped'; projectDir: string }
|
|
|
|
|
| { status: 'back'; projectDir: string }
|
|
|
|
|
| { status: 'missing-input'; projectDir: string }
|
|
|
|
|
| { status: 'failed'; projectDir: string };
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export interface KtxSetupContextStepArgs {
|
2026-05-10 23:12:26 +02:00
|
|
|
projectDir: string;
|
|
|
|
|
inputMode: 'auto' | 'disabled';
|
|
|
|
|
forcePrompt?: boolean;
|
|
|
|
|
allowEmpty?: boolean;
|
|
|
|
|
prompt?: boolean;
|
2026-05-14 01:43:06 +02:00
|
|
|
cliVersion?: string;
|
|
|
|
|
runtimeInstallPolicy?: KtxManagedPythonInstallPolicy;
|
2026-05-13 00:38:26 +02:00
|
|
|
}
|
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
|
|
|
interface KtxSetupContextPromptAdapter {
|
2026-05-13 17:01:48 +02:00
|
|
|
select(options: { message: string; options: KtxSetupPromptOption[] }): Promise<string>;
|
2026-05-10 23:12:26 +02:00
|
|
|
cancel(message: string): void;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export interface KtxSetupContextDeps {
|
|
|
|
|
prompts?: KtxSetupContextPromptAdapter;
|
2026-05-10 23:12:26 +02:00
|
|
|
runIdFactory?: () => string;
|
|
|
|
|
now?: () => Date;
|
|
|
|
|
runContextBuild?: typeof runContextBuild;
|
2026-05-10 23:51:24 +02:00
|
|
|
verifyContextReady?: (projectDir: string) => Promise<KtxSetupContextReadiness>;
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
interface KtxSetupContextTargets {
|
2026-05-10 23:12:26 +02:00
|
|
|
primarySourceConnectionIds: string[];
|
|
|
|
|
contextSourceConnectionIds: string[];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
const SETUP_CONTEXT_STATE_PATH = ['.ktx', 'setup', 'context-build.json'] as const;
|
2026-05-10 23:12:26 +02:00
|
|
|
const LIVE_DATABASE_ADAPTER = 'live-database';
|
|
|
|
|
const SCAN_REPORT_FILE = 'scan-report.json';
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function createPromptAdapter(): KtxSetupContextPromptAdapter {
|
2026-05-13 17:01:48 +02:00
|
|
|
return createKtxSetupPromptAdapter({ selectCancelValue: 'back' });
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function statePath(projectDir: string): string {
|
|
|
|
|
return join(resolve(projectDir), ...SETUP_CONTEXT_STATE_PATH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function pathExists(path: string): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
await access(path);
|
|
|
|
|
return true;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/** @internal */
|
2026-05-21 02:38:18 +02:00
|
|
|
export function contextBuildCommands(projectDir: string): KtxSetupContextCommands {
|
2026-05-10 23:12:26 +02:00
|
|
|
const resolvedProjectDir = resolve(projectDir);
|
|
|
|
|
return {
|
2026-05-13 00:38:26 +02:00
|
|
|
build: `ktx setup --project-dir ${resolvedProjectDir}`,
|
|
|
|
|
status: `ktx status --project-dir ${resolvedProjectDir}`,
|
2026-05-10 23:12:26 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function notStartedState(projectDir: string): KtxSetupContextState {
|
2026-05-10 23:12:26 +02:00
|
|
|
return {
|
|
|
|
|
status: 'not_started',
|
|
|
|
|
primarySourceConnectionIds: [],
|
|
|
|
|
contextSourceConnectionIds: [],
|
|
|
|
|
reportIds: [],
|
|
|
|
|
artifactPaths: [],
|
|
|
|
|
retryableFailedTargets: [],
|
|
|
|
|
commands: contextBuildCommands(projectDir),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function normalizeState(projectDir: string, value: unknown): KtxSetupContextState {
|
2026-05-10 23:12:26 +02:00
|
|
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
|
|
|
return notStartedState(projectDir);
|
|
|
|
|
}
|
2026-05-14 01:43:06 +02:00
|
|
|
const record = value as Record<string, unknown>;
|
|
|
|
|
const rawStatus = typeof record.status === 'string' ? record.status : 'not_started';
|
2026-05-15 07:09:58 -04:00
|
|
|
const status: KtxSetupContextBuildStatus =
|
|
|
|
|
rawStatus === 'completed' || rawStatus === 'failed' || rawStatus === 'not_started' || rawStatus === 'stale'
|
2026-05-14 01:43:06 +02:00
|
|
|
? rawStatus
|
|
|
|
|
: 'not_started';
|
2026-05-10 23:12:26 +02:00
|
|
|
const runId = typeof record.runId === 'string' && record.runId.length > 0 ? record.runId : undefined;
|
|
|
|
|
return {
|
|
|
|
|
...(runId ? { runId } : {}),
|
|
|
|
|
status,
|
|
|
|
|
...(typeof record.startedAt === 'string' ? { startedAt: record.startedAt } : {}),
|
|
|
|
|
...(typeof record.updatedAt === 'string' ? { updatedAt: record.updatedAt } : {}),
|
|
|
|
|
...(typeof record.completedAt === 'string' ? { completedAt: record.completedAt } : {}),
|
|
|
|
|
primarySourceConnectionIds: Array.isArray(record.primarySourceConnectionIds)
|
|
|
|
|
? record.primarySourceConnectionIds.filter((item): item is string => typeof item === 'string')
|
|
|
|
|
: [],
|
|
|
|
|
contextSourceConnectionIds: Array.isArray(record.contextSourceConnectionIds)
|
|
|
|
|
? record.contextSourceConnectionIds.filter((item): item is string => typeof item === 'string')
|
|
|
|
|
: [],
|
|
|
|
|
reportIds: Array.isArray(record.reportIds)
|
|
|
|
|
? record.reportIds.filter((item): item is string => typeof item === 'string')
|
|
|
|
|
: [],
|
|
|
|
|
artifactPaths: Array.isArray(record.artifactPaths)
|
|
|
|
|
? record.artifactPaths.filter((item): item is string => typeof item === 'string')
|
|
|
|
|
: [],
|
|
|
|
|
retryableFailedTargets: Array.isArray(record.retryableFailedTargets)
|
|
|
|
|
? record.retryableFailedTargets.filter((item): item is string => typeof item === 'string')
|
|
|
|
|
: [],
|
2026-05-21 02:38:18 +02:00
|
|
|
commands: contextBuildCommands(projectDir),
|
2026-05-15 07:09:58 -04:00
|
|
|
...(typeof record.failureReason === 'string' ? { failureReason: record.failureReason } : {}),
|
2026-05-10 17:08:55 -07:00
|
|
|
...(normalizeSourceProgress(record.sourceProgress) ? { sourceProgress: normalizeSourceProgress(record.sourceProgress) } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
const VALID_SOURCE_OPERATIONS = new Set(['database-ingest', 'source-ingest']);
|
2026-05-10 17:08:55 -07:00
|
|
|
const VALID_SOURCE_STATUSES = new Set(['queued', 'running', 'done', 'failed']);
|
|
|
|
|
|
|
|
|
|
function normalizeSourceProgress(value: unknown): ContextBuildSourceProgressUpdate[] | undefined {
|
|
|
|
|
if (!Array.isArray(value)) return undefined;
|
|
|
|
|
const entries: ContextBuildSourceProgressUpdate[] = [];
|
|
|
|
|
for (const item of value) {
|
|
|
|
|
if (typeof item !== 'object' || item === null || Array.isArray(item)) continue;
|
|
|
|
|
const rec = item as Record<string, unknown>;
|
|
|
|
|
if (typeof rec.connectionId !== 'string') continue;
|
|
|
|
|
if (!VALID_SOURCE_OPERATIONS.has(String(rec.operation))) continue;
|
|
|
|
|
if (!VALID_SOURCE_STATUSES.has(String(rec.status))) continue;
|
|
|
|
|
entries.push({
|
|
|
|
|
connectionId: rec.connectionId,
|
2026-05-14 01:43:06 +02:00
|
|
|
operation: rec.operation as 'database-ingest' | 'source-ingest',
|
2026-05-10 17:08:55 -07:00
|
|
|
status: rec.status as 'queued' | 'running' | 'done' | 'failed',
|
|
|
|
|
...(typeof rec.startedAtMs === 'number' ? { startedAtMs: rec.startedAtMs } : {}),
|
|
|
|
|
...(typeof rec.elapsedMs === 'number' ? { elapsedMs: rec.elapsedMs } : {}),
|
2026-05-13 17:01:48 +02:00
|
|
|
...(typeof rec.percent === 'number' ? { percent: rec.percent } : {}),
|
|
|
|
|
...(typeof rec.message === 'string' ? { message: rec.message } : {}),
|
|
|
|
|
...(typeof rec.updatedAtMs === 'number' ? { updatedAtMs: rec.updatedAtMs } : {}),
|
2026-05-10 17:08:55 -07:00
|
|
|
...(typeof rec.summaryText === 'string' ? { summaryText: rec.summaryText } : {}),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return entries.length > 0 ? entries : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 16:56:58 -04:00
|
|
|
function setupContextTargetIds(targets: KtxSetupContextTargets): string[] {
|
|
|
|
|
return [...new Set([...targets.primarySourceConnectionIds, ...targets.contextSourceConnectionIds])];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function retryableFailedTargetsFromProgress(
|
|
|
|
|
targets: KtxSetupContextTargets,
|
|
|
|
|
progress: ContextBuildSourceProgressUpdate[] | undefined,
|
|
|
|
|
): string[] {
|
|
|
|
|
const targetIds = setupContextTargetIds(targets);
|
|
|
|
|
if (!progress || progress.length === 0) {
|
|
|
|
|
return targetIds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const failedIds = new Set(progress.filter((source) => source.status === 'failed').map((source) => source.connectionId));
|
|
|
|
|
const failedTargets = targetIds.filter((connectionId) => failedIds.has(connectionId));
|
|
|
|
|
return failedTargets.length > 0 ? failedTargets : targetIds;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export async function readKtxSetupContextState(projectDir: string): Promise<KtxSetupContextState> {
|
2026-05-10 23:12:26 +02:00
|
|
|
const filePath = statePath(projectDir);
|
|
|
|
|
if (!(await pathExists(filePath))) {
|
|
|
|
|
return notStartedState(projectDir);
|
|
|
|
|
}
|
|
|
|
|
return normalizeState(projectDir, JSON.parse(await readFile(filePath, 'utf-8')) as unknown);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/** @internal */
|
2026-05-10 23:51:24 +02:00
|
|
|
export async function writeKtxSetupContextState(projectDir: string, state: KtxSetupContextState): Promise<void> {
|
2026-05-10 23:12:26 +02:00
|
|
|
const resolvedProjectDir = resolve(projectDir);
|
2026-05-10 23:51:24 +02:00
|
|
|
await mkdir(join(resolvedProjectDir, '.ktx', 'setup'), { recursive: true });
|
2026-05-10 23:12:26 +02:00
|
|
|
const normalized = normalizeState(resolvedProjectDir, {
|
|
|
|
|
...state,
|
2026-05-21 02:38:18 +02:00
|
|
|
commands: contextBuildCommands(resolvedProjectDir),
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
await writeFile(statePath(resolvedProjectDir), `${JSON.stringify(normalized, null, 2)}\n`, 'utf-8');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function setupContextStatusFromState(
|
2026-05-10 23:51:24 +02:00
|
|
|
state: KtxSetupContextState,
|
2026-05-10 23:12:26 +02:00
|
|
|
options: { completedStep: boolean } = { completedStep: false },
|
2026-05-10 23:51:24 +02:00
|
|
|
): KtxSetupContextStatusSummary {
|
2026-05-10 23:12:26 +02:00
|
|
|
const status = options.completedStep && state.status === 'not_started' ? 'completed' : state.status;
|
|
|
|
|
const ready = options.completedStep && status === 'completed';
|
|
|
|
|
return {
|
|
|
|
|
ready,
|
|
|
|
|
status,
|
|
|
|
|
...(state.runId ? { runId: state.runId } : {}),
|
2026-05-14 01:43:06 +02:00
|
|
|
...(state.runId ? { statusCommand: state.commands.status } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
retryCommand: state.commands.build,
|
|
|
|
|
...(state.failureReason ? { detail: state.failureReason } : {}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runIdFactory(): string {
|
|
|
|
|
return `setup-context-local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function listContextTargets(project: KtxLocalProject): KtxSetupContextTargets {
|
2026-05-10 23:12:26 +02:00
|
|
|
if (Object.keys(project.config.connections).length === 0) {
|
|
|
|
|
return { primarySourceConnectionIds: [], contextSourceConnectionIds: [] };
|
|
|
|
|
}
|
|
|
|
|
const plan = buildPublicIngestPlan(project, { projectDir: project.projectDir, all: true });
|
|
|
|
|
return {
|
|
|
|
|
primarySourceConnectionIds: plan.targets
|
2026-05-14 01:43:06 +02:00
|
|
|
.filter((target) => target.operation === 'database-ingest')
|
2026-05-10 23:12:26 +02:00
|
|
|
.map((target) => target.connectionId),
|
|
|
|
|
contextSourceConnectionIds: plan.targets
|
|
|
|
|
.filter((target) => target.operation === 'source-ingest')
|
|
|
|
|
.map((target) => target.connectionId),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hasFileWithExtension(
|
|
|
|
|
root: string,
|
|
|
|
|
extensions: Set<string>,
|
|
|
|
|
options: { ignoredDirectoryNames?: Set<string> } = {},
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
if (!(await pathExists(root))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const entries = await readdir(root, { withFileTypes: true });
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
|
const entryPath = join(root, entry.name);
|
|
|
|
|
if (entry.isDirectory()) {
|
|
|
|
|
if (options.ignoredDirectoryNames?.has(entry.name)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (await hasFileWithExtension(entryPath, extensions, options)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (extensions.has(entry.name.slice(entry.name.lastIndexOf('.')))) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stringValue(value: unknown): string | null {
|
|
|
|
|
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stringArrayValue(value: unknown): string[] {
|
|
|
|
|
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function readJsonFile(path: string): Promise<unknown | null> {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(await readFile(path, 'utf-8')) as unknown;
|
2026-05-21 02:38:18 +02:00
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`Failed to read JSON file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function readLatestScanReport(projectDir: string, connectionId: string): Promise<unknown | null> {
|
|
|
|
|
const scanRoot = join(projectDir, 'raw-sources', connectionId, LIVE_DATABASE_ADAPTER);
|
|
|
|
|
if (!(await pathExists(scanRoot))) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reports: Array<{ sortKey: string; report: unknown }> = [];
|
|
|
|
|
for (const entry of await readdir(scanRoot, { withFileTypes: true })) {
|
|
|
|
|
if (!entry.isDirectory()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const report = await readJsonFile(join(scanRoot, entry.name, SCAN_REPORT_FILE));
|
|
|
|
|
if (!isRecord(report)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
reports.push({ sortKey: stringValue(report.createdAt) ?? entry.name, report });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reports.sort((left, right) => left.sortKey.localeCompare(right.sortKey));
|
|
|
|
|
return reports.at(-1)?.report ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
function scanReportHasSchemaManifest(report: unknown, connectionId: string): boolean {
|
|
|
|
|
if (!isRecord(report)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (report.connectionId !== connectionId || report.dryRun === true) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return stringArrayValue(isRecord(report.artifactPaths) ? report.artifactPaths.manifestShards : undefined).length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scanReportHasCompletedDeepEnrichment(
|
|
|
|
|
report: unknown,
|
|
|
|
|
connectionId: string,
|
|
|
|
|
relationshipsRequired: boolean,
|
|
|
|
|
): boolean {
|
2026-05-10 23:12:26 +02:00
|
|
|
if (!isRecord(report)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (report.connectionId !== connectionId || report.mode !== 'enriched' || report.dryRun === true) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!isRecord(report.enrichment) || !isRecord(report.enrichmentState) || !isRecord(report.artifactPaths)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const completedStages = stringArrayValue(report.enrichmentState.completedStages);
|
|
|
|
|
return (
|
|
|
|
|
report.enrichment.tableDescriptions === 'completed' &&
|
|
|
|
|
report.enrichment.columnDescriptions === 'completed' &&
|
|
|
|
|
report.enrichment.embeddings === 'completed' &&
|
|
|
|
|
completedStages.includes('descriptions') &&
|
|
|
|
|
completedStages.includes('embeddings') &&
|
2026-05-14 01:43:06 +02:00
|
|
|
(!relationshipsRequired || completedStages.includes('relationships')) &&
|
2026-05-10 23:12:26 +02:00
|
|
|
stringArrayValue(report.artifactPaths.manifestShards).length > 0
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
function scanReportSatisfiesDepth(input: {
|
|
|
|
|
report: unknown;
|
|
|
|
|
connectionId: string;
|
|
|
|
|
depth: KtxDatabaseContextDepth;
|
|
|
|
|
relationshipsRequired: boolean;
|
|
|
|
|
}): boolean {
|
|
|
|
|
if (input.depth === 'fast') {
|
|
|
|
|
return scanReportHasSchemaManifest(input.report, input.connectionId);
|
|
|
|
|
}
|
|
|
|
|
return scanReportHasCompletedDeepEnrichment(input.report, input.connectionId, input.relationshipsRequired);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:12:26 +02:00
|
|
|
async function verifyPrimarySourceScans(
|
2026-05-14 01:43:06 +02:00
|
|
|
project: KtxLocalProject,
|
2026-05-10 23:12:26 +02:00
|
|
|
connectionIds: string[],
|
|
|
|
|
): Promise<{ ready: boolean; details: string[] }> {
|
|
|
|
|
const details: string[] = [];
|
2026-05-14 01:43:06 +02:00
|
|
|
const relationshipsRequired = project.config.scan.relationships.enabled;
|
2026-05-10 23:12:26 +02:00
|
|
|
for (const connectionId of connectionIds) {
|
2026-05-14 01:43:06 +02:00
|
|
|
const connection = project.config.connections[connectionId];
|
|
|
|
|
const depth = connection ? (databaseContextDepth(connection) ?? 'fast') : 'fast';
|
|
|
|
|
const report = await readLatestScanReport(project.projectDir, connectionId);
|
|
|
|
|
if (!scanReportSatisfiesDepth({ report, connectionId, depth, relationshipsRequired })) {
|
|
|
|
|
details.push(
|
|
|
|
|
depth === 'fast'
|
|
|
|
|
? `${connectionId}: schema context has not completed.`
|
|
|
|
|
: `${connectionId}: deep database context has not completed.`,
|
|
|
|
|
);
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { ready: details.length === 0, details };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
async function defaultVerifyContextReady(projectDir: string): Promise<KtxSetupContextReadiness> {
|
|
|
|
|
const project = await loadKtxProject({ projectDir });
|
2026-05-10 23:12:26 +02:00
|
|
|
const targets = listContextTargets(project);
|
2026-05-14 01:43:06 +02:00
|
|
|
const primarySourceScans = await verifyPrimarySourceScans(project, targets.primarySourceConnectionIds);
|
2026-05-10 23:12:26 +02:00
|
|
|
const semanticLayerContextReady = await hasFileWithExtension(
|
|
|
|
|
join(projectDir, 'semantic-layer'),
|
|
|
|
|
new Set(['.yaml', '.yml']),
|
|
|
|
|
{
|
|
|
|
|
ignoredDirectoryNames: new Set(['_schema']),
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-05-13 16:05:58 +02:00
|
|
|
const wikiReady = await hasFileWithExtension(join(projectDir, 'wiki'), new Set(['.md']));
|
2026-05-10 23:12:26 +02:00
|
|
|
const contextSourceReady =
|
|
|
|
|
targets.contextSourceConnectionIds.length === 0 || semanticLayerContextReady || wikiReady;
|
|
|
|
|
const ready = primarySourceScans.ready && contextSourceReady;
|
|
|
|
|
const semanticSearchReady = semanticLayerContextReady || primarySourceScans.ready;
|
|
|
|
|
const details: string[] = [];
|
|
|
|
|
if (!primarySourceScans.ready) {
|
|
|
|
|
details.push(...primarySourceScans.details);
|
|
|
|
|
}
|
|
|
|
|
if (!contextSourceReady) {
|
|
|
|
|
details.push('No semantic-layer or wiki assets were found after the context build.');
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
ready,
|
|
|
|
|
agentContextReady: ready,
|
|
|
|
|
semanticSearchReady,
|
|
|
|
|
details: ready
|
|
|
|
|
? [
|
|
|
|
|
`Agent context: ${ready ? 'ready' : 'not ready'}`,
|
|
|
|
|
`Semantic search: ${semanticSearchReady ? 'ready' : 'not ready'}`,
|
|
|
|
|
]
|
|
|
|
|
: details,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function markContextComplete(projectDir: string): Promise<void> {
|
2026-05-10 23:51:24 +02:00
|
|
|
const project = await loadKtxProject({ projectDir });
|
2026-05-13 13:55:21 +02:00
|
|
|
await writeFile(project.configPath, serializeKtxProjectConfig(project.config), 'utf-8');
|
2026-05-12 16:26:23 -07:00
|
|
|
await markKtxSetupStateStepComplete(projectDir, 'context');
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function writeMissingCapabilities(missing: string[], io: KtxCliIo): void {
|
|
|
|
|
io.stderr.write('KTX cannot build agent-ready context yet.\n\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
io.stderr.write('Missing:\n');
|
|
|
|
|
for (const item of missing) {
|
|
|
|
|
io.stderr.write(` ${item}\n`);
|
|
|
|
|
}
|
|
|
|
|
io.stderr.write('\nFix this in setup before building context.\n');
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function writeSkippedContext(projectDir: string, io: KtxCliIo): void {
|
|
|
|
|
io.stdout.write('\nKTX is configured, but context has not been built yet.\n\n');
|
|
|
|
|
io.stdout.write('Agents were not connected because KTX has not prepared searchable context for them.\n\n');
|
|
|
|
|
io.stdout.write(`Resume setup:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`);
|
2026-05-13 00:38:26 +02:00
|
|
|
io.stdout.write(`Build context:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`);
|
2026-05-10 23:51:24 +02:00
|
|
|
io.stdout.write(`Check status:\n ktx status --project-dir ${resolve(projectDir)}\n`);
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
function writeSuccess(
|
|
|
|
|
project: KtxLocalProject,
|
|
|
|
|
readiness: KtxSetupContextReadiness,
|
|
|
|
|
targets: KtxSetupContextTargets,
|
|
|
|
|
io: KtxCliIo,
|
|
|
|
|
): void {
|
2026-05-10 23:51:24 +02:00
|
|
|
io.stdout.write('\nKTX context is ready for agents.\n\n');
|
2026-05-14 01:43:06 +02:00
|
|
|
io.stdout.write('Databases:\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
if (targets.primarySourceConnectionIds.length === 0) {
|
|
|
|
|
io.stdout.write(' none\n');
|
|
|
|
|
} else {
|
|
|
|
|
for (const connectionId of targets.primarySourceConnectionIds) {
|
2026-05-14 01:43:06 +02:00
|
|
|
const connection = project.config.connections[connectionId];
|
|
|
|
|
const depth = connection ? (databaseContextDepth(connection) ?? 'fast') : 'fast';
|
|
|
|
|
io.stdout.write(` ${connectionId}: ${depth === 'deep' ? 'deep context complete' : 'schema context complete'}\n`);
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
io.stdout.write('\nContext sources:\n');
|
|
|
|
|
if (targets.contextSourceConnectionIds.length === 0) {
|
|
|
|
|
io.stdout.write(' none\n');
|
|
|
|
|
} else {
|
|
|
|
|
for (const connectionId of targets.contextSourceConnectionIds) {
|
|
|
|
|
io.stdout.write(` ${connectionId}: memory update complete\n`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
io.stdout.write('\nVerification:\n');
|
|
|
|
|
io.stdout.write(` Agent context: ${readiness.agentContextReady ? 'ready' : 'not ready'}\n`);
|
|
|
|
|
io.stdout.write(` Semantic search: ${readiness.semanticSearchReady ? 'ready' : 'not ready'}\n`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
function writeExistingContextSuccess(readiness: KtxSetupContextReadiness, io: KtxCliIo): void {
|
|
|
|
|
io.stdout.write('\nKTX context is ready for agents.\n\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
io.stdout.write('Existing context artifacts were found from setup ingest.\n\n');
|
|
|
|
|
io.stdout.write('Verification:\n');
|
|
|
|
|
io.stdout.write(` Agent context: ${readiness.agentContextReady ? 'ready' : 'not ready'}\n`);
|
|
|
|
|
io.stdout.write(` Semantic search: ${readiness.semanticSearchReady ? 'ready' : 'not ready'}\n`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
async function promptForBuild(prompts: KtxSetupContextPromptAdapter): Promise<'build' | 'skip' | 'back'> {
|
2026-05-10 23:12:26 +02:00
|
|
|
return (await prompts.select({
|
|
|
|
|
message:
|
2026-05-10 23:51:24 +02:00
|
|
|
'Build KTX context for agents?\n\n' +
|
|
|
|
|
'KTX is fully configured and ready to build context. This may take a few minutes to a few hours.',
|
2026-05-10 23:12:26 +02:00
|
|
|
options: [
|
|
|
|
|
{ value: 'build', label: 'Build context now (recommended)' },
|
|
|
|
|
{ value: 'skip', label: 'Leave context unbuilt and exit setup' },
|
|
|
|
|
{ value: 'back', label: 'Back' },
|
|
|
|
|
],
|
|
|
|
|
})) as 'build' | 'skip' | 'back';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runBuild(
|
2026-05-10 23:51:24 +02:00
|
|
|
args: KtxSetupContextStepArgs,
|
|
|
|
|
io: KtxCliIo,
|
|
|
|
|
deps: KtxSetupContextDeps,
|
|
|
|
|
project: KtxLocalProject,
|
|
|
|
|
targets: KtxSetupContextTargets,
|
|
|
|
|
): Promise<KtxSetupContextResult> {
|
2026-05-10 23:12:26 +02:00
|
|
|
const now = deps.now ?? (() => new Date());
|
|
|
|
|
const runId = deps.runIdFactory?.() ?? runIdFactory();
|
|
|
|
|
const startedAt = now().toISOString();
|
2026-05-15 07:09:58 -04:00
|
|
|
const incompleteState: KtxSetupContextState = {
|
2026-05-10 23:12:26 +02:00
|
|
|
runId,
|
2026-05-15 07:09:58 -04:00
|
|
|
status: 'stale',
|
2026-05-10 23:12:26 +02:00
|
|
|
startedAt,
|
|
|
|
|
updatedAt: startedAt,
|
|
|
|
|
primarySourceConnectionIds: targets.primarySourceConnectionIds,
|
|
|
|
|
contextSourceConnectionIds: targets.contextSourceConnectionIds,
|
|
|
|
|
reportIds: [],
|
|
|
|
|
artifactPaths: [],
|
|
|
|
|
retryableFailedTargets: [],
|
2026-05-21 02:38:18 +02:00
|
|
|
commands: contextBuildCommands(args.projectDir),
|
2026-05-15 07:09:58 -04:00
|
|
|
failureReason: 'Previous foreground context build did not finish. Rerun setup or ktx ingest.',
|
2026-05-10 23:12:26 +02:00
|
|
|
};
|
2026-05-15 07:09:58 -04:00
|
|
|
await writeKtxSetupContextState(args.projectDir, incompleteState);
|
2026-05-10 23:12:26 +02:00
|
|
|
|
2026-05-10 17:08:55 -07:00
|
|
|
let lastSourceProgress: ContextBuildSourceProgressUpdate[] | undefined;
|
2026-05-10 23:12:26 +02:00
|
|
|
const contextBuild = deps.runContextBuild ?? runContextBuild;
|
|
|
|
|
const buildResult = await contextBuild(
|
|
|
|
|
project,
|
|
|
|
|
{
|
|
|
|
|
projectDir: args.projectDir,
|
|
|
|
|
inputMode: args.inputMode,
|
2026-05-14 01:43:06 +02:00
|
|
|
...(args.cliVersion ? { cliVersion: args.cliVersion } : {}),
|
|
|
|
|
...(args.runtimeInstallPolicy ? { runtimeInstallPolicy: args.runtimeInstallPolicy } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
},
|
|
|
|
|
io,
|
|
|
|
|
{
|
2026-05-10 17:08:55 -07:00
|
|
|
onSourceProgress: (sources) => {
|
|
|
|
|
lastSourceProgress = sources;
|
|
|
|
|
try {
|
|
|
|
|
const resolvedDir = resolve(args.projectDir);
|
|
|
|
|
mkdirSync(join(resolvedDir, '.ktx', 'setup'), { recursive: true });
|
|
|
|
|
const progressState = normalizeState(resolvedDir, {
|
2026-05-15 07:09:58 -04:00
|
|
|
...incompleteState,
|
2026-05-10 17:08:55 -07:00
|
|
|
sourceProgress: sources,
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
|
|
|
|
});
|
|
|
|
|
writeFileSync(statePath(resolvedDir), `${JSON.stringify(progressState, null, 2)}\n`);
|
|
|
|
|
} catch {
|
|
|
|
|
// Progress reporting is supplementary — don't crash the build
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-05-10 23:12:26 +02:00
|
|
|
},
|
|
|
|
|
);
|
2026-05-10 23:13:17 -07:00
|
|
|
const completedReportIds = buildResult.reportIds ?? [];
|
|
|
|
|
const completedArtifactPaths = buildResult.artifactPaths ?? [];
|
2026-05-10 23:12:26 +02:00
|
|
|
if (buildResult.exitCode !== 0) {
|
|
|
|
|
const updatedAt = now().toISOString();
|
2026-05-10 23:51:24 +02:00
|
|
|
await writeKtxSetupContextState(args.projectDir, {
|
2026-05-15 07:09:58 -04:00
|
|
|
...incompleteState,
|
2026-05-10 23:12:26 +02:00
|
|
|
status: 'failed',
|
|
|
|
|
updatedAt,
|
2026-05-10 23:13:17 -07:00
|
|
|
reportIds: completedReportIds,
|
|
|
|
|
artifactPaths: completedArtifactPaths,
|
2026-05-12 16:56:58 -04:00
|
|
|
retryableFailedTargets: retryableFailedTargetsFromProgress(targets, lastSourceProgress),
|
2026-05-10 23:12:26 +02:00
|
|
|
failureReason: 'Context build failed.',
|
2026-05-10 17:08:55 -07:00
|
|
|
...(lastSourceProgress ? { sourceProgress: lastSourceProgress } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
return { status: 'failed', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const readiness = await (deps.verifyContextReady ?? defaultVerifyContextReady)(args.projectDir);
|
|
|
|
|
if (!readiness.ready) {
|
|
|
|
|
const updatedAt = now().toISOString();
|
2026-05-10 23:51:24 +02:00
|
|
|
await writeKtxSetupContextState(args.projectDir, {
|
2026-05-15 07:09:58 -04:00
|
|
|
...incompleteState,
|
2026-05-10 23:12:26 +02:00
|
|
|
status: 'failed',
|
|
|
|
|
updatedAt,
|
2026-05-10 23:13:17 -07:00
|
|
|
reportIds: completedReportIds,
|
|
|
|
|
artifactPaths: completedArtifactPaths,
|
2026-05-10 23:12:26 +02:00
|
|
|
retryableFailedTargets: readiness.failedTargets ?? [],
|
|
|
|
|
failureReason: readiness.details.join(' '),
|
2026-05-10 17:08:55 -07:00
|
|
|
...(lastSourceProgress ? { sourceProgress: lastSourceProgress } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
2026-05-10 23:51:24 +02:00
|
|
|
io.stderr.write('KTX context build did not pass agent-readiness verification.\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
for (const detail of readiness.details) {
|
|
|
|
|
io.stderr.write(` ${detail}\n`);
|
|
|
|
|
}
|
|
|
|
|
return { status: 'failed', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await markContextComplete(project.projectDir);
|
|
|
|
|
const completedAt = now().toISOString();
|
2026-05-10 23:51:24 +02:00
|
|
|
await writeKtxSetupContextState(args.projectDir, {
|
2026-05-15 07:09:58 -04:00
|
|
|
...incompleteState,
|
2026-05-10 23:12:26 +02:00
|
|
|
status: 'completed',
|
|
|
|
|
updatedAt: completedAt,
|
|
|
|
|
completedAt,
|
2026-05-10 23:13:17 -07:00
|
|
|
reportIds: completedReportIds,
|
|
|
|
|
artifactPaths: completedArtifactPaths,
|
2026-05-10 23:12:26 +02:00
|
|
|
retryableFailedTargets: [],
|
2026-05-15 07:09:58 -04:00
|
|
|
failureReason: undefined,
|
2026-05-10 17:08:55 -07:00
|
|
|
...(lastSourceProgress ? { sourceProgress: lastSourceProgress } : {}),
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
2026-05-14 01:43:06 +02:00
|
|
|
writeSuccess(project, readiness, targets, io);
|
2026-05-10 23:12:26 +02:00
|
|
|
return { status: 'ready', projectDir: args.projectDir, runId };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function completeExistingContext(
|
2026-05-10 23:51:24 +02:00
|
|
|
args: KtxSetupContextStepArgs,
|
|
|
|
|
io: KtxCliIo,
|
|
|
|
|
deps: KtxSetupContextDeps,
|
|
|
|
|
targets: KtxSetupContextTargets,
|
|
|
|
|
): Promise<KtxSetupContextResult | null> {
|
2026-05-10 23:12:26 +02:00
|
|
|
const readiness = await (deps.verifyContextReady ?? defaultVerifyContextReady)(args.projectDir);
|
|
|
|
|
if (!readiness.ready) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const now = deps.now ?? (() => new Date());
|
|
|
|
|
const completedAt = now().toISOString();
|
|
|
|
|
const runId = deps.runIdFactory?.() ?? runIdFactory();
|
|
|
|
|
await markContextComplete(args.projectDir);
|
2026-05-10 23:51:24 +02:00
|
|
|
await writeKtxSetupContextState(args.projectDir, {
|
2026-05-10 23:12:26 +02:00
|
|
|
runId,
|
|
|
|
|
status: 'completed',
|
|
|
|
|
startedAt: completedAt,
|
|
|
|
|
updatedAt: completedAt,
|
|
|
|
|
completedAt,
|
|
|
|
|
primarySourceConnectionIds: targets.primarySourceConnectionIds,
|
|
|
|
|
contextSourceConnectionIds: targets.contextSourceConnectionIds,
|
|
|
|
|
reportIds: [],
|
|
|
|
|
artifactPaths: [],
|
|
|
|
|
retryableFailedTargets: [],
|
2026-05-21 02:38:18 +02:00
|
|
|
commands: contextBuildCommands(args.projectDir),
|
2026-05-10 23:12:26 +02:00
|
|
|
});
|
|
|
|
|
writeExistingContextSuccess(readiness, io);
|
|
|
|
|
return { status: 'ready', projectDir: args.projectDir, runId };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 23:51:24 +02:00
|
|
|
export async function runKtxSetupContextStep(
|
|
|
|
|
args: KtxSetupContextStepArgs,
|
|
|
|
|
io: KtxCliIo,
|
|
|
|
|
deps: KtxSetupContextDeps = {},
|
|
|
|
|
): Promise<KtxSetupContextResult> {
|
2026-05-10 23:12:26 +02:00
|
|
|
try {
|
2026-05-14 01:43:06 +02:00
|
|
|
let project = await loadKtxProject({ projectDir: args.projectDir });
|
|
|
|
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
|
|
|
const depthProject = await ensureSetupDatabaseContextDepths({
|
|
|
|
|
project,
|
|
|
|
|
args,
|
|
|
|
|
prompts,
|
|
|
|
|
});
|
|
|
|
|
if (depthProject === 'back') {
|
|
|
|
|
return { status: 'back', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
project = depthProject;
|
2026-05-10 23:51:24 +02:00
|
|
|
const existingState = await readKtxSetupContextState(args.projectDir);
|
2026-05-13 13:55:21 +02:00
|
|
|
const completedSteps = (await readKtxSetupState(args.projectDir)).completed_steps;
|
2026-05-12 16:26:23 -07:00
|
|
|
if (completedSteps.includes('context') && existingState.status === 'completed') {
|
2026-05-10 23:12:26 +02:00
|
|
|
return { status: 'ready', projectDir: args.projectDir, runId: existingState.runId ?? 'setup-context-completed' };
|
|
|
|
|
}
|
|
|
|
|
if (
|
2026-05-14 01:43:06 +02:00
|
|
|
args.allowEmpty === true &&
|
|
|
|
|
(!completedSteps.includes('databases') || !completedSteps.includes('sources'))
|
2026-05-10 23:12:26 +02:00
|
|
|
) {
|
2026-05-14 01:43:06 +02:00
|
|
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (existingState.status === 'stale') {
|
|
|
|
|
io.stdout.write('Previous context build state is stale; starting a fresh foreground build.\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targets = listContextTargets(project);
|
|
|
|
|
if (targets.primarySourceConnectionIds.length === 0 && targets.contextSourceConnectionIds.length === 0) {
|
|
|
|
|
if (args.allowEmpty === true) {
|
|
|
|
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
|
|
|
}
|
2026-05-14 01:43:06 +02:00
|
|
|
io.stderr.write('No databases or context sources are configured for a KTX context build.\n');
|
2026-05-10 23:12:26 +02:00
|
|
|
return { status: 'failed', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 01:43:06 +02:00
|
|
|
const preflightPlan = buildPublicIngestPlan(project, { projectDir: project.projectDir, all: true });
|
|
|
|
|
const preflightFailures = preflightPlan.targets.flatMap((target) =>
|
|
|
|
|
target.preflightFailure ? [`${target.connectionId}: ${target.preflightFailure}`] : [],
|
|
|
|
|
);
|
|
|
|
|
if (preflightFailures.length > 0) {
|
2026-05-11 22:35:07 +02:00
|
|
|
if (args.allowEmpty === true) {
|
|
|
|
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
|
|
|
}
|
2026-05-14 01:43:06 +02:00
|
|
|
writeMissingCapabilities(preflightFailures, io);
|
2026-05-10 23:12:26 +02:00
|
|
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (args.forcePrompt !== true && args.prompt !== false && deps.verifyContextReady === undefined) {
|
|
|
|
|
const existingContextResult = await completeExistingContext(args, io, deps, targets);
|
|
|
|
|
if (existingContextResult) {
|
|
|
|
|
return existingContextResult;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (args.inputMode !== 'disabled' && args.prompt !== false) {
|
2026-05-14 01:43:06 +02:00
|
|
|
const choice = await promptForBuild(prompts);
|
2026-05-10 23:12:26 +02:00
|
|
|
if (choice === 'back') {
|
|
|
|
|
return { status: 'back', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
if (choice === 'skip') {
|
|
|
|
|
writeSkippedContext(args.projectDir, io);
|
|
|
|
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await runBuild(args, io, deps, project, targets);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
|
|
|
return { status: 'failed', projectDir: args.projectDir };
|
|
|
|
|
}
|
|
|
|
|
}
|