@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
@@ -2,8 +2,8 @@ import YAML from 'yaml';
2
2
  import { SYSTEM_GIT_AUTHOR } from '../../../context/tools/authors.js';
3
3
  import { sourceOverlaySchema } from '../schemas.js';
4
4
  import { SemanticLayerService } from '../semantic-layer.service.js';
5
+ import { resolveSlSourceFile, slSourceFilePath } from '../source-files.js';
5
6
  import { sourceDefinitionSchema } from './base-semantic-layer.tool.js';
6
- const slSourcePath = (connectionId, sourceName) => `semantic-layer/${connectionId}/${sourceName}.yaml`;
7
7
  function resolveDialect(warehouse) {
8
8
  if (!warehouse) {
9
9
  return null;
@@ -33,32 +33,28 @@ function wrapWithSingleRowQuery(sql, dialect) {
33
33
  export async function validateSingleSource(deps, connectionId, sourceName) {
34
34
  const errors = [];
35
35
  const warnings = [];
36
- let content;
37
- try {
38
- const result = await deps.semanticLayerService.readSourceFile(connectionId, sourceName);
39
- content = result.content;
40
- }
41
- catch {
42
- errors.push(`${sourceName}.yaml: file not found`);
36
+ const file = await deps.semanticLayerService.readSourceFile(connectionId, sourceName);
37
+ if (!file) {
38
+ errors.push(`${sourceName}: no standalone or overlay file found`);
43
39
  return { errors, warnings };
44
40
  }
45
41
  let parsed;
46
42
  try {
47
- parsed = YAML.parse(content);
43
+ parsed = YAML.parse(file.content);
48
44
  }
49
45
  catch (e) {
50
- errors.push(`${sourceName}.yaml: invalid YAML — ${e instanceof Error ? e.message : String(e)}`);
46
+ errors.push(`${sourceName}: invalid YAML — ${e instanceof Error ? e.message : String(e)}`);
51
47
  return { errors, warnings };
52
48
  }
53
49
  if (!parsed || typeof parsed !== 'object') {
54
- errors.push(`${sourceName}.yaml: top-level content is not an object`);
50
+ errors.push(`${sourceName}: top-level content is not an object`);
55
51
  return { errors, warnings };
56
52
  }
57
53
  const isOverlay = !parsed.table && !parsed.sql;
58
54
  if (!isOverlay) {
59
55
  const isManifestBacked = await deps.semanticLayerService.isManifestBacked(connectionId, sourceName);
60
56
  if (isManifestBacked) {
61
- errors.push(`${sourceName}.yaml: standalone source shadows an existing manifest entry — ` +
57
+ errors.push(`${sourceName}: standalone source shadows an existing manifest entry — ` +
62
58
  `writing it as-is drops the manifest's columns and joins. ` +
63
59
  `Remove "sql:", "table:", "grain:", and base-table "columns:" and keep only ` +
64
60
  `"name:" plus overlay fields such as "measures:", "segments:", "descriptions:", ` +
@@ -71,16 +67,16 @@ export async function validateSingleSource(deps, connectionId, sourceName) {
71
67
  const result = schema.safeParse(parsed);
72
68
  if (!result.success) {
73
69
  const issues = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
74
- errors.push(`${sourceName}.yaml: schema — ${issues}`);
70
+ errors.push(`${sourceName}: schema — ${issues}`);
75
71
  const errorPaths = new Set(result.error.issues.map((i) => String(i.path[0])));
76
72
  if (errorPaths.has('joins')) {
77
- warnings.push(`${sourceName}.yaml: hint — join format: {to, on: 'local_col = TARGET.col', relationship: 'many_to_one|one_to_many|one_to_one'}`);
73
+ warnings.push(`${sourceName}: hint — join format: {to, on: 'local_col = TARGET.col', relationship: 'many_to_one|one_to_many|one_to_one'}`);
78
74
  }
79
75
  if (errorPaths.has('columns')) {
80
- warnings.push(`${sourceName}.yaml: hint — overlay columns must be computed: {name, expr, type}. Use column_overrides for manifest column descriptions or metadata.`);
76
+ warnings.push(`${sourceName}: hint — overlay columns must be computed: {name, expr, type}. Use column_overrides for manifest column descriptions or metadata.`);
81
77
  }
82
78
  if (errorPaths.has('measures')) {
83
- warnings.push(`${sourceName}.yaml: hint — measure format: {name, expr, description (optional), filter (optional)}`);
79
+ warnings.push(`${sourceName}: hint — measure format: {name, expr, description (optional), filter (optional)}`);
84
80
  }
85
81
  return { errors, warnings };
86
82
  }
@@ -93,7 +89,7 @@ export async function validateSingleSource(deps, connectionId, sourceName) {
93
89
  const seenMeasures = new Set();
94
90
  for (const m of measures) {
95
91
  if (seenMeasures.has(m.name)) {
96
- errors.push(`${sourceName}.yaml: duplicate measure name "${m.name}"`);
92
+ errors.push(`${sourceName}: duplicate measure name "${m.name}"`);
97
93
  }
98
94
  seenMeasures.add(m.name);
99
95
  }
@@ -125,7 +121,7 @@ export async function validateSingleSource(deps, connectionId, sourceName) {
125
121
  const actual = new Set((probe.headers ?? []).map((h) => h.toLowerCase()));
126
122
  const missing = sourceColumns.map((c) => c.name).filter((n) => !actual.has(n.toLowerCase()));
127
123
  if (missing.length > 0) {
128
- errors.push(`${sourceName}.yaml: declared columns absent from sql result — ${missing.join(', ')} (warehouse returned: ${[...actual].slice(0, 10).join(', ')}${actual.size > 10 ? ', …' : ''})`);
124
+ errors.push(`${sourceName}: declared columns absent from sql result — ${missing.join(', ')} (warehouse returned: ${[...actual].slice(0, 10).join(', ')}${actual.size > 10 ? ', …' : ''})`);
129
125
  }
130
126
  }
131
127
  catch (e) {
@@ -151,7 +147,7 @@ function formatProbeError(args) {
151
147
  const errMsg = error instanceof Error ? error.message : String(error);
152
148
  const refColumns = sourceColumns.filter((c) => referencesColumn(probeSql, c.name));
153
149
  const lines = [
154
- measureName ? `${sourceName}.yaml: measure "${measureName}" ${headline}.` : `${sourceName}.yaml: ${headline}.`,
150
+ measureName ? `${sourceName}: measure "${measureName}" ${headline}.` : `${sourceName}: ${headline}.`,
155
151
  ];
156
152
  if (warehouse) {
157
153
  lines.push(` Warehouse: ${warehouse}`);
@@ -179,7 +175,7 @@ async function probeOverlayMeasures(deps, connectionId, sourceName, warehouse) {
179
175
  composed = all.find((s) => s.name === sourceName);
180
176
  }
181
177
  catch (e) {
182
- errors.push(`${sourceName}.yaml: failed to load composed source for probe — ${e instanceof Error ? e.message : String(e)}`);
178
+ errors.push(`${sourceName}: failed to load composed source for probe — ${e instanceof Error ? e.message : String(e)}`);
183
179
  return errors;
184
180
  }
185
181
  if (!composed?.table || composed.measures.length === 0) {
@@ -214,22 +210,54 @@ async function probeOverlayMeasures(deps, connectionId, sourceName, warehouse) {
214
210
  }
215
211
  return errors;
216
212
  }
213
+ /**
214
+ * A read-only view of the config repo at one commit, shaped for
215
+ * `resolveSlSourceFile` so name→file resolution runs against history exactly as
216
+ * it does against the working tree — one resolver, two backing stores. Used to
217
+ * recover the path a source occupied at `preHead` after the live file is gone.
218
+ */
219
+ function gitCommitFileStore(git, commitHash) {
220
+ return {
221
+ async listFiles(path) {
222
+ return { files: await git.listFilesAtCommit(path, commitHash) };
223
+ },
224
+ async readFile(path) {
225
+ return { content: await git.getFileAtCommit(path, commitHash) };
226
+ },
227
+ };
228
+ }
217
229
  /**
218
230
  * Restore `sourceName` to the content it had at `preHead`, or delete it if it didn't
219
231
  * exist then. Used by sl_rollback (agent-driven) and the pre-squash revert gate
220
232
  * (automatic). Returns a short human-readable description of what happened.
221
233
  */
222
234
  export async function revertSourceToPreHead(deps, connectionId, preHead, sourceName) {
223
- const relPath = slSourcePath(connectionId, sourceName);
235
+ // Find the file that defines this source. While it is still on disk
236
+ // (invalid-but-present) the live resolver finds it by its in-file `name:`.
237
+ // Once the session deleted it, the path is gone too — and humans rename files
238
+ // freely, so it is NOT the writer-derived filename. Recover it from history by
239
+ // resolving the name against the preHead commit instead of guessing.
240
+ const live = await resolveSlSourceFile(deps.configService, connectionId, sourceName);
241
+ let relPath;
224
242
  let preContent = null;
225
- if (preHead) {
226
- try {
227
- preContent = await deps.gitService.getFileAtCommit(relPath, preHead);
228
- }
229
- catch {
230
- preContent = null;
243
+ if (live) {
244
+ relPath = live.path;
245
+ if (preHead) {
246
+ try {
247
+ preContent = await deps.gitService.getFileAtCommit(relPath, preHead);
248
+ }
249
+ catch {
250
+ preContent = null;
251
+ }
231
252
  }
232
253
  }
254
+ else {
255
+ const atPreHead = preHead
256
+ ? await resolveSlSourceFile(gitCommitFileStore(deps.gitService, preHead), connectionId, sourceName)
257
+ : null;
258
+ relPath = atPreHead?.path ?? slSourceFilePath(connectionId, sourceName);
259
+ preContent = atPreHead?.content ?? null;
260
+ }
233
261
  if (preContent !== null) {
234
262
  await deps.configService.writeFile(relPath, preContent, SYSTEM_GIT_AUTHOR.name, SYSTEM_GIT_AUTHOR.email, `Revert SL source to pre-session state: ${sourceName}`, { skipLock: true });
235
263
  return 'restored to pre-session content';
@@ -12,8 +12,10 @@ const slWriteSourceInputSchema = z.object({
12
12
  connectionId: slToolConnectionIdSchema.describe('Data source connection ID'),
13
13
  sourceName: z
14
14
  .string()
15
- .regex(/^[a-z0-9][a-z0-9_]*$/, 'Source name must be snake_case (lowercase alphanumeric and underscores)')
16
- .describe('Name of the source to create, edit, or delete'),
15
+ .min(1)
16
+ .describe("Name of the source to create, edit, or delete. Must equal the source's `name:`. Use the verbatim " +
17
+ 'warehouse identifier when overlaying a manifest source (e.g. SIGNED_UP); snake_case is recommended ' +
18
+ 'for new standalone sources.'),
17
19
  source: sourceInputSchema
18
20
  .optional()
19
21
  .describe('Source definition (standalone with table/sql) or overlay (measures, column_overrides, computed columns, etc.)'),
@@ -122,6 +124,12 @@ Do NOT join back to a table that the SQL already aggregates from if the grain co
122
124
  if (!input.source) {
123
125
  return this.buildOutput(false, ['Provide `source` to create or rewrite. For targeted edits, use sl_edit_source.'], sourceName);
124
126
  }
127
+ // The in-file `name:` is the source's identity; the file is written under
128
+ // source.name while the orphan/shadow checks key on sourceName — a mismatch
129
+ // would validate one source and save another.
130
+ if (input.source.name !== sourceName) {
131
+ return this.buildOutput(false, [`source.name "${input.source.name}" does not match sourceName "${sourceName}" — they must be identical.`], sourceName);
132
+ }
125
133
  return this.writeFullSource(connectionId, input.source, sourceName, author, authorEmail, context, semanticLayerService, skipIndex, rawPathValidation.rawPaths);
126
134
  }
127
135
  async writeFullSource(connectionId, source, sourceName, author, authorEmail, context, semanticLayerService, skipIndex, rawPaths) {
@@ -183,13 +191,8 @@ Do NOT join back to a table that the SQL already aggregates from if the grain co
183
191
  }
184
192
  }
185
193
  async readSourceYamlFromService(service, connectionId, sourceName) {
186
- try {
187
- const { content } = await service.readSourceFile(connectionId, sourceName);
188
- return content;
189
- }
190
- catch {
191
- return null;
192
- }
194
+ const file = await service.readSourceFile(connectionId, sourceName);
195
+ return file?.content ?? null;
193
196
  }
194
197
  async rejectOrphanOverlay(semanticLayerService, connectionId, sourceName, content) {
195
198
  let parsed;
@@ -0,0 +1,2 @@
1
+ import type { SqlAnalysisDialect } from './ports.js';
2
+ export declare function sqlAnalysisDialectForDriver(driver: string | undefined): SqlAnalysisDialect;
@@ -0,0 +1,20 @@
1
+ // One mapping from ktx connection identity to the sqlglot dialect name used by
2
+ // the Python daemon (SQL analysis, read-only validation) and semantic-layer
3
+ // compute. Keys cover both vocabularies that name a connection's engine:
4
+ // ktx.yaml driver names ("postgres", "sqlserver") and the local connection-type
5
+ // spellings exposed by KtxConnectionInfo.connectionType ("POSTGRESQL").
6
+ const SQLGLOT_DIALECTS = {
7
+ postgres: 'postgres',
8
+ postgresql: 'postgres',
9
+ bigquery: 'bigquery',
10
+ snowflake: 'snowflake',
11
+ mysql: 'mysql',
12
+ sqlserver: 'tsql',
13
+ sqlite: 'sqlite',
14
+ duckdb: 'duckdb',
15
+ clickhouse: 'clickhouse',
16
+ databricks: 'databricks',
17
+ };
18
+ export function sqlAnalysisDialectForDriver(driver) {
19
+ return SQLGLOT_DIALECTS[(driver ?? '').toLowerCase()] ?? 'postgres';
20
+ }
@@ -1,4 +1,5 @@
1
- import { z, type ZodType } from 'zod';
1
+ import { type Tool } from 'ai';
2
+ import { z } from 'zod';
2
3
  import { type KtxLogger } from '../../context/core/config.js';
3
4
  import type { KtxRuntimeToolDescriptor } from '../llm/runtime-port.js';
4
5
  import type { IngestToolMetadata, ToolSession } from './tool-session.js';
@@ -56,30 +57,16 @@ interface MethodologyEntry {
56
57
  /**
57
58
  * SECURITY: All tools require authentication. userId must always be provided in ToolContext.
58
59
  */
59
- export declare abstract class BaseTool<TInput extends ZodType = ZodType> {
60
+ export declare abstract class BaseTool<TInput extends z.ZodObject<z.ZodRawShape> = z.ZodObject<z.ZodRawShape>> {
60
61
  protected readonly logger: KtxLogger;
61
62
  abstract readonly name: string;
62
63
  constructor(logger?: KtxLogger);
63
64
  abstract get description(): string;
64
65
  abstract get inputSchema(): TInput;
65
- abstract call(input: z.infer<TInput>, context: ToolContext): Promise<any>;
66
- getParametersSchema(): {
67
- type: 'object';
68
- properties: Record<string, any>;
69
- required?: string[];
70
- };
71
- toAnthropicFormat(): {
72
- name: string;
73
- description: string;
74
- input_schema: {
75
- type: 'object';
76
- properties: Record<string, any>;
77
- required?: string[];
78
- };
79
- };
80
- toAiSdkTool(context: ToolContext): any;
66
+ abstract call(input: z.infer<TInput>, context: ToolContext): Promise<unknown>;
67
+ toAiSdkTool(context: ToolContext): Tool;
81
68
  toRuntimeTool(context: ToolContext): KtxRuntimeToolDescriptor;
82
- parseInput(input: Record<string, any>): z.infer<TInput>;
69
+ parseInput(input: Record<string, unknown>): z.infer<TInput>;
83
70
  protected getCurrentUserQuery(context: ToolContext): string | null;
84
71
  }
85
72
  export {};
@@ -1,5 +1,4 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
3
2
  import { noopLogger } from '../../context/core/config.js';
4
3
  import { normalizeKtxRuntimeToolOutput } from '../llm/runtime-tools.js';
5
4
  /**
@@ -10,19 +9,6 @@ export class BaseTool {
10
9
  constructor(logger = noopLogger) {
11
10
  this.logger = logger;
12
11
  }
13
- getParametersSchema() {
14
- const jsonSchema = z.toJSONSchema(this.inputSchema, {
15
- target: 'draft-7',
16
- });
17
- return jsonSchema;
18
- }
19
- toAnthropicFormat() {
20
- return {
21
- name: this.name,
22
- description: this.description,
23
- input_schema: this.getParametersSchema(),
24
- };
25
- }
26
12
  toAiSdkTool(context) {
27
13
  const toolName = this.name;
28
14
  const logger = this.logger;
@@ -260,7 +260,7 @@ export function renderContextBuildView(state, options = {}) {
260
260
  const totalCount = allTargets.length;
261
261
  const hasActive = allTargets.some((t) => t.status === 'running' || t.status === 'queued');
262
262
  const allDone = totalCount > 0 && !hasActive;
263
- const headerParts = [options.title ?? 'Building KTX context'];
263
+ const headerParts = [options.title ?? 'Building ktx context'];
264
264
  if (totalCount > 0) {
265
265
  const progressParts = [`${doneCount}/${totalCount}`];
266
266
  if (state.totalElapsedMs > 0)
@@ -550,7 +550,7 @@ function failedStepDetail(result) {
550
550
  return result.steps.find((step) => step.status === 'failed')?.detail ?? null;
551
551
  }
552
552
  const INTERNAL_FAILURE_LINE_RE = /^(Report|Run|Job|Status|Adapter|Connection|Sync|Mode|Dry run|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
553
- 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)/;
553
+ 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)/;
554
554
  function trimErrorPrefix(line) {
555
555
  return line.replace(/^Error:\s*/, '');
556
556
  }
@@ -559,7 +559,7 @@ function firstCapturedFailureLine(output) {
559
559
  .split(/\r?\n/)
560
560
  .map((candidate) => candidate.trim())
561
561
  .filter((candidate) => candidate.length > 0)
562
- .filter((candidate) => !candidate.startsWith('KTX scan completed'))
562
+ .filter((candidate) => !candidate.startsWith('ktx scan completed'))
563
563
  .filter((candidate) => !INTERNAL_FAILURE_LINE_RE.test(candidate));
564
564
  const line = lines.find((candidate) => ACTIONABLE_FAILURE_LINE_RE.test(candidate)) ?? lines.at(-1) ?? null;
565
565
  return line ? trimErrorPrefix(line) : null;
@@ -584,7 +584,7 @@ function failureTextForTarget(input) {
584
584
  const code = networkErrorCode(input.error, input.capturedOutput);
585
585
  if (code && isLocalSqlAnalysisConnectionRefused({ capturedOutput: input.capturedOutput, fallback: input.fallback })) {
586
586
  return [
587
- `KTX could not reach the local SQL analysis runtime while processing query history for ${input.target.connectionId}.`,
587
+ `ktx could not reach the local SQL analysis runtime while processing query history for ${input.target.connectionId}.`,
588
588
  `Reason: ${NETWORK_ERROR_REASONS[code]} (${code}).`,
589
589
  `Retry: ${retryCommand({
590
590
  projectDir: input.projectDir,
@@ -598,7 +598,7 @@ function failureTextForTarget(input) {
598
598
  if (code) {
599
599
  const operation = input.target.operation === 'database-ingest' ? 'reading schema for' : 'ingesting';
600
600
  return [
601
- `KTX lost its connection to ${friendlyDriverName(input.target.driver)} while ${operation} ${input.target.connectionId}.`,
601
+ `ktx lost its connection to ${friendlyDriverName(input.target.driver)} while ${operation} ${input.target.connectionId}.`,
602
602
  `Reason: ${NETWORK_ERROR_REASONS[code]} (${code}).`,
603
603
  `Retry: ${retryCommand({
604
604
  projectDir: input.projectDir,
@@ -1,5 +1,7 @@
1
1
  import { parseDottedTableEntry } from './context/scan/enabled-tables.js';
2
+ import { createStaticCliSpinner } from './clack.js';
2
3
  import { profileMark } from './startup-profile.js';
4
+ import { withSearchableMultiselectNavigation } from './prompt-navigation.js';
3
5
  import { buildInitialState, buildPickerTree, } from './tree-picker-state.js';
4
6
  import { renderTreePickerTui, } from './tree-picker-tui.js';
5
7
  profileMark('module:database-tree-picker');
@@ -167,7 +169,7 @@ export async function pickDatabaseScope(args, io, render = defaultRenderer) {
167
169
  let selectedSchemas = initialStageOneSchemas(args);
168
170
  while (true) {
169
171
  const pickedSchemas = await args.prompts.autocompleteMultiselect({
170
- message: `Choose ${args.schemaNounPlural} to enable for ${args.connectionId}\nType to filter. Space to select. Enter when done.`,
172
+ message: withSearchableMultiselectNavigation(`Choose ${args.schemaNounPlural} to enable for ${args.connectionId}`),
171
173
  placeholder: `Search ${args.schemaNounPlural}`,
172
174
  options: schemaOptions(args),
173
175
  initialValues: selectedSchemas,
@@ -178,7 +180,7 @@ export async function pickDatabaseScope(args, io, render = defaultRenderer) {
178
180
  }
179
181
  selectedSchemas = pickedSchemas;
180
182
  if (selectedSchemas.length === 0) {
181
- io.stderr.write(`Nothing selected - type to filter, or Escape to skip ${args.schemaNoun} scope.\n`);
183
+ io.stderr.write(`Nothing selected - type to search, or Escape to skip ${args.schemaNoun} scope.\n`);
182
184
  continue;
183
185
  }
184
186
  const selectedNoun = selectedSchemas.length === 1 ? args.schemaNoun : args.schemaNounPlural;
@@ -193,7 +195,20 @@ export async function pickDatabaseScope(args, io, render = defaultRenderer) {
193
195
  if (action === 'back') {
194
196
  continue;
195
197
  }
196
- const discovered = await args.listTablesForSchemas(selectedSchemas);
198
+ // Static (stderr-only) spinner: the stage-two table picker below is a raw-mode
199
+ // Ink TUI, and an animated clack spinner would leave stdin dirty so Ink reads a
200
+ // stray Escape and exits immediately.
201
+ const tablesSpinner = createStaticCliSpinner(io);
202
+ tablesSpinner.start(`Listing tables in ${selectedSchemas.length} ${selectedNoun}…`);
203
+ let discovered;
204
+ try {
205
+ discovered = await args.listTablesForSchemas(selectedSchemas);
206
+ }
207
+ catch (error) {
208
+ tablesSpinner.error('Could not list tables');
209
+ throw error;
210
+ }
211
+ tablesSpinner.stop(`Found ${discovered.length} ${discovered.length === 1 ? 'table' : 'tables'}`);
197
212
  if (action === 'save' && args.existing.enabledTables.length === 0) {
198
213
  return {
199
214
  kind: 'selected',
@@ -46,7 +46,6 @@ function demoConfig(databasePath) {
46
46
  ' state: sqlite',
47
47
  ' search: sqlite-fts5',
48
48
  ' git:',
49
- ' auto_commit: true',
50
49
  ' author: ktx <ktx@example.com>',
51
50
  'llm:',
52
51
  ' provider:',
package/dist/doctor.d.ts CHANGED
@@ -67,7 +67,7 @@ interface RenderOptions {
67
67
  }
68
68
  export declare function formatDoctorReport(report: DoctorReport, options?: Partial<RenderOptions>): string;
69
69
  export declare function renderInvalidConfigMessage(projectDir: string, issues: KtxConfigIssue[], outputMode: KtxDoctorOutputMode, io: KtxDoctorIo): void;
70
- export declare function renderValidConfigMessage(projectDir: string, outputMode: KtxDoctorOutputMode, io: KtxDoctorIo): void;
70
+ export declare function renderValidConfigMessage(projectDir: string, outputMode: KtxDoctorOutputMode, io: KtxDoctorIo, warnings?: KtxConfigIssue[]): void;
71
71
  export declare function renderMissingProjectMessage(projectDir: string, outputMode: KtxDoctorOutputMode, io: KtxDoctorIo): void;
72
72
  export declare function runKtxDoctor(args: KtxDoctorArgs, io?: KtxDoctorIo, deps?: KtxDoctorDeps): Promise<number>;
73
73
  export {};
package/dist/doctor.js CHANGED
@@ -4,6 +4,7 @@ import { access, readFile } from 'node:fs/promises';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { promisify } from 'node:util';
7
+ import { shouldUseColorOutput } from './io/tty.js';
7
8
  import { KTX_NEXT_STEP_DIRECT_COMMANDS } from './next-steps.js';
8
9
  const execFileAsync = promisify(execFile);
9
10
  function workspaceRootDir() {
@@ -121,12 +122,6 @@ const GROUP_LABEL = {
121
122
  search: 'Semantic search',
122
123
  history: 'Query history',
123
124
  };
124
- function shouldUseColor(io) {
125
- if (io.stdout.isTTY !== true)
126
- return false;
127
- const env = process.env;
128
- return !env.NO_COLOR && env.TERM !== 'dumb' && !env.CI;
129
- }
130
125
  function styleStatus(useColor, status, text) {
131
126
  if (!useColor)
132
127
  return text;
@@ -322,17 +317,20 @@ export function renderInvalidConfigMessage(projectDir, issues, outputMode, io) {
322
317
  }, null, 2)}\n`);
323
318
  return;
324
319
  }
325
- const useColor = shouldUseColor(io);
320
+ const useColor = shouldUseColorOutput(io.stdout);
326
321
  const dim = (text) => styleDim(useColor, text);
327
322
  const bold = (text) => styleBold(useColor, text);
328
323
  const status = (s, text) => styleStatus(useColor, s, text);
329
324
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
325
+ const errorCount = issues.filter((issue) => issue.severity === 'error').length;
326
+ const warningCount = issues.length - errorCount;
330
327
  const lines = [];
331
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
328
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
332
329
  lines.push('');
333
- lines.push(` ${status('fail', '✗')} ${bold('Config')} ktx.yaml has ${issues.length} schema issue${issues.length === 1 ? '' : 's'}`);
330
+ lines.push(` ${status('fail', '✗')} ${bold('Config')} ktx.yaml has ${errorCount} schema issue${errorCount === 1 ? '' : 's'}${warningCount > 0 ? ` · ${warningCount} ignored field${warningCount === 1 ? '' : 's'}` : ''}`);
334
331
  for (const issue of issues) {
335
- lines.push(` ${status('fail', '✗')} ${issue.message}`);
332
+ const glyph = issue.severity === 'error' ? status('fail', '✗') : status('warn', '⚠');
333
+ lines.push(` ${glyph} ${issue.message}`);
336
334
  if (issue.fix) {
337
335
  lines.push(` ${dim(`→ ${issue.fix}`)}`);
338
336
  }
@@ -342,23 +340,35 @@ export function renderInvalidConfigMessage(projectDir, issues, outputMode, io) {
342
340
  lines.push('');
343
341
  io.stdout.write(lines.join('\n'));
344
342
  }
345
- export function renderValidConfigMessage(projectDir, outputMode, io) {
343
+ export function renderValidConfigMessage(projectDir, outputMode, io, warnings = []) {
346
344
  if (outputMode === 'json') {
347
345
  io.stdout.write(`${JSON.stringify({
348
346
  ok: true,
349
347
  projectDir,
348
+ ...(warnings.length > 0 ? { warnings } : {}),
350
349
  }, null, 2)}\n`);
351
350
  return;
352
351
  }
353
- const useColor = shouldUseColor(io);
352
+ const useColor = shouldUseColorOutput(io.stdout);
354
353
  const dim = (text) => styleDim(useColor, text);
355
354
  const bold = (text) => styleBold(useColor, text);
356
355
  const status = (s, text) => styleStatus(useColor, s, text);
357
356
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
358
357
  const lines = [];
359
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
358
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
360
359
  lines.push('');
361
- lines.push(` ${status('pass', '✓')} ${bold('Config')} ${dim('ktx.yaml schema valid')}`);
360
+ if (warnings.length > 0) {
361
+ lines.push(` ${status('warn', '⚠')} ${bold('Config')} ktx.yaml schema valid · ${warnings.length} ignored field${warnings.length === 1 ? '' : 's'}`);
362
+ for (const warning of warnings) {
363
+ lines.push(` ${status('warn', '⚠')} ${warning.message}`);
364
+ if (warning.fix) {
365
+ lines.push(` ${dim(`→ ${warning.fix}`)}`);
366
+ }
367
+ }
368
+ }
369
+ else {
370
+ lines.push(` ${status('pass', '✓')} ${bold('Config')} ${dim('ktx.yaml schema valid')}`);
371
+ }
362
372
  lines.push('');
363
373
  io.stdout.write(lines.join('\n'));
364
374
  }
@@ -371,15 +381,15 @@ export function renderMissingProjectMessage(projectDir, outputMode, io) {
371
381
  }, null, 2)}\n`);
372
382
  return;
373
383
  }
374
- const useColor = shouldUseColor(io);
384
+ const useColor = shouldUseColorOutput(io.stdout);
375
385
  const dim = (text) => styleDim(useColor, text);
376
386
  const bold = (text) => styleBold(useColor, text);
377
387
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
378
388
  const envProjectDir = process.env.KTX_PROJECT_DIR;
379
389
  const lines = [];
380
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
390
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
381
391
  lines.push('');
382
- lines.push(` No KTX project here yet. ${dim('(ktx.yaml not found)')}`);
392
+ lines.push(` No ktx project here yet. ${dim('(ktx.yaml not found)')}`);
383
393
  lines.push('');
384
394
  lines.push(` Run ${bold('ktx setup')} to create one.`);
385
395
  if (envProjectDir !== undefined) {
@@ -402,14 +412,13 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
402
412
  return 1;
403
413
  }
404
414
  const { validateKtxProjectConfig } = await import('./context/project/config.js');
405
- ;
406
415
  const rawConfig = await readFile(configPath, 'utf-8');
407
416
  const validation = validateKtxProjectConfig(rawConfig);
408
417
  if (!validation.ok) {
409
418
  renderInvalidConfigMessage(args.projectDir, validation.issues, args.outputMode, io);
410
419
  return 1;
411
420
  }
412
- renderValidConfigMessage(args.projectDir, args.outputMode, io);
421
+ renderValidConfigMessage(args.projectDir, args.outputMode, io, validation.issues);
413
422
  return 0;
414
423
  }
415
424
  if (args.command === 'project') {
@@ -420,7 +429,6 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
420
429
  }
421
430
  const { loadKtxProject } = await import('./context/project/project.js');
422
431
  const { validateKtxProjectConfig } = await import('./context/project/config.js');
423
- ;
424
432
  const { buildProjectStatus, renderProjectStatus } = await import('./status-project.js');
425
433
  const rawConfig = await readFile(configPath, 'utf-8');
426
434
  const validation = validateKtxProjectConfig(rawConfig);
@@ -445,7 +453,7 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
445
453
  else {
446
454
  io.stdout.write(renderProjectStatus(projectStatus, {
447
455
  verbose,
448
- useColor: shouldUseColor(io),
456
+ useColor: shouldUseColorOutput(io.stdout),
449
457
  durationMs: Date.now() - startedAt,
450
458
  toolchainChecks,
451
459
  }));
@@ -453,10 +461,10 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
453
461
  return projectStatus.verdict === 'blocked' ? 1 : 0;
454
462
  }
455
463
  const setupChecks = await runSetupChecks();
456
- const report = { title: 'KTX status', checks: setupChecks };
464
+ const report = { title: 'ktx status', checks: setupChecks };
457
465
  const renderOptions = {
458
466
  verbose: args.verbose ?? false,
459
- useColor: shouldUseColor(io),
467
+ useColor: shouldUseColorOutput(io.stdout),
460
468
  durationMs: Date.now() - startedAt,
461
469
  command: args.command,
462
470
  };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Marks an error as an expected operational outcome that ktx surfaces to its
3
+ * caller (a connected agent or the CLI user) rather than an unexpected ktx
4
+ * fault. Examples: invalid agent input, a warehouse rejecting a query, or a
5
+ * validation guard rejecting a request.
6
+ *
7
+ * `reportException` skips PostHog Error Tracking for these so the bug stream
8
+ * stays free of routine, caller-driven failures. The failure is still surfaced
9
+ * to the caller (as a tool-error result or CLI error) and still recorded by the
10
+ * outcome-tagged telemetry events, so no diagnostic signal is lost.
11
+ */
12
+ export declare class KtxExpectedError extends Error {
13
+ constructor(message: string, options?: ErrorOptions);
14
+ }
15
+ /**
16
+ * A query was rejected at the warehouse/driver boundary — the warehouse refused
17
+ * to compile or run it, or a read-only guard rejected it. Reuses the underlying
18
+ * error's message so the caller still sees the original warehouse diagnostics,
19
+ * and keeps the driver error as `cause`.
20
+ */
21
+ export declare class KtxQueryError extends KtxExpectedError {
22
+ constructor(message: string, options?: ErrorOptions);
23
+ }
24
+ /**
25
+ * True for the native JavaScript error types that signal a programming fault — a
26
+ * bug in ktx code rather than an operational outcome. These are universal
27
+ * language invariants (a `TypeError` never means "the warehouse rejected the
28
+ * query"), so callers can use this to keep genuine faults out of the
29
+ * expected-error classification and let them reach Error Tracking unchanged.
30
+ */
31
+ export declare function isNativeProgrammingFault(error: unknown): boolean;
package/dist/errors.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Marks an error as an expected operational outcome that ktx surfaces to its
3
+ * caller (a connected agent or the CLI user) rather than an unexpected ktx
4
+ * fault. Examples: invalid agent input, a warehouse rejecting a query, or a
5
+ * validation guard rejecting a request.
6
+ *
7
+ * `reportException` skips PostHog Error Tracking for these so the bug stream
8
+ * stays free of routine, caller-driven failures. The failure is still surfaced
9
+ * to the caller (as a tool-error result or CLI error) and still recorded by the
10
+ * outcome-tagged telemetry events, so no diagnostic signal is lost.
11
+ */
12
+ export class KtxExpectedError extends Error {
13
+ constructor(message, options) {
14
+ super(message, options);
15
+ this.name = 'KtxExpectedError';
16
+ }
17
+ }
18
+ /**
19
+ * A query was rejected at the warehouse/driver boundary — the warehouse refused
20
+ * to compile or run it, or a read-only guard rejected it. Reuses the underlying
21
+ * error's message so the caller still sees the original warehouse diagnostics,
22
+ * and keeps the driver error as `cause`.
23
+ */
24
+ export class KtxQueryError extends KtxExpectedError {
25
+ constructor(message, options) {
26
+ super(message, options);
27
+ this.name = 'KtxQueryError';
28
+ }
29
+ }
30
+ /**
31
+ * True for the native JavaScript error types that signal a programming fault — a
32
+ * bug in ktx code rather than an operational outcome. These are universal
33
+ * language invariants (a `TypeError` never means "the warehouse rejected the
34
+ * query"), so callers can use this to keep genuine faults out of the
35
+ * expected-error classification and let them reach Error Tracking unchanged.
36
+ */
37
+ export function isNativeProgrammingFault(error) {
38
+ return (error instanceof TypeError ||
39
+ error instanceof RangeError ||
40
+ error instanceof ReferenceError ||
41
+ error instanceof SyntaxError ||
42
+ error instanceof EvalError ||
43
+ error instanceof URIError);
44
+ }