@kaelio/ktx 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/assets/python/{kaelio_ktx-0.7.0-py3-none-any.whl → kaelio_ktx-0.9.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +7 -0
  5. package/dist/cli-runtime.js +50 -3
  6. package/dist/command-schemas.d.ts +1 -1
  7. package/dist/command-tree.js +5 -1
  8. package/dist/commands/completion-commands.d.ts +3 -0
  9. package/dist/commands/completion-commands.js +38 -0
  10. package/dist/commands/ingest-commands.js +0 -4
  11. package/dist/commands/knowledge-commands.js +15 -2
  12. package/dist/commands/setup-commands.js +3 -3
  13. package/dist/commands/sl-commands.js +19 -7
  14. package/dist/completion/complete-engine.d.ts +19 -0
  15. package/dist/completion/complete-engine.js +128 -0
  16. package/dist/completion/completion-scripts.d.ts +1 -0
  17. package/dist/completion/completion-scripts.js +36 -0
  18. package/dist/completion/dynamic-candidates.d.ts +6 -0
  19. package/dist/completion/dynamic-candidates.js +98 -0
  20. package/dist/connection-drivers.d.ts +3 -0
  21. package/dist/connection-drivers.js +17 -0
  22. package/dist/connection-recovery.d.ts +34 -0
  23. package/dist/connection-recovery.js +82 -0
  24. package/dist/connection.js +3 -1
  25. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  26. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  27. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  28. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  29. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  30. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +29 -0
  31. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +190 -0
  32. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  33. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  34. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  35. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  36. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  37. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  38. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  39. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  40. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  41. package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
  42. package/dist/context/ingest/ingest-bundle.runner.js +72 -15
  43. package/dist/context/ingest/ingest-profile.d.ts +102 -0
  44. package/dist/context/ingest/ingest-profile.js +306 -0
  45. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  46. package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
  47. package/dist/context/ingest/local-adapters.js +21 -4
  48. package/dist/context/ingest/local-bundle-runtime.js +4 -2
  49. package/dist/context/ingest/local-ingest.d.ts +1 -1
  50. package/dist/context/ingest/local-ingest.js +6 -4
  51. package/dist/context/ingest/memory-flow/events.js +2 -1
  52. package/dist/context/ingest/ports.d.ts +2 -0
  53. package/dist/context/ingest/reports.d.ts +3 -0
  54. package/dist/context/ingest/reports.js +10 -0
  55. package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
  56. package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
  57. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
  58. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  59. package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
  60. package/dist/context/ingest/tools/tool-call-logger.js +36 -1
  61. package/dist/context/llm/ai-sdk-runtime.js +32 -3
  62. package/dist/context/llm/claude-code-runtime.js +35 -2
  63. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  64. package/dist/context/llm/codex-exec-events.js +155 -0
  65. package/dist/context/llm/codex-isolation.d.ts +3 -0
  66. package/dist/context/llm/codex-isolation.js +5 -0
  67. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  68. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  69. package/dist/context/llm/codex-models.d.ts +2 -0
  70. package/dist/context/llm/codex-models.js +17 -0
  71. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  72. package/dist/context/llm/codex-runtime-config.js +19 -0
  73. package/dist/context/llm/codex-runtime.d.ts +37 -0
  74. package/dist/context/llm/codex-runtime.js +304 -0
  75. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  76. package/dist/context/llm/codex-sdk-runner.js +63 -0
  77. package/dist/context/llm/local-config.d.ts +2 -0
  78. package/dist/context/llm/local-config.js +12 -1
  79. package/dist/context/llm/runtime-port.d.ts +25 -0
  80. package/dist/context/mcp/context-tools.d.ts +2 -1
  81. package/dist/context/mcp/context-tools.js +82 -15
  82. package/dist/context/mcp/server.js +4 -0
  83. package/dist/context/mcp/types.d.ts +15 -1
  84. package/dist/context/project/config.d.ts +3 -0
  85. package/dist/context/project/config.js +6 -2
  86. package/dist/context/project/driver-schemas.js +1 -1
  87. package/dist/context/search/discover.js +4 -3
  88. package/dist/context/sl/local-sl.d.ts +15 -0
  89. package/dist/context/sl/local-sl.js +30 -0
  90. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  91. package/dist/context/sql-analysis/ports.d.ts +12 -2
  92. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  93. package/dist/context/wiki/local-knowledge.d.ts +10 -0
  94. package/dist/context/wiki/local-knowledge.js +22 -0
  95. package/dist/context-build-view.d.ts +0 -3
  96. package/dist/context-build-view.js +5 -39
  97. package/dist/ingest.js +7 -10
  98. package/dist/io/buffered-command-io.d.ts +11 -0
  99. package/dist/io/buffered-command-io.js +28 -0
  100. package/dist/knowledge.d.ts +5 -0
  101. package/dist/knowledge.js +10 -1
  102. package/dist/llm/types.d.ts +1 -1
  103. package/dist/local-adapters.d.ts +10 -2
  104. package/dist/local-adapters.js +19 -3
  105. package/dist/next-steps.js +1 -2
  106. package/dist/progress-port-adapter.d.ts +6 -0
  107. package/dist/progress-port-adapter.js +18 -0
  108. package/dist/public-ingest-copy.js +1 -1
  109. package/dist/public-ingest.d.ts +20 -8
  110. package/dist/public-ingest.js +198 -61
  111. package/dist/scan.js +3 -1
  112. package/dist/setup-context.d.ts +2 -0
  113. package/dist/setup-context.js +138 -64
  114. package/dist/setup-databases.d.ts +17 -1
  115. package/dist/setup-databases.js +366 -326
  116. package/dist/setup-models.d.ts +10 -1
  117. package/dist/setup-models.js +90 -2
  118. package/dist/setup-ready-menu.d.ts +16 -2
  119. package/dist/setup-ready-menu.js +37 -5
  120. package/dist/setup-sources.js +141 -33
  121. package/dist/setup.js +24 -12
  122. package/dist/skills/analytics/SKILL.md +6 -1
  123. package/dist/sl.d.ts +6 -1
  124. package/dist/sl.js +32 -8
  125. package/dist/status-project.d.ts +11 -0
  126. package/dist/status-project.js +50 -1
  127. package/dist/telemetry/command-hook.d.ts +1 -0
  128. package/dist/telemetry/command-hook.js +3 -1
  129. package/dist/telemetry/emitter.js +1 -1
  130. package/dist/telemetry/events.d.ts +15 -9
  131. package/dist/telemetry/events.js +17 -5
  132. package/dist/telemetry/identity.d.ts +1 -2
  133. package/dist/telemetry/identity.js +13 -10
  134. package/dist/telemetry/index.d.ts +13 -1
  135. package/dist/telemetry/index.js +18 -3
  136. package/dist/telemetry/scrubber.d.ts +10 -0
  137. package/dist/telemetry/scrubber.js +20 -0
  138. package/package.json +20 -19
  139. package/dist/ingest-depth.d.ts +0 -8
  140. package/dist/ingest-depth.js +0 -56
  141. package/dist/setup-database-context-depth.d.ts +0 -23
  142. package/dist/setup-database-context-depth.js +0 -84
@@ -2,14 +2,13 @@ import { type KtxLocalProject } from './context/project/project.js';
2
2
  import type { KtxProgressPort } from './context/scan/types.js';
3
3
  import type { KtxCliIo } from './index.js';
4
4
  import type { KtxIngestArgs, KtxIngestDeps, KtxIngestProgressUpdate } from './ingest.js';
5
- import { type KtxDatabaseContextDepth } from './ingest-depth.js';
6
5
  import { type KtxManagedPythonInstallPolicy, type ManagedPythonCommandRuntime } from './managed-python-command.js';
7
6
  import type { KtxRuntimeFeature } from './managed-python-runtime.js';
8
7
  import type { KtxScanArgs, KtxScanDeps } from './scan.js';
8
+ import type { KtxTableRef } from './context/scan/types.js';
9
9
  type KtxPublicIngestStepName = 'database-schema' | 'query-history' | 'source-ingest' | 'memory-update';
10
10
  type KtxPublicIngestStepStatus = 'done' | 'skipped' | 'failed' | 'not-run';
11
11
  type KtxPublicIngestInputMode = 'auto' | 'disabled';
12
- type KtxPublicIngestDepth = KtxDatabaseContextDepth;
13
12
  type KtxPublicIngestQueryHistoryFlag = 'default' | 'enabled' | 'disabled';
14
13
  type HistoricSqlDialect = 'postgres' | 'bigquery' | 'snowflake';
15
14
  export type KtxPublicIngestArgs = {
@@ -19,7 +18,6 @@ export type KtxPublicIngestArgs = {
19
18
  all: boolean;
20
19
  json: boolean;
21
20
  inputMode: KtxPublicIngestInputMode;
22
- depth?: KtxPublicIngestDepth;
23
21
  queryHistory?: KtxPublicIngestQueryHistoryFlag;
24
22
  queryHistoryWindowDays?: number;
25
23
  scanMode?: Extract<KtxScanArgs, {
@@ -37,7 +35,6 @@ export interface KtxPublicIngestPlanTarget {
37
35
  sourceDir?: string;
38
36
  debugCommand: string;
39
37
  steps: KtxPublicIngestStepName[];
40
- databaseDepth?: KtxPublicIngestDepth;
41
38
  detectRelationships?: boolean;
42
39
  preflightFailure?: string;
43
40
  queryHistory?: {
@@ -46,7 +43,6 @@ export interface KtxPublicIngestPlanTarget {
46
43
  windowDays?: number;
47
44
  pullConfig?: Record<string, unknown>;
48
45
  unsupported?: boolean;
49
- skippedStoredByFast?: boolean;
50
46
  };
51
47
  }
52
48
  export interface KtxPublicIngestPlan {
@@ -94,7 +90,6 @@ interface KtxPublicContextBuildArgs {
94
90
  inputMode: 'auto' | 'disabled';
95
91
  targetConnectionId?: string;
96
92
  all?: boolean;
97
- depth?: KtxPublicIngestDepth;
98
93
  queryHistory?: KtxPublicIngestQueryHistoryFlag;
99
94
  queryHistoryWindowDays?: number;
100
95
  scanMode?: Extract<KtxScanArgs, {
@@ -104,19 +99,36 @@ interface KtxPublicContextBuildArgs {
104
99
  cliVersion?: string;
105
100
  runtimeInstallPolicy?: KtxManagedPythonInstallPolicy;
106
101
  }
102
+ export declare function publicProgressMessage(message: string, target: KtxPublicIngestPlanTarget): string;
103
+ /** @internal */
104
+ export declare function queryHistoryPullConfig(input: {
105
+ stored: Record<string, unknown>;
106
+ dialect: HistoricSqlDialect;
107
+ windowDays?: number;
108
+ enabledTables?: KtxTableRef[];
109
+ enabledSchemas?: string[];
110
+ modeledTableCatalog?: KtxTableRef[];
111
+ scopeFloorWarnings?: string[];
112
+ }): Record<string, unknown>;
107
113
  export declare function buildPublicIngestPlan(project: KtxPublicIngestProject, args: {
108
114
  projectDir: string;
109
115
  targetConnectionId?: string;
110
116
  all: boolean;
111
- depth?: KtxPublicIngestDepth;
112
117
  queryHistory?: KtxPublicIngestQueryHistoryFlag;
113
118
  queryHistoryWindowDays?: number;
114
119
  scanMode?: Extract<KtxScanArgs, {
115
120
  command: 'run';
116
121
  }>['mode'];
117
122
  }): KtxPublicIngestPlan;
123
+ /**
124
+ * Run one ingest target through its scan/ingest steps. The single per-target
125
+ * chokepoint reached by every entrypoint — standalone `ktx ingest` (plain/json
126
+ * and foreground) and `ktx setup` (via `runContextBuild`). The exported
127
+ * `executePublicIngestTarget` wraps this and emits the `ingest_completed`
128
+ * telemetry event exactly once, so every path is counted.
129
+ */
118
130
  export declare function executePublicIngestTarget(target: KtxPublicIngestPlanTarget, args: Extract<KtxPublicIngestArgs, {
119
131
  command: 'run';
120
- }>, io: KtxCliIo, deps: KtxPublicIngestDeps): Promise<KtxPublicIngestTargetResult>;
132
+ }>, io: KtxCliIo, deps: KtxPublicIngestDeps, project: KtxPublicIngestProject): Promise<KtxPublicIngestTargetResult>;
121
133
  export declare function runKtxPublicIngest(args: KtxPublicIngestArgs, io: KtxCliIo, deps?: KtxPublicIngestDeps): Promise<number>;
122
134
  export {};
@@ -1,12 +1,15 @@
1
1
  import { getKtxCliPackageInfo } from './cli-runtime.js';
2
2
  import { loadKtxProject } from './context/project/project.js';
3
- import { databaseContextDepth, deepReadinessGaps, isDatabaseDriver, normalizeConnectionDriver, } from './ingest-depth.js';
3
+ import { isDatabaseDriver, normalizeConnectionDriver } from './connection-drivers.js';
4
+ import { resolveQueryHistoryScopeFloor } from './context/ingest/adapters/historic-sql/scope-floor.js';
4
5
  import { ensureManagedPythonCommandRuntime, } from './managed-python-command.js';
5
- import { publicIngestOutputLine } from './public-ingest-copy.js';
6
+ import { publicDatabaseIngestMessage, publicIngestOutputLine, publicQueryHistoryMessage, } from './public-ingest-copy.js';
7
+ import { createAggregateProgressPort } from './progress-port-adapter.js';
6
8
  import { resolvePublicIngestRuntimeRequirements } from './runtime-requirements.js';
7
9
  import { profileMark } from './startup-profile.js';
8
10
  import { isDemoConnection } from './telemetry/demo-detect.js';
9
11
  import { emitProjectStackSnapshot, emitTelemetryEvent } from './telemetry/index.js';
12
+ import { formatErrorDetail } from './telemetry/scrubber.js';
10
13
  profileMark('module:public-ingest');
11
14
  const sourceAdapterByDriver = new Map([
12
15
  ['metabase', 'metabase'],
@@ -17,6 +20,16 @@ const sourceAdapterByDriver = new Map([
17
20
  ['dbt', 'dbt'],
18
21
  ['lookml', 'lookml'],
19
22
  ]);
23
+ export function publicProgressMessage(message, target) {
24
+ let current = message;
25
+ if (target.operation === 'database-ingest') {
26
+ current = publicDatabaseIngestMessage(current);
27
+ }
28
+ if (target.steps.includes('query-history')) {
29
+ current = publicQueryHistoryMessage(current, target.connectionId);
30
+ }
31
+ return current;
32
+ }
20
33
  const queryHistoryDialectByDriver = new Map([
21
34
  ['postgres', 'postgres'],
22
35
  ['bigquery', 'bigquery'],
@@ -25,7 +38,6 @@ const queryHistoryDialectByDriver = new Map([
25
38
  function createWarningAccumulator() {
26
39
  return {
27
40
  warnings: [],
28
- ignoredDepthForSources: [],
29
41
  ignoredQueryHistoryForSources: [],
30
42
  unsupportedQueryHistoryForDatabases: [],
31
43
  };
@@ -71,12 +83,6 @@ function finalizeWarnings(accumulator, args) {
71
83
  ...accumulator.warnings,
72
84
  ...unsupportedQueryHistoryWarnings(accumulator.unsupportedQueryHistoryForDatabases, args.all),
73
85
  ];
74
- const depthOption = args.depth ? `--${args.depth}` : null;
75
- if (depthOption) {
76
- const warning = sourceIgnoredWarning(depthOption, accumulator.ignoredDepthForSources, args.all);
77
- if (warning)
78
- warnings.push(warning);
79
- }
80
86
  if (args.queryHistory === 'enabled' || args.queryHistoryWindowDays !== undefined) {
81
87
  const warning = sourceIgnoredWarning('--query-history', accumulator.ignoredQueryHistoryForSources, args.all);
82
88
  if (warning)
@@ -106,20 +112,20 @@ function storedQueryHistory(connection) {
106
112
  function positiveInteger(value) {
107
113
  return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
108
114
  }
109
- function enabledTablesForConnection(connection) {
110
- const raw = connection.enabled_tables;
111
- if (!Array.isArray(raw)) {
112
- return undefined;
113
- }
114
- const tables = raw.filter((value) => typeof value === 'string' && value.trim().length > 0);
115
- return tables.length > 0 ? tables : undefined;
116
- }
117
- function queryHistoryPullConfig(input) {
118
- const { enabled: _enabled, dialect: _dialect, ...storedConfig } = input.stored;
115
+ /** @internal */
116
+ export function queryHistoryPullConfig(input) {
117
+ const { enabled: _enabled, dialect: _dialect, enabledTables: _enabledTables, enabledSchemas: _enabledSchemas, scopeFloorWarnings: _scopeFloorWarnings, ...storedConfig } = input.stored;
119
118
  return {
120
119
  ...storedConfig,
121
120
  dialect: input.dialect,
122
- ...(input.enabledTables ? { enabledTables: input.enabledTables } : {}),
121
+ ...(input.enabledTables && input.enabledTables.length > 0 ? { enabledTables: input.enabledTables } : {}),
122
+ ...(input.enabledSchemas && input.enabledSchemas.length > 0 ? { enabledSchemas: input.enabledSchemas } : {}),
123
+ ...(input.modeledTableCatalog && input.modeledTableCatalog.length > 0
124
+ ? { modeledTableCatalog: input.modeledTableCatalog }
125
+ : {}),
126
+ ...(input.scopeFloorWarnings && input.scopeFloorWarnings.length > 0
127
+ ? { scopeFloorWarnings: input.scopeFloorWarnings }
128
+ : {}),
123
129
  ...(input.windowDays !== undefined ? { windowDays: input.windowDays } : {}),
124
130
  };
125
131
  }
@@ -135,7 +141,6 @@ function resolveDatabaseTargetOptions(input) {
135
141
  const windowOverrideRequested = input.args.queryHistoryWindowDays !== undefined;
136
142
  const requestedQh = explicitQueryHistory === 'enabled' ||
137
143
  (explicitQueryHistory !== 'disabled' && (windowOverrideRequested || storedEnabled));
138
- let depth = input.args.depth ?? databaseContextDepth(input.connection) ?? 'fast';
139
144
  const queryHistory = {
140
145
  enabled: false,
141
146
  ...(input.args.queryHistoryWindowDays !== undefined
@@ -151,18 +156,12 @@ function resolveDatabaseTargetOptions(input) {
151
156
  reason: explicitQueryHistory === 'enabled' || input.args.queryHistoryWindowDays !== undefined ? 'explicit' : 'stored',
152
157
  });
153
158
  return {
154
- databaseDepth: depth,
155
159
  queryHistory: { ...queryHistory, unsupported: true },
156
160
  steps: ['database-schema'],
157
161
  };
158
162
  }
159
163
  if (requestedQh && dialect) {
160
- if (depth === 'fast') {
161
- input.warnings.warnings.push(`--query-history requires deep ingest; running ${input.connectionId} with --deep.`);
162
- }
163
- depth = 'deep';
164
164
  return {
165
- databaseDepth: depth,
166
165
  queryHistory: {
167
166
  ...queryHistory,
168
167
  enabled: true,
@@ -171,34 +170,66 @@ function resolveDatabaseTargetOptions(input) {
171
170
  stored: storedQh,
172
171
  dialect,
173
172
  windowDays: queryHistory.windowDays,
174
- enabledTables: enabledTablesForConnection(input.connection),
175
173
  }),
176
174
  },
177
175
  steps: ['database-schema', 'query-history'],
178
176
  };
179
177
  }
180
- if (input.args.depth === 'fast' && explicitQueryHistory !== 'enabled' && storedEnabled) {
181
- input.warnings.warnings.push(`${input.connectionId} has query history enabled in ktx.yaml, but --fast skips query-history processing.`);
182
- return {
183
- databaseDepth: 'fast',
184
- queryHistory: { ...queryHistory, skippedStoredByFast: true },
185
- steps: ['database-schema'],
186
- };
187
- }
188
178
  return {
189
- databaseDepth: depth,
190
179
  queryHistory,
191
180
  steps: ['database-schema'],
192
181
  };
193
182
  }
183
+ async function resolvedQueryHistoryPullConfigForTarget(target, project) {
184
+ if (target.operation !== 'database-ingest' || target.queryHistory?.enabled !== true || !target.queryHistory.dialect) {
185
+ return null;
186
+ }
187
+ const connection = project.config.connections[target.connectionId];
188
+ if (!connection) {
189
+ return (target.queryHistory.pullConfig ??
190
+ queryHistoryPullConfig({
191
+ stored: {},
192
+ dialect: target.queryHistory.dialect,
193
+ windowDays: target.queryHistory.windowDays,
194
+ }));
195
+ }
196
+ const stored = storedQueryHistory(connection);
197
+ const scopeFloor = await resolveQueryHistoryScopeFloor({
198
+ projectDir: project.projectDir,
199
+ connectionId: target.connectionId,
200
+ driver: target.driver,
201
+ connection: connection,
202
+ storedQueryHistory: stored,
203
+ });
204
+ return queryHistoryPullConfig({
205
+ stored,
206
+ dialect: target.queryHistory.dialect,
207
+ windowDays: target.queryHistory.windowDays,
208
+ enabledTables: scopeFloor.enabledTables,
209
+ enabledSchemas: scopeFloor.enabledSchemas,
210
+ modeledTableCatalog: scopeFloor.modeledTableCatalog,
211
+ scopeFloorWarnings: scopeFloor.warnings,
212
+ });
213
+ }
214
+ function enrichmentReadinessGaps(config) {
215
+ const gaps = [];
216
+ if (config.llm.provider.backend === 'none' || !config.llm.models.default) {
217
+ gaps.push('model configuration');
218
+ }
219
+ if (config.scan.enrichment.mode !== 'llm') {
220
+ gaps.push('scan enrichment mode');
221
+ }
222
+ const embeddings = config.scan.enrichment.embeddings;
223
+ if (!embeddings || embeddings.backend === 'none' || !embeddings.model || embeddings.dimensions <= 0) {
224
+ gaps.push('scan embeddings');
225
+ }
226
+ return gaps;
227
+ }
194
228
  function targetForConnection(connectionId, connection, projectConfig, args, warnings) {
195
229
  const driver = normalizeConnectionDriver(connection);
196
230
  const adapter = sourceAdapterByDriver.get(driver);
197
231
  const sourceDir = sourceDirForConnection(connection);
198
232
  if (adapter) {
199
- if (args.depth) {
200
- warnings.ignoredDepthForSources.push(connectionId);
201
- }
202
233
  if (args.queryHistory === 'enabled' || args.queryHistoryWindowDays !== undefined) {
203
234
  warnings.ignoredQueryHistoryForSources.push(connectionId);
204
235
  }
@@ -214,16 +245,16 @@ function targetForConnection(connectionId, connection, projectConfig, args, warn
214
245
  }
215
246
  if (isDatabaseDriver(driver)) {
216
247
  const options = resolveDatabaseTargetOptions({ connectionId, driver, connection, args, warnings });
217
- const gaps = options.databaseDepth === 'deep' ? deepReadinessGaps(projectConfig) : [];
248
+ const gaps = enrichmentReadinessGaps(projectConfig);
218
249
  return {
219
250
  connectionId,
220
251
  driver,
221
252
  operation: 'database-ingest',
222
253
  debugCommand: `ktx ingest ${connectionId} --debug`,
223
- detectRelationships: options.databaseDepth === 'deep' && projectConfig.scan.relationships.enabled,
254
+ detectRelationships: projectConfig.scan.relationships.enabled,
224
255
  ...(gaps.length > 0
225
256
  ? {
226
- preflightFailure: `${connectionId} requires deep ingest readiness: ${gaps.join(', ')}. Run ktx setup or rerun with --fast.`,
257
+ preflightFailure: `${connectionId} cannot be ingested: enrichment is not configured (${gaps.join(', ')}). Run ktx setup to configure a model and embeddings.`,
227
258
  }
228
259
  : {}),
229
260
  ...options,
@@ -281,12 +312,11 @@ function defaultSteps(target) {
281
312
  }
282
313
  function retryCommandForTarget(target, args) {
283
314
  const projectPart = ` --project-dir ${args.projectDir}`;
284
- const depthPart = target.databaseDepth ? ` --${target.databaseDepth}` : '';
285
315
  const queryHistoryPart = target.queryHistory?.enabled === true ? ' --query-history' : '';
286
316
  const windowPart = target.queryHistory?.enabled === true && target.queryHistory.windowDays !== undefined
287
317
  ? ` --query-history-window-days ${target.queryHistory.windowDays}`
288
318
  : '';
289
- return `ktx ingest ${target.connectionId}${projectPart}${depthPart}${queryHistoryPart}${windowPart}`;
319
+ return `ktx ingest ${target.connectionId}${projectPart}${queryHistoryPart}${windowPart}`;
290
320
  }
291
321
  function trimTrailingPeriod(value) {
292
322
  return value.endsWith('.') ? value.slice(0, -1) : value;
@@ -361,6 +391,9 @@ function rowsBucket() {
361
391
  }
362
392
  async function emitIngestCompleted(input) {
363
393
  const failed = resultFailed(input.result);
394
+ const failureDetail = failed
395
+ ? formatErrorDetail(input.result.steps.find((step) => step.status === 'failed')?.detail)
396
+ : undefined;
364
397
  await emitTelemetryEvent({
365
398
  name: 'ingest_completed',
366
399
  projectDir: input.args.projectDir,
@@ -374,6 +407,7 @@ async function emitIngestCompleted(input) {
374
407
  rowsBucket: rowsBucket(),
375
408
  durationMs: Math.max(0, performance.now() - input.startedAt),
376
409
  outcome: failed ? 'error' : 'ok',
410
+ ...(failureDetail ? { errorDetail: failureDetail } : {}),
377
411
  },
378
412
  });
379
413
  }
@@ -440,6 +474,65 @@ function createCapturedPublicIngestIo() {
440
474
  },
441
475
  };
442
476
  }
477
+ function isCapturedPublicIngestIo(io) {
478
+ return typeof io.capturedOutput === 'function';
479
+ }
480
+ const PLAIN_PUBLIC_INGEST_PHASE_LABELS = {
481
+ 'database-schema': 'database schema',
482
+ 'query-history': 'query history',
483
+ 'source-ingest': 'source ingest',
484
+ };
485
+ function firstSummaryLine(summary) {
486
+ if (!summary)
487
+ return undefined;
488
+ return summary.split(/\r?\n/).find((line) => line.trim().length > 0)?.trim();
489
+ }
490
+ function plainPhaseHeader(options, phaseKey) {
491
+ const prefix = options.total > 1 ? `[${options.index + 1}/${options.total}] ` : '';
492
+ return `${prefix}${options.target.connectionId} · ${PLAIN_PUBLIC_INGEST_PHASE_LABELS[phaseKey]}`;
493
+ }
494
+ function plainPhaseEndLine(status, summary) {
495
+ const firstLine = firstSummaryLine(summary);
496
+ return firstLine ? ` ${status} · ${firstLine}` : ` ${status}`;
497
+ }
498
+ function createPlainPublicIngestProgress(io, options) {
499
+ let currentPhase = null;
500
+ const startedPhases = new Set();
501
+ const lastPercentByPhase = new Map();
502
+ const startPhase = (phaseKey) => {
503
+ currentPhase = phaseKey;
504
+ startedPhases.add(phaseKey);
505
+ lastPercentByPhase.set(phaseKey, -1);
506
+ io.stderr.write(`${plainPhaseHeader(options, phaseKey)}\n`);
507
+ };
508
+ const ensurePhaseStarted = (phaseKey) => {
509
+ if (!startedPhases.has(phaseKey)) {
510
+ startPhase(phaseKey);
511
+ return;
512
+ }
513
+ currentPhase = phaseKey;
514
+ };
515
+ const emitProgress = (update) => {
516
+ if (currentPhase === null)
517
+ return;
518
+ const rounded = Math.max(0, Math.min(100, Math.round(update.percent)));
519
+ const lastPercent = lastPercentByPhase.get(currentPhase) ?? -1;
520
+ if (rounded <= lastPercent)
521
+ return;
522
+ lastPercentByPhase.set(currentPhase, rounded);
523
+ io.stderr.write(` [${rounded}%] ${publicProgressMessage(update.message, options.target)}\n`);
524
+ };
525
+ return {
526
+ onPhaseStart: startPhase,
527
+ onPhaseEnd(phaseKey, status, summary) {
528
+ ensurePhaseStarted(phaseKey);
529
+ io.stderr.write(`${plainPhaseEndLine(status, summary)}\n`);
530
+ currentPhase = null;
531
+ },
532
+ scanProgress: createAggregateProgressPort(emitProgress),
533
+ ingestProgress: emitProgress,
534
+ };
535
+ }
443
536
  const INTERNAL_STATUS_LINE_RE = /^(Report|Run|Job|Status|Adapter|Connection|Sync|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
444
537
  const ACTIONABLE_FAILURE_LINE_RE = /^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX daemon HTTP|Error:|Failed\b|Could not\b|Cannot\b)/;
445
538
  const RUNTIME_BACKED_RETRY_LINE_RE = /^Then retry the runtime-backed KTX command\.?$/;
@@ -470,7 +563,23 @@ function capturedFailureMessage(output) {
470
563
  .filter((line) => line.startsWith('In a source checkout, build the local runtime assets with:'));
471
564
  return [firstLine, ...followupLines].join('\n');
472
565
  }
473
- export async function executePublicIngestTarget(target, args, io, deps) {
566
+ /**
567
+ * Run one ingest target through its scan/ingest steps. The single per-target
568
+ * chokepoint reached by every entrypoint — standalone `ktx ingest` (plain/json
569
+ * and foreground) and `ktx setup` (via `runContextBuild`). The exported
570
+ * `executePublicIngestTarget` wraps this and emits the `ingest_completed`
571
+ * telemetry event exactly once, so every path is counted.
572
+ */
573
+ export async function executePublicIngestTarget(target, args, io, deps, project) {
574
+ const startedAt = performance.now();
575
+ const result = await runIngestTargetSteps(target, args, io, deps, project);
576
+ // `io` may be a capture buffer for the scan/ingest step output; the telemetry
577
+ // debug echo belongs on the real user-facing stream, which callers expose as
578
+ // `deps.runtimeIo` (falling back to `io` when the step io is already real).
579
+ await emitIngestCompleted({ args, project, target, result, startedAt, io: deps.runtimeIo ?? io });
580
+ return result;
581
+ }
582
+ async function runIngestTargetSteps(target, args, io, deps, project) {
474
583
  if (target.preflightFailure) {
475
584
  if (target.operation === 'database-ingest') {
476
585
  deps.onPhaseEnd?.('database-schema', 'failed', target.preflightFailure);
@@ -488,7 +597,7 @@ export async function executePublicIngestTarget(target, args, io, deps) {
488
597
  ? {
489
598
  ...step,
490
599
  status: 'failed',
491
- detail: target.preflightFailure,
600
+ detail: `${target.connectionId} failed: ${target.preflightFailure}`,
492
601
  }
493
602
  : step),
494
603
  };
@@ -499,14 +608,18 @@ export async function executePublicIngestTarget(target, args, io, deps) {
499
608
  command: 'run',
500
609
  projectDir: args.projectDir,
501
610
  connectionId: target.connectionId,
502
- mode: target.databaseDepth === 'deep' ? 'enriched' : 'structural',
611
+ mode: 'enriched',
503
612
  detectRelationships: target.detectRelationships === true,
504
613
  dryRun: false,
505
614
  ...(args.cliVersion ? { cliVersion: args.cliVersion } : {}),
506
615
  ...(args.runtimeInstallPolicy ? { runtimeInstallPolicy: args.runtimeInstallPolicy } : {}),
507
616
  };
508
617
  const runScan = deps.runScan ?? runKtxScan;
509
- const capturedScanIo = deps.scanProgress ? null : createCapturedPublicIngestIo();
618
+ const capturedScanIo = deps.scanProgress
619
+ ? isCapturedPublicIngestIo(io)
620
+ ? io
621
+ : null
622
+ : createCapturedPublicIngestIo();
510
623
  const scanIo = capturedScanIo ?? io;
511
624
  const scanDeps = {
512
625
  ...(deps.scanProgress ? { progress: deps.scanProgress } : {}),
@@ -525,6 +638,10 @@ export async function executePublicIngestTarget(target, args, io, deps) {
525
638
  if (target.queryHistory?.enabled === true) {
526
639
  const { runKtxIngest } = await import('./ingest.js');
527
640
  const runIngest = deps.runIngest ?? runKtxIngest;
641
+ const historicSqlPullConfigOverride = (await resolvedQueryHistoryPullConfigForTarget(target, project)) ?? {
642
+ dialect: target.queryHistory.dialect,
643
+ ...(target.queryHistory.windowDays !== undefined ? { windowDays: target.queryHistory.windowDays } : {}),
644
+ };
528
645
  const ingestArgs = {
529
646
  command: 'run',
530
647
  projectDir: args.projectDir,
@@ -535,12 +652,14 @@ export async function executePublicIngestTarget(target, args, io, deps) {
535
652
  ...(args.cliVersion ? { cliVersion: args.cliVersion } : {}),
536
653
  ...(args.runtimeInstallPolicy ? { runtimeInstallPolicy: args.runtimeInstallPolicy } : {}),
537
654
  allowImplicitAdapter: true,
538
- historicSqlPullConfigOverride: target.queryHistory.pullConfig ?? {
539
- dialect: target.queryHistory.dialect,
540
- ...(target.queryHistory.windowDays !== undefined ? { windowDays: target.queryHistory.windowDays } : {}),
541
- },
655
+ historicSqlPullConfigOverride,
542
656
  };
543
- const capturedIngestIo = deps.ingestProgress ? null : createCapturedPublicIngestIo();
657
+ // Query history runs after the schema scan has already written its report
658
+ // into the shared target io, so it needs a phase-local capture. Reusing
659
+ // `io` here would let leftover scan text (e.g. "Mode: enriched") surface as
660
+ // the query-history failure detail. Only skip capture when progress is
661
+ // active and the caller manages its own buffer (io is not a capture).
662
+ const capturedIngestIo = deps.ingestProgress && !isCapturedPublicIngestIo(io) ? null : createCapturedPublicIngestIo();
544
663
  const ingestIo = capturedIngestIo ?? io;
545
664
  const ingestDeps = {
546
665
  ...(deps.ingestProgress ? { progress: deps.ingestProgress } : {}),
@@ -577,7 +696,11 @@ export async function executePublicIngestTarget(target, args, io, deps) {
577
696
  allowImplicitAdapter: true,
578
697
  };
579
698
  const runIngest = deps.runIngest ?? runKtxIngest;
580
- const capturedIngestIo = deps.ingestProgress ? null : createCapturedPublicIngestIo();
699
+ const capturedIngestIo = deps.ingestProgress
700
+ ? isCapturedPublicIngestIo(io)
701
+ ? io
702
+ : null
703
+ : createCapturedPublicIngestIo();
581
704
  const ingestIo = capturedIngestIo ?? io;
582
705
  const ingestDeps = {
583
706
  ...(deps.ingestProgress ? { progress: deps.ingestProgress } : {}),
@@ -622,7 +745,6 @@ export async function runKtxPublicIngest(args, io, deps = {}) {
622
745
  all: args.all,
623
746
  entrypoint: 'ingest',
624
747
  inputMode: args.inputMode,
625
- ...(args.depth ? { depth: args.depth } : {}),
626
748
  ...(args.queryHistory ? { queryHistory: args.queryHistory } : {}),
627
749
  ...(args.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: args.queryHistoryWindowDays } : {}),
628
750
  ...(args.scanMode ? { scanMode: args.scanMode } : {}),
@@ -642,11 +764,26 @@ export async function runKtxPublicIngest(args, io, deps = {}) {
642
764
  io.stderr.write(`Warning: ${warning}\n`);
643
765
  }
644
766
  }
645
- for (const target of plan.targets) {
646
- const startedAt = performance.now();
647
- const result = await executePublicIngestTarget(target, args, io, deps);
648
- results.push(result);
649
- await emitIngestCompleted({ args, project, target, result, startedAt, io });
767
+ for (const [index, target] of plan.targets.entries()) {
768
+ if (args.json) {
769
+ results.push(await executePublicIngestTarget(target, args, io, deps, project));
770
+ continue;
771
+ }
772
+ const capture = createCapturedPublicIngestIo();
773
+ const progress = createPlainPublicIngestProgress(io, {
774
+ target,
775
+ index,
776
+ total: plan.targets.length,
777
+ });
778
+ const targetDeps = {
779
+ ...deps,
780
+ scanProgress: progress.scanProgress,
781
+ ingestProgress: progress.ingestProgress,
782
+ onPhaseStart: progress.onPhaseStart,
783
+ onPhaseEnd: progress.onPhaseEnd,
784
+ runtimeIo: deps.runtimeIo ?? io,
785
+ };
786
+ results.push(await executePublicIngestTarget(target, args, capture, targetDeps, project));
650
787
  }
651
788
  if (args.json) {
652
789
  io.stdout.write(`${JSON.stringify({ plan, results }, null, 2)}\n`);
package/dist/scan.js CHANGED
@@ -6,7 +6,7 @@ import { createKtxCliLocalIngestAdapters } from './local-adapters.js';
6
6
  import { createKtxCliScanConnector } from './local-scan-connectors.js';
7
7
  import { profileMark } from './startup-profile.js';
8
8
  import { emitTelemetryEvent } from './telemetry/index.js';
9
- import { scrubErrorClass } from './telemetry/scrubber.js';
9
+ import { formatErrorDetail, scrubErrorClass } from './telemetry/scrubber.js';
10
10
  profileMark('module:scan');
11
11
  function shouldUseStyledOutput(io) {
12
12
  return io.stdout.isTTY === true && !process.env.NO_COLOR && process.env.TERM !== 'dumb' && !process.env.CI;
@@ -306,6 +306,7 @@ export async function runKtxScan(args, io = process, deps = {}) {
306
306
  }
307
307
  catch (error) {
308
308
  const errorClass = scrubErrorClass(error);
309
+ const errorDetail = formatErrorDetail(error);
309
310
  await emitTelemetryEvent({
310
311
  name: 'scan_completed',
311
312
  projectDir: args.projectDir,
@@ -319,6 +320,7 @@ export async function runKtxScan(args, io = process, deps = {}) {
319
320
  durationMs: Math.max(0, performance.now() - startedAt),
320
321
  outcome: 'error',
321
322
  ...(errorClass ? { errorClass } : {}),
323
+ ...(errorDetail ? { errorDetail } : {}),
322
324
  },
323
325
  });
324
326
  io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
@@ -54,6 +54,7 @@ export type KtxSetupContextResult = {
54
54
  } | {
55
55
  status: 'failed';
56
56
  projectDir: string;
57
+ errorDetail?: string;
57
58
  };
58
59
  export interface KtxSetupContextStepArgs {
59
60
  projectDir: string;
@@ -77,6 +78,7 @@ export interface KtxSetupContextDeps {
77
78
  now?: () => Date;
78
79
  runContextBuild?: typeof runContextBuild;
79
80
  verifyContextReady?: (projectDir: string) => Promise<KtxSetupContextReadiness>;
81
+ testConnection?: (projectDir: string, connectionId: string, io: KtxCliIo) => Promise<number>;
80
82
  }
81
83
  /** @internal */
82
84
  export declare function contextBuildCommands(projectDir: string): KtxSetupContextCommands;