diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 167681a6..cc2f483d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,8 +41,13 @@ repos: language: system pass_filenames: false - id: knip-dead-code - name: knip dead-code check - entry: pnpm exec knip --reporter compact + name: knip dead-code (auto-fix) + entry: pnpm exec knip --fix --reporter compact + language: system + pass_filenames: false + - id: knip-dead-code-production + name: knip dead-code (production mode) + entry: pnpm exec knip --production --reporter compact language: system pass_filenames: false diff --git a/AGENTS.md b/AGENTS.md index f00c7c3f..83659a07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,7 +166,16 @@ pnpm run test 2>&1 | tee /tmp/ktx-test-output.log analysis. These checks are intentionally part of CI and pre-commit because the normal development workflow is agent-based. -- Run `pnpm run dead-code` after TypeScript changes. +- `pnpm run dead-code` runs three checks: Biome (`dead-code:biome`), Knip + default-mode (`dead-code:knip`), and Knip production-mode + (`dead-code:knip:production`). All three must pass. +- Default-mode Knip catches dead code reachable from no entry at all (broken + graph). Production-mode Knip catches code reachable only via tests — + i.e. code that's tested but doesn't ship. +- Pre-commit runs `knip --fix` (auto-removes the `export` keyword from + symbols that are exported but unused) plus `knip --production` (alerts on + test-only paths). CI runs the same checks without `--fix` and fails on any + finding. - Treat Knip findings as investigation prompts, not automatic deletion orders. - Remove private dead code when you confirm there are no imports, dynamic references, generated references, or tests that still need it. @@ -177,6 +186,26 @@ normal development workflow is agent-based. - Update `knip.json` when adding dynamic entrypoints, generated files, package exports, CLI bins, or framework files that Knip cannot infer. +#### Internal exports for testability + +When a function, type, or constant must be exported solely so a unit test can +import it (i.e. it has no production cross-file consumer), annotate the +declaration with `/** @internal */` JSDoc. Knip's production-mode check +ignores `@internal` exports, so the convention keeps the gate clean without +silencing the rest of the file. + +```typescript +/** @internal */ +export function reindexHasErrors(result: ReindexResult): boolean { ... } +``` + +Do NOT use Vitest in-source testing (`if (import.meta.vitest)` blocks). Keep +tests in separate `*.test.ts(x)` files. + +If the only consumer of an export is its own test and the underlying behavior +isn't used in production, delete both the export AND the test — testing dead +code is still dead code. + ### CLI Standards - Use Commander for CLI command trees, arguments, options, help text, custom diff --git a/knip.json b/knip.json index 9437bf23..6b692f62 100644 --- a/knip.json +++ b/knip.json @@ -14,33 +14,25 @@ }, "packages/cli": { "entry": [ - "src/index.ts", - "src/bin.ts", - "src/llm/index.ts", - "src/context/index.ts", - "src/context/**/index.ts", - "src/connectors/*/index.ts", - "src/**/*.test.ts", - "src/**/*.test.tsx", - "scripts/**/*.mjs" + "src/llm/index.ts!", + "src/context/**/index.ts!", + "src/connectors/*/index.ts!", + "src/print-command-tree.ts!", + "src/**/*.test-utils.ts", + "src/**/acceptance-fixtures.ts" ], - "project": ["src/**/*.{ts,tsx}", "scripts/**/*.mjs", "vitest.config.ts"] + "project": ["src/**/*.{ts,tsx}!", "scripts/**/*.mjs", "vitest.config.ts"] }, "docs-site": { "entry": [ - "app/**/*.{ts,tsx}", - "components/**/*.{ts,tsx}", - "lib/**/*.{ts,tsx}", - "middleware.ts", - "next.config.mjs", - "source.config.ts", - "tests/**/*.mjs" + "components/**/*.{ts,tsx}!", + "source.config.ts!" ], "project": [ - "app/**/*.{ts,tsx}", - "components/**/*.{ts,tsx}", - "lib/**/*.{ts,tsx}", - "*.ts", + "app/**/*.{ts,tsx}!", + "components/**/*.{ts,tsx}!", + "lib/**/*.{ts,tsx}!", + "*.ts!", "*.mjs", "tests/**/*.mjs" ], @@ -51,41 +43,7 @@ "**/dist/**", "**/coverage/**", "**/.next/**", - "**/node_modules/**", - "**/*.gen.ts", - "**/*.generated.ts" + "**/node_modules/**" ], - "ignoreBinaries": ["uv", "lsof"], - "ignoreIssues": { - "packages/cli/src/clack.ts": ["exports"], - "packages/cli/src/commands/connection-metabase-setup.ts": ["exports", "types"], - "packages/cli/src/ingest.test-utils.ts": ["exports"], - "packages/cli/src/io/symbols.ts": ["exports"], - "packages/cli/src/managed-python-command.ts": ["types"], - "packages/cli/src/managed-python-daemon.ts": ["types"], - "packages/cli/src/managed-python-http.ts": ["exports", "types"], - "packages/cli/src/managed-python-runtime.ts": ["types"], - "packages/cli/src/memory-flow-tui.tsx": ["types"], - "packages/cli/src/next-steps.ts": ["exports"], - "packages/cli/src/print-command-tree.ts": ["exports"], - "packages/cli/src/setup-agents.ts": ["exports", "types"], - "packages/cli/src/setup-context.ts": ["types"], - "packages/cli/src/setup-demo-tour.ts": ["exports"], - "packages/cli/src/setup-models.ts": ["exports"], - "packages/cli/src/setup-project.ts": ["types"], - "packages/cli/src/setup-ready-menu.ts": ["types"], - "packages/cli/src/setup-sources.ts": ["types"], - "packages/cli/src/context/ingest/adapters/historic-sql/pattern-inputs.ts": ["exports", "types"], - "packages/cli/src/context/ingest/adapters/lookml/pull-config.ts": ["exports"], - "packages/cli/src/context/ingest/adapters/metabase/serialize-card.ts": ["types"], - "packages/cli/src/context/ingest/adapters/metabase/types.ts": ["exports"], - "packages/cli/src/context/ingest/adapters/metricflow/parse.ts": ["types"], - "packages/cli/src/context/ingest/ports.ts": ["types"], - "packages/cli/src/context/ingest/stages/stage-3-work-units.ts": ["types"], - "packages/cli/src/context/ingest/stages/stage-index.types.ts": ["types"], - "packages/cli/src/context/project/config.ts": ["types"], - "packages/cli/src/context/scan/relationship-candidates.ts": ["types"], - "packages/cli/src/context/scan/relationship-diagnostics.ts": ["types"], - "packages/cli/src/context/tools/context-evidence-tool-store.ts": ["types"] - } + "ignoreBinaries": ["uv", "lsof"] } diff --git a/package.json b/package.json index 73b5f1bf..968c566b 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,11 @@ "artifacts:verify-manifest": "node scripts/package-artifacts.mjs verify-manifest", "build": "pnpm --filter './packages/*' run build", "check": "node scripts/check-boundaries.mjs && node --test scripts/*.test.mjs && pnpm --filter './packages/*' run build && pnpm --filter './packages/*' run test", - "dead-code": "pnpm run dead-code:biome && pnpm run dead-code:knip", + "dead-code": "pnpm run dead-code:biome && pnpm run dead-code:knip && pnpm run dead-code:knip:production", "dead-code:biome": "biome ci . --formatter-enabled=false --assist-enabled=false", "dead-code:fix": "biome check . --formatter-enabled=false --assist-enabled=false --write && knip --fix --format", "dead-code:knip": "knip --reporter compact", + "dead-code:knip:production": "knip --production --reporter compact", "docs": "kill $(lsof -ti:3000) 2>/dev/null; pnpm --filter ktx-docs run dev", "ktx": "node scripts/run-ktx.mjs", "link:dev": "node scripts/link-dev-cli.mjs", diff --git a/packages/cli/src/admin-reindex.ts b/packages/cli/src/admin-reindex.ts index 481c0bc0..bbd7b2f6 100644 --- a/packages/cli/src/admin-reindex.ts +++ b/packages/cli/src/admin-reindex.ts @@ -55,10 +55,12 @@ function quotePlainValue(value: string): string { return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`; } +/** @internal */ export function reindexHasErrors(summary: ReindexSummary): boolean { return summary.scopes.some((scope) => scope.error); } +/** @internal */ export function renderReindexPlain(summary: ReindexSummary, io: KtxCliIo): void { const updateKey = summary.force ? 'rebuilt' : 'updated'; for (const scope of summary.scopes) { @@ -88,6 +90,7 @@ export function renderReindexPlain(summary: ReindexSummary, io: KtxCliIo): void ); } +/** @internal */ export function renderReindexJson(summary: ReindexSummary, io: KtxCliIo): void { io.stdout.write(`${JSON.stringify({ kind: 'reindex', data: summary, meta: { command: 'admin reindex' } }, null, 2)}\n`); } diff --git a/packages/cli/src/clack.ts b/packages/cli/src/clack.ts index ce6f5872..55d3e802 100644 --- a/packages/cli/src/clack.ts +++ b/packages/cli/src/clack.ts @@ -26,7 +26,7 @@ export interface KtxCliPromptAdapter { spinner(): KtxCliSpinner; } -export class KtxCliPromptCancelledError extends Error { +class KtxCliPromptCancelledError extends Error { constructor(message = 'Operation cancelled.') { super(message); this.name = 'KtxCliPromptCancelledError'; diff --git a/packages/cli/src/cli-project.test.ts b/packages/cli/src/cli-project.test.ts deleted file mode 100644 index daee263c..00000000 --- a/packages/cli/src/cli-project.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { buildDefaultKtxProjectConfig, type KtxLocalProject, type KtxProjectConfig } from './context/project/index.js'; -import { loadKtxCliProject } from './cli-project.js'; - -function projectWithConfig(config: KtxProjectConfig): KtxLocalProject { - return { - projectDir: '/work/proj', - configPath: '/work/proj/ktx.yaml', - config, - coreConfig: {} as KtxLocalProject['coreConfig'], - git: {} as KtxLocalProject['git'], - fileStore: {} as KtxLocalProject['fileStore'], - }; -} - -describe('loadKtxCliProject', () => { - it('delegates to loadKtxProject and returns the project unchanged', async () => { - const project = projectWithConfig(buildDefaultKtxProjectConfig()); - const loadProject = vi.fn(async () => project); - - const result = await loadKtxCliProject({ projectDir: '/work/proj' }, { loadProject }); - - expect(result).toBe(project); - expect(loadProject).toHaveBeenCalledWith({ projectDir: '/work/proj' }); - }); -}); diff --git a/packages/cli/src/cli-project.ts b/packages/cli/src/cli-project.ts deleted file mode 100644 index 178936cb..00000000 --- a/packages/cli/src/cli-project.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { loadKtxProject, type KtxLocalProject } from './context/project/index.js'; - -export interface LoadKtxCliProjectOptions { - projectDir: string; -} - -export interface LoadKtxCliProjectDeps { - loadProject?: typeof loadKtxProject; -} - -/** - * Thin wrapper around `loadKtxProject`. Kept as a single entrypoint so the CLI can grow shared - * pre-load behavior later (telemetry, project lock, etc.). Today it does no extra work. - */ -export async function loadKtxCliProject( - options: LoadKtxCliProjectOptions, - deps: LoadKtxCliProjectDeps = {}, -): Promise { - return (deps.loadProject ?? loadKtxProject)({ projectDir: options.projectDir }); -} diff --git a/packages/cli/src/context-build-view.ts b/packages/cli/src/context-build-view.ts index c53fc8cb..adb5d459 100644 --- a/packages/cli/src/context-build-view.ts +++ b/packages/cli/src/context-build-view.ts @@ -444,17 +444,20 @@ export function renderContextBuildView( const ESC_K_RE = new RegExp(`${ESC.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\[K`, 'g'); const ANSI_RE = /\x1b\[[0-9;]*m/g; +/** @internal */ export function extractProgressMessage(chunk: string): string | null { const cleaned = chunk.replace(/^\r/, '').replace(ESC_K_RE, '').replace(/\n$/, '').trim(); const match = cleaned.match(/^\[(\d+)%\]\s*(.+)$/); return match ? `[${match[1]}%] ${match[2]}` : null; } +/** @internal */ export function parseScanSummary(output: string): string | null { const match = output.match(/(\d+) changes? across (\d+) tables?/); return match ? `${match[2]} tables` : null; } +/** @internal */ export function parseIngestSummary(output: string): string | null { const savedMemory = output.match(/Saved memory: (.+)/); if (savedMemory) return savedMemory[1]; @@ -560,6 +563,7 @@ function collectSourceProgress(targets: ContextBuildTargetState[]): ContextBuild }); } +/** @internal */ export function viewStateFromSourceProgress( sources: ContextBuildSourceProgressUpdate[], now: number, diff --git a/packages/cli/src/context/ingest/adapters/dbt/parse.ts b/packages/cli/src/context/ingest/adapters/dbt/parse.ts index 2402df36..3465c4ad 100644 --- a/packages/cli/src/context/ingest/adapters/dbt/parse.ts +++ b/packages/cli/src/context/ingest/adapters/dbt/parse.ts @@ -3,6 +3,7 @@ import { join, relative } from 'node:path'; const YAML_EXT_RE = /\.(ya?ml)$/i; +/** @internal */ export function normalizeDbtPath(path: string): string { return path.replaceAll('\\', '/'); } diff --git a/packages/cli/src/context/ingest/adapters/historic-sql/pattern-inputs.ts b/packages/cli/src/context/ingest/adapters/historic-sql/pattern-inputs.ts index c9380239..025fa43c 100644 --- a/packages/cli/src/context/ingest/adapters/historic-sql/pattern-inputs.ts +++ b/packages/cli/src/context/ingest/adapters/historic-sql/pattern-inputs.ts @@ -1,13 +1,14 @@ import { Buffer } from 'node:buffer'; import type { StagedPatternsInput } from './types.js'; -export const HISTORIC_SQL_PATTERN_WORKUNIT_DIR = 'patterns-input'; +const HISTORIC_SQL_PATTERN_WORKUNIT_DIR = 'patterns-input'; +/** @internal */ export const HISTORIC_SQL_PATTERN_WORKUNIT_MAX_BYTES = 110_000; -export const HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE = /^patterns-input\/part-\d{4}\.json$/; +const HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE = /^patterns-input\/part-\d{4}\.json$/; type PatternTemplate = StagedPatternsInput['templates'][number]; -export interface HistoricSqlPatternInputShard { +interface HistoricSqlPatternInputShard { path: string; input: StagedPatternsInput; byteLength: number; @@ -27,10 +28,11 @@ export function isHistoricSqlPatternInputShardPath(path: string): boolean { return HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE.test(path); } -export function serializeStagedPatternsInput(input: StagedPatternsInput): string { +function serializeStagedPatternsInput(input: StagedPatternsInput): string { return `${JSON.stringify(input, null, 2)}\n`; } +/** @internal */ export function serializedStagedPatternsInputByteLength(input: StagedPatternsInput): number { return Buffer.byteLength(serializeStagedPatternsInput(input), 'utf-8'); } diff --git a/packages/cli/src/context/ingest/adapters/live-database/stage.ts b/packages/cli/src/context/ingest/adapters/live-database/stage.ts index 03e60f5a..ba925986 100644 --- a/packages/cli/src/context/ingest/adapters/live-database/stage.ts +++ b/packages/cli/src/context/ingest/adapters/live-database/stage.ts @@ -33,6 +33,7 @@ function tableSortKey(table: KtxTableRef): string { return `${table.catalog ?? ''}\u0000${table.db ?? ''}\u0000${table.name}`; } +/** @internal */ export function liveDatabaseTablePath(table: KtxTableRef): string { return `${LIVE_DATABASE_TABLES_DIR}/${encodePathPart(table.catalog)}.${encodePathPart(table.db)}.${encodePathPart( table.name, diff --git a/packages/cli/src/context/ingest/adapters/looker/fetch.ts b/packages/cli/src/context/ingest/adapters/looker/fetch.ts index 2086b48c..6959e9b0 100644 --- a/packages/cli/src/context/ingest/adapters/looker/fetch.ts +++ b/packages/cli/src/context/ingest/adapters/looker/fetch.ts @@ -1,11 +1,11 @@ import { mkdir, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; +import type { ParsedTargetTable } from '../../parsed-target-table.js'; import type { FetchContext } from '../../types.js'; import { writeLookerEvidenceDocuments } from './evidence-documents.js'; import { writeLookerFetchReport } from './fetch-report.js'; import { type LookerPullConfig, - type ParsedTargetTable, parseLookerPullConfig, STAGED_FILES, type StagedDashboardFile, diff --git a/packages/cli/src/context/ingest/adapters/looker/mapping.ts b/packages/cli/src/context/ingest/adapters/looker/mapping.ts index 92ba82ce..abfffcdf 100644 --- a/packages/cli/src/context/ingest/adapters/looker/mapping.ts +++ b/packages/cli/src/context/ingest/adapters/looker/mapping.ts @@ -1,11 +1,6 @@ +import type { ParsedTargetTable } from '../../parsed-target-table.js'; import type { LookerWarehouseConnectionInfo } from './client.js'; -import type { - LookerPullConfig, - LookerRuntimeCursors, - ParsedTargetTable, - StagedExploreFile, - StagedLookmlModelsFile, -} from './types.js'; +import type { LookerPullConfig, LookerRuntimeCursors, StagedExploreFile, StagedLookmlModelsFile } from './types.js'; export const LOOKER_DIALECT_TO_CONNECTION_TYPE = { bigquery: 'BIGQUERY', diff --git a/packages/cli/src/context/ingest/adapters/looker/reconcile.test.ts b/packages/cli/src/context/ingest/adapters/looker/reconcile.test.ts index 68e2cda8..09e8685f 100644 --- a/packages/cli/src/context/ingest/adapters/looker/reconcile.test.ts +++ b/packages/cli/src/context/ingest/adapters/looker/reconcile.test.ts @@ -1,17 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { buildLookerReconcileNotes, lookerRuntimeSourceToFileAdapterSource } from './reconcile.js'; - -describe('lookerRuntimeSourceToFileAdapterSource', () => { - it('maps API-derived Looker source names to file-adapter source names', () => { - expect(lookerRuntimeSourceToFileAdapterSource('looker__b2b__sales_pipeline')).toBe('b2b__sales_pipeline'); - expect(lookerRuntimeSourceToFileAdapterSource('looker__finance__orders')).toBe('finance__orders'); - }); - - it('ignores non-Looker and malformed source names', () => { - expect(lookerRuntimeSourceToFileAdapterSource('b2b__sales_pipeline')).toBeNull(); - expect(lookerRuntimeSourceToFileAdapterSource('looker__missing_explore')).toBeNull(); - }); -}); +import { buildLookerReconcileNotes } from './reconcile.js'; describe('buildLookerReconcileNotes', () => { it('instructs reconciliation to record subsumed provenance', () => { diff --git a/packages/cli/src/context/ingest/adapters/looker/reconcile.ts b/packages/cli/src/context/ingest/adapters/looker/reconcile.ts index fe5e74a5..1dde846f 100644 --- a/packages/cli/src/context/ingest/adapters/looker/reconcile.ts +++ b/packages/cli/src/context/ingest/adapters/looker/reconcile.ts @@ -1,16 +1,3 @@ -export function lookerRuntimeSourceToFileAdapterSource(sourceName: string): string | null { - if (!sourceName.startsWith('looker__')) { - return null; - } - const stripped = sourceName.slice('looker__'.length); - const parts = stripped.split('__'); - if (parts.length < 2 || parts.some((part) => part.length === 0)) { - return null; - } - const [model, ...exploreParts] = parts; - return `${model}__${exploreParts.join('__')}`; -} - export function buildLookerReconcileNotes(): string[] { return [ [ diff --git a/packages/cli/src/context/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts b/packages/cli/src/context/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts index 67bb68c6..fd515bad 100644 --- a/packages/cli/src/context/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts +++ b/packages/cli/src/context/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts @@ -1,7 +1,8 @@ import { tool } from 'ai'; import { z } from 'zod'; import type { ToolOutput } from '../../../../tools/index.js'; -import { type ParsedTargetTable, stagedLookerQuerySchema } from '../types.js'; +import type { ParsedTargetTable } from '../../../parsed-target-table.js'; +import { stagedLookerQuerySchema } from '../types.js'; const lookerUsageInputSchema = z.object({ queryCount30d: z.number().int().nonnegative().default(0), diff --git a/packages/cli/src/context/ingest/adapters/looker/types.test.ts b/packages/cli/src/context/ingest/adapters/looker/types.test.ts index 2d517d50..2a4c2b8c 100644 --- a/packages/cli/src/context/ingest/adapters/looker/types.test.ts +++ b/packages/cli/src/context/ingest/adapters/looker/types.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; +import { parsedTargetTableSchema } from '../../parsed-target-table.js'; import { lookerPullConfigSchema, parseLookerPullConfig, - parsedTargetTableSchema, stagedDashboardFileSchema, stagedExploreFileSchema, stagedLookerFetchIssueSchema, diff --git a/packages/cli/src/context/ingest/adapters/looker/types.ts b/packages/cli/src/context/ingest/adapters/looker/types.ts index ea515b9d..cda4f91e 100644 --- a/packages/cli/src/context/ingest/adapters/looker/types.ts +++ b/packages/cli/src/context/ingest/adapters/looker/types.ts @@ -7,8 +7,6 @@ const nullableLookerIdSchema = z.union([lookerIdSchema, z.null()]).default(null) export const lookerConnectionIdSchema = z.string().min(1).regex(/^[A-Za-z0-9_-]+$/); -export { parsedTargetTableSchema, type ParsedTargetTable } from '../../parsed-target-table.js'; - export const lookerRuntimeCursorsSchema = z.object({ dashboardsLastSyncedAt: z.iso.datetime().nullable().default(null), looksLastSyncedAt: z.iso.datetime().nullable().default(null), @@ -16,6 +14,7 @@ export const lookerRuntimeCursorsSchema = z.object({ export type LookerRuntimeCursors = z.infer; +/** @internal */ export const lookerPullConfigSchema = z.object({ lookerConnectionId: lookerConnectionIdSchema.optional(), instanceBaseUrl: z.url().optional(), diff --git a/packages/cli/src/context/ingest/adapters/lookml/fetch-report.ts b/packages/cli/src/context/ingest/adapters/lookml/fetch-report.ts index e626b392..75231f73 100644 --- a/packages/cli/src/context/ingest/adapters/lookml/fetch-report.ts +++ b/packages/cli/src/context/ingest/adapters/lookml/fetch-report.ts @@ -4,7 +4,9 @@ import * as z from 'zod'; import type { SourceFetchReport } from '../../types.js'; import type { ParsedLookmlProject } from './parse.js'; +/** @internal */ export const LOOKML_FETCH_REPORT_FILE = 'lookml-fetch-report.json'; +/** @internal */ export const LOOKML_MISMATCHED_MODELS_FILE = 'lookml-mismatched-models.json'; const fetchIssueKindSchema = z.enum([ diff --git a/packages/cli/src/context/ingest/adapters/lookml/pull-config.ts b/packages/cli/src/context/ingest/adapters/lookml/pull-config.ts index a6eefc9f..1e555c64 100644 --- a/packages/cli/src/context/ingest/adapters/lookml/pull-config.ts +++ b/packages/cli/src/context/ingest/adapters/lookml/pull-config.ts @@ -1,7 +1,7 @@ import * as z from 'zod'; import { parsedTargetTableSchema } from '../../parsed-target-table.js'; -export const lookmlPullConfigSchema = z.object({ +const lookmlPullConfigSchema = z.object({ repoUrl: z.string().url(), branch: z.string().default('main'), path: z.string().nullable().default(null), diff --git a/packages/cli/src/context/ingest/adapters/metabase/serialize-card.ts b/packages/cli/src/context/ingest/adapters/metabase/serialize-card.ts index 57f1acf9..4cb844f7 100644 --- a/packages/cli/src/context/ingest/adapters/metabase/serialize-card.ts +++ b/packages/cli/src/context/ingest/adapters/metabase/serialize-card.ts @@ -6,6 +6,7 @@ const CARD_REF_RE = /\{\{#(\d+)\}\}/g; * Input TemplateTag shape mirrors `MetabaseClient.getTemplateTags` output. We keep the * shape loose — only `name`, `type`, and optional `cardReference`/`default` are needed here. */ +/** @internal */ export interface InputTemplateTag { name: string; type: string; @@ -13,6 +14,7 @@ export interface InputTemplateTag { defaultValue?: string | null; } +/** @internal */ export function extractReferencedCardIds(templateTags: InputTemplateTag[], sql: string): number[] { const ids = new Set(); for (const tag of templateTags) { @@ -34,7 +36,7 @@ export function extractReferencedCardIds(templateTags: InputTemplateTag[], sql: * care about. The adapter reads whatever the client returns; this helper stays * duck-typed so the client's type can evolve without churn here. */ -export interface InputCard { +interface InputCard { id: number; name: string; description?: string | null; diff --git a/packages/cli/src/context/ingest/adapters/metabase/types.ts b/packages/cli/src/context/ingest/adapters/metabase/types.ts index fc2ecb87..1021b212 100644 --- a/packages/cli/src/context/ingest/adapters/metabase/types.ts +++ b/packages/cli/src/context/ingest/adapters/metabase/types.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -export const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']); +const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']); export type MetabaseSyncMode = z.infer; export const metabaseLocalConnectionIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/); @@ -25,7 +25,7 @@ export function parseMetabasePullConfig(raw: unknown): MetabasePullConfig { } /** A Metabase column from `card.result_metadata`. Mirrors what the LLM consumes today. */ -export const stagedResultColumnSchema = z.object({ +const stagedResultColumnSchema = z.object({ name: z.string(), display_name: z.string().optional().nullable(), base_type: z.string(), @@ -37,7 +37,7 @@ export const stagedResultColumnSchema = z.object({ export type StagedResultColumn = z.infer; -export const stagedParameterSchema = z.object({ +const stagedParameterSchema = z.object({ id: z.string(), name: z.string(), type: z.string(), @@ -49,7 +49,7 @@ export const stagedParameterSchema = z.object({ export type StagedParameter = z.infer; /** A template tag pulled from an MBQL card's `dataset_query.stages[0].template-tags`. */ -export const stagedTemplateTagSchema = z.object({ +const stagedTemplateTagSchema = z.object({ name: z.string(), type: z.string(), defaultValue: z.string().optional().nullable(), @@ -87,7 +87,7 @@ export const stagedCardFileSchema = z.object({ export type StagedCardFile = z.infer; /** A serialized collection file, `collections/.json`. Minimal — path lives on the card. */ -export const stagedCollectionFileSchema = z.object({ +const stagedCollectionFileSchema = z.object({ metabaseId: z.union([z.number().int(), z.literal('root')]), name: z.string(), parentId: z.union([z.number().int(), z.literal('root')]).nullable(), @@ -96,7 +96,7 @@ export const stagedCollectionFileSchema = z.object({ export type StagedCollectionFile = z.infer; /** A serialized database-mapping snapshot, `databases/.json`. */ -export const stagedDatabaseFileSchema = z.object({ +const stagedDatabaseFileSchema = z.object({ metabaseDatabaseId: z.number().int().positive(), metabaseDatabaseName: z.string(), metabaseEngine: z.string().nullable(), diff --git a/packages/cli/src/context/ingest/adapters/metricflow/parse.ts b/packages/cli/src/context/ingest/adapters/metricflow/parse.ts index 935858d0..6232e548 100644 --- a/packages/cli/src/context/ingest/adapters/metricflow/parse.ts +++ b/packages/cli/src/context/ingest/adapters/metricflow/parse.ts @@ -2,7 +2,7 @@ import { readdir, readFile } from 'node:fs/promises'; import { join, relative } from 'node:path'; import { parse as parseYaml } from 'yaml'; -export interface ParsedMetricFlowSemanticModel { +interface ParsedMetricFlowSemanticModel { /** Path relative to stagedDir, e.g. "models/orders.yml". */ path: string; /** `name:` on the semantic_model. */ @@ -24,9 +24,9 @@ export interface ParsedMetricFlowSemanticModel { defaultTimeDimension: string | null; } -export type MetricFlowMetricType = 'simple' | 'derived' | 'cumulative' | 'ratio' | 'conversion'; +type MetricFlowMetricType = 'simple' | 'derived' | 'cumulative' | 'ratio' | 'conversion'; -export interface ParsedMetricFlowMetric { +interface ParsedMetricFlowMetric { path: string; name: string; type: MetricFlowMetricType; diff --git a/packages/cli/src/context/ingest/adapters/notion/cluster.ts b/packages/cli/src/context/ingest/adapters/notion/cluster.ts index 090ff4f7..db5fbcb5 100644 --- a/packages/cli/src/context/ingest/adapters/notion/cluster.ts +++ b/packages/cli/src/context/ingest/adapters/notion/cluster.ts @@ -5,6 +5,7 @@ import { kmeans, pickK } from '../../clustering/kmeans.js'; import type { WorkUnit } from '../../types.js'; import { notionMetadataSchema } from './types.js'; +/** @internal */ export const MIN_PAGES_TO_CLUSTER = 5; const CLUSTER_TEXT_BODY_CHARS = 1024; const CLUSTER_SEED = 42; diff --git a/packages/cli/src/context/ingest/adapters/notion/normalize.ts b/packages/cli/src/context/ingest/adapters/notion/normalize.ts index 0e608697..fad1ff95 100644 --- a/packages/cli/src/context/ingest/adapters/notion/normalize.ts +++ b/packages/cli/src/context/ingest/adapters/notion/normalize.ts @@ -14,6 +14,7 @@ function richTextToMarkdown(value: unknown): string { .trim(); } +/** @internal */ export function propertyValueToText(value: unknown): string { if (!value || typeof value !== 'object' || !('type' in value)) { return ''; diff --git a/packages/cli/src/context/ingest/adapters/notion/notion-client.ts b/packages/cli/src/context/ingest/adapters/notion/notion-client.ts index c7cb007c..0bbde862 100644 --- a/packages/cli/src/context/ingest/adapters/notion/notion-client.ts +++ b/packages/cli/src/context/ingest/adapters/notion/notion-client.ts @@ -89,6 +89,7 @@ function shouldRetryNotionError(error: unknown): boolean { return code === 'rate_limited' || transientErrorCodes.has(code ?? '') || transientStatusCodes.has(status ?? 0); } +/** @internal */ export async function retryNotionRequest(operation: () => Promise, options: RetryOptions = {}): Promise { const maxAttempts = options.maxAttempts ?? 4; const sleep = options.sleep ?? defaultSleep; diff --git a/packages/cli/src/context/ingest/ports.ts b/packages/cli/src/context/ingest/ports.ts index 5c856e54..d579e6ec 100644 --- a/packages/cli/src/context/ingest/ports.ts +++ b/packages/cli/src/context/ingest/ports.ts @@ -2,7 +2,7 @@ import type { KtxModelRole } from '../../llm/index.js'; import type { KtxEmbeddingPort } from '../core/embedding.js'; import type { GitService, KtxFileStorePort, KtxLogger, SessionOutcome } from '../core/index.js'; import type { AgentRunnerPort, KtxLlmRuntimePort, KtxRuntimeToolSet } from '../llm/index.js'; -import type { CaptureSession, MemoryAction, MemoryKnowledgeSlRefsPort } from '../memory/index.js'; +import type { MemoryAction, MemoryKnowledgeSlRefsPort } from '../memory/index.js'; import type { PromptService } from '../prompts/index.js'; import type { SkillsRegistryService } from '../skills/index.js'; import type { @@ -34,7 +34,7 @@ import type { SourceAdapter, } from './types.js'; -export type JsonPrimitive = string | number | boolean | null; +type JsonPrimitive = string | number | boolean | null; export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined }; export interface IngestRunRecord { @@ -359,9 +359,4 @@ export interface IngestBundleRunnerDeps { logger?: KtxLogger; } -export interface IngestCaptureState { - session: CaptureSession; - actions: MemoryAction[]; -} - export type IngestRunnerJob = IngestBundleJob; diff --git a/packages/cli/src/context/ingest/semantic-layer-target-policy.ts b/packages/cli/src/context/ingest/semantic-layer-target-policy.ts index adf63b3b..1fffbbc5 100644 --- a/packages/cli/src/context/ingest/semantic-layer-target-policy.ts +++ b/packages/cli/src/context/ingest/semantic-layer-target-policy.ts @@ -3,6 +3,7 @@ export interface SemanticLayerTargetPolicyInput { allowedConnectionIds: ReadonlySet; } +/** @internal */ export interface SemanticLayerTargetPolicyViolation { path: string; connectionId: string; diff --git a/packages/cli/src/context/ingest/stages/stage-3-work-units.ts b/packages/cli/src/context/ingest/stages/stage-3-work-units.ts index 73677279..250cba4d 100644 --- a/packages/cli/src/context/ingest/stages/stage-3-work-units.ts +++ b/packages/cli/src/context/ingest/stages/stage-3-work-units.ts @@ -6,7 +6,7 @@ import type { WorkUnit } from '../types.js'; const MAX_WORK_UNIT_PROMPT_CHARS = 240_000; -export interface TouchedValidationResult { +interface TouchedValidationResult { invalidSources: string[]; validSources: string[]; } diff --git a/packages/cli/src/context/ingest/stages/stage-index.types.ts b/packages/cli/src/context/ingest/stages/stage-index.types.ts index fa1a652e..cae9526d 100644 --- a/packages/cli/src/context/ingest/stages/stage-index.types.ts +++ b/packages/cli/src/context/ingest/stages/stage-index.types.ts @@ -1,7 +1,7 @@ import type { MemoryAction } from '../../memory/index.js'; import type { TouchedSlSource } from '../../tools/index.js'; -export interface StageIndexWorkUnit { +interface StageIndexWorkUnit { unitKey: string; rawFiles: string[]; status: 'success' | 'failed'; diff --git a/packages/cli/src/context/mcp/types.ts b/packages/cli/src/context/mcp/types.ts index 49bf7b55..0697e3fb 100644 --- a/packages/cli/src/context/mcp/types.ts +++ b/packages/cli/src/context/mcp/types.ts @@ -141,6 +141,7 @@ export interface KtxSqlExecutionResponse { rowCount: number; } +/** @internal */ export interface KtxSqlExecutionMcpPort { execute( input: { connectionId: string; sql: string; maxRows: number }, diff --git a/packages/cli/src/context/project/config.ts b/packages/cli/src/context/project/config.ts index 930914e2..65a884e0 100644 --- a/packages/cli/src/context/project/config.ts +++ b/packages/cli/src/context/project/config.ts @@ -256,14 +256,10 @@ const ktxProjectConfigSchema = z export type KtxProjectConfig = z.infer; export type KtxProjectLlmConfig = z.infer; -export type KtxProjectLlmProviderConfig = z.infer; export type KtxProjectEmbeddingConfig = z.infer; export type KtxScanEnrichmentConfig = z.infer; -export type KtxIngestWorkUnitsConfig = z.infer; export type KtxScanRelationshipConfig = z.infer; -export type KtxProjectScanConfig = z.infer; export type KtxProjectConnectionConfig = z.infer; -export type KtxProjectSetupConfig = z.infer; export type KtxStorageState = z.infer['state']; export type KtxSearchBackend = z.infer['search']; diff --git a/packages/cli/src/context/scan/local-enrichment.test.ts b/packages/cli/src/context/scan/local-enrichment.test.ts index fe4239c1..66b66fc2 100644 --- a/packages/cli/src/context/scan/local-enrichment.test.ts +++ b/packages/cli/src/context/scan/local-enrichment.test.ts @@ -12,7 +12,6 @@ import { runLocalScanEnrichment, snapshotToKtxEnrichedSchema, } from './local-enrichment.js'; -import { createLocalScanEnrichmentProvidersFromConfig } from './local-scan.js'; import { createKtxConnectorCapabilities, type KtxQueryResult, @@ -813,46 +812,4 @@ describe('local scan enrichment', () => { } }); - it('resolves gateway LLM providers and passes injected embedding provider through to scan enrichment', () => { - const createKtxLlmProvider = vi.fn(() => ({ - getModel: vi.fn().mockReturnValue({ modelId: 'provider/language-model', provider: 'gateway' }), - })); - const embeddingProvider = { - dimensions: 1536, - maxBatchSize: 8, - embed: vi.fn(), - [['embed', 'Many'].join('')]: vi.fn(), - }; - - const providers = createLocalScanEnrichmentProvidersFromConfig( - { - mode: 'llm', - embeddings: { - backend: 'openai', - model: 'provider/embedding-model', - dimensions: 1536, - batchSize: 8, - openai: { api_key: 'env:OPENAI_API_KEY' }, // pragma: allowlist secret - }, - }, - { - provider: { - backend: 'gateway', - gateway: {}, - }, - models: { default: 'provider/language-model' }, - }, - { - createKtxLlmProvider: createKtxLlmProvider as any, - env: { OPENAI_API_KEY: 'openai-key' }, // pragma: allowlist secret - embeddingProvider: embeddingProvider as any, - }, - ); - - expect(providers?.embedding?.dimensions).toBe(1536); - expect(providers?.embedding?.maxBatchSize).toBe(8); - expect(createKtxLlmProvider).toHaveBeenCalledWith( - expect.objectContaining({ backend: 'gateway', modelSlots: { default: 'provider/language-model' } }), - ); - }); }); diff --git a/packages/cli/src/context/scan/local-scan.ts b/packages/cli/src/context/scan/local-scan.ts index c83a9847..9b925fd2 100644 --- a/packages/cli/src/context/scan/local-scan.ts +++ b/packages/cli/src/context/scan/local-scan.ts @@ -226,15 +226,6 @@ function resolveLocalScanEnrichmentProviders( }; } -export function createLocalScanEnrichmentProvidersFromConfig( - config: KtxScanEnrichmentConfig, - llmConfig: KtxProjectLlmConfig, - deps: LocalScanEnrichmentProviderDeps = {}, -): KtxLocalScanEnrichmentProviders | null { - const resolved = resolveLocalScanEnrichmentProviders(config, llmConfig, deps); - return resolved.status === 'ready' ? resolved.providers : null; -} - function createLocalScanEnrichmentStateStore(options: RunLocalScanOptions): SqliteLocalScanEnrichmentStateStore | null { if (options.dryRun) { return null; diff --git a/packages/cli/src/context/scan/relationship-candidates.ts b/packages/cli/src/context/scan/relationship-candidates.ts index cd3b7767..9b78d3b7 100644 --- a/packages/cli/src/context/scan/relationship-candidates.ts +++ b/packages/cli/src/context/scan/relationship-candidates.ts @@ -12,7 +12,7 @@ import { pluralizeKtxRelationshipToken, singularizeKtxRelationshipToken, } from './relationship-name-similarity.js'; -export type { KtxRelationshipNormalizedName } from './relationship-name-similarity.js'; +; export { normalizeKtxRelationshipName } from './relationship-name-similarity.js'; import type { KtxRelationshipProfileArtifact } from './relationship-profiling.js'; import { diff --git a/packages/cli/src/context/scan/relationship-diagnostics.ts b/packages/cli/src/context/scan/relationship-diagnostics.ts index aa9564ae..69d8886a 100644 --- a/packages/cli/src/context/scan/relationship-diagnostics.ts +++ b/packages/cli/src/context/scan/relationship-diagnostics.ts @@ -66,7 +66,7 @@ export interface KtxRelationshipDiagnosticsThresholds { reviewThreshold: number; } -export interface KtxRelationshipDiagnosticsPolicy { +interface KtxRelationshipDiagnosticsPolicy { validationRequiredForManifest: boolean; maxCandidatesPerColumn: number; profileSampleRows: number; diff --git a/packages/cli/src/context/search/pglite-owner-process.test.ts b/packages/cli/src/context/search/pglite-owner-process.test.ts index 0121b1e2..22da646a 100644 --- a/packages/cli/src/context/search/pglite-owner-process.test.ts +++ b/packages/cli/src/context/search/pglite-owner-process.test.ts @@ -4,8 +4,8 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Client } from 'pg'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { assertSearchBackendCapabilities, assertSearchBackendConformanceCase } from './index.js'; -import { KtxPGliteOwnerProcess, PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES } from './pglite-owner-process.js'; +import { assertSearchBackendConformanceCase } from './index.js'; +import { KtxPGliteOwnerProcess } from './pglite-owner-process.js'; async function allocatePort(): Promise { const server = createServer(); @@ -107,20 +107,6 @@ describe('KtxPGliteOwnerProcess', () => { await rm(tempDir, { recursive: true, force: true }); }); - it('declares the advanced PGlite search capabilities observed by the spike', () => { - assertSearchBackendCapabilities({ - backendName: 'pglite-owner-process', - capabilities: PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES, - expected: { - fts: true, - vector: true, - fuzzy: true, - jsonSearch: true, - arraySearch: false, - }, - }); - }); - it('starts a socket owner process and serves PostgreSQL clients', async () => { const owner = await KtxPGliteOwnerProcess.start({ dataDir, diff --git a/packages/cli/src/context/search/pglite-owner-process.ts b/packages/cli/src/context/search/pglite-owner-process.ts index 373be59c..b6459639 100644 --- a/packages/cli/src/context/search/pglite-owner-process.ts +++ b/packages/cli/src/context/search/pglite-owner-process.ts @@ -3,15 +3,6 @@ import { pg_trgm } from '@electric-sql/pglite/contrib/pg_trgm'; import { vector } from '@electric-sql/pglite/vector'; import { PGLiteSocketServer } from '@electric-sql/pglite-socket'; import { Client, type ClientConfig, type QueryResult, type QueryResultRow } from 'pg'; -import type { SearchBackendCapabilities } from './types.js'; - -export const PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES = { - fts: true, - vector: true, - fuzzy: true, - jsonSearch: true, - arraySearch: false, -} satisfies SearchBackendCapabilities; export interface KtxPGliteOwnerProcessOptions { dataDir: string; diff --git a/packages/cli/src/context/sl/semantic-layer.service.ts b/packages/cli/src/context/sl/semantic-layer.service.ts index 00149d3b..3c9194ac 100644 --- a/packages/cli/src/context/sl/semantic-layer.service.ts +++ b/packages/cli/src/context/sl/semantic-layer.service.ts @@ -25,8 +25,11 @@ export interface LoadAllSourcesResult { loadErrors: string[]; } +/** @internal */ export class UnknownColumnOverrideError extends Error {} +/** @internal */ export class ColumnNameCollisionError extends Error {} +/** @internal */ export class ConflictingExcludeAndOverrideError extends Error {} class ComposeContractError extends Error {} diff --git a/packages/cli/src/context/tools/context-evidence-tool-store.ts b/packages/cli/src/context/tools/context-evidence-tool-store.ts index f7100706..b6fac3d4 100644 --- a/packages/cli/src/context/tools/context-evidence-tool-store.ts +++ b/packages/cli/src/context/tools/context-evidence-tool-store.ts @@ -13,7 +13,7 @@ export interface ContextEvidenceSearchArgs { export type ContextEvidenceSearchMatchReason = 'lexical' | 'semantic' | 'token' | (string & {}); -export interface ContextEvidenceSearchLaneSummary { +interface ContextEvidenceSearchLaneSummary { lane: string; status: 'available' | 'skipped' | 'failed'; requestedCandidatePoolLimit: number; diff --git a/packages/cli/src/context/wiki/tools/wiki-search.tool.ts b/packages/cli/src/context/wiki/tools/wiki-search.tool.ts index d8a40fc3..88081a06 100644 --- a/packages/cli/src/context/wiki/tools/wiki-search.tool.ts +++ b/packages/cli/src/context/wiki/tools/wiki-search.tool.ts @@ -23,6 +23,7 @@ interface WikiSearchStructured { totalFound: number; } +/** @internal */ export interface WikiSearchAdapterPort { search(input: { userId: string; query: string; limit: number }): Promise<{ results: Array<{ diff --git a/packages/cli/src/demo-assets.ts b/packages/cli/src/demo-assets.ts index 674af8cf..dcc7ac1f 100644 --- a/packages/cli/src/demo-assets.ts +++ b/packages/cli/src/demo-assets.ts @@ -17,8 +17,11 @@ interface EnsureDemoProjectOptions { force: boolean; } +/** @internal */ export const DEMO_CONNECTION_ID = 'orbit_demo'; +/** @internal */ export const DEMO_ADAPTER = 'live-database'; +/** @internal */ export const DEMO_REPLAY_FILE = 'replay.memory-flow.v1.json'; const REQUIRED_PACKAGED_BASE_ASSET_PATHS = ['demo.db', 'manifest.json', DEMO_REPLAY_FILE] as const; @@ -115,6 +118,7 @@ async function assertPackagedSeededAssetsPresent(): Promise { } } +/** @internal */ export async function ensureDemoProject(options: EnsureDemoProjectOptions): Promise { const projectDir = resolve(options.projectDir); const configPath = join(projectDir, 'ktx.yaml'); diff --git a/packages/cli/src/demo-metrics.ts b/packages/cli/src/demo-metrics.ts index 31a1f225..280a869f 100644 --- a/packages/cli/src/demo-metrics.ts +++ b/packages/cli/src/demo-metrics.ts @@ -140,6 +140,7 @@ export function formatDuration(ms: number): string { return `${hr}h${(min % 60).toString().padStart(2, '0')}m`; } +/** @internal */ export function formatEta(ms: number | null, status: MemoryFlowReplayInput['status']): string { if (status !== 'running') return 'done'; if (ms === null) return 'estimating...'; @@ -153,6 +154,7 @@ export function formatCost(usd: number): string { return `$${usd.toFixed(2)}`; } +/** @internal */ export function formatTokens(n: number): string { if (!Number.isFinite(n) || n <= 0) return '0'; if (n < 1000) return `${Math.round(n)}`; @@ -160,6 +162,7 @@ export function formatTokens(n: number): string { return `${(n / 1_000_000).toFixed(2)}M`; } +/** @internal */ export function formatTokensPerSec(n: number): string { if (!Number.isFinite(n) || n <= 0) return '0/s'; if (n < 1000) return `${Math.round(n)}/s`; @@ -167,6 +170,7 @@ export function formatTokensPerSec(n: number): string { } const PROGRESS_BAR_WIDTH = 12; +/** @internal */ export function progressBar(ratio: number, width: number = PROGRESS_BAR_WIDTH): string { const clamped = Math.max(0, Math.min(1, ratio)); const filled = Math.round(clamped * width); diff --git a/packages/cli/src/io/logger.ts b/packages/cli/src/io/logger.ts index e9952254..fc84a559 100644 --- a/packages/cli/src/io/logger.ts +++ b/packages/cli/src/io/logger.ts @@ -14,6 +14,7 @@ function writeLine(io: KtxCliIo, message: string): void { io.stderr.write(message.endsWith('\n') ? message : `${message}\n`); } +/** @internal */ export function createNoopOperationalLogger(): KtxOperationalLogger { return { log: () => undefined, diff --git a/packages/cli/src/managed-mcp-daemon.ts b/packages/cli/src/managed-mcp-daemon.ts index dd3fb821..881574de 100644 --- a/packages/cli/src/managed-mcp-daemon.ts +++ b/packages/cli/src/managed-mcp-daemon.ts @@ -17,6 +17,7 @@ export interface KtxMcpDaemonState { logPath: string; } +/** @internal */ export interface KtxMcpDaemonChild { pid?: number; unref(): void; diff --git a/packages/cli/src/managed-python-command.ts b/packages/cli/src/managed-python-command.ts index 014e65bc..36ab7060 100644 --- a/packages/cli/src/managed-python-command.ts +++ b/packages/cli/src/managed-python-command.ts @@ -33,7 +33,7 @@ export interface ManagedPythonCommandRuntime { manifest: InstalledKtxRuntimeManifest; } -export interface ManagedPythonCommandDeps { +interface ManagedPythonCommandDeps { readStatus?: (options: ManagedPythonRuntimeLayoutOptions) => Promise; installRuntime?: (options: ManagedPythonRuntimeInstallOptions) => Promise; confirmInstall?: (message: string, io: KtxCliIo) => Promise; @@ -51,6 +51,7 @@ export interface ManagedPythonSemanticLayerComputeOptions extends ManagedPythonC createPythonCompute?: typeof createPythonSemanticLayerComputePort; } +/** @internal */ export function managedRuntimeInstallCommand(feature: KtxRuntimeFeature): string { return feature === 'local-embeddings' ? 'ktx admin runtime install --feature local-embeddings --yes' diff --git a/packages/cli/src/managed-python-daemon.ts b/packages/cli/src/managed-python-daemon.ts index e36435c3..7bc92e14 100644 --- a/packages/cli/src/managed-python-daemon.ts +++ b/packages/cli/src/managed-python-daemon.ts @@ -51,7 +51,7 @@ export interface ManagedPythonDaemonProcessInfo { command: string; } -export type ManagedPythonDaemonStopAllSource = 'state' | 'process'; +type ManagedPythonDaemonStopAllSource = 'state' | 'process'; export interface ManagedPythonDaemonStopAllEntry { pid: number; @@ -74,11 +74,13 @@ export interface ManagedPythonDaemonStopAllResult { scanErrors: string[]; } +/** @internal */ export interface ManagedPythonDaemonChild { pid?: number; unref(): void; } +/** @internal */ export type ManagedPythonDaemonSpawn = ( command: string, args: string[], @@ -89,6 +91,7 @@ export type ManagedPythonDaemonSpawn = ( }, ) => ManagedPythonDaemonChild; +/** @internal */ export type ManagedPythonDaemonFetch = ( url: string, ) => Promise<{ @@ -98,7 +101,7 @@ export type ManagedPythonDaemonFetch = ( text(): Promise; }>; -export type ManagedPythonDaemonKillProcess = (pid: number, signal?: NodeJS.Signals) => void; +type ManagedPythonDaemonKillProcess = (pid: number, signal?: NodeJS.Signals) => void; export interface ManagedPythonDaemonStartOptions extends ManagedPythonDaemonLayoutOptions { features: KtxRuntimeFeature[]; diff --git a/packages/cli/src/managed-python-http.ts b/packages/cli/src/managed-python-http.ts index 73e9f73e..1f7cb775 100644 --- a/packages/cli/src/managed-python-http.ts +++ b/packages/cli/src/managed-python-http.ts @@ -21,12 +21,13 @@ import { } from './managed-python-command.js'; import { startManagedPythonDaemon, type ManagedPythonDaemonStartResult } from './managed-python-daemon.js'; +/** @internal */ export type ManagedPythonHttpJsonRunner = ( path: string, payload: Record, ) => Promise>; -export type ManagedPythonHttpPostJson = ( +type ManagedPythonHttpPostJson = ( baseUrl: string, path: string, payload: Record, @@ -75,7 +76,7 @@ function parseJsonObject(raw: string, path: string): Record { return parsed as Record; } -export async function postManagedDaemonJson( +async function postManagedDaemonJson( baseUrl: string, path: string, payload: Record, @@ -117,6 +118,7 @@ export async function postManagedDaemonJson( }); } +/** @internal */ export function createManagedPythonDaemonBaseUrlResolver( options: ManagedPythonCoreDaemonOptions, ): () => Promise { @@ -158,6 +160,7 @@ function isResolveBaseUrlOnly( return 'resolveBaseUrl' in options; } +/** @internal */ export function createManagedDaemonHttpJsonRunner( options: ManagedPythonDaemonHttpOptions, ): ManagedPythonHttpJsonRunner { diff --git a/packages/cli/src/managed-python-runtime.ts b/packages/cli/src/managed-python-runtime.ts index 68272840..5aef2b32 100644 --- a/packages/cli/src/managed-python-runtime.ts +++ b/packages/cli/src/managed-python-runtime.ts @@ -25,7 +25,7 @@ const runtimeAssetManifestSchema = z.object({ }), }); -export type KtxRuntimeAssetManifest = z.infer; +type KtxRuntimeAssetManifest = z.infer; const installedRuntimeManifestSchema = z.object({ schemaVersion: z.literal(1), @@ -76,6 +76,7 @@ export interface ManagedPythonDaemonLayout extends ManagedPythonRuntimeLayout { daemonStderrPath: string; } +/** @internal */ export interface ManagedRuntimeAsset { manifest: KtxRuntimeAssetManifest; wheelPath: string; @@ -104,7 +105,7 @@ export interface ManagedPythonRuntimeInstallResult { manifest: InstalledKtxRuntimeManifest; } -export type ManagedPythonRuntimeStatusKind = 'missing' | 'ready' | 'mismatched' | 'broken'; +type ManagedPythonRuntimeStatusKind = 'missing' | 'ready' | 'mismatched' | 'broken'; export interface ManagedPythonRuntimeStatus { kind: ManagedPythonRuntimeStatusKind; @@ -121,6 +122,7 @@ export interface ManagedPythonRuntimeDoctorCheck { fix?: string; } +/** @internal */ export const MISSING_UV_RUNTIME_INSTALL_MESSAGE = 'uv is required to install the KTX Python runtime. KTX does not download uv automatically. Install uv, make sure it is on PATH, and retry: ktx admin runtime install --yes'; @@ -142,6 +144,7 @@ function executablePath(venvDir: string, platform: NodeJS.Platform, name: string return join(venvDir, 'bin', name); } +/** @internal */ export function managedPythonRuntimeLayout(options: ManagedPythonRuntimeLayoutOptions): ManagedPythonRuntimeLayout { const platform = options.platform ?? process.platform; const env = options.env ?? process.env; @@ -235,6 +238,7 @@ function parseRequiresPythonFromWheel(input: { wheelPath: string; contents: Buff }; } +/** @internal */ export async function verifyRuntimeAsset(input: { assetDir: string }): Promise { const manifestPath = join(input.assetDir, 'manifest.json'); let manifestData: unknown; diff --git a/packages/cli/src/mcp-http-server.ts b/packages/cli/src/mcp-http-server.ts index a81c9b0b..47c12f1a 100644 --- a/packages/cli/src/mcp-http-server.ts +++ b/packages/cli/src/mcp-http-server.ts @@ -25,6 +25,7 @@ export interface McpSecurityConfig { allowedOrigins: string[]; } +/** @internal */ export type McpAuthorizationResult = | { ok: true } | { ok: false; status: 401 | 403; message: string }; @@ -34,6 +35,7 @@ function isLoopbackHost(host: string): boolean { return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1'; } +/** @internal */ export function normalizeHostHeader(value: string): string { const trimmed = value.trim().toLowerCase(); if (trimmed.startsWith('[')) { @@ -85,6 +87,7 @@ function headerValue(headers: IncomingHttpHeaders | Record }, config: McpSecurityConfig, diff --git a/packages/cli/src/memory-flow-interactive.ts b/packages/cli/src/memory-flow-interactive.ts index 299138fb..bb7813dd 100644 --- a/packages/cli/src/memory-flow-interactive.ts +++ b/packages/cli/src/memory-flow-interactive.ts @@ -42,6 +42,7 @@ function defaultPrepareKeypressEvents(stdin: KtxMemoryFlowStdin): void { emitKeypressEvents(stdin as Parameters[0]); } +/** @internal */ export function memoryFlowCommandForKey( chunk: string, search: MemoryFlowInteractionState['search'], diff --git a/packages/cli/src/memory-flow-tui.tsx b/packages/cli/src/memory-flow-tui.tsx index f117b605..3f10970e 100644 --- a/packages/cli/src/memory-flow-tui.tsx +++ b/packages/cli/src/memory-flow-tui.tsx @@ -66,6 +66,7 @@ export interface MemoryFlowTuiLiveSession { isClosed(): boolean; } +/** @internal */ export interface MemoryFlowInkInstance { rerender(tree: ReactNode): void; unmount(): void; @@ -73,7 +74,7 @@ export interface MemoryFlowInkInstance { clear?(): void; } -export interface MemoryFlowInkRenderOptions { +interface MemoryFlowInkRenderOptions { stdin?: KtxMemoryFlowTuiIo['stdin']; stdout: KtxMemoryFlowTuiIo['stdout']; stderr: KtxMemoryFlowTuiIo['stderr']; @@ -157,6 +158,7 @@ export function sanitizeMemoryFlowTuiError(error: unknown): string { .replace(/\b(api[_-]?key|password|token|secret)=\S+/gi, '[redacted]'); } +/** @internal */ export function memoryFlowCommandForInkInput( input: string, key: InkKey, @@ -285,6 +287,7 @@ function TrustIssues(props: { view: MemoryFlowViewModel; theme: MemoryFlowTuiThe ); } +/** @internal */ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode { const app = useApp(); const totalEvents = props.input.events.length; diff --git a/packages/cli/src/next-steps.ts b/packages/cli/src/next-steps.ts index 5410eee8..80a1b441 100644 --- a/packages/cli/src/next-steps.ts +++ b/packages/cli/src/next-steps.ts @@ -1,3 +1,4 @@ +/** @internal */ export const KTX_CONTEXT_BUILD_COMMANDS = [ { command: 'ktx ingest', @@ -24,9 +25,10 @@ export const KTX_NEXT_STEP_DIRECT_COMMANDS = [ }, ] as const; +/** @internal */ export const KTX_NEXT_STEP_COMMANDS = [...KTX_NEXT_STEP_DIRECT_COMMANDS] as const; -export const KTX_NEXT_STEP_COMMAND_WIDTH = Math.max( +const KTX_NEXT_STEP_COMMAND_WIDTH = Math.max( ...[...KTX_CONTEXT_BUILD_COMMANDS, ...KTX_NEXT_STEP_COMMANDS].map((step) => step.command.length), ); diff --git a/packages/cli/src/notion-page-picker.ts b/packages/cli/src/notion-page-picker.ts index 79739710..b5bd5abf 100644 --- a/packages/cli/src/notion-page-picker.ts +++ b/packages/cli/src/notion-page-picker.ts @@ -24,6 +24,7 @@ export interface PickNotionRootPagesArgs { connection: KtxProjectConnectionConfig; } +/** @internal */ export type NotionPickerApi = Pick; export type NotionRootPagePickResult = | { kind: 'selected'; rootPageIds: string[] } @@ -50,6 +51,7 @@ function assertSafeNotionPickerConnectionId(connectionId: string): void { } } +/** @internal */ export function normalizeNotionPageId(value: string): string { const trimmed = value.trim(); const compact = trimmed.includes('-') ? trimmed.replace(/-/g, '') : trimmed; @@ -106,6 +108,7 @@ function extractParentPageId(page: Record): string | null { return normalizeNotionPageId(parent.page_id); } +/** @internal */ export function notionPickerPageFromSearchResult(result: Record): TreePickerNodeInput { const id = typeof result.id === 'string' ? normalizeNotionPageId(result.id) : ''; if (!id) { @@ -119,6 +122,7 @@ export function notionPickerPageFromSearchResult(result: Record }; } +/** @internal */ export async function discoverNotionPickerPages( api: NotionPickerApi, options: { cap?: number } = {}, @@ -161,6 +165,7 @@ export async function discoverNotionPickerPages( return { pages, cappedAtCount: cap, warnings }; } +/** @internal */ export async function resolveNotionWorkspaceLabel(api: NotionPickerApi, connectionId: string): Promise { try { const bot = (await api.retrieveBotUser()) as NotionBotInfo; diff --git a/packages/cli/src/prompt-navigation.ts b/packages/cli/src/prompt-navigation.ts index c3644338..619ee027 100644 --- a/packages/cli/src/prompt-navigation.ts +++ b/packages/cli/src/prompt-navigation.ts @@ -38,6 +38,7 @@ function withTextInputBodySpacing(message: string): string { return `${title}\n\n${bodyLines.join('\n')}`; } +/** @internal */ export function withMenuOptionSpacing(message: string): string { if (!message.includes('\n') || message.endsWith('\n')) { return message; diff --git a/packages/cli/src/scan.ts b/packages/cli/src/scan.ts index 3eb13a25..9b48aba0 100644 --- a/packages/cli/src/scan.ts +++ b/packages/cli/src/scan.ts @@ -274,6 +274,7 @@ interface KtxCliScanProgress extends Omit { flush(): void; } +/** @internal */ export function createCliScanProgress( io: KtxCliIo, state: KtxCliScanProgressState = { progress: 0, hasPendingTransient: false }, diff --git a/packages/cli/src/setup-agents.ts b/packages/cli/src/setup-agents.ts index 24ee115a..6a389257 100644 --- a/packages/cli/src/setup-agents.ts +++ b/packages/cli/src/setup-agents.ts @@ -21,6 +21,7 @@ import { readKtxMcpDaemonStatus } from './managed-mcp-daemon.js'; export type KtxAgentTarget = 'claude-code' | 'claude-desktop' | 'codex' | 'cursor' | 'opencode' | 'universal'; export type KtxAgentScope = 'project' | 'global' | 'local'; +/** @internal */ export type KtxAgentInstallMode = 'mcp' | 'mcp-cli'; export interface KtxSetupAgentsArgs { @@ -118,6 +119,7 @@ function writeSetupOutro(io: KtxCliIo, message: string): void { const STEP_HEADING_RE = /^(\d+)\. (.+)$/; const ACTION_MARKER_RE = /^(RUN|PASTE|USE|OPEN):$/; +/** @internal */ export function createAgentNextActionsLineFormatter( stdout: KtxCliIo['stdout'], ): (line: string) => string { @@ -301,7 +303,7 @@ function claudeDesktopConfigPath(): { path: string; jsonPath: string[] } { const CLAUDE_DESKTOP_FORWARDED_ENV_KEYS = ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'] as const; -export function collectClaudeDesktopForwardedEnv(source: NodeJS.ProcessEnv): Record { +function collectClaudeDesktopForwardedEnv(source: NodeJS.ProcessEnv): Record { const captured: Record = {}; for (const [key, value] of Object.entries(source)) { if (value === undefined || value === '') continue; @@ -394,7 +396,7 @@ function plannedMcpJsonEntries(input: { return []; } -export function agentInstallManifestPath(projectDir: string): string { +function agentInstallManifestPath(projectDir: string): string { return join(resolve(projectDir), '.ktx/agents/install-manifest.json'); } @@ -410,6 +412,7 @@ function claudeDesktopLauncherPath(projectDir: string): string { return join(resolve(projectDir), '.ktx/agents/claude/ktx-plugin-runner.sh'); } +/** @internal */ export function plannedKtxAgentFiles(input: { projectDir: string; target: KtxAgentTarget; @@ -750,6 +753,7 @@ function mergeManifest( }; } +/** @internal */ export async function removeKtxAgentInstall(projectDir: string, io: KtxCliIo): Promise { const manifest = await readKtxAgentInstallManifest(projectDir); if (!manifest) { @@ -765,7 +769,7 @@ export async function removeKtxAgentInstall(projectDir: string, io: KtxCliIo): P return 0; } -export interface KtxSetupAgentsPromptAdapter { +interface KtxSetupAgentsPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; multiselect(options: { message: string; @@ -853,6 +857,7 @@ function hasAdminCliEntries(entries: InstallEntry[]): boolean { ); } +/** @internal */ export interface InstallSummaryEntry { title: string; lines: string[]; @@ -869,6 +874,7 @@ function formatInlinePath(path: string): string { return path; } +/** @internal */ export function formatInstallSummaryLines( installs: Array<{ target: KtxAgentTarget; scope: KtxAgentScope; mode: KtxAgentInstallMode }>, entries: InstallEntry[], diff --git a/packages/cli/src/setup-context.ts b/packages/cli/src/setup-context.ts index 820676d0..f3da7325 100644 --- a/packages/cli/src/setup-context.ts +++ b/packages/cli/src/setup-context.ts @@ -25,12 +25,13 @@ import { type KtxSetupPromptOption, } from './setup-prompts.js'; -export type KtxSetupContextBuildStatus = +type KtxSetupContextBuildStatus = | 'not_started' | 'completed' | 'failed' | 'stale'; +/** @internal */ export interface KtxSetupContextCommands { build: string; status: string; @@ -61,7 +62,7 @@ export interface KtxSetupContextStatusSummary { detail?: string; } -export interface KtxSetupContextReadiness { +interface KtxSetupContextReadiness { ready: boolean; agentContextReady: boolean; semanticSearchReady: boolean; @@ -86,7 +87,7 @@ export interface KtxSetupContextStepArgs { runtimeInstallPolicy?: KtxManagedPythonInstallPolicy; } -export interface KtxSetupContextPromptAdapter { +interface KtxSetupContextPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; cancel(message: string): void; } @@ -125,6 +126,7 @@ async function pathExists(path: string): Promise { } } +/** @internal */ export function contextBuildCommands(projectDir: string): KtxSetupContextCommands { const resolvedProjectDir = resolve(projectDir); return { @@ -236,6 +238,7 @@ export async function readKtxSetupContextState(projectDir: string): Promise { const resolvedProjectDir = resolve(projectDir); await mkdir(join(resolvedProjectDir, '.ktx', 'setup'), { recursive: true }); diff --git a/packages/cli/src/setup-databases.ts b/packages/cli/src/setup-databases.ts index 99701c00..dd7a28ad 100644 --- a/packages/cli/src/setup-databases.ts +++ b/packages/cli/src/setup-databases.ts @@ -64,6 +64,7 @@ export type KtxSetupDatabasesResult = | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; +/** @internal */ export interface KtxSetupDatabasesPromptAdapter { multiselect(options: { message: string; diff --git a/packages/cli/src/setup-demo-tour.ts b/packages/cli/src/setup-demo-tour.ts index b2acb9f1..79f71fbe 100644 --- a/packages/cli/src/setup-demo-tour.ts +++ b/packages/cli/src/setup-demo-tour.ts @@ -64,6 +64,7 @@ function createTargetState(target: KtxPublicIngestPlanTarget): ContextBuildTarge // Pure rendering functions // --------------------------------------------------------------------------- +/** @internal */ export function renderDemoBanner(projectDir?: string): string { const lines = [ '', @@ -76,6 +77,7 @@ export function renderDemoBanner(projectDir?: string): string { return lines.join('\n'); } +/** @internal */ export function renderDemoCardContent(title: string, selections: string[]): string { const lines = [ `┌ ${title}`, @@ -88,6 +90,7 @@ export function renderDemoCardContent(title: string, selections: string[]): stri return lines.join('\n'); } +/** @internal */ export function renderDemoAgentTransition(): string { const lines = [ '┌ Demo project is ready — let\'s connect your agent', @@ -99,6 +102,7 @@ export function renderDemoAgentTransition(): string { return lines.join('\n'); } +/** @internal */ export function renderDemoCompletionSummary(projectDir: string, agentInstalled: boolean): string { const lines: string[] = [ '', @@ -129,7 +133,7 @@ export function renderDemoCompletionSummary(projectDir: string, agentInstalled: // Keypress navigation // --------------------------------------------------------------------------- -export async function waitForDemoNavigation( +async function waitForDemoNavigation( stdin?: NodeJS.ReadStream, ): Promise<'forward' | 'back'> { const input = stdin ?? process.stdin; @@ -169,7 +173,7 @@ export async function waitForDemoNavigation( // Interactive card // --------------------------------------------------------------------------- -export async function renderDemoCard( +async function renderDemoCard( title: string, selections: string[], io: KtxCliIo, @@ -186,6 +190,7 @@ export async function renderDemoCard( // Context build replay // --------------------------------------------------------------------------- +/** @internal */ export interface DemoReplayEvent { delayMs: number; connectionId: string; @@ -194,6 +199,7 @@ export interface DemoReplayEvent { summaryText: string | null; } +/** @internal */ export const DEMO_REPLAY_TARGETS = { primarySources: [ createDemoTarget('postgres-warehouse', 'database-ingest', 'postgres'), @@ -205,6 +211,7 @@ export const DEMO_REPLAY_TARGETS = { ], } as const; +/** @internal */ export function buildDemoReplayTimeline(): DemoReplayEvent[] { return [ // postgres-warehouse: database schema context @@ -239,7 +246,7 @@ function renderDemoContextCompletionSummary(): string { return lines.join('\n'); } -export async function runDemoContextReplay( +async function runDemoContextReplay( io: KtxCliIo, stdin?: NodeJS.ReadStream, ): Promise<'forward' | 'back'> { diff --git a/packages/cli/src/setup-embeddings.ts b/packages/cli/src/setup-embeddings.ts index f53ebe16..b95ea1d5 100644 --- a/packages/cli/src/setup-embeddings.ts +++ b/packages/cli/src/setup-embeddings.ts @@ -46,6 +46,7 @@ export type KtxSetupEmbeddingsResult = | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; +/** @internal */ export interface KtxSetupEmbeddingsPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; password(options: { message: string }): Promise; diff --git a/packages/cli/src/setup-interrupt.ts b/packages/cli/src/setup-interrupt.ts index 9baa0f1f..782abbf5 100644 --- a/packages/cli/src/setup-interrupt.ts +++ b/packages/cli/src/setup-interrupt.ts @@ -9,6 +9,7 @@ export class KtxSetupExitError extends Error { } } +/** @internal */ export interface SetupInterruptTracker { track(run: () => Promise): Promise; wasCtrlC(): boolean; diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index e4d59b45..818b12ab 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -51,6 +51,7 @@ export type KtxSetupModelResult = | { status: 'missing-input'; projectDir: string } | { status: 'failed'; projectDir: string }; +/** @internal */ export interface AnthropicModelChoice { id: string; label: string; @@ -59,6 +60,7 @@ export interface AnthropicModelChoice { export type KtxSetupLlmBackend = 'anthropic' | 'vertex' | 'claude-code'; +/** @internal */ export interface KtxSetupModelPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; text(options: { message: string; placeholder?: string }): Promise; @@ -82,8 +84,7 @@ export interface KtxSetupModelDeps { spinner?: () => KtxCliSpinner; } -export const BUNDLED_ANTHROPIC_MODEL_REGISTRY_VERSION = '2026-05-07'; - +/** @internal */ export const BUNDLED_ANTHROPIC_MODELS: AnthropicModelChoice[] = [ { id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6', recommended: true }, { id: 'claude-opus-4-6', label: 'Claude Opus 4.6', recommended: false }, @@ -131,7 +132,7 @@ const execFileAsync = promisify(execFile); type AnthropicModelDiscoveryErrorReason = 'authentication' | 'http' | 'empty-response'; -export class AnthropicModelDiscoveryError extends Error { +class AnthropicModelDiscoveryError extends Error { constructor( message: string, public readonly reason: AnthropicModelDiscoveryErrorReason, @@ -212,6 +213,7 @@ async function defaultListGcloudProjects(): Promise { .filter((project): project is GcloudProjectChoice => Boolean(project)); } +/** @internal */ export async function fetchAnthropicModels( apiKey: string, fetchFn: typeof fetch = fetch, diff --git a/packages/cli/src/setup-project.ts b/packages/cli/src/setup-project.ts index d093b04d..f5fa5c5c 100644 --- a/packages/cli/src/setup-project.ts +++ b/packages/cli/src/setup-project.ts @@ -18,8 +18,8 @@ import { type KtxSetupPromptOption, } from './setup-prompts.js'; -export type KtxSetupProjectMode = 'auto' | 'prompt-new'; -export type KtxSetupInputMode = 'auto' | 'disabled'; +type KtxSetupProjectMode = 'auto' | 'prompt-new'; +type KtxSetupInputMode = 'auto' | 'disabled'; export interface KtxSetupProjectArgs { projectDir: string; @@ -45,6 +45,7 @@ export type KtxSetupProjectResult = | { status: 'cancelled'; projectDir: string } | { status: 'missing-input'; projectDir: string }; +/** @internal */ export interface KtxSetupProjectPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; text(options: { message: string; placeholder?: string }): Promise; diff --git a/packages/cli/src/setup-ready-menu.ts b/packages/cli/src/setup-ready-menu.ts index afb89a0f..f1f736e4 100644 --- a/packages/cli/src/setup-ready-menu.ts +++ b/packages/cli/src/setup-ready-menu.ts @@ -14,7 +14,7 @@ export type KtxSetupReadyAction = | 'agents' | 'exit'; -export interface KtxSetupReadyMenuPromptAdapter { +interface KtxSetupReadyMenuPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; cancel(message: string): void; } diff --git a/packages/cli/src/setup-sources.ts b/packages/cli/src/setup-sources.ts index 92f5a51c..f4042037 100644 --- a/packages/cli/src/setup-sources.ts +++ b/packages/cli/src/setup-sources.ts @@ -89,7 +89,7 @@ export interface KtxSetupSourcesPromptAdapter { log?(message: string): void; } -export type SourceValidationResult = { ok: true; detail?: string } | { ok: false; message: string }; +type SourceValidationResult = { ok: true; detail?: string } | { ok: false; message: string }; export interface KtxSetupSourcesDeps { prompts?: KtxSetupSourcesPromptAdapter; diff --git a/packages/cli/src/text-ingest.ts b/packages/cli/src/text-ingest.ts index 0be1534f..9ba64e13 100644 --- a/packages/cli/src/text-ingest.ts +++ b/packages/cli/src/text-ingest.ts @@ -17,6 +17,7 @@ export interface KtxTextIngestArgs { failFast: boolean; } +/** @internal */ export interface TextMemoryIngestPort { ingest(input: MemoryAgentInput): Promise<{ runId: string }>; waitForRun(runId: string): Promise; diff --git a/packages/cli/src/tree-picker-state.ts b/packages/cli/src/tree-picker-state.ts index 3f9d13bf..3e96e096 100644 --- a/packages/cli/src/tree-picker-state.ts +++ b/packages/cli/src/tree-picker-state.ts @@ -93,6 +93,7 @@ function transientHint(text: string, now: number): PickerState['transientHint'] return { text, expiresAt: now + TRANSIENT_HINT_DURATION_MS }; } +/** @internal */ export function clearExpiredTransientHint(state: PickerState, now = Date.now()): PickerState { if (!state.transientHint || state.transientHint.expiresAt > now) { return state; @@ -249,6 +250,7 @@ function checkedAncestor(nodeId: string, state: PickerState): TreePickerNode | n return null; } +/** @internal */ export function canToggle(nodeId: string, state: PickerState): { ok: true } | { ok: false; reason: string } { if (!state.byId.has(nodeId)) { return { ok: false, reason: 'Node not found' }; @@ -260,6 +262,7 @@ export function canToggle(nodeId: string, state: PickerState): { ok: true } | { return { ok: true }; } +/** @internal */ export function toggleChecked(state: PickerState, nodeId: string, now = Date.now()): PickerState { const toggle = canToggle(nodeId, state); if (!toggle.ok) { @@ -335,6 +338,7 @@ export function visibleNodeIds(state: PickerState): string[] { return result; } +/** @internal */ export function selectAllVisible(state: PickerState): PickerState { const candidates = state.search.query.trim().length > 0 ? matchingIds(state) : new Set(visibleNodeIds(state)); const checked = new Set(state.checked); @@ -358,6 +362,7 @@ export function selectAllVisible(state: PickerState): PickerState { }); } +/** @internal */ export function selectNone(state: PickerState): PickerState { return cloneState(state, { checked: new Set(), transientHint: null }); } @@ -383,6 +388,7 @@ function setExpanded(state: PickerState, nodeId: string, value: boolean | 'toggl return cloneState(state, { expanded }); } +/** @internal */ export function moveCursor(state: PickerState, dir: 'up' | 'down' | 'left' | 'right'): PickerState { const node = state.byId.get(state.cursorId); if (!node) { diff --git a/packages/cli/src/tree-picker-tui.tsx b/packages/cli/src/tree-picker-tui.tsx index a2c4a9ab..57525270 100644 --- a/packages/cli/src/tree-picker-tui.tsx +++ b/packages/cli/src/tree-picker-tui.tsx @@ -77,12 +77,14 @@ interface TreePickerAppProps extends TreePickerRenderInput { onExit(result: TreePickerResult): void; } +/** @internal */ export interface TreePickerInkInstance { rerender(tree: ReactNode): void; unmount(): void; waitUntilExit(): Promise; } +/** @internal */ export interface TreePickerInkRenderOptions { stdin?: TreePickerTuiIo['stdin']; stdout: TreePickerTuiIo['stdout']; @@ -97,6 +99,7 @@ function resolveTheme(env: NodeJS.ProcessEnv = process.env): TreePickerTheme { return env.NO_COLOR || env.TERM === 'dumb' ? NO_COLOR_THEME : COLOR_THEME; } +/** @internal */ export function resolveTreePickerWidth(columns: number | undefined): number { const resolvedColumns = columns ?? 100; return Math.max(60, Math.min(120, resolvedColumns - 4)); @@ -114,6 +117,7 @@ function rowMatchesSearch(state: PickerState, nodeId: string): boolean { return node.title.toLocaleLowerCase().includes(query) || node.path.toLocaleLowerCase().includes(query); } +/** @internal */ export function sanitizeTreePickerTuiError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); return message @@ -121,11 +125,13 @@ export function sanitizeTreePickerTuiError(error: unknown): string { .replace(/\b(api[_-]?key|password|token|secret)=\S+/gi, '[redacted]'); } +/** @internal */ export function windowOffset(count: number, selected: number, visible: number): number { if (count <= visible) return 0; return Math.max(0, Math.min(count - visible, selected - Math.floor(visible / 2))); } +/** @internal */ export function windowItems(items: T[], selected: number, visible: number): { items: T[]; offset: number } { const offset = windowOffset(items.length, selected, visible); return { items: items.slice(offset, offset + visible), offset }; @@ -137,6 +143,7 @@ function truncateText(value: string, width: number): string { return `${value.slice(0, width - 3)}...`; } +/** @internal */ export function treePickerCommandForInkInput( input: string, key: InkKey, @@ -205,6 +212,7 @@ function PickerRow(props: { state: PickerState; nodeId: string; width: number; t ); } +/** @internal */ export function TreePickerApp(props: TreePickerAppProps): ReactNode { const app = useApp(); const [state, setState] = useState(props.initialState); diff --git a/packages/cli/src/viz-fallback.ts b/packages/cli/src/viz-fallback.ts index 140d700f..c0f9c281 100644 --- a/packages/cli/src/viz-fallback.ts +++ b/packages/cli/src/viz-fallback.ts @@ -88,6 +88,7 @@ export function warnVizFallbackOnce(io: KtxVizFallbackIo, decision: KtxVizFallba io.stderr.write(`Visualization requested but ${decision.message}; printing plain output.\n`); } +/** @internal */ export function resetVizFallbackWarningsForTest(): void { warnedFallbackReasons.clear(); }