@kaelio/ktx 0.10.0 → 0.12.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 (193) hide show
  1. package/assets/python/{kaelio_ktx-0.10.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/admin.js +1 -1
  5. package/dist/clack.d.ts +16 -0
  6. package/dist/clack.js +37 -6
  7. package/dist/claude-code-prompt-caching.js +1 -1
  8. package/dist/cli-program.js +7 -3
  9. package/dist/cli-runtime.d.ts +2 -0
  10. package/dist/cli-runtime.js +14 -8
  11. package/dist/commands/connection-commands.js +1 -1
  12. package/dist/commands/ingest-commands.js +4 -4
  13. package/dist/commands/mcp-commands.js +12 -12
  14. package/dist/commands/runtime-commands.js +4 -4
  15. package/dist/commands/setup-commands.js +6 -5
  16. package/dist/commands/sl-commands.js +1 -1
  17. package/dist/commands/sql-commands.js +1 -1
  18. package/dist/commands/status-commands.js +1 -1
  19. package/dist/community-cta.d.ts +11 -0
  20. package/dist/community-cta.js +19 -0
  21. package/dist/connection.js +1 -1
  22. package/dist/connectors/clickhouse/connector.js +1 -1
  23. package/dist/connectors/mysql/connector.js +1 -1
  24. package/dist/connectors/snowflake/connector.d.ts +1 -1
  25. package/dist/connectors/sqlite/connector.js +2 -25
  26. package/dist/connectors/sqlserver/connector.js +3 -3
  27. package/dist/context/connections/connection-type.d.ts +1 -1
  28. package/dist/context/connections/read-only-sql.d.ts +1 -0
  29. package/dist/context/connections/read-only-sql.js +116 -2
  30. package/dist/context/core/git-env.d.ts +12 -1
  31. package/dist/context/core/git-env.js +17 -2
  32. package/dist/context/core/git.service.d.ts +23 -0
  33. package/dist/context/core/git.service.js +86 -15
  34. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  35. package/dist/context/ingest/adapters/looker/client.js +7 -2
  36. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  37. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  38. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  39. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  40. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  41. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  42. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  43. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  44. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  45. package/dist/context/ingest/artifact-gates.js +5 -47
  46. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  47. package/dist/context/ingest/constrained-repair.js +167 -0
  48. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  49. package/dist/context/ingest/final-gate-repair.js +40 -128
  50. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  51. package/dist/context/ingest/finalization-scope.js +15 -15
  52. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  53. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  54. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  55. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  56. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  57. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  58. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  59. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  60. package/dist/context/ingest/local-ingest.d.ts +2 -0
  61. package/dist/context/ingest/local-ingest.js +2 -0
  62. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  63. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  64. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  65. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  66. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  67. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  68. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  69. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  70. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  71. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  72. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  73. package/dist/context/llm/claude-code-runtime.js +1 -1
  74. package/dist/context/llm/local-config.js +1 -1
  75. package/dist/context/llm/runtime-tools.js +2 -2
  76. package/dist/context/mcp/context-tools.js +7 -7
  77. package/dist/context/mcp/local-project-ports.js +23 -54
  78. package/dist/context/memory/local-memory.js +4 -1
  79. package/dist/context/memory/memory-agent.service.js +1 -1
  80. package/dist/context/project/config.d.ts +11 -4
  81. package/dist/context/project/config.js +85 -30
  82. package/dist/context/project/driver-schemas.js +1 -1
  83. package/dist/context/project/mappings-yaml-schema.js +2 -2
  84. package/dist/context/project/project.js +12 -4
  85. package/dist/context/scan/description-generation.js +4 -4
  86. package/dist/context/scan/local-enrichment-artifacts.js +2 -1
  87. package/dist/context/scan/local-scan.js +2 -2
  88. package/dist/context/scan/local-structural-artifacts.js +5 -5
  89. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  90. package/dist/context/scan/relationship-discovery.js +3 -3
  91. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  92. package/dist/context/sl/local-query.js +3 -33
  93. package/dist/context/sl/local-sl.d.ts +0 -8
  94. package/dist/context/sl/local-sl.js +44 -69
  95. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  96. package/dist/context/sl/semantic-layer.service.js +109 -56
  97. package/dist/context/sl/source-files.d.ts +46 -0
  98. package/dist/context/sl/source-files.js +131 -0
  99. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  100. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  101. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  102. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  103. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  104. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  105. package/dist/context/sql-analysis/dialect.js +20 -0
  106. package/dist/context/tools/base-tool.d.ts +6 -19
  107. package/dist/context/tools/base-tool.js +0 -14
  108. package/dist/context-build-view.js +5 -5
  109. package/dist/database-tree-picker.js +18 -3
  110. package/dist/demo-assets.js +0 -1
  111. package/dist/doctor.d.ts +1 -1
  112. package/dist/doctor.js +31 -23
  113. package/dist/errors.d.ts +31 -0
  114. package/dist/errors.js +44 -0
  115. package/dist/ingest.d.ts +1 -1
  116. package/dist/ingest.js +8 -2
  117. package/dist/io/symbols.d.ts +2 -0
  118. package/dist/io/symbols.js +2 -0
  119. package/dist/io/tty.d.ts +17 -0
  120. package/dist/io/tty.js +21 -0
  121. package/dist/links.d.ts +1 -0
  122. package/dist/links.js +1 -0
  123. package/dist/llm/embedding-health.js +1 -1
  124. package/dist/llm/embedding-provider.js +3 -3
  125. package/dist/llm/model-provider.js +1 -1
  126. package/dist/local-adapters.d.ts +1 -0
  127. package/dist/local-adapters.js +2 -2
  128. package/dist/local-scan-connectors.js +1 -1
  129. package/dist/managed-local-embeddings.js +17 -8
  130. package/dist/managed-mcp-daemon.js +3 -3
  131. package/dist/managed-python-command.d.ts +7 -0
  132. package/dist/managed-python-command.js +34 -8
  133. package/dist/managed-python-daemon.js +2 -2
  134. package/dist/managed-python-http.js +3 -3
  135. package/dist/managed-python-runtime.d.ts +30 -1
  136. package/dist/managed-python-runtime.js +134 -18
  137. package/dist/managed-uv-release.d.ts +7 -0
  138. package/dist/managed-uv-release.js +11 -0
  139. package/dist/mcp-http-server.js +4 -4
  140. package/dist/mcp-server-factory.js +3 -3
  141. package/dist/mcp-stdio-server.js +1 -1
  142. package/dist/memory-flow-hud.js +2 -2
  143. package/dist/next-steps.js +2 -2
  144. package/dist/prompt-navigation.d.ts +17 -0
  145. package/dist/prompt-navigation.js +49 -3
  146. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  147. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  148. package/dist/public-ingest-copy.js +1 -1
  149. package/dist/public-ingest.js +3 -3
  150. package/dist/release-version.js +1 -1
  151. package/dist/runtime-requirements.js +1 -1
  152. package/dist/runtime.js +9 -9
  153. package/dist/scan.js +1 -1
  154. package/dist/setup-agents.js +22 -35
  155. package/dist/setup-banner.d.ts +20 -0
  156. package/dist/setup-banner.js +39 -0
  157. package/dist/setup-context.js +24 -15
  158. package/dist/setup-databases.js +31 -59
  159. package/dist/setup-demo-tour.js +12 -8
  160. package/dist/setup-embeddings.js +9 -9
  161. package/dist/setup-interrupt.js +1 -1
  162. package/dist/setup-models.d.ts +4 -1
  163. package/dist/setup-models.js +54 -28
  164. package/dist/setup-project.js +29 -5
  165. package/dist/setup-prompts.js +16 -5
  166. package/dist/setup-ready-menu.js +1 -1
  167. package/dist/setup-sources.js +27 -7
  168. package/dist/setup.d.ts +25 -0
  169. package/dist/setup.js +90 -19
  170. package/dist/skills/analytics/SKILL.md +3 -3
  171. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  172. package/dist/skills/looker_ingest/SKILL.md +3 -3
  173. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  174. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  175. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  176. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  177. package/dist/skills/sl/SKILL.md +3 -3
  178. package/dist/skills/sl_capture/SKILL.md +1 -1
  179. package/dist/skills/wiki_capture/SKILL.md +1 -1
  180. package/dist/source-mapping.js +1 -1
  181. package/dist/startup-profile.js +1 -1
  182. package/dist/status-project.d.ts +0 -2
  183. package/dist/status-project.js +4 -6
  184. package/dist/telemetry/command-hook.d.ts +24 -0
  185. package/dist/telemetry/command-hook.js +37 -3
  186. package/dist/telemetry/events.d.ts +1 -1
  187. package/dist/telemetry/exception.js +14 -0
  188. package/dist/telemetry/index.d.ts +2 -2
  189. package/dist/telemetry/index.js +2 -2
  190. package/dist/text-ingest.js +1 -1
  191. package/dist/tree-picker-tui.d.ts +0 -1
  192. package/dist/tree-picker-tui.js +2 -3
  193. package/package.json +1 -1
@@ -25,7 +25,7 @@ function parseWarning(rawWarning, path) {
25
25
  !scanWarningCodes.has(rawWarning.code) ||
26
26
  typeof rawWarning.message !== 'string' ||
27
27
  typeof rawWarning.recoverable !== 'boolean') {
28
- throw new Error(`Invalid KTX schema warning artifact: ${path}`);
28
+ throw new Error(`Invalid ktx schema warning artifact: ${path}`);
29
29
  }
30
30
  return {
31
31
  code: rawWarning.code,
@@ -42,7 +42,7 @@ async function readWarnings(input) {
42
42
  const warningRaw = await input.project.fileStore.readFile(path);
43
43
  const parsed = JSON.parse(warningRaw.content);
44
44
  if (!isRecord(parsed) || !Array.isArray(parsed.warnings)) {
45
- throw new Error(`Invalid KTX schema warnings artifact: ${path}`);
45
+ throw new Error(`Invalid ktx schema warnings artifact: ${path}`);
46
46
  }
47
47
  return parsed.warnings.map((warning) => parseWarning(warning, path));
48
48
  }
@@ -68,7 +68,7 @@ function parseColumn(rawColumn, path) {
68
68
  rawColumn.dimensionType !== 'string' &&
69
69
  rawColumn.dimensionType !== 'number' &&
70
70
  rawColumn.dimensionType !== 'boolean')) {
71
- throw new Error(`Invalid KTX schema column artifact: ${path}`);
71
+ throw new Error(`Invalid ktx schema column artifact: ${path}`);
72
72
  }
73
73
  return {
74
74
  name: rawColumn.name,
@@ -85,7 +85,7 @@ function parseForeignKey(rawForeignKey, path) {
85
85
  typeof rawForeignKey.fromColumn !== 'string' ||
86
86
  typeof rawForeignKey.toTable !== 'string' ||
87
87
  typeof rawForeignKey.toColumn !== 'string') {
88
- throw new Error(`Invalid KTX schema foreign key artifact: ${path}`);
88
+ throw new Error(`Invalid ktx schema foreign key artifact: ${path}`);
89
89
  }
90
90
  return {
91
91
  fromColumn: rawForeignKey.fromColumn,
@@ -99,7 +99,7 @@ function parseForeignKey(rawForeignKey, path) {
99
99
  function parseTable(raw, path) {
100
100
  const parsed = JSON.parse(raw);
101
101
  if (!isRecord(parsed) || typeof parsed.name !== 'string' || !Array.isArray(parsed.columns)) {
102
- throw new Error(`Invalid KTX schema table artifact: ${path}`);
102
+ throw new Error(`Invalid ktx schema table artifact: ${path}`);
103
103
  }
104
104
  return {
105
105
  catalog: optionalStringOrNull(parsed.catalog) ?? null,
@@ -211,7 +211,7 @@ function compositeSkipBlocks(report) {
211
211
  }
212
212
  export function formatKtxRelationshipBenchmarkReportMarkdown(report) {
213
213
  const lines = [
214
- '# KTX Relationship Discovery Benchmark Evidence',
214
+ '# ktx Relationship Discovery Benchmark Evidence',
215
215
  '',
216
216
  `Generated: ${report.generatedAt}`,
217
217
  '',
@@ -87,7 +87,7 @@ async function detectCompositeRelationships(input) {
87
87
  catch (error) {
88
88
  input.warnings.push({
89
89
  code: 'relationship_validation_failed',
90
- message: `KTX composite relationship detection failed: ${error instanceof Error ? error.message : String(error)}`,
90
+ message: `ktx composite relationship detection failed: ${error instanceof Error ? error.message : String(error)}`,
91
91
  recoverable: true,
92
92
  metadata: { source: 'composite_relationship_detection' },
93
93
  });
@@ -110,7 +110,7 @@ function sqlExecutor(input) {
110
110
  warnings: [
111
111
  {
112
112
  code: 'connector_capability_missing',
113
- message: 'KTX scan connector cannot run read-only SQL relationship validation',
113
+ message: 'ktx scan connector cannot run read-only SQL relationship validation',
114
114
  recoverable: true,
115
115
  metadata: { capability: 'readOnlySql' },
116
116
  },
@@ -123,7 +123,7 @@ function sqlExecutor(input) {
123
123
  warnings: [
124
124
  {
125
125
  code: 'relationship_validation_failed',
126
- message: 'KTX scan connector advertises readOnlySql but does not expose executeReadOnly',
126
+ message: 'ktx scan connector advertises readOnlySql but does not expose executeReadOnly',
127
127
  recoverable: true,
128
128
  metadata: { capability: 'readOnlySql' },
129
129
  },
@@ -116,7 +116,7 @@ function mapValidProposals(schema, output, settings) {
116
116
  const fromColumn = fromTable ? findColumn(fromTable, item.fromColumn) : null;
117
117
  const toColumn = toTable ? findColumn(toTable, item.toColumn) : null;
118
118
  if (!fromTable || !toTable || !fromColumn || !toColumn) {
119
- warnings.push(invalidReferenceWarning('KTX relationship LLM proposal referenced a table or column that is not in the schema.', {
119
+ warnings.push(invalidReferenceWarning('ktx relationship LLM proposal referenced a table or column that is not in the schema.', {
120
120
  proposal: item,
121
121
  }));
122
122
  continue;
@@ -148,7 +148,7 @@ function generationFailureWarning(error) {
148
148
  const message = error instanceof Error ? error.message : String(error);
149
149
  return {
150
150
  code: 'relationship_llm_proposal_failed',
151
- message: `KTX relationship LLM proposal failed: ${message}`,
151
+ message: `ktx relationship LLM proposal failed: ${message}`,
152
152
  recoverable: true,
153
153
  };
154
154
  }
@@ -159,7 +159,7 @@ export async function proposeKtxRelationshipCandidatesWithLlm(input) {
159
159
  const settings = mergeSettings(input.settings);
160
160
  const evidence = buildEvidencePacket(input.schema, input.profile, settings);
161
161
  const system = [
162
- 'You are helping KTX review possible SQL relationships before validation.',
162
+ 'You are helping ktx review possible SQL relationships before validation.',
163
163
  'Use only the compact schema evidence. Propose likely primary keys and foreign keys for later SQL validation.',
164
164
  'Return structured output only; never assume a join is accepted.',
165
165
  ].join('\n');
@@ -1,38 +1,8 @@
1
+ import { sqlAnalysisDialectForDriver } from '../sql-analysis/dialect.js';
1
2
  import { loadLocalSlSourceRecords } from './local-sl.js';
2
3
  import { toResolvedWire } from './semantic-layer.service.js';
4
+ import { assertSafeConnectionId } from './source-files.js';
3
5
  const COMPILE_ONLY_REASON = 'Local semantic-layer query compiled SQL but no data-source execution adapter is configured.';
4
- function assertSafePathToken(kind, value) {
5
- if (value.trim().length === 0 ||
6
- value.includes('..') ||
7
- value.includes('\\') ||
8
- value.startsWith('/') ||
9
- value.startsWith('.') ||
10
- value.includes('//')) {
11
- throw new Error(`Unsafe ${kind}: ${value}`);
12
- }
13
- return value;
14
- }
15
- function assertSafeConnectionId(connectionId) {
16
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
17
- throw new Error(`Unsafe connection id: ${connectionId}`);
18
- }
19
- return assertSafePathToken('connection id', connectionId);
20
- }
21
- function dialectForDriver(driver) {
22
- const normalized = (driver ?? 'postgres').toUpperCase();
23
- const map = {
24
- POSTGRES: 'postgres',
25
- BIGQUERY: 'bigquery',
26
- SNOWFLAKE: 'snowflake',
27
- MYSQL: 'mysql',
28
- SQLSERVER: 'tsql',
29
- SQLITE: 'sqlite',
30
- DUCKDB: 'duckdb',
31
- CLICKHOUSE: 'clickhouse',
32
- DATABRICKS: 'databricks',
33
- };
34
- return map[normalized] ?? 'postgres';
35
- }
36
6
  function resolveLocalConnectionId(project, requested) {
37
7
  if (requested) {
38
8
  return assertSafeConnectionId(requested);
@@ -56,7 +26,7 @@ function headersFromColumns(columns) {
56
26
  export async function compileLocalSlQuery(project, options) {
57
27
  await options.onProgress?.({ progress: 0, message: 'Compiling query' });
58
28
  const connectionId = resolveLocalConnectionId(project, options.connectionId);
59
- const dialect = dialectForDriver(project.config.connections[connectionId]?.driver);
29
+ const dialect = sqlAnalysisDialectForDriver(project.config.connections[connectionId]?.driver);
60
30
  const sources = await loadComputableSources(project, connectionId);
61
31
  await options.onProgress?.({ progress: 0.3, message: 'Generating SQL' });
62
32
  const response = await options.compute.query({
@@ -1,5 +1,4 @@
1
1
  import type { KtxEmbeddingPort } from '../../context/core/embedding.js';
2
- import type { KtxFileWriteResult } from '../../context/core/file-store.js';
3
2
  import type { KtxLocalProject } from '../../context/project/project.js';
4
3
  import type { PgliteSlSearchPrototypeOwnerOptions } from './pglite-sl-search-prototype.js';
5
4
  import type { SemanticLayerSource, SlDictionaryMatch, SlSearchLaneSummary, SlSearchMatchReason } from './types.js';
@@ -56,13 +55,6 @@ export declare function validateLocalSlSource(rawYaml: string, options?: {
56
55
  connectionId?: string;
57
56
  sourceName?: string;
58
57
  }): Promise<LocalSlValidationResult>;
59
- /** @internal */
60
- export declare function writeLocalSlSource(project: KtxLocalProject, input: {
61
- connectionId: string;
62
- sourceName: string;
63
- yaml: string;
64
- }): Promise<KtxFileWriteResult>;
65
- /** @internal */
66
58
  export declare function readLocalSlSource(project: KtxLocalProject, input: {
67
59
  connectionId: string;
68
60
  sourceName: string;
@@ -7,48 +7,12 @@ import { normalizeSemanticLayerDescriptions } from './description-normalization.
7
7
  import { sourceDefinitionSchema, sourceOverlaySchema } from './schemas.js';
8
8
  import { composeOverlay, projectManifestEntry, SemanticLayerService, toResolvedWire, } from './semantic-layer.service.js';
9
9
  import { loadLatestSlDictionaryEntries } from './sl-dictionary-profile.js';
10
+ import { assertSafeConnectionId, isSafeConnectionId, isSlYamlPath, slSourceNameForFile, sourceNameFromPath, } from './source-files.js';
10
11
  import { buildSemanticLayerSourceSearchText, SlSearchService } from './sl-search.service.js';
11
12
  import { SqliteSlSourcesIndex } from './sqlite-sl-sources-index.js';
12
- const LOCAL_AUTHOR = 'ktx';
13
- const LOCAL_AUTHOR_EMAIL = 'ktx@example.com';
14
- function assertSafePathToken(kind, value) {
15
- if (value.trim().length === 0 ||
16
- value.includes('..') ||
17
- value.includes('\\') ||
18
- value.startsWith('/') ||
19
- value.startsWith('.') ||
20
- value.includes('//')) {
21
- throw new Error(`Unsafe ${kind}: ${value}`);
22
- }
23
- return value;
24
- }
25
- function assertSafeConnectionId(connectionId) {
26
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
27
- throw new Error(`Unsafe connection id: ${connectionId}`);
28
- }
29
- return assertSafePathToken('connection id', connectionId);
30
- }
31
- function isSafeConnectionId(connectionId) {
32
- return typeof connectionId === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId);
33
- }
34
- function assertSafeSourceName(sourceName) {
35
- if (!/^[a-z0-9][a-z0-9_]*$/.test(sourceName)) {
36
- throw new Error(`Unsafe semantic-layer source name: ${sourceName}`);
37
- }
38
- return assertSafePathToken('semantic-layer source name', sourceName);
39
- }
40
13
  function isRecord(value) {
41
14
  return typeof value === 'object' && value !== null && !Array.isArray(value);
42
15
  }
43
- function slPath(connectionId, sourceName) {
44
- return `semantic-layer/${assertSafeConnectionId(connectionId)}/${assertSafeSourceName(sourceName)}.yaml`;
45
- }
46
- function sourceNameFromPath(path) {
47
- return (path
48
- .split('/')
49
- .at(-1)
50
- ?.replace(/\.ya?ml$/, '') ?? path);
51
- }
52
16
  function parseYamlRecord(raw) {
53
17
  const parsed = YAML.parse(raw);
54
18
  if (!isRecord(parsed)) {
@@ -126,11 +90,17 @@ export async function loadLocalSlSourceRecords(project, input) {
126
90
  const dir = `semantic-layer/${connectionId}`;
127
91
  const schemaDir = `${dir}/_schema`;
128
92
  const listed = await project.fileStore.listFiles(dir);
129
- const paths = listed.files.filter((file) => file.endsWith('.yaml') || file.endsWith('.yml')).sort();
93
+ const paths = listed.files.filter(isSlYamlPath).sort();
130
94
  const sources = new Map();
131
95
  for (const path of paths.filter((file) => file.startsWith(`${schemaDir}/`))) {
132
96
  const raw = await project.fileStore.readFile(path);
133
- const tables = manifestTables(parseYamlRecord(raw.content));
97
+ let tables;
98
+ try {
99
+ tables = manifestTables(parseYamlRecord(raw.content));
100
+ }
101
+ catch (error) {
102
+ throw new Error(`${path}: ${error instanceof Error ? error.message : String(error)}`);
103
+ }
134
104
  if (!tables) {
135
105
  continue;
136
106
  }
@@ -146,7 +116,30 @@ export async function loadLocalSlSourceRecords(project, input) {
146
116
  }
147
117
  for (const path of paths.filter((file) => !file.startsWith(`${schemaDir}/`))) {
148
118
  const raw = await project.fileStore.readFile(path);
149
- const parsed = parseYamlRecord(raw.content);
119
+ let parsed;
120
+ try {
121
+ parsed = parseYamlRecord(raw.content);
122
+ }
123
+ catch {
124
+ // A source mid-edit (e.g. an agent saved half-written YAML) must not take
125
+ // down reads, listings, or search for its siblings. Key it by the same
126
+ // name the writer side uses (the intact top-level `name:`, recovered even
127
+ // when the YAML is broken below it; filename only as a last resort) so a
128
+ // broken uppercase/hashed/human-renamed source stays reachable under its
129
+ // real name, and surface the raw content for repair.
130
+ const brokenName = slSourceNameForFile(path, raw.content);
131
+ sources.set(brokenName, {
132
+ connectionId,
133
+ name: brokenName,
134
+ path,
135
+ columnCount: 0,
136
+ measureCount: 0,
137
+ joinCount: 0,
138
+ yaml: raw.content,
139
+ source: { name: brokenName, grain: [], columns: [], joins: [], measures: [] },
140
+ });
141
+ continue;
142
+ }
150
143
  const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : sourceNameFromPath(path);
151
144
  if (parsed.table || parsed.sql) {
152
145
  const source = parsedStandaloneSource(parsed, name);
@@ -191,36 +184,18 @@ export async function validateLocalSlSource(rawYaml, options) {
191
184
  return { valid: false, errors: validationErrors(error) };
192
185
  }
193
186
  }
194
- /** @internal */
195
- export async function writeLocalSlSource(project, input) {
196
- const validation = await validateLocalSlSource(input.yaml, { project, connectionId: input.connectionId });
197
- if (!validation.valid) {
198
- throw new Error(`Invalid semantic-layer source: ${validation.errors.join('; ')}`);
199
- }
200
- const parsed = parseYamlRecord(input.yaml);
201
- if (typeof parsed.name === 'string' && parsed.name !== input.sourceName) {
202
- throw new Error(`Semantic-layer source name "${parsed.name}" does not match requested path "${input.sourceName}"`);
203
- }
204
- const path = slPath(input.connectionId, input.sourceName);
205
- return project.fileStore.writeFile(path, input.yaml.endsWith('\n') ? input.yaml : `${input.yaml}\n`, LOCAL_AUTHOR, LOCAL_AUTHOR_EMAIL, `Write semantic-layer source: ${input.connectionId}/${input.sourceName}`);
206
- }
207
- /** @internal */
208
187
  export async function readLocalSlSource(project, input) {
209
- const path = slPath(input.connectionId, input.sourceName);
210
- try {
211
- const result = await project.fileStore.readFile(path);
212
- return {
213
- ...summarizeSource({ connectionId: input.connectionId, path, raw: result.content }),
214
- yaml: result.content,
215
- };
216
- }
217
- catch {
218
- const records = await loadLocalSlSourceRecords(project, {
219
- connectionId: input.connectionId,
220
- });
221
- const record = records.find((source) => source.name === input.sourceName);
222
- return record ? { ...record } : null;
223
- }
188
+ // Source identity is the in-file `name:` (mirroring the warehouse identifier
189
+ // verbatim, e.g. Snowflake's uppercase `WIDGET_SALES`), never the filename. The
190
+ // record loader resolves standalone files, overlays, manifest-backed sources,
191
+ // and mid-edit files whose YAML no longer parses — so readers — `ktx sl read`,
192
+ // `ktx sl validate`, and the `sl_read_source` MCP tool — can surface broken
193
+ // content for repair instead of failing on it.
194
+ const records = await loadLocalSlSourceRecords(project, {
195
+ connectionId: input.connectionId,
196
+ });
197
+ const record = records.find((source) => source.name === input.sourceName);
198
+ return record ? { ...record } : null;
224
199
  }
225
200
  export async function resolveLocalSlSource(project, input) {
226
201
  if (input.connectionId !== undefined) {
@@ -38,17 +38,23 @@ export declare class SemanticLayerService {
38
38
  name: string;
39
39
  connectionType: string;
40
40
  }>>;
41
- private sourcePath;
41
+ private resolveWritePath;
42
42
  writeSource(connectionId: string, source: SemanticLayerSource, author: string, authorEmail: string, commitMessage?: string, options?: WriteSourceOptions & {
43
43
  skipLock?: boolean;
44
44
  }): Promise<{
45
+ path: string;
45
46
  warnings: string[];
46
47
  commitHash?: string | null;
47
48
  }>;
49
+ /**
50
+ * Raw standalone/overlay file for a source, resolved by its in-file `name:`.
51
+ * Returns null when no file declares the name (the source may still exist as
52
+ * a manifest entry under `_schema/`).
53
+ */
48
54
  readSourceFile(connectionId: string, sourceName: string): Promise<{
49
55
  content: string;
50
56
  path: string;
51
- }>;
57
+ } | null>;
52
58
  loadSource(connectionId: string, sourceName: string): Promise<SemanticLayerSource | null>;
53
59
  loadAllSources(connectionId: string): Promise<LoadAllSourcesResult>;
54
60
  /**
@@ -87,14 +93,8 @@ export declare class SemanticLayerService {
87
93
  } | null>;
88
94
  validatePhysicalTableReferences(connectionId: string, sources: SemanticLayerSource[]): Promise<string[]>;
89
95
  getDialectForConnection(connectionId: string): Promise<string>;
90
- listSourceNames(connectionId: string): Promise<string[]>;
91
96
  listFilesForConnection(connectionId: string): Promise<string[]>;
92
- readFileByPath(connectionId: string, relativePath: string): Promise<{
93
- content: string;
94
- readOnly: boolean;
95
- }>;
96
97
  deleteSource(connectionId: string, sourceName: string, author: string, authorEmail: string): Promise<import("../../context/core/file-store.js").KtxFileWriteResult | null>;
97
- getSourceHistory(connectionId: string, sourceName: string): Promise<unknown>;
98
98
  /**
99
99
  * Validate the semantic layer state that *would* exist if `proposedSource`
100
100
  * were written, without persisting anything. Used by write/edit tools to
@@ -203,6 +203,23 @@ export interface ManifestTableEntry {
203
203
  usage?: TableUsageOutput;
204
204
  }
205
205
  export declare function projectManifestEntry(name: string, entry: ManifestTableEntry): SemanticLayerSource;
206
+ export interface MissingJoinTarget {
207
+ to: string;
208
+ /** Source whose name matches only case-insensitively, if any — the usual authoring mistake. */
209
+ caseMismatch: string | null;
210
+ }
211
+ /**
212
+ * Join targets that do not exactly match a known source name. The Python
213
+ * engine resolves `joins[].to` by exact name within one connection's source
214
+ * set (`engine._collect_orphan_join_target_errors`) and `query()` raises on a
215
+ * miss, so anything looser here — case-insensitive matches, table refs,
216
+ * sources in other connections — would pass this gate and then fail
217
+ * query/validation as an orphan join target.
218
+ */
219
+ export declare function findMissingJoinTargets(joins: Array<{
220
+ to: string;
221
+ }> | undefined, knownSourceNames: Iterable<string>): MissingJoinTarget[];
222
+ export declare function formatMissingJoinTarget(missing: MissingJoinTarget): string;
206
223
  /**
207
224
  * Returns one message per measure-level segment reference that doesn't resolve to
208
225
  * a segment defined on the source. Array is empty when every reference checks out.