@kaelio/ktx 0.7.0 → 0.8.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 (80) hide show
  1. package/assets/python/{kaelio_ktx-0.7.0-py3-none-any.whl → kaelio_ktx-0.8.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/command-schemas.d.ts +1 -1
  6. package/dist/command-tree.js +5 -1
  7. package/dist/commands/completion-commands.d.ts +3 -0
  8. package/dist/commands/completion-commands.js +38 -0
  9. package/dist/commands/ingest-commands.js +0 -4
  10. package/dist/commands/knowledge-commands.js +15 -2
  11. package/dist/commands/setup-commands.js +2 -2
  12. package/dist/commands/sl-commands.js +19 -7
  13. package/dist/completion/complete-engine.d.ts +19 -0
  14. package/dist/completion/complete-engine.js +128 -0
  15. package/dist/completion/completion-scripts.d.ts +1 -0
  16. package/dist/completion/completion-scripts.js +36 -0
  17. package/dist/completion/dynamic-candidates.d.ts +6 -0
  18. package/dist/completion/dynamic-candidates.js +98 -0
  19. package/dist/connection-drivers.d.ts +3 -0
  20. package/dist/connection-drivers.js +17 -0
  21. package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
  22. package/dist/context/ingest/ingest-bundle.runner.js +72 -15
  23. package/dist/context/ingest/ingest-profile.d.ts +102 -0
  24. package/dist/context/ingest/ingest-profile.js +306 -0
  25. package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
  26. package/dist/context/ingest/local-bundle-runtime.js +1 -0
  27. package/dist/context/ingest/local-ingest.d.ts +1 -1
  28. package/dist/context/ingest/local-ingest.js +6 -4
  29. package/dist/context/ingest/memory-flow/events.js +2 -1
  30. package/dist/context/ingest/ports.d.ts +2 -0
  31. package/dist/context/ingest/reports.d.ts +3 -0
  32. package/dist/context/ingest/reports.js +10 -0
  33. package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
  34. package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
  35. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
  36. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  37. package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
  38. package/dist/context/ingest/tools/tool-call-logger.js +36 -1
  39. package/dist/context/llm/ai-sdk-runtime.js +32 -3
  40. package/dist/context/llm/claude-code-runtime.js +35 -2
  41. package/dist/context/llm/runtime-port.d.ts +25 -0
  42. package/dist/context/mcp/context-tools.d.ts +2 -1
  43. package/dist/context/mcp/context-tools.js +82 -15
  44. package/dist/context/mcp/server.js +4 -0
  45. package/dist/context/mcp/types.d.ts +15 -1
  46. package/dist/context/project/config.d.ts +1 -0
  47. package/dist/context/project/config.js +4 -0
  48. package/dist/context/project/driver-schemas.js +1 -1
  49. package/dist/context/search/discover.js +4 -3
  50. package/dist/context/sl/local-sl.d.ts +15 -0
  51. package/dist/context/sl/local-sl.js +30 -0
  52. package/dist/context/wiki/local-knowledge.d.ts +10 -0
  53. package/dist/context/wiki/local-knowledge.js +22 -0
  54. package/dist/context-build-view.d.ts +0 -3
  55. package/dist/context-build-view.js +1 -7
  56. package/dist/ingest.js +7 -10
  57. package/dist/knowledge.d.ts +5 -0
  58. package/dist/knowledge.js +10 -1
  59. package/dist/public-ingest-copy.js +1 -1
  60. package/dist/public-ingest.d.ts +0 -7
  61. package/dist/public-ingest.js +20 -34
  62. package/dist/setup-context.js +6 -38
  63. package/dist/setup-databases.js +13 -82
  64. package/dist/setup-sources.js +33 -5
  65. package/dist/setup.js +2 -2
  66. package/dist/skills/analytics/SKILL.md +6 -1
  67. package/dist/sl.d.ts +6 -1
  68. package/dist/sl.js +32 -8
  69. package/dist/telemetry/emitter.js +1 -1
  70. package/dist/telemetry/events.d.ts +4 -3
  71. package/dist/telemetry/events.js +7 -3
  72. package/dist/telemetry/identity.d.ts +1 -1
  73. package/dist/telemetry/identity.js +13 -10
  74. package/dist/telemetry/index.d.ts +1 -1
  75. package/dist/telemetry/index.js +5 -1
  76. package/package.json +22 -22
  77. package/dist/ingest-depth.d.ts +0 -8
  78. package/dist/ingest-depth.js +0 -56
  79. package/dist/setup-database-context-depth.d.ts +0 -23
  80. package/dist/setup-database-context-depth.js +0 -84
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { Command, InvalidArgumentError } from '@commander-js/extra-typings';
4
+ import { registerCompletionCommands } from './commands/completion-commands.js';
4
5
  import { registerConnectionCommands } from './commands/connection-commands.js';
5
6
  import { registerIngestCommands } from './commands/ingest-commands.js';
6
7
  import { registerWikiCommands } from './commands/knowledge-commands.js';
@@ -319,6 +320,11 @@ export function collectCommandFlagsPresent(command) {
319
320
  export function buildKtxProgram(options) {
320
321
  const program = createBaseProgram(options.packageInfo, options.io);
321
322
  program.hook('preAction', async (_thisCommand, actionCommand) => {
323
+ // The hidden completion command must stay silent and side-effect free: skip
324
+ // the telemetry notice, command span, and project checks entirely.
325
+ if (commandPath(actionCommand).includes('__complete')) {
326
+ return;
327
+ }
322
328
  const telemetry = await import('./telemetry/index.js');
323
329
  options.setTelemetryModule?.(telemetry);
324
330
  await telemetry.showTelemetryNoticeIfNeeded(options.io, options.packageInfo);
@@ -362,6 +368,7 @@ export function buildKtxProgram(options) {
362
368
  registerStatusCommands(program, context);
363
369
  registerMcpCommands(program, context);
364
370
  registerAdminCommands(program, context);
371
+ registerCompletionCommands(program, context);
365
372
  return program;
366
373
  }
367
374
  export async function runCommanderKtxCli(argv, io, deps, info, options) {
@@ -20,8 +20,8 @@ export declare const slQueryCommandSchema: z.ZodObject<{
20
20
  }, z.core.$strip>>;
21
21
  queryFile: z.ZodOptional<z.ZodString>;
22
22
  format: z.ZodEnum<{
23
- sql: "sql";
24
23
  json: "json";
24
+ sql: "sql";
25
25
  }>;
26
26
  execute: z.ZodBoolean;
27
27
  cliVersion: z.ZodString;
@@ -5,7 +5,11 @@ export function walkCommandTree(command) {
5
5
  description: command.description(),
6
6
  aliases: command.aliases(),
7
7
  arguments: command.registeredArguments.map(formatArgumentDeclaration),
8
- children: command.commands.map((child) => walkCommandTree(child)),
8
+ // Internal commands (e.g. the shell-completion helper `__complete`) use a
9
+ // `__` prefix and are omitted from the human-facing command tree.
10
+ children: command.commands
11
+ .filter((child) => !child.name().startsWith('__'))
12
+ .map((child) => walkCommandTree(child)),
9
13
  };
10
14
  }
11
15
  export function formatCommandTree(node) {
@@ -0,0 +1,3 @@
1
+ import { type Command } from '@commander-js/extra-typings';
2
+ import type { KtxCliCommandContext } from '../cli-program.js';
3
+ export declare function registerCompletionCommands(program: Command, context: KtxCliCommandContext): void;
@@ -0,0 +1,38 @@
1
+ import { Argument } from '@commander-js/extra-typings';
2
+ import { computeCompletions } from '../completion/complete-engine.js';
3
+ import { completionScript } from '../completion/completion-scripts.js';
4
+ import { createProjectCompletionProviders } from '../completion/dynamic-candidates.js';
5
+ import { profileMark } from '../startup-profile.js';
6
+ profileMark('module:commands/completion-commands');
7
+ export function registerCompletionCommands(program, context) {
8
+ program
9
+ .command('completion')
10
+ .description('Print a shell completion script for ktx')
11
+ .addArgument(new Argument('<shell>', 'Target shell').choices(['zsh', 'bash']))
12
+ .addHelpText('after', '\nEnable completion by adding the matching line to your shell startup file:\n' +
13
+ ' zsh: eval "$(ktx completion zsh)"\n' +
14
+ ' bash: eval "$(ktx completion bash)"\n')
15
+ .action((shell) => {
16
+ context.io.stdout.write(completionScript(shell));
17
+ });
18
+ // Hidden command invoked by the generated shell scripts. It must only ever
19
+ // print newline-separated candidates to stdout and exit 0, so a TAB press is
20
+ // never disrupted by an error, a telemetry notice, or a parse failure.
21
+ program
22
+ .command('__complete', { hidden: true })
23
+ .argument('[words...]')
24
+ .allowUnknownOption(true)
25
+ .helpOption(false)
26
+ .action(async (words) => {
27
+ try {
28
+ const candidates = await computeCompletions(program, words, createProjectCompletionProviders());
29
+ if (candidates.length > 0) {
30
+ context.io.stdout.write(`${candidates.join('\n')}\n`);
31
+ }
32
+ }
33
+ catch {
34
+ // Swallow: completion must never break the shell.
35
+ }
36
+ context.setExitCode(0);
37
+ });
38
+ }
@@ -11,8 +11,6 @@ export function registerIngestCommands(program, context, commandOptions) {
11
11
  .usage('[options] [connectionId]')
12
12
  .argument('[connectionId]', 'Configured connection id to ingest (omit to ingest all)')
13
13
  .option('--all', 'Ingest all configured connections', false)
14
- .addOption(new Option('--fast', 'Use deterministic database schema ingest').conflicts('deep'))
15
- .addOption(new Option('--deep', 'Use AI-enriched database ingest').conflicts('fast'))
16
14
  .addOption(new Option('--query-history', 'Include database query-history usage patterns').conflicts('noQueryHistory'))
17
15
  .addOption(new Option('--no-query-history', 'Skip database query-history usage patterns'))
18
16
  .option('--query-history-window-days <days>', 'Query-history lookback window for this run', parsePositiveIntegerOption)
@@ -57,8 +55,6 @@ export function registerIngestCommands(program, context, commandOptions) {
57
55
  all: selection.kind === 'all',
58
56
  json: options.json === true,
59
57
  inputMode: options.input === false ? 'disabled' : 'auto',
60
- ...(options.fast === true ? { depth: 'fast' } : {}),
61
- ...(options.deep === true ? { depth: 'deep' } : {}),
62
58
  queryHistory,
63
59
  ...(options.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: options.queryHistoryWindowDays } : {}),
64
60
  cliVersion: context.packageInfo.version,
@@ -11,9 +11,9 @@ function isDebugEnabled(command) {
11
11
  return options.debug === true;
12
12
  }
13
13
  export function registerWikiCommands(program, context) {
14
- program
14
+ const wiki = program
15
15
  .command('wiki')
16
- .description('List or search local wiki pages')
16
+ .description('List, search, or read local wiki pages')
17
17
  .usage('[options] [query...]')
18
18
  .argument('[query...]', 'Search query; omit to list all pages')
19
19
  .option('--user-id <id>', 'Local user id', 'local')
@@ -50,4 +50,17 @@ export function registerWikiCommands(program, context) {
50
50
  cliVersion: context.packageInfo.version,
51
51
  });
52
52
  });
53
+ wiki
54
+ .command('read')
55
+ .description('Read a wiki page file by key')
56
+ .argument('<key>', 'Wiki page key')
57
+ .action(async (key, _options, command) => {
58
+ const parentOpts = command.parent?.opts();
59
+ await runKnowledgeArgs(context, {
60
+ command: 'read',
61
+ projectDir: resolveCommandProjectDir(command),
62
+ key,
63
+ userId: parentOpts?.userId ?? 'local',
64
+ });
65
+ });
53
66
  }
@@ -195,9 +195,9 @@ export function registerSetupCommands(program, context) {
195
195
  .addOption(new Option('--source-git-url <url>', 'Git URL for dbt, MetricFlow, or LookML').hideHelp())
196
196
  .addOption(new Option('--source-branch <branch>', 'Git branch for source setup').hideHelp())
197
197
  .addOption(new Option('--source-subpath <path>', 'Repo subpath for source setup').hideHelp())
198
- .addOption(new Option('--source-auth-token-ref <ref>', 'env: or file: credential ref for source repo auth').hideHelp())
198
+ .addOption(new Option('--source-auth-token-ref <ref>', 'env: or file: credential ref for source repo auth or Notion integration token').hideHelp())
199
199
  .addOption(new Option('--source-url <url>', 'Source service URL for Metabase or Looker').hideHelp())
200
- .addOption(new Option('--source-api-key-ref <ref>', 'env: or file: API key ref for Metabase or Notion').hideHelp())
200
+ .addOption(new Option('--source-api-key-ref <ref>', 'env: or file: API key ref for Metabase').hideHelp())
201
201
  .addOption(new Option('--source-client-id <id>', 'Looker client id').hideHelp())
202
202
  .addOption(new Option('--source-client-secret-ref <ref>', 'env: or file: Looker client secret ref').hideHelp())
203
203
  .addOption(new Option('--source-warehouse-connection-id <id>', 'Mapped warehouse connection id').hideHelp())
@@ -63,19 +63,27 @@ export function registerSlCommands(program, context, commandName = 'sl') {
63
63
  cliVersion: context.packageInfo.version,
64
64
  });
65
65
  });
66
+ sl.command('read')
67
+ .description('Read a semantic-layer source YAML file')
68
+ .argument('<sourceName>', 'Semantic-layer source name')
69
+ .action(async (sourceName, _options, command) => {
70
+ const parentOpts = command.parent?.opts();
71
+ await runSlArgs(context, {
72
+ command: 'read',
73
+ projectDir: resolveCommandProjectDir(command),
74
+ connectionId: parentOpts?.connectionId,
75
+ sourceName,
76
+ });
77
+ });
66
78
  sl.command('validate')
67
- .description('Validate a semantic-layer source (set --connection-id on `ktx sl`)')
79
+ .description('Validate a semantic-layer source')
68
80
  .argument('<sourceName>', 'Semantic-layer source name')
69
81
  .action(async (sourceName, _options, command) => {
70
82
  const parentOpts = command.parent?.opts();
71
- const connectionId = parentOpts?.connectionId;
72
- if (connectionId === undefined) {
73
- command.error("error: required option '--connection-id <id>' not specified");
74
- }
75
83
  await runSlArgs(context, {
76
84
  command: 'validate',
77
85
  projectDir: resolveCommandProjectDir(command),
78
- connectionId: connectionId,
86
+ connectionId: parentOpts?.connectionId,
79
87
  sourceName,
80
88
  });
81
89
  });
@@ -99,10 +107,14 @@ export function registerSlCommands(program, context, commandName = 'sl') {
99
107
  throw new Error('sl query requires at least one --measure');
100
108
  }
101
109
  const parentOpts = command.parent?.opts();
110
+ const connectionId = parentOpts?.connectionId;
111
+ if (connectionId === undefined) {
112
+ command.error("error: required option '--connection-id <id>' not specified");
113
+ }
102
114
  const args = slQueryCommandSchema.parse({
103
115
  command: 'query',
104
116
  projectDir: resolveCommandProjectDir(command),
105
- connectionId: parentOpts?.connectionId,
117
+ connectionId,
106
118
  ...(options.queryFile
107
119
  ? { queryFile: options.queryFile }
108
120
  : {
@@ -0,0 +1,19 @@
1
+ import type { CommandUnknownOpts } from '@commander-js/extra-typings';
2
+ /**
3
+ * Dynamic completion candidates that depend on project state (semantic-layer
4
+ * source names, wiki page keys, connection ids). Injected so the engine stays
5
+ * pure and unit-testable without touching the filesystem.
6
+ */
7
+ export interface CompletionProviders {
8
+ /** Candidate operands for a positional argument of the active command path. */
9
+ positionalCandidates(commandPath: string[], typedTokens: string[]): Promise<string[]>;
10
+ /** Candidate values for an option that has no static `choices` (e.g. `--connection-id`). */
11
+ optionValueCandidates(commandPath: string[], optionFlag: string, typedTokens: string[]): Promise<string[]>;
12
+ }
13
+ /**
14
+ * Compute completion candidates for the partial last element of `words`
15
+ * (everything the shell has on the line after `ktx`). The active command and
16
+ * its flags are derived by walking the live Commander tree, so completion never
17
+ * drifts from the real command structure.
18
+ */
19
+ export declare function computeCompletions(program: CommandUnknownOpts, words: string[], providers: CompletionProviders): Promise<string[]>;
@@ -0,0 +1,128 @@
1
+ function isHiddenCommand(command) {
2
+ // Completion mirrors `ktx --help`: commands registered with `{ hidden: true }`
3
+ // (the `__complete` helper and `mcp serve-internal`) are internal and must not
4
+ // surface. Commander exposes this only through the private `_hidden` field its
5
+ // own help renderer reads, so a name heuristic like a `__` prefix is not enough.
6
+ return command._hidden === true;
7
+ }
8
+ function resolveCommand(program, typedTokens) {
9
+ let command = program;
10
+ const commandPath = [];
11
+ for (let index = 0; index < typedTokens.length; index += 1) {
12
+ const token = typedTokens[index];
13
+ if (token.startsWith('-')) {
14
+ // A value-taking option in the `--flag value` form consumes the next token
15
+ // as its value, so skip that value before matching subcommands. Otherwise a
16
+ // connection id like `query` would be resolved as the `sl query` subcommand
17
+ // instead of being treated as the `--connection-id` value. The `--flag=value`
18
+ // form carries its own value and consumes nothing extra.
19
+ if (!token.includes('=')) {
20
+ const option = findOption(command, token);
21
+ if (option && !option.isBoolean()) {
22
+ index += 1;
23
+ }
24
+ }
25
+ continue;
26
+ }
27
+ const sub = command.commands.find((candidate) => candidate.name() === token || candidate.aliases().includes(token));
28
+ if (sub) {
29
+ command = sub;
30
+ commandPath.push(sub.name());
31
+ }
32
+ }
33
+ return { command, commandPath };
34
+ }
35
+ function collectOptions(command) {
36
+ const options = [];
37
+ let current = command;
38
+ while (current) {
39
+ options.push(...current.options);
40
+ current = current.parent;
41
+ }
42
+ return options;
43
+ }
44
+ function findOption(command, flag) {
45
+ return collectOptions(command).find((option) => option.long === flag || option.short === flag);
46
+ }
47
+ function isRepeatableOption(option) {
48
+ // Variadic options, and options backed by a collector with an array default
49
+ // (e.g. `--measure`/`--dimension`), may be supplied more than once.
50
+ return option.variadic || Array.isArray(option.defaultValue);
51
+ }
52
+ function flagCandidates(command, typedTokens) {
53
+ const present = new Set(typedTokens.filter((token) => token.startsWith('-')));
54
+ const candidates = [];
55
+ for (const option of collectOptions(command)) {
56
+ if (option.hidden || !option.long) {
57
+ continue;
58
+ }
59
+ if (present.has(option.long) && !isRepeatableOption(option)) {
60
+ continue;
61
+ }
62
+ candidates.push(option.long);
63
+ }
64
+ return candidates;
65
+ }
66
+ async function optionValueCandidates(resolved, option, typedTokens, providers) {
67
+ if (option.argChoices && option.argChoices.length > 0) {
68
+ return option.argChoices;
69
+ }
70
+ return providers.optionValueCandidates(resolved.commandPath, option.long ?? option.name(), typedTokens);
71
+ }
72
+ function dedupeSortFilter(candidates, partial) {
73
+ const seen = new Set();
74
+ const matches = [];
75
+ for (const candidate of candidates) {
76
+ if (!candidate.startsWith(partial) || seen.has(candidate)) {
77
+ continue;
78
+ }
79
+ seen.add(candidate);
80
+ matches.push(candidate);
81
+ }
82
+ return matches.sort();
83
+ }
84
+ /**
85
+ * Compute completion candidates for the partial last element of `words`
86
+ * (everything the shell has on the line after `ktx`). The active command and
87
+ * its flags are derived by walking the live Commander tree, so completion never
88
+ * drifts from the real command structure.
89
+ */
90
+ export async function computeCompletions(program, words, providers) {
91
+ const partial = words.length > 0 ? (words[words.length - 1] ?? '') : '';
92
+ const typedTokens = words.slice(0, -1);
93
+ const resolved = resolveCommand(program, typedTokens);
94
+ // (a) Option value via the `--opt=value` form.
95
+ const equalsMatch = /^(--[^=]+)=(.*)$/.exec(partial);
96
+ if (equalsMatch) {
97
+ const [, flag, valuePartial] = equalsMatch;
98
+ const option = findOption(resolved.command, flag);
99
+ if (!option || option.isBoolean()) {
100
+ return [];
101
+ }
102
+ const values = await optionValueCandidates(resolved, option, typedTokens, providers);
103
+ return dedupeSortFilter(values.map((value) => `${flag}=${value}`), `${flag}=${valuePartial}`);
104
+ }
105
+ // (b) Option value via the `--opt value` form (previous token is a value-taking option).
106
+ const previous = typedTokens[typedTokens.length - 1];
107
+ if (previous && previous.startsWith('-') && !partial.startsWith('-')) {
108
+ const option = findOption(resolved.command, previous);
109
+ if (option && !option.isBoolean()) {
110
+ return dedupeSortFilter(await optionValueCandidates(resolved, option, typedTokens, providers), partial);
111
+ }
112
+ }
113
+ // (c) Flag completion.
114
+ if (partial.startsWith('-')) {
115
+ return dedupeSortFilter(flagCandidates(resolved.command, typedTokens), partial);
116
+ }
117
+ // (d) Positional: subcommand names union static argument choices union dynamic operand candidates.
118
+ const candidates = resolved.command.commands
119
+ .filter((sub) => !isHiddenCommand(sub))
120
+ .map((sub) => sub.name());
121
+ for (const argument of resolved.command.registeredArguments) {
122
+ if (argument.argChoices) {
123
+ candidates.push(...argument.argChoices);
124
+ }
125
+ }
126
+ candidates.push(...(await providers.positionalCandidates(resolved.commandPath, typedTokens)));
127
+ return dedupeSortFilter(candidates, partial);
128
+ }
@@ -0,0 +1 @@
1
+ export declare function completionScript(shell: 'zsh' | 'bash'): string;
@@ -0,0 +1,36 @@
1
+ // Static shell completion scripts emitted by `ktx completion <shell>`.
2
+ //
3
+ // Both scripts gather the words on the current command line (excluding the
4
+ // leading `ktx`), append the partial word under the cursor, and delegate to the
5
+ // hidden `ktx __complete` command, which prints newline-separated candidates.
6
+ // All command/flag/entity knowledge lives in `ktx __complete` so these scripts
7
+ // never have to encode the command tree.
8
+ //
9
+ // Lines are single-quoted JS strings so the shell `${...}` expansions are
10
+ // emitted verbatim (a template literal would try to interpolate them).
11
+ const ZSH_SCRIPT = [
12
+ '#compdef ktx',
13
+ '_ktx() {',
14
+ ' local -a candidates',
15
+ ' local out',
16
+ ' out="$(ktx __complete -- "${words[@]:1:$((CURRENT-1))}" 2>/dev/null)" || return 0',
17
+ ' candidates=("${(@f)out}")',
18
+ ' compadd -- $candidates',
19
+ '}',
20
+ 'compdef _ktx ktx',
21
+ '',
22
+ ].join('\n');
23
+ const BASH_SCRIPT = [
24
+ '_ktx() {',
25
+ ' local cur out',
26
+ ' cur="${COMP_WORDS[COMP_CWORD]}"',
27
+ ' out="$(ktx __complete -- "${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null)" || { COMPREPLY=(); return 0; }',
28
+ " local IFS=$'\\n'",
29
+ ' COMPREPLY=($(compgen -W "${out}" -- "$cur"))',
30
+ '}',
31
+ 'complete -F _ktx ktx',
32
+ '',
33
+ ].join('\n');
34
+ export function completionScript(shell) {
35
+ return shell === 'zsh' ? ZSH_SCRIPT : BASH_SCRIPT;
36
+ }
@@ -0,0 +1,6 @@
1
+ import type { CompletionProviders } from './complete-engine.js';
2
+ /**
3
+ * Project-backed completion providers. Every entry swallows its own errors so a
4
+ * failed lookup never breaks the shell — completion degrades to commands/flags.
5
+ */
6
+ export declare function createProjectCompletionProviders(): CompletionProviders;
@@ -0,0 +1,98 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { resolveKtxProjectDir } from '../project-resolver.js';
4
+ /** Extract an option value from already-typed tokens (`--flag value` or `--flag=value`). */
5
+ function extractOptionValue(tokens, flag) {
6
+ const prefix = `${flag}=`;
7
+ for (let index = 0; index < tokens.length; index += 1) {
8
+ const token = tokens[index];
9
+ if (token === flag) {
10
+ const next = tokens[index + 1];
11
+ if (next !== undefined && !next.startsWith('-')) {
12
+ return next;
13
+ }
14
+ }
15
+ else if (token.startsWith(prefix)) {
16
+ return token.slice(prefix.length);
17
+ }
18
+ }
19
+ return undefined;
20
+ }
21
+ /**
22
+ * Resolve and load the project the user is completing against. Honors a
23
+ * `--project-dir` typed on the line, then `KTX_PROJECT_DIR`, then the nearest
24
+ * `ktx.yaml`. Returns null (no completions) when there is no project, without
25
+ * creating any files.
26
+ */
27
+ async function loadCompletionProject(typedTokens) {
28
+ const explicitProjectDir = extractOptionValue(typedTokens, '--project-dir');
29
+ const projectDir = resolveKtxProjectDir(explicitProjectDir !== undefined ? { explicitProjectDir } : {});
30
+ if (!existsSync(join(projectDir, 'ktx.yaml'))) {
31
+ return null;
32
+ }
33
+ const { loadKtxProject } = await import('../context/project/project.js');
34
+ return loadKtxProject({ projectDir });
35
+ }
36
+ async function sourceNames(typedTokens) {
37
+ const project = await loadCompletionProject(typedTokens);
38
+ if (!project) {
39
+ return [];
40
+ }
41
+ const connectionId = extractOptionValue(typedTokens, '--connection-id');
42
+ const { listLocalSlSources } = await import('../context/sl/local-sl.js');
43
+ const summaries = await listLocalSlSources(project, connectionId !== undefined ? { connectionId } : {});
44
+ return [...new Set(summaries.map((summary) => summary.name))];
45
+ }
46
+ async function wikiPageKeys(typedTokens) {
47
+ const project = await loadCompletionProject(typedTokens);
48
+ if (!project) {
49
+ return [];
50
+ }
51
+ const userId = extractOptionValue(typedTokens, '--user-id');
52
+ const { listLocalKnowledgePageKeys } = await import('../context/wiki/local-knowledge.js');
53
+ return listLocalKnowledgePageKeys(project, userId !== undefined ? { userId } : {});
54
+ }
55
+ async function connectionIds(typedTokens) {
56
+ const project = await loadCompletionProject(typedTokens);
57
+ if (!project) {
58
+ return [];
59
+ }
60
+ return Object.keys(project.config.connections).sort();
61
+ }
62
+ /**
63
+ * Project-backed completion providers. Every entry swallows its own errors so a
64
+ * failed lookup never breaks the shell — completion degrades to commands/flags.
65
+ */
66
+ export function createProjectCompletionProviders() {
67
+ return {
68
+ async positionalCandidates(commandPath, typedTokens) {
69
+ try {
70
+ const key = commandPath.join(' ');
71
+ if (key === 'sl read' || key === 'sl validate') {
72
+ return await sourceNames(typedTokens);
73
+ }
74
+ if (key === 'wiki read') {
75
+ return await wikiPageKeys(typedTokens);
76
+ }
77
+ if (key === 'connection test' || key === 'ingest') {
78
+ return await connectionIds(typedTokens);
79
+ }
80
+ return [];
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ },
86
+ async optionValueCandidates(_commandPath, optionFlag, typedTokens) {
87
+ try {
88
+ if (optionFlag === '--connection-id' || optionFlag === '--connection') {
89
+ return await connectionIds(typedTokens);
90
+ }
91
+ return [];
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,3 @@
1
+ import type { KtxProjectConnectionConfig } from './context/project/config.js';
2
+ export declare function normalizeConnectionDriver(connection: KtxProjectConnectionConfig): string;
3
+ export declare function isDatabaseDriver(driver: string): boolean;
@@ -0,0 +1,17 @@
1
+ const KTX_DATABASE_DRIVER_IDS = new Set([
2
+ 'sqlite',
3
+ 'postgres',
4
+ 'mysql',
5
+ 'clickhouse',
6
+ 'sqlserver',
7
+ 'bigquery',
8
+ 'snowflake',
9
+ ]);
10
+ export function normalizeConnectionDriver(connection) {
11
+ return String(connection.driver ?? '')
12
+ .trim()
13
+ .toLowerCase();
14
+ }
15
+ export function isDatabaseDriver(driver) {
16
+ return KTX_DATABASE_DRIVER_IDS.has(driver.trim().toLowerCase());
17
+ }
@@ -9,6 +9,14 @@ export declare class IngestBundleRunner {
9
9
  private readonly chainByConnection;
10
10
  constructor(deps: IngestBundleRunnerDeps);
11
11
  run(job: IngestBundleJob, ctx?: IngestJobContext): Promise<IngestBundleResult>;
12
+ /**
13
+ * When profiling is enabled — via the `KTX_PROFILE_INGEST` env var or the
14
+ * `ingest.profile` config setting — read the job's trace + tool transcripts
15
+ * and print a rolled-up timing breakdown to stderr. `json` emits the raw
16
+ * structured profile for coding agents; `table` emits a human summary.
17
+ * Best-effort: profiling never affects the run outcome.
18
+ */
19
+ private maybeEmitIngestProfile;
12
20
  protected stageRawFilesStage1: typeof stageRawFilesStage1;
13
21
  private syncKnowledgeSlRefsFromActions;
14
22
  protected materializeOverrideSnapshot(report: IngestReportSnapshot, ctx: {